代码分割
Parcel 开箱即用地支持零配置代码分割。这允许您将应用程序代码拆分为可以按需加载的单独捆绑包,从而减小初始捆绑包大小并加快加载时间。
代码分割由动态 import()
语法控制,其工作方式类似于普通的 import
语句,但返回一个 Promise。这意味着模块可以异步加载。
使用动态导入
#下面的示例展示了如何使用动态导入按需加载应用程序的子页面。
import("./pages/about").then(function (page) {
// 渲染页面
page.render();
});
export function render() {
// 渲染页面
}
因为 import()
返回一个 Promise,您还可以使用 async/await 语法。
async function load() {
const page = await import("./pages/about");
// 渲染页面
page.render();
}
load();
export function render() {
// 渲染页面
}
树摇(Tree Shaking)
#当 Parcel 可以确定您使用了动态导入模块的哪些导出时,它将从该模块中摇掉未使用的导出。这适用于静态属性访问或解构,无论是使用 await
还是 Promise .then
语法。
**注意:**对于 await
情况,不幸的是,只有当 await
未被转译时(即使用现代浏览器列表配置),未使用的导出才能被移除。
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>
标签依赖于相同的公共模块,这些模块将被拆分到一个"共享捆绑包"中。这样,如果用户从一个页面导航到另一个页面,他们只需下载该页面的新代码,而不是页面之间的公共依赖项。
<!DOCTYPE html>
<div id="app"></div>
<script type="module" src="home.js"></script>
import { createRoot } from "react-dom";
createRoot(app).render(<h1>首页</h1>, app);
<!DOCTYPE html>
<div id="app"></div>
<script type="module" src="profile.js"></script>
import { createRoot } from "react-dom";
createRoot(app).render(<h1>个人资料</h1>, app);
编译后的 HTML:
<!DOCTYPE html>
<div id="app"></div>
<script type="module" src="react-dom.23f6d9.js"></script>
<script type="module" src="home.fac9ed.js"></script>
<!DOCTYPE html>
<div id="app"></div>
<script type="module" src="react-dom.23f6d9.js"></script>
<script type="module" src="profile.9fc67e.js"></script>
在上面的示例中,home.js
和 profile.js
都依赖于 react-dom
,因此它被拆分到一个单独的捆绑包中,并通过为两个 HTML 页面添加额外的 <script>
标签来并行加载。
这也适用于使用动态 import()
进行代码分割的应用程序的不同部分。两个动态导入之间共享的公共依赖项将被拆分出来,并与动态导入的模块并行加载。
配置
#默认情况下,Parcel 仅在共享模块达到大小阈值时才创建共享捆绑包。这避免了拆分非常小的模块并创建额外的 HTTP 请求,即使在 HTTP/2 下也会有开销。如果模块低于阈值,它将在页面之间重复。
Parcel 还有最大并行请求限制,以避免浏览器一次性请求过多,如果达到此限制,将重复模块。创建共享捆绑包时,较大的模块优先于较小的模块。
默认情况下,这些参数已针对 HTTP/2 进行了调整。但是,您可以调整这些选项以提高或降低应用程序的这些参数。您可以通过在项目根目录的 package.json 中配置 @parcel/bundler-default
键来实现。
{
"@parcel/bundler-default": {
minBundles: 1,
minBundleSize: 3000,
maxParallelRequests: 20,
},
}
可用的选项是:
- minBundles – 要拆分资源,它必须被超过
minBundles
个捆绑包使用。 - minBundleSize – 要创建共享捆绑包,它必须至少有
minBundleSize
字节大(在最小化和树摇之前)。 - maxParallelRequests – 为防止网络过载太多并发请求,这确保最多可以同时加载
maxParallelRequests
个同级捆绑包。 - http – 设置上述值的快捷方式,针对 HTTP/1 或 HTTP/2 进行了优化。请参见下表中的默认值。
http |
minBundles |
minBundleSize |
maxParallelRequests |
---|---|---|---|
1 | 1 | 30000 | 6 |
2(默认) | 1 | 20000 | 25 |
您可以在 web.dev 上阅读更多关于这个主题的内容。
内部化异步捆绑包
#如果一个模块在同一个捆绑包中同时被同步和异步导入,它将不会被拆分到单独的捆绑包中,而是被"内部化"。这意味着它将保留在动态导入的同一个捆绑包中以避免重复,但被包装在 Promise
中以保留语义。
因此,动态导入仅仅是一个提示,表明依赖项不是同步需要的,而不保证会创建新的捆绑包。
去重
#如果动态导入的模块有一个依赖项在其所有可能的祖先中已经可用,则该依赖项将被去重。例如,如果一个页面导入了一个库,而该库也被动态导入的模块使用,则该库将仅包含在父级中,因为它在运行时已经在页面上。
手动共享捆绑包
#note:手动共享捆绑包目前是实验性的,可能会发生变化。
默认情况下,Parcel 会自动将常用模块拆分为"共享捆绑包",并在上述场景中创建捆绑包。但是,在某些情况下,您可能希望精确指定捆绑包的内容以及谁可以请求该捆绑包。
这些场景包括但不限于:
- 将配置从另一个构建工具或打包器移植到 Parcel
- 减少 HTTP 请求而不复制资源,以支持过度获取
- 过度获取和整体加载较少的捆绑包可以使交互时间等测量指标受益,尤其是对于非常大的项目。
手动共享捆绑包可以在项目根目录的 package.json
中指定。assets
属性必须设置为 glob 模式列表。任何匹配这些 glob 模式的资源文件路径都将包含在手动共享捆绑包中。
这个示例创建了一个供应商捆绑包,其中包含从 manual.js
开始的图中的所有 JS 资源,分成 3 个并行的 HTTP 请求。
{
"@parcel/bundler-default": {
manualSharedBundles: [
{
name: "vendor",
root: "manual.js",
assets: ["**/*"],
types: ["js"],
split: 3,
},
],
},
}
完整的选项列表如下:
- name(可选)- 将捆绑包的
manualSharedBundle
字段设置为 <name>,这可以在自定义报告器或命名器中读取,用于开发目的 - root(可选)- 将 glob 的范围缩小到指定的文件。在上面的示例中,glob
**/*
将匹配manual.js
中的所有导入 - assets(必需)- Parcel 要匹配的 glob。匹配 glob 的文件将被放置到单个捆绑包中,并在整个项目中去重,除非另有指定。如果未指定
root
,Parcel 将尝试全局匹配 glob。 - types(可选)- 限制 glob 仅匹配特定类型。如果您的
root
文件导入多种类型(例如 JS 和 CSS)或assets
glob 可以匹配不同类型,则必须设置此字段。一个捆绑包只能包含相同类型的资源。- root 文件可以包含多种类型的导入。请确保在
manualSharedBundle
数组中为每种类型添加一个对象。
- root 文件可以包含多种类型的导入。请确保在
- split(可选)- 将手动捆绑包拆分为 x 个捆绑包。
- 对于非常大的捆绑包,拆分为多个并行 HTTP 请求可以改善诸如 CHR(缓存命中率)等测量指标,因为对于给定的更改,较小的捆绑包会失效。
请小心!
配置手动共享捆绑包会覆盖 Parcel 通常进行的所有自动代码分割,并可能导致意外的加载顺序问题,因为它映射了整个代码库,包括 node_modules
。请谨慎使用 glob,每种文件类型只指定一个捆绑包,并建议您指定一个 root 文件。