Appearance
高级 Chunk
¥Advanced Chunks
advancedChunks 是一个强大的功能,它允许你使用 manual chunking 来补充 代码拆分 完成的 automatic chunking。当你想通过将应用拆分成更小、更易于管理的部分来优化加载性能时,此功能非常有用。
¥advancedChunks is a powerful feature that allows you to do manual chunking to complement the automatic chunking done by code splitting. This is useful when you want to optimize the loading performance of your application by splitting it into smaller, more manageable pieces.
在阅读本指南之前,你应该首先了解 Rolldown 的 代码拆分 功能。本指南将解释 advancedChunks 的工作原理以及如何有效地使用它。
¥Before reading this guide, you should first understand the code splitting feature of Rolldown. This guide will explain how advancedChunks works and how to use it effectively.
在深入探讨细节之前,让我们先澄清一些事项。
¥Before we dive into the details, let's clarify some things first.
automatic chunking和manual chunking并不矛盾。使用manual chunking并不意味着禁用automatic chunking。根据你的配置,模块将被automatic chunking或manual chunking捕获,但不会同时被捕获。如果模块未被manual chunking捕获,它仍将被放入由automatic chunking创建的块中,同时遵循我们在 代码拆分 指南中解释的规则。¥
automatic chunkingandmanual chunkingare not contradictory. Usingmanual chunkingdoes not mean disablingautomatic chunking. A module will be either captured byautomatic chunkingormanual chunkingdepending on your configuration, but not both. If a module is not captured bymanual chunking, it will still be put into a chunk which is created byautomatic chunkingwhile respecting the rules we explained in the code splitting guide.
为什么要使用 advancedChunks?
¥Why use advancedChunks?
automatic chunking 不考虑加载性能或缓存失效。它只是根据模块的静态导入对其进行分组。这可能导致分块效果不佳,创建较大的块,这些块可能无法实现高性能加载,或导致每次部署时缓存失效。
¥The automatic chunking doesn't take loading performance or cache invalidation into account. It simply groups modules based on their static imports. This can lead to suboptimal chunking, where large chunks are created that may not be performant for loading or cause cache invalidation for every deployment.
如何使用 advancedChunks?
¥How to use advancedChunks?
我们来看以下示例:
¥Let's take a look at the following example:
jsx
// index.jsx
import * as ReactDom from 'react-dom';
import App from './App.jsx';
ReactDom.createRoot(document.getElementById('root')).render(
<App />,
);
// App.jsx
import * as React from 'react';
import { Button } from 'ui-lib';
export default function App() {
return <Button onClick={() => alert('Button clicked!')} />;
}你将获得以下输出:
¥and you get the following output:
js
// node_modules/react/index.js
'React library code';
// node_modules/ui-lib/index.js
'UI library code';
// node_modules/react-dom/index.js
'ReactDOM library code';
// App.js
function App() {
return <Button onClick={() => alert('Button clicked!')} />;
}
// index.js
ReactDom.createRoot(document.getElementById('root')).render(<App />);在此示例中:
¥In this example,
我们使用了 3 个库:
react、react-dom和ui-lib。¥We used 3 libraries:
react,react-dom, andui-lib.output-hash0.js是 Rolldown 生成的输出文件。¥
output-hash0.jsis the output file generated by Rolldown.hash0是输出文件的哈希值,如果文件内容发生变化,哈希值也会随之变化。¥
hash0is the hash of the output file, it changes if the content of the file changes.
减少缓存失效
¥Reduce cache invalidation
我们先来谈谈缓存失效。此处的缓存失效意味着当你部署应用的新版本时,浏览器需要下载文件的新版本。如果文件较大,这可能会导致用户体验不佳。
¥Let's talk about cache invalidation first. Cache invalidation here means that when you deploy a new version of your application, the browser will need to download the new version of the file. If the file is large, this can lead to a poor user experience.
例如,如果你更改了 app.jsx 文件:
¥For example, if you change the app.jsx file:
jsx
function App() {
return <Button onClick={() => alert('Button clicked!')} />;
return <Button onClick={() => alert('Button clicked!!!')} />;
}然后,自然而然地,你将获得一个与 output-hash0.js 内容相同的 output-hash1.js 文件,除了 App 函数的变化。
¥then naturally, you get a output-hash1.js file with the same content as output-hash0.js, except for the change in the App function.
现在,如果你部署应用的新版本,浏览器将需要下载整个 output-hash1.js 文件,即使其中只有一小部分发生了更改。这是因为文件的哈希值已更改,浏览器会将其视为新文件。
¥Now, if you deploy this new version of your application, the browser will need to download the entire output-hash1.js file, even though only a small part of it has changed. This is because the hash of the file has changed, and the browser will treat it as a new file.
为了解决这个问题,我们可以使用 advancedChunks 将输出库拆分成单独的块,因为与应用代码相比,它们不会频繁更改。
¥To solve this problem, we can use advancedChunks to split output libraries into separate chunks, because they don't change frequently compared to application code.
js
export default {
// ... other configurations
advancedChunks: {
groups: [
{
test: /node_modules/,
name: 'libs',
},
],
},
};使用上述 advancedChunks 选项,输出将如下所示:
¥By using the above advancedChunks option, the output will look like this:
js
import ... from './libs-hash0.js';
// App.js
function App() {
return <Button onClick={() => alert("Button clicked!")} />;
}
// index.js
ReactDom.createRoot(document.getElementById("root")).render(<App />);js
// node_modules/react/index.js
"React library code";
// node_modules/ui-lib/index.js
"UI library code";
// node_modules/react-dom/index.js
"ReactDOM library code";
export { ... };例如,在你更改 app.jsx 文件后
¥For example, after you change the app.jsx file
jsx
function App() {
return <Button onClick={() => alert('Button clicked!')} />;
return <Button onClick={() => alert('Button clicked!!!')} />;
}你将获得如下输出:
¥you will get output like this:
js
import ... from './libs-hash0.js';
// App.js
function App() {
return <Button onClick={() => alert("Button clicked!!!")} />;
}
// index.js
ReactDom.createRoot(document.getElementById("root")).render(<App />);js
// node_modules/react/index.js
"React library code";
// node_modules/ui-lib/index.js
"UI library code";
// node_modules/react-dom/index.js
"ReactDOM library code";
export { ... };libs-hash0.js文件未更改,因此浏览器可以使用文件的缓存版本。¥The
libs-hash0.jsfile is not changed, so the browser can use the cached version of the file.output-hash1.js文件已更改,因此浏览器将下载文件的新版本。¥The
output-hash1.jsfile is changed, so the browser will download the new version of the file.
提升加载性能
¥Improve loading performance
advancedChunks 还可以通过将应用拆分为多个实际数量的块并利用浏览器的并行加载功能来提高应用的加载性能。
¥advancedChunks can also be used to improve the loading performance of your application by splitting it into a practical number of chunks and taking advantage of browser's parallel loading capabilities.
在上例中,我们将所有库放在一个块中,这对于加载性能来说并非最佳。如果库太大,浏览器将花费很长时间下载该块,这会导致糟糕的用户体验。
¥In the previous example, we put all the libraries into a single chunk, which is not optimal for loading performance. If the libraries are too large, the browser will spend a long time downloading the chunk, which can lead to a poor user experience.
为了解决这个问题,我们可以使用 advancedChunks 将库拆分成单独的块,以便浏览器可以并行下载它们。
¥To solve this problem, we can use advancedChunks to split the libraries into separate chunks, so that the browser can download them in parallel.
js
export default {
// ... other configurations
advancedChunks: {
groups: [
{
test: /node_modules\/react/,
name: 'react',
},
{
test: /node_modules\/react-dom/,
name: 'react-dom',
},
{
test: /node_modules\/ui-lib/,
name: 'ui-lib',
},
],
},
};使用上述 advancedChunks 选项,输出将如下所示:
¥By using the above advancedChunks option, the output will look like this:
js
import ... from './react-hash0.js';
import ... from './react-dom-hash0.js';
import ... from './ui-lib-hash0.js';
// App.js
function App() {
return <Button onClick={() => alert("Button clicked!")} />;
}
// index.js
ReactDom.createRoot(document.getElementById("root")).render(<App />);js
"React library code";
export { ... };js
"ReactDOM library code";
export { ... };js
"UI library code";
export { ... };现在,库被拆分成单独的块,浏览器可以并行下载它们。这可以显著提高应用的加载性能,尤其是在库很大的情况下。
¥Now, the libraries are split into separate chunks, and the browser can download them in parallel. This can significantly improve the loading performance of your application, especially if the libraries are large.
限制
¥Limitations
为什么总是有一个 runtime.js chunk?
¥Why there's always a runtime.js chunk?
tl;dr:如果你使用了 advancedChunks 选项,rolldown 将强制生成一个 runtime.js 块,以确保运行时代码始终在任何其他块之前执行。
¥tl;dr: If you used advancedChunks option, rolldown will forcefully generate a runtime.js chunk to ensure that the runtime code is always executed before any other chunks.
runtime.js 块是一个特殊的块,它只包含加载和执行应用所需的运行时代码。它由打包器强制生成,以确保运行时代码始终在任何其他块之前执行。
¥The runtime.js chunk is a special chunk that only contains the runtime code necessary for loading and executing your application. It is generated forcefully by the bundler to ensure that the runtime code is always executed before any other chunks.
由于高级块允许你在块之间移动模块,因此很容易在输出代码中创建循环导入。这可能导致运行时代码未在其他块之前执行的情况,从而导致应用出错。
¥Since advanced chunks allows you to move modules between chunks, it's easily to create a circular import in the output code. This can lead to a situation where the runtime code is not executed before the other chunks, causing errors in your application.
循环导入的示例输出代码:
¥A example output code with circular import:
js
// first.js
import { __esm, __export, init_second, value$1 as value } from './second.js';
var first_exports = {};
__export(first_exports, { value: () => value$1 });
var value$1;
var init_first = __esm({
'first.js'() {
init_second();
// ...
},
});
export { first_exports, init_first, value$1 as value };
// main.js
import { first_exports, init_first } from './first.js';
import { __esm, init_second, second_exports } from './second.js';
var init_main = __esm({
'main.js'() {
init_first();
init_second();
// ...
},
});
init_main();
// second.js
import { init_first, value } from './first.js';
var __esm = '...';
var __export = '...';
var second_exports = {};
__export(second_exports, { value: () => value$1 });
var value$1;
var init_second = __esm({
'second.js'() {
init_first();
// ...
},
});
export { __esm, __export, init_second, second_exports, value$1 };当我们运行 node ./main.js 时,模块的遍历顺序将是 main.js -> first.js -> second.js。模块执行顺序为 second.js -> first.js -> main.js。
¥When we run node ./main.js, the traversal order of the modules would be main.js -> first.js -> second.js. The module execution order would be second.js -> first.js -> main.js.
second.js 在初始化之前尝试调用 __esm 函数。这将导致运行时错误,即试图将 undefined 作为函数调用。
¥second.js tries to call __esm function before it gets initialized. This will lead to a runtime error which is trying to call undefined as a function.
使用强制生成的 runtime.js,打包器会确保任何依赖于运行时代码的块在执行自身之前都会先加载 runtime.js。这保证了运行时代码始终在任何其他代码块之前执行,从而避免了循环导入问题。
¥With forcefully generated runtime.js, the bundler ensures any chunk that depends on runtime code would first load runtime.js before executing itself. This guarantees that the runtime code is always executed before any other chunks, preventing circular import issues.
为什么该组包含不满足约束的模块?
¥Why does the group contain modules that don't satisfy the constraints?
当模块被某个组捕获时,Rolldown 将尝试以递归方式捕获其依赖,而不考虑约束。这是因为 Rolldown 默认只允许修改非入口块的导出。
¥When a module is captured by a group, Rolldown will try to capture its dependencies recursively without considering constraints. This is because Rolldown is only allowed to mangle the exports of non-entry chunks by default.
例如,如果你有以下代码:
¥For example, if you have the following code:
js
// entry.js
import { value } from './a.js';
console.log(value);
export const foo = 'foo';
// a.js
import { value as valueB } from './b.js';
export const value = 'a' + valueB;
// b.js
export const value = 'b';假设我们想将 a.js 模块移到一个单独的块中,同时将 b.js 模块与 entry.js 模块保留在同一个块中。我们得到
¥Let's say we want to move the a.js module into a separate chunk while keeping the b.js module in the same chunk as entry.js. We get
js
import { value } from './a.js';
// b.js
const value = 'b';
// entry.js
const foo = 'foo';
console.log(value);
export { foo, value };js
import { value } from './entry.js';
// a.js
export const value = 'a' + value;你可以看到,为了使 a.js 正常工作,我们必须更改入口块 entry.js 的导出签名,并添加额外的导出 value。这完全违背了代码的初衷,即仅从 entry.js 导出 foo。
¥You could see, to make a.js work, we have to change the export signature of the entry chunk entry.js and add an additional export value. This totally violates the original intention of the code, which is to only export foo from entry.js.
如果你不想要这种行为,你可以使用 advancedChunks.includeDependenciesRecursively: false 来禁用它。
¥If you don't want this behavior, you could use advancedChunks.includeDependenciesRecursively: false to disable it.
注意事项
使用 includeDependenciesRecursively: false,某个组的依赖模块可能会保留在入口块中。从入口块导出非入口模块是无效的。为了避免这种情况,如果你没有明确设置 preserveEntrySignatures: 'allow-extension',Rolldown 将隐式设置它。
¥With includeDependenciesRecursively: false, depended modules of a group might be left in the entry chunks. It's invalid to export non-entry module from an entry chunk. To avoid this, Rolldown will implicitly set preserveEntrySignatures: 'allow-extension' if you didn't set it explicitly.
includeDependenciesRecursively: false 增加了生成无效输出代码的可能性。如果你遇到由于执行顺序或循环依赖导致的问题,请考虑启用:
¥includeDependenciesRecursively: false increases the chance of generating invalid output code. If you encounter issues due to execution order or circular dependencies, consider enabling:
为什么 chunk 比 maxSize 大?
¥Why is the chunk bigger than maxSize?
如果输入包含大于 maxSize 的模块,则生成的块将包含该模块,因此块将大于 maxSize。
¥If the input has a module that is bigger than maxSize, the generated chunk will contain that module and thus the chunk will be bigger than maxSize.
可以通过将单个模块拆分成多个块来改进这个问题。但目前尚不支持。
¥This can be improved by splitting a single module into multiple chunks. But this is not supported yet.