Node模块导入规则总结
@
node模块总结
node模块引入的步骤:
- 路径分析
- 文件定位
- 编译执行
模块分类
- 由于node提供的模块,称为核心模块;
- 用户编写的,称为文件模块(包括自己编写的或者第三方引入的模块)
node支持的模块系统
ESModule
node v12版本后原生支持ESM,需要以ESM模块系统加载模块,需要如下的一种设置:
- 文件扩展名:
mjs
- 最近的父 package.json 文件
types
字段:module
,扩展名为.js
的文件。 - 增加执行参数
--input-type
也可以实现相同效果
v12之前的版本esm是实验特性,需要全局变量
–experimental-modules
去开启这一特性且文件修改为mjs
CommonJS
该模块系统为node最原始的模块加载方式,默认情况下,Node.js 会将以下内容视为 CommonJS 模块:
- 扩展名为
.cjs
的文件; - 当最近的父
package.json
文件包含值为commonjs
的顶层字段type
时,则扩展名为.js
的文件。 - 当最近的父 package.json 文件不包含顶层字段 "type" 时,则扩展名为 .js 的文件。 包作者应该包括 "type" 字段,即使在所有源都是 CommonJS 的包中也是如此。 明确包的 type 将使构建工具和加载器更容易确定包中的文件应该如何解释。
- 扩展名不是 .mjs、.cjs、.json、.node、或 .js 的文件(当最近的父 package.json 文件包含值为 "module" 的顶层字段 "type" 时,这些文件只有在它们是 require 的,而不是用作程序的命令行入口点)。
注意:CommonJS不能引入ESM模块,ESM能引入CommonJS模块。ESM具有向下兼容性。
模块加载
CommonJS 模块引入
路径分析
commonjs通过require()方法引入模块,该函数参数被称为标识符,node基于该表标识符进行模块查找,不同标识符查找方式不同,标识符分类如下:
- 带路径形式的文件模块;包括绝对路径和相对路径的模块;
- 不带路径的文件模块;
- 不带路径的核心模块
核心模块的查找:node自动识别,且核心模块已经被编译成二进制文件;如果试图添加一个与核心模块标识符相同的自定义模块是不会成功的。
带路径形式的文件模块的查找:带路径的标识符,会被当做文件模块来处理,路径分析中 require 会把路径转为真是路径进行查找。
不带路径的文件模块的查找:这种文件模块可能是一个文件或者是一个包;这类模块查找是最为费时;node会向上递归逐个尝试当前路径中的
node_modules
文件夹,来寻址该模块,当文件的路径越深,耗时越多,这也是这种标识符模块寻址最慢的原因。
文件定位
标识符可能有带文件扩展名或者不带扩展名的情况
带扩展名的文件定位
带扩展名的文件定位:直接定位带扩展名的文件;
不带扩展名的文件定位
不带扩展名的文件定位:分为文件扩名,目录分析和包两种情况。
文件扩名
node会首先尝试按照.js
,.json
,.node
的次序补足扩展名,依次尝试是否有该文件;加载具有不同的扩展名(文件扩展名不是.js
,.json
,.node
)的文件,如.cjs
,必须明确指明。CommonJS不能加载ESM模块。
目录分析和包(支持文件夹和包)
- 如文件扩名不能定位到文件,但是在定位中获取到一个目录(文件夹),node会将目录当做一个包处理,将尝试目录分析来定位;
- node在该目录下查找
package.json
,从中取出main
属性或exports
属性指定的文件名进行定位,如果文件名缺少扩展名,将会进入扩展名分析的步骤;点击查看关于包入口main和exports详情 - 而如果main属性指定的文件名错误,或者压根没有
package.json
文件,node会将index当做默认文件名,然后依次查找index.js
,index.json
,index.node
。 - 在目录分析的过程中,没有成功定位任何文件,如果标识符是不带路径的文件模块定位,会进入下一个模块路径继续查找,直到查找到文件或者查找失败抛出异常错误。
ESM模块引入
路径分析
与CommonJS路径分析基本相同;不同于ESM使用
import
引入模块;ESM还支持支持 file:、node: 和 data: URL 协议(HTTPS 和 HTTP 导入尚在实验阶段,不建议使用)。
参考链接:ECMAScript 模块
文件定位
ESM的文件定位主要关注于标识符是否带路径;
带路径
解析相对或绝对的说明符时,必须提供文件扩展名。
不带路径(裸说明符)
不带路径的文件定位基本相同于 CommonJS 的目录分析和包
不同点在于:
- 它不支持目录(文件夹)作为模块,必须完全指定目录索引(例如 './startup/index.js'),或者目录文件夹以包的形式放入从 node_modules 目录加载(例如 'startup')
- 包不支持文件自动扩名(main属性现阶段可以不带扩展名,但node会提示警告,exports需要强制带扩展名)
模块加载器总结对比
CommonJS 模块加载器
. 它是完全同步的。
. 它负责处理 require() 调用。
. 它是可修补的。
. 它支持文件夹作为模块。
. 当解析说明符时,如果没有找到完全的匹配,则它将尝试添加扩展名(.js、.json,最后是 .node),然后尝试将文件夹作为模块解析。
. 它将 .json 视为 JSON 文本文件。
. node 文件被解释为加载了 process.dlopen() 的编译插件模块。
. 它将所有缺少 .json 或 .node 扩展名的文件视为 JavaScript 文本文件。
. 它不能用于加载 ECMAScript 模块(尽管可以从 CommonJS 模块加载 ECMASCript 模块)。 当用于加载不是 ECMAScript 模块的 JavaScript 文本文件时,则它将作为 CommonJS 模块加载。
ECMAScript 模块加载器
. 它是异步的。
. 负责处理 import 语句和 import() 表达式。
. 它不是可修补的,可以使用加载器钩子自定义。
. 它不支持文件夹作为模块,必须完全指定目录索引(例如 './startup/index.js')。
. 它不进行扩展名搜索。 当说明符是相对或绝对的文件 URL 时,必须提供文件扩展名。
. 它可以加载 JSON 模块,但需要导入断言。
. 它只接受 JavaScript 文本文件的 .js、.mjs 和 .cjs 扩展名。
. 它可以用来加载 JavaScript CommonJS 模块。 这样的模块通过 es-module-lexer 来尝试识别命名的导出,如果可以通过静态分析确定的话是可用的。 导入的 CommonJS 模块将其 URL 转换为绝对路径,然后通过 CommonJS 模块加载器加载。