Appearance
代码拆分
¥Code Splitting
代码拆分是从模块创建代码块的过程。本章介绍其行为及其背后的原理。
¥Code splitting is the process of creating chunks from modules. This chapter describes its behavior and the principles behind it.
代码拆分不可控。它遵循某些规则运行。因此,我们也将其称为自动分块,而不是由 advancedChunks 手动分块。
¥Code splitting is not controllable. It runs following certain rules. Thus, we will also refer to it as automatic chunking against manual chunking done by advancedChunks.
代码拆分会生成两种类型的代码块。
¥Two types of chunks are generated by code splitting.
条目块
¥Entry chunks
入口块由静态连接的模块组合而成。"statically" 表示静态 import ... from '...' 或 require(...)。
¥Entry chunks are generated by combining modules connected statically into a chunk. "statically" means static import ... from '...' or require(...).
有两种类型的入口块。
¥There are two types of entry chunks.
第一个是初始块。初始块由用户配置生成。例如,input: ['./a.js', './b.js'] 定义了两个初始块。
¥The first one is initial chunks. Initial chunks are generated due to users' configuration. For example, input: ['./a.js', './b.js'] defines two initial chunks.
第二个是动态块。动态导入会生成动态代码块。动态导入用于按需加载代码,因此我们不会将导入的代码与导入器放在一起。
¥The second one is dynamic chunks. Dynamic chunks are generated due to dynamic imports. Dynamic imports are used to load code on demand, so we don't put imported code together with the importers.
对于以下代码,将生成两个块:
¥For the following code, two chunks will be generated:
js
// entry.js (included in `input` option)
import foo from './foo.js';
import('./dyn-entry.js');
// dyn-entry.js
require('./bar.js');
// foo.js
export default 'foo';
// bar.js
module.exports = 'bar';在本例中,有两组静态连接的模块。
¥In this case, there are two groups of statically connected modules.
第 1 组:
entry.js和foo.js¥Group 1:
entry.jsandfoo.js第 2 组:
dyn-entry.js和bar.js¥Group 2:
dyn-entry.jsandbar.js
由于存在两个组,最终自动分块将生成两个块。
¥Since there are two groups, in the end, automatic chunking will generate two chunks.
常用代码块
¥Common chunks
当模块被至少两个不同的条目静态导入时,会生成通用块。这些模块被放入一个单独的 chunk 中。
¥Common chunks are generated when a module gets statically imported by at least two different entries. Those modules are put into a separate chunk.
此行为的目的是:
¥The purpose of this behavior is:
确保每个 JavaScript 模块在最终包输出中都是单例的。
¥Ensure every JavaScript module is singleton in the final bundle output.
执行条目时,只有导入的模块才会被执行。
¥When a entry gets executed, only imported modules should get executed.
需要注意的是,一个模块是否可以放入同一个 common chunk 取决于它是否被相同的条目导入。
¥It is important to note that whether a module could be put into the same common chunk is determined by if it is imported by the same entries.
对于以下代码,将生成六个块:
¥For the following code, six chunks will be generated:
js
// entry-a.js (included in `input` option)
import 'shared-by-ab.js';
import 'shared-by-abc.js';
console.log(globalThis.value);
// entry-b.js (included in `input` option)
import 'shared-by-ab.js';
import 'shared-by-bc.js';
import 'shared-by-abc.js';
console.log(globalThis.value);
// entry-c.js (included in `input` option)
import 'shared-by-bc.js';
import 'shared-by-abc.js';
console.log(globalThis.value);
// shared-by-ab.js
globalThis.value = globalThis.value || [];
globalThis.value.push('ab');
// shared-by-bc.js
globalThis.value = globalThis.value || [];
globalThis.value.push('bc');
// shared-by-abc.js
globalThis.value = globalThis.value || [];
globalThis.value.push('abc');块将按如下方式生成:
¥The chunks will be generated as follows:
js
import './common-ab.js';
import './common-abc.js';js
import './common-ab.js';
import './common-bc.js';
import './common-abc.js';js
import './common-bc.js';
import './common-abc.js';js
globalThis.value = globalThis.value || [];
globalThis.value.push('ab');js
globalThis.value = globalThis.value || [];
globalThis.value.push('bc');js
globalThis.value = globalThis.value || [];
globalThis.value.push('abc');entry-*.js 块的生成原因如上所述。common-*.js 块是常见的块。创建这些是因为:
¥entry-*.js chunks are generated by the reason discussed above. common-*.js chunks are the common chunks. These are created because:
common-ab.js:shared-by-ab.js被entry-a.js和entry-b.js导入。¥
common-ab.js:shared-by-ab.jsis imported by bothentry-a.jsandentry-b.js.common-bc.js:shared-by-bc.js被entry-b.js和entry-c.js导入。¥
common-bc.js:shared-by-bc.jsis imported by bothentry-b.jsandentry-c.js.common-abc.js:shared-by-abc.js被所有 3 个条目导入。¥
common-abc.js:shared-by-abc.jsis imported by all 3 entries.
你可能会问,为什么自动分块功能不将 shared-by-*.js 文件放入一个公共的块中?原因是这样做会违背原始代码的意图。
¥You may ask why automatic chunking doesn't place shared-by-*.js files into a single common chunk. The reason is that doing so would violate the original code's intention.
对于上面的例子,如果创建了一个公共块,它将如下所示:
¥For the example above, if a single common chunk were created, it will be like:
js
globalThis.value = globalThis.value || [];
globalThis.value.push('ab');
globalThis.value = globalThis.value || [];
globalThis.value.push('bc');
globalThis.value = globalThis.value || [];
globalThis.value.push('abc');对于此输出,执行每个条目都会输出 ['ab', 'bc', 'abc']。然而,原始代码会为每个条目输出不同的结果:
¥For this output, executing each entry will output ['ab', 'bc', 'abc']. However, the original code outputs a different result for each entry:
entry-a.js:['ab', 'abc']entry-b.js:['ab', 'bc', 'abc']entry-c.js:['bc', 'abc']
模块放置顺序
¥Module Placing Order
Rolldown 会尝试按照原始代码中声明的顺序放置模块。
¥Rolldown tries to place your modules in the order declared in the original code.
对于以下代码:
¥For the following code:
js
// entry.js
import { foo } from './foo.js';
console.log(foo);
// foo.js
export var foo = 'foo';Rolldown 将尝试通过模拟执行来计算顺序,从入口文件开始。
¥Rolldown will try to calculate the order by emulating the execution, starting from entries.
在本例中,执行顺序为 [foo.js, entry.js]。因此,打包输出将如下所示:
¥In this case, the execution order is [foo.js, entry.js]. So the bundle output will be like:
js
// foo.js
var foo = 'foo';
// entry.js
console.log(foo);执行顺序不优先
¥Respecting Execution Order doesn't take precedence
但是,Rolldown 有时会不遵循模块的原始顺序来放置它们。这是因为确保模块是单例优先于按声明顺序放置它们。
¥However, Rolldown sometimes places modules without respecting their original order. This is because ensuring that modules are singletons takes precedence over placing them in the declared order.
对于以下代码:
¥For the following code:
js
// entry.js (included in `input` option)
import './setup.js';
import './execution.js';
import('./dyn-entry.js');
// setup.js
globalThis.value = 'hello, world';
// execution.js
console.log(globalThis.value);
// dyn-entry.js
import './execution.js';打包输出将为:
¥The bundle output will be:
js
import './common-execution.js';
// setup.js
globalThis.value = 'hello, world';js
import './common-execution.js';js
console.log(globalThis.value);common-execution.js 是一个通用的 chunk。之所以生成它,是因为 execution.js 被 entry.js 和 dyn-entry.js 导入。
¥common-execution.js is a common chunk. It is generated because execution.js is imported by both entry.js and dyn-entry.js.
此示例展示了问题所在:在打包之前,代码输出 hello, world,但在打包之后,输出 undefined。目前,没有简单的方法可以解决这个问题,对于其他输出 ESM 的打包器也是如此。
¥This example shows the problem, before bundling, the code outputs hello, world, but after bundling, it outputs undefined. Currently, there's no easy way to solve this problem, as well for other bundlers that output ESM.
其他打包器的相关问题
关于如何解决这个问题有一些讨论。一种方法是,当模块违反其原始顺序时,生成更通用的块。但这会生成更多通用的块,这不是一个好主意。Rolldown 尝试通过 InputOptions#experimental#strictExecutionOrder 解决这个问题,它会注入一些辅助代码,以确保在保留 ESM 输出并避免额外公共块的同时,遵循执行顺序。
¥There are some discussions on how to solve this problem. One way is to generate more common chunks once a module violates its original order. But this will generate more common chunks, which is not a good idea. Rolldown tries to solve this issue by InputOptions#experimental#strictExecutionOrder, which injects some helper code to ensure the execution order is respected while keeping esm output and avoiding additional common chunks.