esModule + NodeJS + TypeScript 的工程配置

2022-09-22Frontend
nodejs
nodejs

JavaScript的模块化方案经历了长时间的发展,最终于2015年在ES6实现了语言层面的标准化,即esModule(以下简称esm), 现在JS社区开始拥抱esm,很多npm包仅采用esm发布。

而NodeJS一直依赖采用CommonJS的模块化方案,在最近发布的版本中也开始支持ems, 由于巨大的历史包袱,NodeJS并没有抛弃CommonJS,所以在NodeJS中实际支持两种模块化方式,esm和CommonJS.

此外,现在很多前端工程使用TypeScript开发,而TS的模块解析方式也需要进行一些配置,在这3者结合的过程中有很多坑,本文介绍将三者完美结合的最佳实践。

<!--more-->

同时引入CommonJS包和ESM包

为了测试我们同时引入lodash的CommonJS版本和ESM版本

npm i lodash-es lodash

在NodeJS中,模块作者除非显示指定使用esm方式加载模块:

  • 使用.mjs
  • package.json中的type字段指定为module
  • 使用--input-type标记

否则默认加载方式为CommonJS。

而上面的例子中lodash-espackage.json中的type就是module

使用CommonJS包

创建一个common-js.js文件:

const _ = require('lodash');
console.log(_.VERSION); // print '4.17.21'

以上是CommonJS的写法,很习以为常,能够正确打印。

使用esm包

创建一个esm.js文件:

import _ from 'lodash-es';
console.log(_.VERSION);

第1个坑出现了, 报语法错误:SyntaxError: Cannot use import statement outside a module

说无法在模块外部使用import语句,一旦一个文件有顶级(top-level)的importexport,它会被当作一个模块,否则是一个脚本文件。

所以我们需要把这个文件变成esm,两种方式:

  1. 重命名文件,修改扩展名为.mjs;
  2. package.json中添加"type": "module"

但如果采用选项2,第2个坑就会出现了, 回去运行common-js.js报错:ReferenceError: require is not defined in ES module scope, you can use import instead。 说require在esm中不支持,此时可以重命名common-js.js后缀为.cjs,告诉模块加载器,此文件以CommonJS加载。

引入Typescript

安装TS:

npm i typescript ts-node -D

ts-node 可以在nodejs中直接运行ts文件而无需编译

然后将刚才的esm.jscommon-js.cjs扩展名改为.ts

使用ts-node命令直接运行esm.ts

npx ts-node esm.ts

第3个坑出现了,扩展名不支持:TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for xxx

这是因为esm的默认扩展名是js而不是ts,此时我们需要:

  1. ts-node --esm
  2. 添加 "ts-node": {"esm": true}tsconfig.json

如果采用方案1,第4个坑出现了,无法找到模块:error TS7016: Could not find a declaration file for module 'lodash-es'

此时添加配置: "esModuleInterop": truetsconfig.jsoncompilerOptions中。

添加完成后第5个坑出现了,引用错误:ReferenceError: exports is not defined in ES module scope

此时添加配置module": "ESNext"tsconfig.jsoncompilerOptions中。

添加完成后第6个坑出现了,找不到模块:ReferenceError: exports is not defined in ES module scope

此时配置模块的解析方式为最新的"moduleResolution": "Node16"

到此终于可以成功运行esm.ts了。

返回去运行common-js.ts:

npx ts-node common-js.ts

第7个坑出现了,它把我们的CommonJS模块当esm了:ReferenceError: require is not defined in ES module scope, you can use import instead

此时处于鱼和熊掌不可兼得,因为我们设置了module": "ESNext", 所以ts的编译结果会添加export {},所以如果要在纯esm的工程中仍然要使用CommonJS,则使用.cjs作为扩展名即可。

完整的代码在这里:https://github.com/wangyucode/esm-node-ts

评论区

暂无评论