# treeshake

类型: boolean | TreeshakingPreset | TreeshakingOptions
CLI: --treeshake/--no-treeshake
默认: true
type TreeshakingPreset = 'smallest' | 'safest' | 'recommended';

interface TreeshakingOptions {
	annotations?: boolean;
	correctVarValueBeforeDeclaration?: boolean;
	moduleSideEffects?: ModuleSideEffectsOption;
	preset?: TreeshakingPreset;
	propertyReadSideEffects?: boolean | 'always';
	tryCatchDeoptimization?: boolean;
	unknownGlobalSideEffects?: boolean;
}

type ModuleSideEffectsOption =
	| boolean
	| 'no-external'
	| string[]
	| HasModuleSideEffects;
type HasModuleSideEffects = (id: string, external: boolean) => boolean;

该选项用于决定是否应用除屑优化(tree-shaking),并微调除屑优化的过程。该选项的值设置为 false 时,Rollup 将生成更大的 bundle,但是可能会提高构建性能。你也可以从三个预设中选择一个,如果有新的选项加入,就会自动更新:

  • 值为

    "smallest"
    

    ,将选择选项值以尽可能减小输出大小。这对大多数代码库都应该有效,只要你不依赖于某些模式,目前是:

    • 有副作用的 getters 只有在返回值被使用时才会被保留(treeshake.propertyReadSideEffects: false)
    • 只有在至少使用了一个导出值的情况下,引入模块的代码才会被保留(treeshake.moduleSideEffects: false)
    • 你不应该打包依赖于检测损坏的内置函数的 polyfill(treeshake.tryCatchDeoptimization: false)
    • 一些语义问题可能被吞没(treeshake.unknownGlobalSideEffects: false,treeshake.correctVarValueBeforeDeclaration: false)
  • 值为 "recommended",对于大多数的使用模式来说,应该可以很好地工作。虽然一些语义问题可能会被吞没(treeshake.unknownGlobalSideEffects: false,treeshake.correctVarValueBeforeDeclaration: false)

  • 值为 "safest",试图在提供一些基本的除屑优化功能的同时,尽可能地符合规范。

  • 值为 true,相当于不指定该选项,并将始终选择默认值(见下文)

如果你发现由除屑优化算法造成的 bug,请给我们提 issue!将此选项的值设置为对象意味着启用除屑优化,并启用以下选项:

# treeshake.annotations

类型: boolean
CLI: --treeshake.annotations/--no-treeshake.annotations
默认: true

如果该选项值为 false,那么在确定函数调用和构造函数调用的副作用时,将会忽略纯注释的提示,比如,包含 @PURE 或 #PURE 的注释。这些注释需要紧接在调用代码之前才能生效。该选项的值设置为 false,以下代码将原封不动,否则以下包含 @PURE 注释的代码将被完全删除。

/*@__PURE__*/ console.log('side-effect');

class Impure {
	constructor() {
		console.log('side-effect');
	}
}

/*@__PURE__*/ new Impure();

# treeshake.correctVarValueBeforeDeclaration

类型: boolean
CLI: --treeshake.correctVarValueBeforeDeclaration/--no-treeshake.correctVarValueBeforeDeclaration
默认: false

在某些极端情况下,如果一个变量在其声明赋值之前被访问并且未被重新赋值,那么 Rollup 可能会错误地假设该变量在整个程序中都是常量,就像下面的示例一样。但是,如果使用 var 声明变量,则是不正确的,因为这些变量可以在其声明之前被访问,此时它们将被定义为 undefined。值为 true 时可以确保 Rollup 不会对使用 var 声明的变量的值做任何假设。但请注意,这可能会对除屑优化的结果产生明显的负面影响。

// 除非 treeshake.correctVarValueBeforeDeclaration === true,否则一切都将被除屑优化
let logBeforeDeclaration = false;

function logIfEnabled() {
	if (logBeforeDeclaration) {
		log();
	}

	var value = true;

	function log() {
		if (!value) {
			console.log('should be retained, value is undefined');
		}
	}
}

logIfEnabled(); // 可被移除
logBeforeDeclaration = true;
logIfEnabled(); // 需要保留,因为它展示日志

# treeshake.manualPureFunctions

类型: string[]
CLI: --treeshake.manualPureFunctions <names>

该选项允许手动定义一个函数名列表,这些函数名应该总是被认为是“纯”的,即它们在调用时没有副作用,如改变全局状态等。检查只按名称进行。

这不仅可以帮助去除死代码,而且还可以改善 JavaScript chunk 的生成,特别是在使用 output.experimentalMinChunkSize 时。

除了任何与该名称相匹配的函数,纯函数上的任何属性以及从纯函数返回的任何函数也将被视为纯函数,访问任何属性都不会被检查出副作用。

// rollup.config.js
export default {
	treeshake: {
		preset: 'smallest',
		manualPureFunctions: ['styled', 'local']
	}
	// ...
};

// code
import styled from 'styled-components';
const local = console.log;

local(); // 去除
styled.div`
	color: blue;
`; // 去除
styled?.div(); // 去除
styled()(); // 去除
styled().div(); // 去除

# treeshake.moduleSideEffects

类型: boolean| "no-external"| string[]| (id: string, external: boolean) => boolean
CLI: --treeshake.moduleSideEffects/--no-treeshake.moduleSideEffects/--treeshake.moduleSideEffects no-external
默认: true

如果该选项的值为 false,则假定像改变全局变量或不执行检查就记录等行为一样,没有引入任何内容的模块和外部依赖没有其他副作用。对于外部依赖,该选项将影响未使用的引入:

// 输入文件
import { unused } from 'external-a';
import 'external-b';
console.log(42);
// treeshake.moduleSideEffects === true 时的输出
import 'external-a';
import 'external-b';
console.log(42);
// treeshake.moduleSideEffects === false 时的输出
console.log(42);

对于非外部依赖模块,该选项值为 false 时,除非来自改模块的引入被使用,否则输出中将不会包含来自该模块的任何语句:

// 输入文件 a.js
import { unused } from './b.js';
console.log(42);

// 输入文件 b.js
console.log('side-effect');
const ignored = 'will still be removed';
// treeshake.moduleSideEffects === true 时的输出
console.log('side-effect');

console.log(42);
// treeshake.moduleSideEffects === false 时的输出
console.log(42);

该选项的值也可以是具有副作用的模块列表,或者是一个返回指定模块的函数。该选项的值为 "no-external" 将意味着如果可能,则仅删除外部依赖,它等同于函数 (id, external) => !external。

如果一个模块将此选项设置为 false 并从另一个模块重新导出变量,而且该变量被使用了,则扫描重新导出模块是否存在副作用的问题取决于变量的重新导出方式:

// 输入文件 a.js
import { foo } from './b.js';
console.log(foo);

// 输入文件 b.js
// 直接重新导出将忽略副作用
export { foo } from './c.js';
console.log('this side-effect is ignored');

// 输入文件 c.js
// 非直接重新导出将包含副作用
import { foo } from './d.js';
foo.mutated = true;
console.log('this side-effect and the mutation are retained');
export { foo };

// 输入文件 d.js
export const foo = 42;
// treeshake.moduleSideEffects === false 时的输出
const foo = 42;

foo.mutated = true;
console.log('this side-effect and the mutation are retained');

console.log(foo);

请注意,尽管名字有点误导,但此选项不会向没有副作用的模块“添加”副作用。重要的是,例如,因为你需要这个模块来跟踪依赖关系,而将空模块“包含”在 bundle 中,则插件接口允许你通过 resolveId,load 或 transform 钩子将模块指定为不被除屑优化删除。

# treeshake.preset

类型: "smallest" | "safest"| "recommended"
CLI: --treeshake <value>

该选项可以选择上面列出的预设之一,同时覆盖一些选项。

export default {
	treeshake: {
		preset: 'smallest',
		propertyReadSideEffects: true
	}
	// ...
};

# treeshake.propertyReadSideEffects

类型: boolean| 'always'
CLI: --treeshake.propertyReadSideEffects/--no-treeshake.propertyReadSideEffects
默认: true

如果该选项值为 true,则保留未使用的属性读取,这会被 Rollup 确定为具有副作用。这包括访问 null 或 undefined 的属性,或通过属性访问触发显式 getter。请注意,这并不包括解构赋值或对象上当作函数参数传递的 getter。

如果值为 false,则假定读取对象的属性将永远不会有副作用。根据你的代码,禁用该属性能够显著缩小 bundle 的大小,但是如果你依赖了 getters 或非法属性访问的造成的错误,那么可能会破坏该功能。

如果值为 'always',则假定所有成员属性访问,包括解构赋值,都具有副作用。建议对依赖具有副作用 getter 的代码使用此设置。它通常会导致更大的包大小,但比完全禁用 treeshake 更小。

// 如果 treeshake.propertyReadSideEffects === false 将会被移除
const foo = {
	get bar() {
		console.log('effect');
		return 'bar';
	}
};
const result = foo.bar;
const illegalAccess = foo.quux.tooDeep;

# treeshake.tryCatchDeoptimization

类型: boolean
CLI: --treeshake.tryCatchDeoptimization/--no-treeshake.tryCatchDeoptimization
默认: true

默认情况下,Rollup 假定在进行除屑优化时,很多运行时内置的全局变量的行为均符合最新规范,并且不会引发意外错误。为了支持,像依赖于抛出错误的特征检测工作流,Rollup 将默认禁用 try 语句中的除屑优化。如果函数参数在 try 语句中被使用,那么该参数也不会被优化处理。如果你不需要此功能,想要在 try 语句中使用除屑优化,则可以设置 treeshake.tryCatchDeoptimization 为 false。

function otherFn() {
	// 尽管该函数时在 try 语句中使用,但是下列代码
	// 然会被当做无副作用而被移除
	Object.create(null);
}

function test(callback) {
	try {
		// tryCatchDeoptimization: true 时,在 try 语句块中,
		// 将保留对本来没有副作用的全局函数的调用
		Object.create(null);

		// 对其他函数的调用也被保留,但该函数的主体
		// 可能再次受到除屑优化
		otherFn();

		// 如果函数类型参数被调用,那么所有被该函数使用的
		// 参数将不会被优化
		callback();
	} catch {}
}

test(() => {
	// 将会保留
	Object.create(null);
});

// 调用将保留,但同样,otherFn 会被优化
test(otherFn);

# treeshake.unknownGlobalSideEffects

类型: boolean
CLI: --treeshake.unknownGlobalSideEffects/--no-treeshake.unknownGlobalSideEffects
默认: true

因为访问不存在的全局变量会引起错误,所以默认情况下 Rollup 保留所有对非内置全局变量的访问。该选项的值设置为 false 可以避免该检查。对于大多数代码库而言,这样做很可能更安全。

// 输入
const jQuery = $;
const requestTimeout = setTimeout;
const element = angular.element;

// unknownGlobalSideEffects == true 时的输出
const jQuery = $;
const element = angular.element;

// unknownGlobalSideEffects == false 时的输出
const element = angular.element;

在这个例子中,最后一行将被始终保留,用于访问 element 属性,但如果 angular 值比如为 null,也可能抛出错误。为了避免这种情况的检查,可以设置 treeshake.propertyReadSideEffects 为 false。

Last Updated: 6/14/2023, 8:56:23 AM