外观
require 是按什么顺序找文件的?
require 先看缓存,再按路径类型找文件,最后才找 node_modules。找到的模块会执行一次,导出对象放进缓存,后面再 require 得到的是同一份。
原理
核心模块,例如 fs,优先于同名文件。
相对路径或绝对路径:先按确切文件,再补 .js、.json、.node,再把路径当目录,读里面的 package.json 的 main,否则试 index.js。
非路径的名字从当前目录的 node_modules 开始,一层层往上,直到根。作用域包 @scope/name 对应 node_modules/@scope/name。
package.json 的 exports 会限制能引用的入口。只写 main 的旧包,深层路径可能被直接引用;写了 exports 的包,未列出的路径会失败。
循环依赖时,正在执行的模块先放进缓存,对方拿到的 exports 可能还是空的。见 模块化。
ESM 的 import 是静态的,解析规则看 type 和 exports 的 import 条件,不要和 require 的后缀补全假设成一样。
追问
改了文件为什么还是旧的?
同一进程里的 require 有缓存。测试里才会去删 require.cache。生产上更新代码是重启进程或重新部署,不是指望再次 require 读到新文件。
require 为什么是同步的?
它在启动和首次加载时把模块图建完。放在请求路径上的临时 require 会挡住事件循环。动态加载用已经设计好的异步接口,不要在热路径上现找文件。
易错点
- 忘记核心模块优先,自己的文件和内置模块同名时根本不会被加载。
- 循环依赖里使用尚未赋值的导出。
- 以为每个
require都会重新执行模块,从而把模块当成工厂。要每次都新实例,就导出工厂函数。