代码拆分

Parcel 默认支持零配置代码拆分。这允许您将应用程序代码拆分为单独的包,这些包可以按需加载,从而减少初始包大小并加快加载时间。

代码拆分通过使用动态 import() 语法来控制,该语法与正常的 import 语句类似,但返回一个 Promise。这意味着模块可以异步加载。

使用动态导入

#

以下示例展示了如何使用动态导入按需加载应用程序的子页面。

pages/index.js
import("./pages/about").then(function (page) {
// Render page
page.render();
});
pages/about.js
export function render() {
// Render the page
}

由于 import() 返回一个 Promise,您也可以使用 async/await 语法。

pages/index.js
async function load() {
const page = await import("./pages/about");
// Render page
page.render();
}
load();
pages/about.js
export function render() {
// Render the page
}

摇树优化

#

当 Parcel 可以确定您使用了动态导入模块的哪些导出时,它将从该模块中摇树优化未使用的导出。这适用于静态属性访问或解构,无论是使用 await 还是 Promise .then 语法。

注意:对于 await 的情况,不幸的是,只有在 await 没有被转译掉的情况下(例如,使用现代的 browserslist 配置),才能删除未使用的导出。

let { x: y } = await import("./b.js");
let ns = await import("./b.js");
console.log(ns.x);
import("./b.js").then((ns) => console.log(ns.x));
import("./b.js").then(({ x: y }) => console.log(y));

共享包

#

当应用程序的多个部分依赖于相同的公共模块时,它们会自动去重到一个单独的包中。这允许将常用依赖项与应用程序代码并行加载,并由浏览器单独缓存。

例如,如果您的应用程序有多个页面带有 <script> 标签,这些标签依赖于相同的公共模块,那么这些模块将被拆分为一个“共享包”。这样,如果用户从一个页面导航到另一个页面,他们只需要下载该页面的新代码,而不需要下载页面之间的公共依赖项。

home.html
<!doctype html>
<div id="app"></div>
<script type="module" src="home.js"></script>
home.js
import { createRoot } from 'react-dom';

createRoot(app).render(<h1>Home</h1>, app);
profile.html
<!doctype html>
<div id="app"></div>
<script type="module" src="profile.js"></script>
profile.js
import { createRoot } from 'react-dom';

createRoot(app).render(<h1>Profile</h1>, app);

编译后的 HTML

home.html
<!doctype html>
<div id="app"></div>
<script type="module" src="react-dom.23f6d9.js"></script>
<script type="module" src="home.fac9ed.js"></script>
profile.html
<!doctype html>
<div id="app"></div>
<script type="module" src="react-dom.23f6d9.js"></script>
<script type="module" src="profile.9fc67e.js"></script>

在上面的示例中,home.jsprofile.js 都依赖于 react-dom,因此它被拆分为一个单独的包,并通过在两个 HTML 页面中添加额外的 <script> 标签来并行加载。

这在使用动态 import() 代码拆分的应用程序的不同部分之间也适用。两个动态导入之间共享的公共依赖项将被拆分出来,并与动态导入的模块并行加载。

配置

#

默认情况下,Parcel 只有在共享模块达到一定大小阈值时才会创建共享包。这避免了拆分非常小的模块并创建额外的 HTTP 请求,即使使用 HTTP/2,这些请求也会有开销。如果模块小于阈值,它将在页面之间重复而不是拆分。

Parcel 还具有最大并行请求限制,以避免同时向浏览器发送过多的请求,并在达到此限制时会重复模块。在创建共享包时,较大的模块优先于较小的模块。

默认情况下,这些参数已针对 HTTP/2 进行调整。但是,您可以调整这些选项以提高或降低应用程序的这些选项。您可以通过在项目根目录的 package.json 中配置 @parcel/bundler-default 键来实现。

package.json
{
"@parcel/bundler-default": {
"minBundles": 1,
"minBundleSize": 3000,
"maxParallelRequests": 20
}
}

可用的选项是

http minBundles minBundleSize maxParallelRequests
1 1 30000 6
2 (默认) 1 20000 25

您可以在 web.dev 上阅读有关此主题的更多信息。

内部化异步包

#

如果一个模块在同一个包中被同步和异步导入,那么它不会被拆分为一个单独的包,而是会“内部化”。这意味着它将保留在与动态导入相同的包中以避免重复,但会包装在 Promise 中以保留语义。

因此,动态导入仅仅是一个提示,表明依赖项不需要同步加载,而不是保证会创建一个新包。

去重

#

如果动态导入的模块具有其所有可能的祖先中已有的依赖项,则它将被去重。例如,如果一个页面导入了一个库,而该库也被动态导入的模块使用,那么该库将只包含在父级中,因为它在运行时已在页面上。

手动共享包

#

注意:手动共享包目前处于实验阶段,可能会发生变化。

默认情况下,Parcel 会自动将常用模块拆分为“共享包”,并在上面列出的情况下创建包。但是,在某些情况下,您可能希望精确指定包中包含的内容以及谁可以请求该包。

这些场景包括但不限于...

可以在项目根目录的 package.json 中指定手动共享包。assets 属性必须设置为一个 glob 列表。与这些 glob 匹配的任何资产文件路径都将包含在手动共享包中。

此示例创建一个供应商包,其中包含从 manual.js 开始的图中的所有 JS 资产,拆分为 3 个并行的 HTTP 请求。

package.json
{
"@parcel/bundler-default": {
"manualSharedBundles": [
{
"name": "vendor",
"root": "manual.js",
"assets": ["**/*"],
"types": ["js"],
"split": 3
},
],
},
}

完整的选项列表如下

小心!

配置手动共享包会覆盖 Parcel 通常执行的所有自动代码拆分,并可能导致意外的加载顺序问题,因为它映射到您的整个代码库,包括 node_modules。请注意您使用的 glob,每个文件类型只指定一个包,我们建议您指定一个 root 文件。