Node.js 中 require 和 import 混用导致的循环依赖问题排查
在一个维护中的 Express 项目里,最近加了一个新的业务模块,启动时偶尔报 `TypeError: Cannot read properties of undefined`,刷新页面有时正常有时挂掉。看堆栈指向一个工具函数文件,但单独跑这个文件没问题。这个项目历史比较久,代码里有些文件用 `require` 加载依赖,有些新写的文件用 `import`,tsconfig 虽然开了 esModuleInterop,但并没有统一模块格式。Node 在 CommonJS 和 ESM 混用时,循环依赖的解析顺序会变得很不直观:`require` 拿到的是未完成的 `module.exports`,而 `import` 的绑定提升又会让某些变量在初始化前被访问。这篇文章从实际报错出发,梳理循环依赖在两种模块系统下的表现差异,说明 Node 的模块加载时序如何影响导出对象的赋值,并给出几种在不改动整体架构的前提下定位和规避这类问题的做法。 先看一个能复现问题的最小结构。项目里有三个文件:`config.js` 负责导出配置对象,`logger.js` 依赖 config 并导出日志方法,`app.js` 又同时依赖两者。 ```js // config.js const logger = require('./logger'); const config = { env: process.env.NODE_ENV || 'development', logLevel: 'info' }; logger.init(config); module.exports = config; ``` ```js // logger.js const config = require('./config'); let currentConfig = null; function init(cfg) { currentConfig = cfg; } function log(msg) { console.log(`[${currentConfig ? currentConfig.logLevel : 'unknown'}]`, msg); } module.exports = { init, log }; ``` `app.js` 只要先引 logger 再引 config,就会出问题。Node 的 CommonJS 加载流程是:遇到 `require('./logger')` 时开始执行 logger.js,logger 内部 `require('./config')` 会去加载 config.js,config 内部又 `require('./logger')`,此时 logger 在缓存里但 `module.exports` 还是初始的空对象,于是 config 拿到的 logger 是 `{}`,调用 `logger.init` 直接报错。如果 app.js 先引 config,同样的环依然存在,只是报错位置换到 config 里。 改一下加载顺序能绕过,但这不是可靠做法,因为入口文件多了以后顺序很难保证。更实际的方式是在出现环的地方延迟加载: ```js // config.js let logger = null; function getLogger() { if (!logger) { logger = require('./logger'); } return logger; } const config = { env: process.env.NODE_ENV || 'development', logLevel: 'info' }; getLogger().init(config); module.exports = config; ``` 把 `require` 挪到函数内部,保证 config 导出对象已经构建完成后再去取 logger,环就被打破了。这种写法在旧项目里改动成本低,也不需要调整模块系统。 ESM 下的循环依赖表现不同。`import` 是静态提升的,绑定是活引用,但初始化顺序仍然按依赖图深度优先执行。比如: ```js // a.mjs import { b } from './b.mjs'; export const a = 'a-value'; console.log('a module sees b:', b); ``` ```js // b.mjs import { a } from './a.mjs'; export const b = 'b-value'; console.log('b module sees a:', a); ``` 入口是 a.mjs 时,a 先执行到 import b,转去执行 b,b 里访问 a 得到的是暂时性死区里的未初始化绑定,直接抛 `ReferenceError: Cannot access 'a' before initialization`。如果 b 只是把 a 存起来稍后使用,不立即读取,就不会报错,因为 ESM 的绑定是活引用,等 a 初始化完成后能拿到值。这个差异意味着从 CommonJS 迁到 ESM 时,原本靠“先拿到空对象再赋值”苟活的循环依赖会直接暴露成运行时错误。 混合使用 `require` 和 `import` 时,问题会叠加。Node 对 `.mjs` 和 `type: module` 的文件按 ESM 处理,对 `.cjs` 和默认的 `.js` 按 CommonJS 处理。ESM 里用 `createRequire` 去 require 一个 CJS 模块,如果这个 CJS 模块又通过动态 `import()` 反向依赖 ESM,加载时序就取决于调用发生的时刻。我曾遇到一个情况:CJS 侧在模块顶层调用了 `require('./esm-wrapper')`,而 esm-wrapper 顶层又 `await import('./cjs-module')`,结果 CJS 模块的导出还没完成,ESM 侧拿到的 default 是 undefined。 排查这类问题,比较直接的办法是在可疑文件顶部打日志,记录模块开始执行和 `module.exports` 赋值完成的时间点: ```js console.log('[config] start'); // ... 模块逻辑 module.exports = config; console.log('[config] exports assigned'); ``` 同时在 require 之后打印拿到的对象,看它是空对象还是缺少某个属性。Node 的 `--trace-warnings` 对循环依赖没有直接提示,但 `require.cache` 里能看到模块是否处于已缓存但未加载完成的状态。 如果项目里循环依赖数量不多,用 `madge` 扫一遍依赖图比较省事: ```bash npx madge --circular --extensions js,ts src/ ``` 它会列出所有环,再结合上面的日志定位具体是哪个环在运行时触发。对于新代码,尽量让依赖方向单向:工具函数不反向依赖业务模块,配置模块不依赖使用配置的模块。已经存在的环,优先用函数内延迟 require 或依赖注入把环打断,而不是调整文件加载顺序,后者在代码分割和懒加载场景下很容易再次失效。 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-200.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-201.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-202.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-203.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-204.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-205.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-206.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-207.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-208.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-209.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-210.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-211.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-212.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-213.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-214.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-215.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-216.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-217.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-218.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-219.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-220.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-221.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-222.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-223.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-224.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-225.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-226.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-227.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-228.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-229.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-230.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-231.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-232.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-233.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-234.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-235.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-236.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-237.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-238.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-239.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-240.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-241.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-242.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-243.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-244.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-245.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-246.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-247.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-248.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-249.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-250.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-251.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-252.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-253.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-254.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-255.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-256.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-257.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-258.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-259.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-260.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-261.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-262.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-263.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-264.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-265.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-266.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-267.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-268.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-269.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-270.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-271.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-272.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-273.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-274.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-275.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-276.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-277.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-278.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-279.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-280.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-281.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-282.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-283.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-284.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-285.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-286.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-287.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-288.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-289.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-290.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-291.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-292.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-293.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-294.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-295.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-296.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-297.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-298.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-299.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-300.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-301.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-302.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-303.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-304.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-305.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-306.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-307.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-308.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-309.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-310.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-311.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-312.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-313.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-314.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-315.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-316.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-317.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-318.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-319.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-320.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-321.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-322.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-323.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-324.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-325.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-326.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-327.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-328.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-329.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-330.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-331.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-332.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-333.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-334.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-335.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-336.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-337.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-338.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-339.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-340.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-341.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-342.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-343.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-344.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-345.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-346.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-347.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-348.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-349.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-350.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-351.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-352.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-353.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-354.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-355.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-356.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-357.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-358.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-359.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-360.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-361.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-362.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-363.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-364.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-365.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-366.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-367.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-368.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-369.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-370.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-371.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-372.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-373.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-374.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-375.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-376.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-377.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-378.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-379.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-380.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-381.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-382.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-383.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-384.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-385.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-386.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-387.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-388.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-389.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-390.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-391.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-392.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-393.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-394.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-395.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-396.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-397.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-398.html 推荐查看天气:http://www.tianqi98.com/jizhuanwan/list-399.html