也许大家在刚学习node的时候都有这样的疑惑:
为什么在导入 Node.js 核心模块时,写法是
node:fs而不是fs?这个“node:”前缀是个啥?咋这么独特呢?
我们都知道,Node.js 历史上一直用 CommonJS 模块规范,使用require/module.exports语法:
const fs = require('fs');
而时代变迁, ES6 引入了 ES Modules。
简称ESM的ES Modules,是 JavaScript 官方标准化的模块系统,通过 import / export 来组织代码,Node.js 从 v12+ 起就开始支持ESM了。
于是写法变成了这样:
import fs from 'fs';
问题来了——
在使用ESM的项目中,如果导入第三方模块或有名字冲突,
fs可能不是 Node 核心模块,而是 node_modules 下的包!
举个例子,假设你安装了一个叫 fs的第三方包,又使用import fs from 'fs';这种写法导入,那么这个时候导入的其实是“node_modules/fs”,而不是 Node 的内置 fs!
为解决这一点,Node.js 引入了node:前缀这种“显式导入”的新语法形式。
import fs from 'node:fs';
这样写,等于直白地告诉解释器:
“我要导入的是 Node 内置的模块!不是第三方包!”
也就是说,node:fs实际上和fs是同一个模块,只是指向更明确。
哪些模块支持 node: 前缀呢?
答案是:所有 Node.js 的核心模块都支持。
细心的同学又要问了:
为什么
require不会导入名字重复的包呢?
这个问题问的好,太好了,给细心的你递bug(不是)
require()的导入流程
require('fs') 之所以不会误导入名字重复的第三方包,是因为 Node.js 的模块加载机制。
require() 对fs、path等内建模块,是硬映射,会跳过模块路径解析,在处理“核心模块名”时,会优先从 Node.js 内部核心模块加载,不会进入模块查找流程;
当你使用require()时,Node.js 的模块加载器会按如下优先级查找:
1. 核心模块优先
Node 会先检查是不是它的内建模块,如:
'fs''http''crypto''path'- ......
全部内建模块列表,可以在node交互环境中输入require('module').builtinModules,就会打印所有内建模块名啦,如下图所示:
也就是说,只要你导入的包名,恰恰命中了内建模块表,就会立即返回对应对象,不会查文件,也不会看
node_modules。
2. 如果不是内建模块,则进入模块路径解析规则
Step 1:查找 node_modules/foo
从当前文件目录出发,向上递归查找 node_modules:
./node_modules/foo
../node_modules/foo
../../node_modules/foo
...直到根目录
一旦找到 foo 文件夹后,进入下一步。
Step 2:解析 package.json 文件
如果 node_modules/foo/package.json 存在,Node 会检查以下字段:
| 字段 | 含义 | 使用顺序 |
|---|---|---|
exports | (ESM 专属)入口映射规则 | 优先级最高 |
module | 非官方字段(某些打包器识别) | Node 忽略 |
main | CommonJS 的默认入口文件路径 | ESM fallback 用 |
没有 package.json | 使用默认规则(index.js) | 仅 CommonJS 支持 |
1、处理 exports 字段(ESM 模式)
如果有:
{
"exports": {
".": "./dist/index.js",
"./sub": "./lib/sub.js"
}
}
就完全按照它解析路径:
import 'foo'→foo/dist/index.jsimport 'foo/sub'→foo/lib/sub.jsimport 'foo/lib/raw.js'→ 报错:ERR_PACKAGE_PATH_NOT_EXPORTED,这是 包边界限制机制,防止访问非公开 API。
2、没有 exports 字段 → 回退查 main
{
"main": "./lib/index.js"
}
然后加载 node_modules/foo/lib/index.js。
3、main也没有 → 回退到 index.js
Node先默认尝试查找:
node_modules/foo/index.js
node_modules/foo/index.json
node_modules/foo/index.node
这是最经典、最老的模块加载方式,只有在没有 exports 和 main 的老包中才出现。
4、还没找到
那只能抛出ERR_MODULE_NOT_FOUND: Cannot find package 'foo' ...啦……
至此,我们终于知道node:是个啥,果然每个规则的背后都有一定的原因~