前言
自从 esm 的模块规范出来之后,开发时的代码基本都是使用 esm 的模块规范,哪怕是开发只在 node 环境使用的包,也会使用 esm 模块规范开发,使用工具转换成 cjs 的模块规范。
由于 esm 模块规范出来的比较晚,好多三方包都是 cjs 模块规范的包,如何在 esm 模块规范的工程中使用 cjs 模块规范的包,是一个大问题。
很多构建工具都为此做了适配:
// 原 cjs 代码
function test(){}
module.exports = test;
exports.a = 123;
/*
构建工具会进行转换,给其加上 default 属性,以便于 esm import default 时拿到默认导出。
例如 typescript 的处理:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
*/
// 在 esm 工程中使用时
import test from "./a.js"; // test(){}
import { a } from "./a.js"; // 123
import * as all from "./a.js"; // { default: test(){}, a: 123 }
// 由 esm 转换而来的 cjs 语法
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = exports.a = void 0;
function test() {}
var _default = exports.default = test;
const a = exports.a = 123;
/*
构建工具识别到 __esModule 属性,得知该 cjs 模块是由 esm 转换而来的,自带 default 属性。
则不再添加 default 属性。
例如 typescript 的处理:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
*/
// 在 esm 工程中使用时
import test from "./a.js"; // test(){}
import { a } from "./a.js"; // 123
import * as all from "./a.js"; // { default: test(){}, a: 123 }
构建工具的适配
typescript
typescript 工具需要配置一些相关的属性才能正确加载 cjs 模块。
// ts.config.json
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}
// 处理方式如下:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
babel
babel 默认自动转换,不用额外配置。
// 处理方式如下:
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != _typeof(e) && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return (n["default"] = e), t && t.set(e, n), n;
}
webpack
webpack 也默认做了自动转换,不需要额外配置。
// 处理方式如下:
(() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
node
当用 node 直接执行 esm 语法时,当引入的是个原始的 cjs 模块时,也会自动转换拿到默认导出为 module.exports
。但是当引入的是一个经过 esm 转译过来的 cjs 模块时,node 不像其他构建工具那样判断 __esModule
属性,仍会继续添加 default
属性,导致如下效果:
import test from "./a.js"; // { default: test(){}, a: 123 }
import * as all from "./a.js"; // { default: { default: test(){}, a: 123 } }
这是 node 本身的问题,node 暂时不适配业界公认的 __esModule
属性。详见 nodejs issue。
解决方式是和 前一篇文章 最后的解决方案一样,可以解决这个问题。但一般情况下,import 语法只使用于前端工程,前端工程的构建工具都会自动转换。node 环境直接执行 esm 很少。