Open kangyana opened 1 year ago
由于 Bundless 构建工具的兴起,要求所有的模块都是 ESM 模块化格式。
目前社区有一部分模块同时支持 ESM 与 CommonJS,但仍有许多模块仅支持 CommonJS/UMD,因此将 CommonJS 转化为 ESM 是全部模块 ESM 化的过渡阶段。
在 ESM 中,导入导出有两种方式:
代码示例如下:
// Named export/import export { sum }; import { sum } from "sum"; // Default export/import export default sum; import sum from "sum";
而在 CommonJS 中,导入导出的方法只有一种:
// 实际上的 exports exports = module.exports; // 以下两个是等价的 exports.a = 3; module.exports.a = 3;
正因为有二者的不同,因此在二者转换的时候有一些兼容问题需要解决。
正因为,二者有所不同,当 exports 转化时,既要转化为 export {},又要转化为 export default {}
export {}
export default {}
// Input: index.cjs exports.a = 3; // Output: index.mjs // 此处既要转化为默认导出,又要转化为具名导出! export const a = 3; export default { a };
如果仅仅转为 export const a = 3 的具名导出,而不转换 export default { a },将会出现什么问题?以下为例:
export const a = 3
export default { a }
// Input: CJS exports.a = 3; // index.cjs const o = require("."); // foo.cjs console.log(o.a); // foo.cjs // Output: ESM // 这是有问题的错误转换示例: // 此处 a 应该再 export default { a } 一次 export const a = 3; // index.mjs import o from "."; // foo.mjs console.log(o.a); // foo.mjs 这里有问题,这里有问题,这里有问题
对于 module.exports,我们可以遍历其中的 key (通过 AST),将 key 转化为 Named Export,将 module.exports 转化为 Default Export
module.exports
Named Export
Default Export
// Input: index.cjs module.exports = { a: 3, b: 4, }; // Output: index.mjs // 此处既要转化为默认导出,又要转化为具名导出! export default { a: 3, b: 4, }; export const a = 3; export const b = 4;
如果 module.exports 导出的是函数如何处理呢,特别是 exports 与 module.exports 的程序逻辑混合在一起? 以下是一个正确的转换结果:
exports
// Input: index.cjs module.exports = () => {} exports.a = 3 exports.b = 4 // Output: index.mjs const sum = () => {} sum.a = 3 sum.b = 4 export const a = 3 export const b = 4 export default = sum
也可以这么处理,将 module.exports 与 exports 的代码使用函数包裹起来,此时我们无需关心其中的逻辑细节。
var esm$1 = { exports: {} }; (function (module, exports) { module.exports = () => {}; exports.a = 3; exports.b = 4; })(esm$1, esm$1.exports); var esm = esm$1.exports; export { esm as default };
ESM 与 CommonJS 不仅仅是简单的语法上的不同,它们在思维方式上就完全不同,因此还有一些较为复杂的转换。
__dirname
require(dynamicString)
以下代码涉及到编程逻辑,由于 exports 是一个动态的 Javascript 对象,而它自然可以使用两次,那应该如何正确编译为 ESM 呢?
// input: index.cjs exports.sum = 0; Promise.resolve().then(() => { exports.sum = 100; });
以下是一种不会出问题的代码转换结果:
// output: index.mjs const _default = {}; let sum = (_default.sum = 0); Promise.resolve().then(() => { sum CommonJS 向 ESM 转化,自然有构建工具的参与,比如= _default.sum = 100; }); export default _default; export { sum };
由于 Bundless 构建工具的兴起,要求所有的模块都是 ESM 模块化格式。
目前社区有一部分模块同时支持 ESM 与 CommonJS,但仍有许多模块仅支持 CommonJS/UMD,因此将 CommonJS 转化为 ESM 是全部模块 ESM 化的过渡阶段。
1. ESM 与 CommonJS 的导入导出的不同
在 ESM 中,导入导出有两种方式:
代码示例如下:
而在 CommonJS 中,导入导出的方法只有一种:
正因为有二者的不同,因此在二者转换的时候有一些兼容问题需要解决。
2. exports 的转化
正因为,二者有所不同,当 exports 转化时,既要转化为
export {}
,又要转化为export default {}
如果仅仅转为
export const a = 3
的具名导出,而不转换export default { a }
,将会出现什么问题?以下为例:3. module.exports 的转化
对于
module.exports
,我们可以遍历其中的 key (通过 AST),将 key 转化为Named Export
,将module.exports
转化为Default Export
如果
module.exports
导出的是函数如何处理呢,特别是exports
与module.exports
的程序逻辑混合在一起? 以下是一个正确的转换结果:也可以这么处理,将
module.exports
与exports
的代码使用函数包裹起来,此时我们无需关心其中的逻辑细节。4. 一些复杂的转化
ESM 与 CommonJS 不仅仅是简单的语法上的不同,它们在思维方式上就完全不同,因此还有一些较为复杂的转换。
__dirname
require(dynamicString)
以下代码涉及到编程逻辑,由于
exports
是一个动态的 Javascript 对象,而它自然可以使用两次,那应该如何正确编译为 ESM 呢?以下是一种不会出问题的代码转换结果:
5. CommonJS 向 ESM 的构建工具