在 Node.js 中使用原生的 ES 模块

3,843 阅读3分钟
原文链接: qianduan.group
本文译自:Using ES modules natively in Node.js,原作者是 Dr. Axel Rauschmayer,圈内称之为"德国阮一峰"。

axel_head.07bc895d6fc6.jpg

Node.js 从 8.5.0 开始原生支持 ES 模块,不过还需要通过命令行参数控制。这还得感谢 Bradley Farias 对这个新特性的贡献。

本文就给大家详细讲讲这个特性。

举个栗子

文件结构:

esm-demo/
    lib.mjs
    main.mjs

lib.mjs

exportfunctionadd(x, y) {
    return x + y;
}

main.mjs


import {add} from'./lib.mjs';

console.log('Result: '+add(2, 3));

运行示例:

$ node --experimental-modules main.mjs
Result: 5

注意注意!

ES 的模块名称:

  • 所有的模块名称都是 URL —— 这对 Node.js 来说引入了新的东西;
  • 文件:最好使用相对的带 .mjs 后缀的路径来引用 ES 模块,这样可以保证对浏览器的兼容。如果不包含后缀名,则模块搜索方式与 CJS 保持一致。如果同时找到了 .mjs.js 的文件,则使用 .mjs 文件;
    • 例如:../util/tools.mjs
  • 类库模块:直接使用模块名,省略扩展名是最佳方式。这能与现阶段类库模块发布的方式保持最好的兼容;
    • 例如:lodash
  • 如何让 node_modules 里的模块在浏览器里工作还有待研究(不使用 bundler 工具)。一种可选方案是引入类 RequireJS 的配置方式,将裸路径转换到真实路径上。就目前而言,裸路径的模块在浏览器是中是用不了的;

ES 模块的特性:

  • 无法动态引入模块;但是很快就会引入动态操作符 import() 来实现;
  • __dirname__filename 和这样的元变量将不再提供,有一个新的提案引入将会给 ES 模块提供类似的方式——Domenic Denicola 提出 import.meta
console.log(import.meta.url);

与 CJS 模块的互操作性

  • ES 模块可以 import CJS 模块,但是引入的模块只包含一个 default export——即 module.exports 的值。已经开始计划让 CJS 模块输出具名 export(通过在文件开始加编译指令的方式),不过可能实现需要花一些时间。如果你想帮忙,看这里

      import fs1 from'fs';
      console.log(Object.keys(fs1).length); // 86
    
      import * as fs2 from'fs';
      console.log(Object.keys(fs2)); // ['default']
    
  • 在 ES 模块中不能调用 require(),主要是因为:

    • 路径解析在两种规范中是很不一样的:ESM 不支持 NODE_PATHrequire.extensions;它的模块标识符永远都是 URL,这会造成小小的区别;
    • 为了与浏览器保持最大的兼容性,ES 模块总是异步加载的。这种异步加载的方式与 CJS 通过 require() 同步加载的方式无法很好的在一起工作;
    • 同步模块的禁用同样也给在 ES 模块中使用顶级 await关上了大门(一个目前正在考虑的特性);
  • CJS 无法 require() ES 模块。原因与上一条提到的类似,同步加载异步的模块各种问题啊。不过也许可以通过 import() 来加载 ES 模块。

在老版本的 Node.js 上使用 ES 模块

好吧,如果你想在老的 Node.js 的使用 ES 模块,可以看看 John-David Dalton 的 @std/esm

小贴士:只要不使用 unlockables 特性,就可以保证与原生的 ES Module 完全兼容。

Standards.b333a73e81d1.jpg

FAQ

什么时候可以不借助命令配置就可以使用 ES 模块?

目前的计划是,在 Node.js 10 LTS 版本中可以直接支持 ES 模块;

.mjs 在浏览器中可以工作么?

可以的,只需要提供正确的 Media Type(text/javascript 或者 application/javascript)即可。.mjs 标准化和周边工具的升级正在进行中

文件扩展名 .mjs 是 ES 模块所必须的么?

是的,对于 Node.js 来说是这样的。浏览器不在乎文件扩展名,只关心 Media Type。

为什么 Node.js 需要 .mjs

Node.js 本可以检查一个文件是 CJS 模块还是 ES 模块。在选择添加扩展名这个方案之前也讨论过多个其他方案。可以阅读本博的另外一篇了解。每种方案都有其优势和缺点。在我看来,.mjs 是正确的选择。