Webpack 4: import() and CommonJs

翻译自:webpack 4: import() and CommonJsopen in new window

webpack 4breaking changes之一是,使用import()导入非 EcmaScript 模块时的行为。

实际上,当使用import()时,有许多问题需要考虑。让我们先从如下概念开始:

  • Source: 包含了import()表达式的模块

  • Target: 被Source里的import()表达式引用的模块

  • non-ESM: 一个 CommonJs 或 AMD 模块,没有设置__esModule: true

  • transpiled-ESM: 一个 CommonJs 模块,设置了__esModule: true(因为它已经被从ESM转换为 CommonJs)

  • ESM: 一个常规的 EcmaScript 模块

  • strict-ESM: 一个更加严格的 EcmaScript 模块,比如来自于一个.mjs文件

  • JSON: 一个json文件

需要考虑如下这些情况:

  • (A) Source: non-ESM, transpiled-ESMESM
  • (B) Source: strict-ESM (mjs)
  • (1) Target: non-ESM
  • (2) Target: transpiled-ESM (__esModule)
  • (3) Target: ESMstrict-ESM (mjs)
  • (4) Target: JSON

如下示例可以让问题更加容易明白:

// (A) source.js
import("./target").then(result => console.log(result));

// (B) source.mjs
import("./target").then(result => console.log(result));

// (1) target.js
exports.name = "name";
exports.default = "default";

// (2) target.js
exports.__esModule = true;
exports.name = "name";
exports.default = "default";

// (3) target.js or target.mjs
export const name = "name";
export default "default";

// (4) target.json
{ name: "name", default: "default" }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

A3 and B3: import(ESM)

// (A) source.js
import("./target").then(result => console.log(result));

// (B) source.mjs
import("./target").then(result => console.log(result));

// (3) target.js or target.mjs
export const name = "name";
export default "default";
1
2
3
4
5
6
7
8
9

实际上这两种情况在 ESM 规格文档里已有提及,也是所有这些情况里唯一被提及的。

import()将解析到Target模块的命名空间对象。为了兼容性考虑,我们也添加一个__esModule标志到命名空间对象上,对于转义后的导入来说是可用的。

{ __esModule: true, name: "name", default: "default" }
1

A1: import(CJS)

// (A) source.js
import("./target").then(result => console.log(result));

// (1) target.js
exports.name = "name";
exports.default = "default";
1
2
3
4
5
6

我们导入了一个 CommonJs 模块,webpack 3只解析到module.exports的值,而webpack 4将为这个 CommonJs 模块创建一个人工的命名空间对象,以便让import()保持一致,也解析到这个命名空间对象。

CommonJs 模块的默认导出是module.exports的值。webpack也允许通过import { property } from "cjs"的方式从 CommonJs 模块里导入特定的属性,因此我们也允许import()这么做。

注意:在这种情况下,default属性会被默认的default隐藏。

// webpack 3
{ name: "name", default: "default" }

// webpack 4
{ name: "name", default: { name: "name", default: "default" } }
1
2
3
4
5

B1: import(CJS).mjs

// (B) source.mjs
import("./target").then(result => console.log(result));

// (1) target.js
exports.name = "name";
exports.default = "default";
1
2
3
4
5
6

strict-ESM模块里,我们不允许通过import导入特定的属性,只允许non-ESM的默认导出。

{ default: { name: "name", default: "default" } }
1

A2: import(transpiled-ESM)

// (A) source.js
import("./target").then(result => console.log(result));

// (2) target.js
exports.__esModule = true;
exports.name = "name";
exports.default = "default";
1
2
3
4
5
6
7

webpack支持__esModule标志,将把 CommonJs 模块升级为 ESM 模块。

{ __esModule: true, name: "name", default: "default" }
1

B2: import(transpiled-ESM).mjs

// (B) source.mjs
import("./target").then(result => console.log(result));

// (2) target.js
exports.__esModule = true;
exports.name = "name";
exports.default = "default";
1
2
3
4
5
6
7

strict-ESM模块里,不支持__esModule标志。你可以认为这是破坏性改变,但是它是与 Node.js 保持一致的。

{ default: { __esModule: true, name: "name", default: "default" } }
1

A4 and B4: import(json)

// (A) source.js
import("./target").then(result => console.log(result));

// (B) source.mjs
import("./target").then(result => console.log(result));

// (4) target.json
{ name: "name", default: "default" }
1
2
3
4
5
6
7
8

当导入 JSON 时,导入特定属性也是支持的,即使是在strict-ESM模块里。JSON 也会暴露整个对象作为默认输出。

{ name: "name", default: { name: "name", default: "default" } }
1

总结

总而言之,只有一种情况改变了。当输出一个对象时,是没有问题的。但是当使用module.exports导出一个非对象,就会存在问题。比如:

module.exports = 42;
1

你将需要使用default属性。

// webpack 3
42
// webpack 4
{ default: 42 }
1
2
3
4