# output.interop
类型: | "compat" | "auto"| "esModule"| "default"| "defaultOnly"| ((id: string) => "compat"| "auto"| "esModule"| "default"| "defaultOnly") |
---|---|
CLI: | --interop <value> |
默认: | "default" |
该选项用于控制 Rollup 如何处理默认值,命名空间和动态引入像 CommonJS 这样并不支持这些概念的外部依赖格式。请注意,"default" 的默认模式是模仿 NodeJS 的行为,与 TypeScript 的 esModuleInterop 不同。要获得像 TypeScript 中的行为,需要明确地设置该值为 "auto"。在例子中,我们将使用 CommonJS 格式,但该互操作(interop)的选择也同样适用于 AMD、IIFE 和 UMD 目标。
为了理解不同的取值,我们假设打包以下代码为 cjs:
import ext_default, * as external from 'external1';
console.log(ext_default, external.bar, external);
import('external2').then(console.log);
请记住,对于 Rollup 来说,import * as ext_namespace from 'external'; console.log(ext_namespace.bar); 完全等同于 import {bar} from 'external'; console.log(bar);,并且会打包出同样的代码。然而,在上面的例子中,命名空间对象本身也被传递给一个全局函数,这意味着我们需要它组成一个正确的对象。
"default" 意为所需的值应该被视为引入模块的默认出口,就像在 NodeJS 中从 ES 模块上下文引入 CommonJS 一样。其也支持命名的引入,它们被视为默认引入的属性。为了创建命名空间对象,Rollup 注入了这些辅助函数:
var external = require('external1'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty( n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } } ); } }); } n.default = e; return Object.freeze(n); } var external__namespace = /*#__PURE__*/ _interopNamespaceDefault(external); console.log(external, external__namespace.bar, external__namespace); Promise.resolve() .then(function () { return /*#__PURE__*/ _interopNamespaceDefault(require('external2')); }) .then(console.log);
"esModule" 意为 ES 模块转译为所需模块,其中所需的值对应模块的命名空间,而默认的导出是导出对象的 .default 属性。这是唯一不会注入任何辅助函数的互操作类型:
var external = require('external1'); console.log(external.default, external.bar, external); Promise.resolve() .then(function () { return require('external2'); }) .then(console.log);
当使用 esModule 时,Rollup 不添加额外的辅助函数,并且对默认出口也支持实时绑定。
"auto" 结合了 "esModule" 和 "default",通过注入辅助函数,其包含在运行时检测所需的值是否包含 __esModule 属性 的代码。添加这个属性是 TypeScript esModuleInterop、Babel 和其他工具实现的一种解决方式,标志着所需值是 ES 模块编译后的命名空间:
var external = require('external1'); function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty( n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } } ); } }); } n.default = e; return Object.freeze(n); } var external__namespace = /*#__PURE__*/ _interopNamespace(external); console.log( external__namespace.default, external__namespace.bar, external__namespace ); Promise.resolve() .then(function () { return /*#__PURE__*/ _interopNamespace(require('external2')); }) .then(console.log);
注意 Rollup 是如何重复使用创建的命名空间对象来获得 default 导出的。如果不需要命名空间对象,Rollup 将使用一个更简单的辅助函数:
// 输入 import ext_default from 'external'; console.log(ext_default); // 输出 var ext_default = require('external'); function _interopDefault(e) { return e && e.__esModule ? e : { default: e }; } var ext_default__default = /*#__PURE__*/ _interopDefault(ext_default); console.log(ext_default__default.default);
compat 等同于 "auto",只是它对默认导出使用了一个稍微不同的辅助函数,其检查是否存在一个 default 属性而不是 __esModule 属性。除了 CommonJS 模块导出的属性 "default" 不应该是默认导出的这种罕见情况,该值通常有助于使互操作“正常工作”,因为它不依赖于特异的实现,而是使用鸭子类型(duck-typing):
var external = require('external1'); function _interopNamespaceCompat(e) { if (e && typeof e === 'object' && 'default' in e) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty( n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } } ); } }); } n.default = e; return Object.freeze(n); } var external__namespace = /*#__PURE__*/ _interopNamespaceCompat(external); console.log( external__namespace.default, external__namespace.bar, external__namespace ); Promise.resolve() .then(function () { return /*#__PURE__*/ _interopNamespaceCompat(require('external2')); }) .then(console.log);
于 "auto" 类似,如果不需要命名空间,Rollup 将使用一个更简单的辅助函数:
// 输入 import ext_default from 'external'; console.log(ext_default); // 输出 var ext_default = require('external'); function _interopDefaultCompat(e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var ext_default__default = /*#__PURE__*/ _interopDefaultCompat(ext_default); console.log(ext_default__default.default);
"defaultOnly" 与 "default" 类似,但有以下几点区别:
- 禁止命名引入。如果遇到这样的引入,Rollup 会抛出一个错误,即使是 es 和 system 格式。这样可以确保 es 版本的代码能够正确引入 Node 中的非内置 CommonJS 模块。
- 虽然命名空间内再次输出 export * from 'external'; 不被禁止,但会被忽略掉,并且会导致 Rollup 抛出警告,因为如果没有命名的输出,它们并不会产生影响。
- 当一个命名空间对象生成时,Rollup 将会使用一个更简单的辅助函数。
下面示例代码展示了 Rollup 产生的内容。注意,我们从代码中删除了 external.bar,否则,Rollup 会抛出一个错误,因为如上所述,这等同于一个命名引入。
var ext_default = require('external1'); function _interopNamespaceDefaultOnly(e) { return Object.freeze({ __proto__: null, default: e }); } var ext_default__namespace = /*#__PURE__*/ _interopNamespaceDefaultOnly(ext_default); console.log(ext_default, ext_default__namespace); Promise.resolve() .then(function () { return /*#__PURE__*/ _interopNamespaceDefaultOnly( require('external2') ); }) .then(console.log);
当值为一个函数时,Rollup 将把每个外部依赖 id 传递给这个函数,以控制每个依赖关系的互操作类型。
例如,如果所有的依赖都是 CommonJs,下面的配置将确保只允许从 Node 内置的命名引入:
// rollup.config.js import builtins from 'builtins'; const nodeBuiltins = new Set(builtins()); export default { // ... output: { // ... interop(id) { if (nodeBuiltins.has(id)) { return 'default'; } return 'defaultOnly'; } } };
有一些额外的选项对生成互操作的代码有影响:
- 设置 output.externalLiveBindings 为 false 将生成简化的命名空间辅助函数,以及简化提取默认引入的代码。
- 设置 output.freeze 为 false 将防止生成的互操作命名空间对象被冻结(Object.freeze())。