跳到主要内容

通过 Node.js 自定义加载器运行代码

· 阅读需 5 分钟
不如怀念
Web 前端工程师 (Web Front-end Engineer)

最后更新于 2023-05-28 23:07:00

鉴于 Node.js 的诸多历史遗留原因,目前文件有多种扩展名,在文件引用时很多开发者习惯不写扩展名,这在 ES Modules 代码中需要额外的命令行 flag 才能实现。但在 Node.js v19 的版本发布后,其中 --experimental-specifier-resolution 命令行 flag 被删除,为了能继续运行无扩展名的 ES Modules 代码,就需要借助自定义加载器来实现。

目前,Node.js 文件的扩展名就有数种,比如常见的 .js.mjs.cjs.node,之所以有这些东西存在,是因为了 Node.js 从最初的版本发展到现在,已历经了数个大版本的更新,有很沉重的历史包袱。

CommonJS:扩展名不是必须的

相信很多使用 Node.js 的开发者目前熟悉的应该是 CommonJS 风格,其中不需要强制在代码中写文件的扩展名,这实际上和其它后端语言体验一致。

const foo = require('./foo.js'); // work!
const foo = require('./foo'); // work too!

ES Modules:扩展名是必须的

在现如今 Node.js 已全面支持 ES Modules 的情况下,使用 ES Modules 编写代码默认必须写文件的扩展名,据官方文档说明,这与浏览器环境下的 import 行为一致。

import * as foo from './foo.js'; // work!
import * as foo from './foo'; // error, not work!

--experimental-specifier-resolution=node

为了实现用 ES Modules 编写代码,又不需要写文件扩展名,Node.js 在很久以前就给出了一个命令行的 flag 来应对这类问题。

$ node --experimental-specifier-resolution=node index.js

不过,近期在使用 Node.js v20 版本时,突然发现该 flag 失效了,导致写的没有扩展名的 ES Modules 代码无法运行。遂开始查阅官方文档,发现在 v18 的文档中该 flag 还可以索引,v20 的文档中已经无法索引了,在后续查阅资料的过程中终于从 Node.js 的 v19 发布公告中发现了问题。

Node.js has removed the --experimental-specifier-resolution flag. Its functionality can now be achieved via custom loaders.

Node.js v18 将是最后一个可以使用 --experimental-specifier-resolution 命令行 flag 的大版本。

自定义加载器

相信这一变化给很多人造成了困扰,对于我也是。官方给的方案是自定义加载器,但没有具体的文档说明,只能在 v19 发布公告中提及的 Github Issue 中寻得蛛丝马迹。

最终,为了解决这个问题,需要安装一个官方提供的自定义加载器包

$ npm i commonjs-extension-resolution-loader

然后改变运行文件的命令:

# 以前
$ node --experimental-specifier-resolution=node index.js

# 现在
$ node --experimental-loader=commonjs-extension-resolution-loader index.js

但需要注意的是,使用该自定义加载器运行代码,如果遇到第三方包是 ES Modules,且没有指定 package.json 文件中的 main 字段将会出现找不到模块的错误。这是因为 CommonJS 规范模块解析依赖 main 字段,而 ES Modules 规范则推荐使用 exports 字段指定模块入口文件,详见官方文档

鉴于以上情况,如果是新项目,而且采用 ES Modules 编写代码,那还是带上文件扩展名遵循其规范比较好,放弃采用 flag 以及自定义加载器的方式运行代码,毕竟会带来一些额外的问题。

参考资料