CommonJS
如以下代码:cjs导出name,changeName(),main导入cjs并打印name。
//cjs.js
let name = 'cjs'
function changeName() {
name += ' changed'
}
setTimeout(() => {
console.log('3:', name);
})
module.exports = {
name,
changeName
}
// main.js
let cjs = require('./cjs')
console.log('1:', cjs.name)
cjs.changeName()
console.log('2:',cjs.name)
// result
1: cjs
2: cjs
3: cjs changed
简单解释:当遇到require('./cjs')时,首先查看require.cache有没有该模块的记录,如果有直接返回记录的Module.exports,没有就进行模块解析(CommonJS的解析方式)来查找模块,找到模块就创建一个Module对象放入require.cache中:注意创建的Module对象中的exports为{},接下来就去执行cjs,执行完将模块的module.exports返回给main。
当main调用changeName()时,因为闭包的因素,实际改变的是cjs中的变量。
ES6
// esm.js
let name = 'esm'
let age = 2015
function changeName() {
name += ' changed'
}
function changeAge() {
age += 7
}
setTimeout(() => {
console.log('3:', name)
console.log('3:', age)
})
export { name, changeAge }
export default {
age,
changeName
}
// main.js
import * as esm from './esm.js'
console.log('1:', esm.name);
console.log('1:', esm.default.age);
esm.default.changeName()
esm.changeAge()
console.log('2:', esm.name);
console.log('2:', esm.default.age);
// result
1: esm
1: 2015
2: esm changed
2: 2015
3: esm changed
3: 2022
简单解释:export中所有标识符(name, changeAge, default)都是模块内部变量的引用,遇到import再生成一个只读引用。
TS
在TypeScript中,当配置为"module" : "CommonJS"时,ts可以同时使用CommonJS和ES6两种导入导出语法:
//cjs:
import x = require('./x')
export = { x }
// ems:
export { x, y }
import { x, y } from './x'
export default { x }
import X from './x'
-
当用
cjs语法引入cjs模块时:tsc不对导入导出做额外处理,并且导出的行为和CommonJS一致。
-
当用
esm语法引入esm模块时:tsc会将esm转为cjs,上述代码被转为:
// esm.js
"use strict";
exports.__esModule = true;
exports.changeAge = exports.name = void 0;
var name = 'esm';
exports.name = name;
var age = 2015;
function changeName() {
exports.name = name += ' changed';
}
function changeAge() {
age += 7;
}
exports.changeAge = changeAge;
setTimeout(function () {
console.log('3:', name);
console.log('3:', age);
});
exports["default"] = {
age: age,
changeName: changeName
};
// main.js
"use strict";
exports.__esModule = true;
var esm = require("./esmt");
console.log('1:', esm.name);
console.log('1:', esm["default"].age);
esm["default"].changeName();
esm.changeAge();
console.log('2:', esm.name);
console.log('2:', esm["default"].age);
export转为exports,export default转为exports.default。
import { x }取exports,import *取exports和exports.default,import X取exports.default。- 为了保证转换后的
export的行为和esm一致,做了如exports.name = name += ' changed';处理。
-
当用
cjs语法引入esm模块时:tsc同样将esm转为cjs,导出的行为和esm一致。 -
当用
esm语法引入cjs模块时:
- 如果
"esModuleInterop": false,ts不进行交叉导入处理,那么就会出现错误:
import X会去找exports.default,但根本就没有exports.default,就会出现undefined。
import { x }和import *虽然会报错,但实际生成的js是可以运行的。 - 如果
"esModuleInterop": true,ts会对交叉导入进行额外处理:
import X:再包裹一层default
// main.ts
import cjs from './cjst'
console.log(cjs.name);
// 转化为main.js
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
exports.__esModule = true;
var cjst_1 = __importDefault(require("./cjst"));
console.log(cjst_1["default"].name);
import { x }:无需处理
// main.ts
import { name } from './cjst'
console.log(name);
// 转换成main.js
"use strict";
exports.__esModule = true;
var cjst_1 = require("./cjst");
console.log(cjst_1.name);
import *:再包裹一层default,并打平对象
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
exports.__esModule = true;
var cjs = __importStar(require("./cjst"));
console.log(cjs.name);
这么多处理,谁能记得住,所以ts就规定了其中一种方式:import X为正统方式,使用其他方法都会出现:只能通过启用 "esModuleInterop" 标志并引用其默认导出,使用 ECMAScript 导入/导出来引用此模块。ts(2497)即:This module can only be referenced with ECMAScript imports/exports by turning on the 'esModuleInterop' flag and referencing its default export.ts(2497)
当然,导出的行为和CommonJS一致。
__esModule
常见于exports.__esModule = true;用来告知导入方,我是用esm的语法导出的。ts不写导出默认是esm导出。CommonJS导出则没有这个标识。
webpack
__webpack_require__大杀四方,直接使用js解决各种模块形式。
cjs引用cjs:行为和CommonJS一致。esm引用esm:行为和esm一致。cjs引用esm:行为和esm一致。esm引用cjs:行为和CommonJS一致。
总之:导入后的行为只与模块的导出方式有关。