“node:fs”中的“node:”前缀是个啥?require和import又在寻找些什么?

1,919 阅读3分钟

也许大家在刚学习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,就会打印所有内建模块名啦,如下图所示:

image.png 也就是说,只要你导入的包名,恰恰命中了内建模块表,就会立即返回对应对象,不会查文件,也不会看 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 忽略
mainCommonJS 的默认入口文件路径ESM fallback 用
没有 package.json使用默认规则(index.js)仅 CommonJS 支持
1、处理 exports 字段(ESM 模式)

如果有:

{
  "exports": {
    ".": "./dist/index.js",
    "./sub": "./lib/sub.js"
  }
}

就完全按照它解析路径:

  • import 'foo'foo/dist/index.js
  • import 'foo/sub'foo/lib/sub.js
  • import '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

这是最经典、最老的模块加载方式,只有在没有 exportsmain 的老包中才出现。

4、还没找到

那只能抛出ERR_MODULE_NOT_FOUND: Cannot find package 'foo' ...啦……


至此,我们终于知道node:是个啥,果然每个规则的背后都有一定的原因~