目录:网上冲浪指南

在 Node.js 中加载 ES 模块

2019/10/11

原由

问题是这样发生的,因为微软突然弃坑了 NodeServices ,我只好重新去实现博客的文本渲染功能——用 NestJS。在实现这个功能的过程中我用到了 lodash-es 这个库,之前我在 Angular 中使用 lodash-es 的体验还是很不错的,所以这次就算写服务端程序也还是用到了它。但是 lodash-es 这个库却没有提供 CommonJS 的模块,毕竟叫做 lodash-es,情有可原。直接使用 Node.js 运行程序就会报语法错误,因为目前的 Node.js(12.11.x)还不支持 ECMA module。为了能够让程序正常的跑起来,我做了一下尝试。

实验性 esm 选项

在 Node.js 的文档中提到了启用 esm 支持的一个实验性选项——--experimental-modules,试了一下之后发现并没有什么卵用:

node --experimental-modules dist/main.js
# export { default as add } from './add.js';
# ^^^^^^
#
# SyntaxError: Unexpected token 'export'

原来这个选项只会把在 package.json 有 esm 声明的文件当作 ES module 导入。

esm loader

esm 是一个号称世界上最快的 ES module loader,无需借助 babel 编译,也不用 webpack 之类的 bundler 打包,只需添加一个参数就可以让 Node.js 支持 ES module:

yarn add esm
node -r esm dist/main.js

然而事与愿违,虽然 lodash-es 导入成功了,但是另一个间接引用的第三方库出错了,这样一个已知的问题就转化成了一个未知的问题,GG。

@babel/register

@babel/register 是一个类似 esm 的包,同样是通过 hook require 函数来实现的 ES module 加载,不过这个就是实打实的使用 babel 进行的转译,具体的使用方法如下:

安装相关依赖
yarn add @babel/core @babel/preset-env @babel/register

@babel/core 是必备的软件包, @babel/preset-env 用来一键加载所需的 babel 插件

接着,在 main.ts 的开头加上下面的代码:

require('@babel/register')({
  presets: [
    '@babel/preset-env',
  ],
  ignore: [], (1)
  only: [
    function (filePath) {
      const result = filePath.indexOf('node_modules/lodash-es') >= 0; (2)
      return result;
    },
  ],
});
1 由于 @babel/register 默认忽略 node_modules ,所以需要取消这个默认设置
2 使用 babel 转译代码的成本还是挺高的,所以设置仅转译 lodash-es 中的文件

这样,nest 项目就可以成功运行了,不过由于 babel 会在程序运行的时候进行转译的操作,所以程序的启动速度会出现明显的下降,而且这个东西也并没有表明可以用于生产环境。

Webpack

“既然问题出现在模块加载的环节,干脆就直接使用 webpack 来打包吧”——一开始我是这么想的,但是想到需要为单元测试跟生产环境构建各写一份 webpack.config.js 就感到了一丝丝的头疼。而且在 NestJS 的 Issue 中找寻了一下,发现直接使用 webpack 打包的话很可能会导致一些 ORM 无法正常工作。

最终的选择

反正服务端代码也用不上摇树优化,不如直接使用 lodash:

yarn remove lodash-es
yarn add lodash