前言
自从 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"; // 123import * 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"; // 123import * 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 很少。
