背景
ESM(ES 模块)走向广泛支持的道路比较漫长。
ES 模块在 2015 年标准化,到 2017 年大多数浏览器开始支持,2019 年 Node.js v12 开始支持。在这段时间里,虽然大家都知道 ESM 是 JavaScript 模块的未来,但能直接使用它的运行环境却很少。
为了让开发者能尽快的使用标准化的模块化规范写代码,同时也为将来全面拥抱 ESM 打下基础,Babel 等工具允许用 ESM 编写代码,然后将其转换为其他可以在 Node.js 或浏览器中使用的模块格式。比如 ESM 转 CJS (CommonJS)、ESM 转 UMD(Universal Module Definition) 等。
在 Babel 中将 ESM 的代码转为 CJS 的代码是通过插件实现的,该插件为 @babel/plugin-transform-modules-commonjs
这个插件将 ECMAScript 模块(ESM)转换为 CommonJS 模块。需要注意的是,它只转换 import
和 export
语句的语法(例如 import "./mod.js"
)以及动态导入表达式(例如 import('./mod.js')
),因为 Babel 并不了解 ESM 和 CommonJS 之间不同的模块解析算法。
模块的实际解析和加载逻辑,依赖于运行环境的具体实现。
下面具体介绍一下 @babel/plugin-transform-modules-commonjs 插件的用法。
@babel/plugin-transform-modules-commonjs 插件的具体用法
@babel/plugin-transform-modules-commonjs 插件支持 5 个选项配置,分别为:
-
importInterop
-
loose
-
strict
-
lazy
-
noInterop
importInterop
我们知道 CommonJS 模块和 ECMAScript 模块并非完全兼容。不过,编译器、打包工具以及 JavaScript 运行时环境都制定了不同的策略,以尽可能让它们协同工作。
importInterop
选项用于指定 Babel 应该使用哪种互操作策略。
importInterop
可以传入的值有:babel
、node
、none
和函数,默认为 babel
。
函数为 (specifier: string, requestingFilename: string | undefined) => "babel" | "node" | "none"
的形式。例如,当 Babel 编译一个包含 import { a } from 'b'
的文件,该文件的路径为 /full/path/to/foo.js
时,specifier
为 b
,requestingFilename
为 /full/path/to/foo.js
。
babel
importInterop
选项的默认值是 babel
当 ESM 转 CJS ,并使用了导出(export
)的模块中,转换后的模块代码会导出一个不可枚举的 __esModule
属性。随后,该属性会被用于判断导入的是默认导出,还是包含默认导出的情况。该属性的主要作用是用于在 CJS 中模拟 ESM 的默认导出行为。
例如以下这个例子,该例子的目录结构如下:
Babel 的配置文件 babel.config.json
的代码如下,此配置的作用是将 ESM 格式的代码转为 CJS 格式的,互操作策略(importInterop
)为 babel
:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-modules-commonjs",
{
"importInterop": "babel"
}
]
]
}
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
}
}
src
文件夹下的文件的代码很简单:
// src/index.js
import add from "./util";
console.log("total ", add(3, 3));
// src/util.js
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
var _util = _interopRequireDefault(require("./util"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
console.log("total ", (0, _util.default)(3, 3));
// lib/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
从 lib
文件夹下的编译结果可知,lib/util.js
文件导出了不可枚举的 __esModule
属性。在 ESM 中的默认导出(export default
)add
函数被挂到了 exports.default
下。
从 lib/index.js
中可看出,为了模拟 ESM 的默认导出,代码中出现了 _interopRequireDefault
辅助函数,该函数的作用是用于模拟 ESM 的默认导入,该函数会判断传入的模块 e
的 __esModule
属性是否为 true ,如果为 true 则直接返回该模块,否则将该模块包装到一个新的对象中,并将原模块赋值给 default
属性:
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
我们再看一个纯 ESM 默认导入的例子,也许大家会对 _interopRequireDefault
函数的作用有更加深刻的理解,该例子的目录结构为:
package.json
用于告诉 Node.js ,这是个 ES 模块:
{
"type": "module"
}
index.js
代码为:
import * as u from "./util.js";
console.log("total ", u.default(3, 3));
util.js
代码为:
function add(a, b) {
return a + b;
}
export default add;
在 index.js
中整体导入了 util
模块,然在 index.js
中打上断点,并借助 VSCode 的调试工具,可以看到,util
模块的默认导出挂在了 default
上:
当使用 babel
这种导入互操作时(即 importInterop
设置为 babel
),如果被导入的模块和导入模块都通过 Babel 进行了编译,那么它们的行为表现就好像二者都没有被编译过一样。
在用 Babel 将 ESM 编译为 CJS 时,Babel 会在 CJS 中尽量模拟原始的 ES 模块行为,仿佛这些模块从未被编译成 CommonJS 模块,对用户尽量做到无感知。
node
在导入 CommonJS 文件时(无论是直接用 CommonJS 编写的文件,还是通过编译器生成的文件),Node.js 总是将默认导出绑定到 module.exports
的值上。
这一点与 Babel 原来的方案不同,Babel 在 ESM 转 CJS 中,将 ESM 的默认导出挂在 exports.default
上,当 Node.js 同时支持 ESM 和 CJS 时(旧版本的 Node.js 只支持 CJS), Node.js 是将 CJS 的默认导出挂在 module.exports
上。
Babel 为了兼容 Node.js 这样的行为,可将 importInterop
设置为 node
。
当 importInterop
设置为 node
时,编译后的代码不会有 _interopRequireDefault
辅助函数,对于默认导出,也不会取 default
属性的值。
但是 Babel 的这种方式不完美,如下面的例子,此例子的目录结构如下:
此例子的 Babel 配置 babel.config.json
为:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-modules-commonjs",
{
"importInterop": "node"
}
]
]
}
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
}
}
src/index.js
文件的代码如下:
import add from "./util.js";
console.log("total ", add(3, 3));
src/util.js
文件的代码如下:
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
var _util = require("./util.js");
console.log("total ", _util(3, 3));
// lib/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
但是在终端使用 node 运行 lib/index.js
,发现报错了:
原因是 lib/util.js
将默认导出挂在 exports.default
上,而 lib/index.js
中没有从 default
属性中取值,因此报错了。
所以在日常开发中尽量不要设置 importInterop
为 node
。
none
如果你知道被导入的文件已经通过某个编译器(比如 Babel)进行了转换,且该编译器会将默认导出存储在 exports.default
上,则可将 importInterop
设置为 none
。
将 importInterop
设置为 none
后,编译后的代码也不会有 _interopRequireDefault
辅助函数。
例如以下这个例子,该例子的目录结构如下:
Babel 的配置文件 babel.config.json
的代码如下,此配置的作用是将 ESM 格式的代码转为 CJS 格式的,互操作策略(importInterop
)为 none
:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-modules-commonjs",
{
"importInterop": "none"
}
]
]
}
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
}
}
src
文件夹下的文件的代码很简单,跟上面的例子一样:
// src/index.js
import add from "./util";
console.log("total ", add(3, 3));
// src/util.js
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
var _util = require("./util");
console.log("total ", (0, _util.default)(3, 3));
// lib/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
从 lib
文件夹下的编译结果可知,lib/util.js
文件也导出了不可枚举的 __esModule
属性。在 ESM 中的默认导出(export default
)add
函数被挂到了 exports.default
下。
在 lib/index.js
中可以发现,没有出现 _interopRequireDefault
,但会从 default
属性中取得默认导出的值。
并且在终端可以使用 Node.js 正常运行 lib/index.js
文件:
在实际开发中,可依据实际情况,放心地设置 importInterop
为 none
。
loose
loose
默认为 false ,会将 __esModule
属性设置为不可枚举的。
在将 ES 模块(ESM)编译为 CommonJS(CJS)模块时,Babel 会在 module.exports
对象上定义一个 __esModule
属性。假设你从不使用 for..in
循环或 Object.keys
去遍历 module.exports
或 require("your-module")
的键,那么将 __esModule
属性定义为可枚举的就是安全的。
如下面的例子,此例子的目录结构如下:
Babel 的配置文件 babel.config.json
的代码如下,此配置的作用是将 ESM 格式的代码转为 CJS 格式的,互操作策略(importInterop
)为 babel
,loose
为 true :
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-modules-commonjs",
{
"importInterop": "babel",
"loose": true
}
]
]
}
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
}
}
src
文件夹下的文件的代码很简单:
// src/index.js
import add from "./util";
console.log("total ", add(3, 3));
// src/util.js
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
var _util = _interopRequireDefault(require("./util"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
console.log("total ", (0, _util.default)(3, 3));
// lib/util.js
"use strict";
// 已经不是使用 Object.defineProperty 定义的不可枚举的 __esModule 属性了
exports.__esModule = true;
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
如果将上面的例子的 Babel 配置 loose
设置为 false ,则编译结果 lib/util.js
文件中,在 exports
对象中定义的 __esModule
是不可枚举的:
要注意的是,Babel 推荐使用 enumerableModuleMeta
编译器假设代替 loose
的配置。当 enumerableModuleMeta
设置为 true 时,编译后,exports
对象中定义的 __esModule
是可枚举的:
// 原文件
function add(a, b) {
return a + b;
}
export default add;
// 编译结果
"use strict";
exports.__esModule = true;// __esModule 是可枚举的
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
默认情况下,Babel 会尝试编译你的代码,使其尽可能贴近原生行为。不过,这有时意味着要生成更多的输出代码,或者生成运行速度更慢的输出代码,仅仅是为了支持一些你并不在意的边缘情况。
从 Babel 7.13.0 版本开始,你可以在配置中指定一个
assumptions
选项,告知 Babel 它可以对你的代码做出哪些假设,以便更好地优化编译结果。
strict
strict
默认为 false 。
在 ESM 转 CJS 的场景下,在使用 Babel 并通过 exports
进行导出时,会导出一个不可枚举的 __esModule
属性。在某些情况下,该属性用于判断导入的是默认导出,还是包含默认导出的内容。
如果不想导出 __esModule
属性,你可以将 strict
选项设置为 true 。
例如以下这个例子,该例子的目录结构如下:
Babel 的配置文件 babel.config.json
的代码如下,将 strict
选项设置为 true:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-modules-commonjs",
{
"strict": true
}
]
]
}
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
}
}
src
文件夹下的文件的代码很简单:
import add from "./util";
console.log("total ", add(3, 3));
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
var _util = _interopRequireDefault(require("./util"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
console.log("total ", (0, _util.default)(3, 3));
// lib/util.js
"use strict";
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
从文件 lib/util.js
中可以看出,将 strict
选项设置为 true,已经不会导出 __esModule
属性了。
lazy
可选值为 boolean
、Array<string>
、(string) => boolean
,默认为 false
。
更改 Babel 编译后的导入语句,使其在首次使用导入的绑定内容时才进行加载。
这可以缩短模块的初始加载时间,因为预先计算依赖项有时完全没有必要。在编写提供给他人使用的库时,尤其如此。
lazy
的值有几种可能的效果:
-
false
- 任何导入的模块都不会进行懒加载。 -
true
- 不对本地的./foo
导入进行懒加载,但foo
是项目的依赖包(通过 npm 安装的)会懒加载。
本地的模块之间可能存在循环依赖问题,如果进行懒加载,可能会出错,所以默认情况下它们不会懒加载,而项目的依赖模块(通过 npm 等下载的)之间很少会出现循环依赖的情况。
-
Array<string>
- 提供一个字符串数组,其中每个字符串代表一个模块路径或名称。对于所有导入来源匹配这些字符串之一的模块,将会进行懒加载。 -
(string) => boolean
- 提供一个接收字符串参数并返回布尔值的回调函数。这个回调函数会在需要确定某个特定的模块(通过其来源字符串标识)是否应该懒加载时被调用。根据回调函数的返回值(true
或false
),系统会决定是否对该模块实行懒加载。
以下两种导入情况永远不会懒加载:
import "foo";
副作用导入自动是非懒加载的,因为这种导入的主要目的是为了确保模块中的代码被执行(例如,执行某些全局操作或修改全局状态),而不是为了从模块中导入特定的变量、函数或类。因此,副作用导入需要在程序启动时立即执行,以确保所有预期的副作用都能正确发生,而不会延迟到某个具体的绑定被使用时才加载。
export * from "foo"
模块整体转发没法懒加载,需要提前执行。因为否则没有办法知道哪些函数或变量(API)需要被导出。
接下来看看 lazy
使用的具体案例
lazy 为 true
lazy
为 true 不对本地的模块导入进行懒加载,但是项目的依赖包(通过 npm 等安装的)会懒加载。
此例子的目录结构如下:
Babel 的配置文件 babel.config.json
的代码如下:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-modules-commonjs",
{
"lazy": true
}
]
]
}
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
src
文件夹下的文件的代码很简单:
// src/index.js
import add from "./util";
import { concat } from "lodash";
console.log("total ", add(3, 3));
console.log("concat ", concat([1], 6));
// src/util.js
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
var _util = _interopRequireDefault(require("./util"));
function _lodash() {
const data = require("lodash");
_lodash = function _lodash() {
return data;
};
return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
console.log("total ", (0, _util.default)(3, 3));
console.log("concat ", (0, _lodash().concat)([1], 6));
// lib/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
从文件 lib/index.js
可以知道,外部依赖包 lodash
,已经是懒加载的了,如下面的 _lodash
函数 ,当第一次调用 _lodash
函数时,lodash
会被加载一次,后续调用则直接返回已经加载好的模块,因此 lodash
只会被加载一次:
function _lodash() {
const data = require("lodash");
_lodash = function _lodash() {
return data;
};
return data;
}
lazy 为 Array<string>
如果 lazy
传入的是字符串数组,则会对导入来源匹配到字符串数组的模块进行懒加载。
此例子的目录结构如下:
Babel 的配置文件 babel.config.json
的代码如下:
{
"presets": [
[
"@babel/preset-env",
{
"targets": {
"edge": "17",
"firefox": "60",
"chrome": "67",
"safari": "11.1"
},
"useBuiltIns": "usage",
"corejs": "3.6.5"
}
]
],
"plugins": [
[
"@babel/plugin-transform-modules-commonjs",
{
"lazy": ["./util"]
}
]
]
}
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
}
}
src
文件夹下的文件的代码很简单:
// src/index.js
import add from "./util";
console.log("total ", add(3, 3));
// src/util.js
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
function _util() {
const data = _interopRequireDefault(require("./util"));
_util = function _util() {
return data;
};
return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
console.log("total ", (0, _util().default)(3, 3));
// lib/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
从文件 lib/index.js
可以知道,本地模块 util
是懒加载的了。
lazy 为 (string) => boolean
lazy
为回调函数,如果传入该回调函数的模块说明符返回 true
,则该模块会懒加载,否则不会。
此例子的目录结构如下:
Babel 的配置文件 babel.config.js
(因为要定义函数,所以不能用 json 文件的形式)的代码如下:
module.exports = function (api) {
api.cache(false);
return {
presets: [
[
"@babel/preset-env",
{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},
useBuiltIns: "usage",
corejs: "3.6.5",
},
],
],
plugins: [
[
"@babel/plugin-transform-modules-commonjs",
{
lazy: (specifier) => {
return specifier === "./util";
},
},
],
],
};
};
package.json
文件如下,其中包含了本例子的依赖包和 npm script 命令,运行 npm run babel
命令,会使用 Babel 将 src
文件夹下的文件编译到 lib
文件夹下:
{
"name": "babel-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"babel": "babel src --out-dir lib"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.26.4",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-modules-commonjs": "^7.26.3",
"@babel/preset-env": "^7.26.0",
"core-js": "^3.39.0"
},
"dependencies": {
"lodash": "^4.17.21"
}
}
src
文件夹下的文件的代码很简单:
// src/index.js
import add from "./util";
import { concat } from "lodash";
console.log("total ", add(3, 3));
console.log("concat ", concat([1], 6));
// src/util.js
function add(a, b) {
return a + b;
}
export default add;
然后在终端运行 npm run babel
命令,lib
文件夹下的编译结果如下:
// lib/index.js
"use strict";
function _util() {
const data = _interopRequireDefault(require("./util"));
_util = function _util() {
return data;
};
return data;
}
var _lodash = require("lodash");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
console.log("total ", (0, _util().default)(3, 3));
console.log("concat ", (0, _lodash.concat)([1], 6));
// lib/util.js
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function add(a, b) {
return a + b;
}
var _default = exports.default = add;
从文件 lib/index.js
可以知道,本地模块 util
是懒加载的了。
noInterop
默认为 false
,Babel 官方推荐使用 importInterop
配置代替。
当 noInterop
设置为 true 时,与 importInterop
设置为 none
是等价的。
总结
由于 ESM 得到广泛支持的时间比较晚,为了让开发者尽快的使用标准化的模块化规范写代码,Babel 等工具允许用 ESM 编写代码,然后将其转换为 CJS 格式的。
Babel 是通过 @babel/plugin-transform-modules-commonjs 插件将 ESM 的代码转为 CJS 的。
如果大家在实际中有 ESM 转 CJS 的需求,可以借助 Babel 来实现,赶快收藏学习吧!