# 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())。
Last Updated: 6/14/2023, 8:56:23 AM