代码拆分
Parcel 默认支持零配置代码拆分。这允许您将应用程序代码拆分为单独的包,这些包可以按需加载,从而减少初始包大小并加快加载时间。
代码拆分通过使用动态 import()
语法来控制,该语法与正常的 import
语句类似,但返回一个 Promise。这意味着模块可以异步加载。
使用动态导入
#以下示例展示了如何使用动态导入按需加载应用程序的子页面。
import("./pages/about").then(function (page) {
// Render page
page.render();
});
export function render() {
// Render the page
}
由于 import()
返回一个 Promise,您也可以使用 async/await 语法。
async function load() {
const page = await import("./pages/about");
// Render page
page.render();
}
load();
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>
标签,这些标签依赖于相同的公共模块,那么这些模块将被拆分为一个“共享包”。这样,如果用户从一个页面导航到另一个页面,他们只需要下载该页面的新代码,而不需要下载页面之间的公共依赖项。
<!doctype html>
<div id="app"></div>
<script type="module" src="home.js"></script>
import { createRoot } from 'react-dom';
createRoot(app).render(<h1>Home</h1>, app);
<!doctype html>
<div id="app"></div>
<script type="module" src="profile.js"></script>
import { createRoot } from 'react-dom';
createRoot(app).render(<h1>Profile</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
中以保留语义。
因此,动态导入仅仅是一个提示,表明依赖项不需要同步加载,而不是保证会创建一个新包。
去重
#如果动态导入的模块具有其所有可能的祖先中已有的依赖项,则它将被去重。例如,如果一个页面导入了一个库,而该库也被动态导入的模块使用,那么该库将只包含在父级中,因为它在运行时已在页面上。
手动共享包
#注意:手动共享包目前处于实验阶段,可能会发生变化。
默认情况下,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 文件。