Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(cn): adjust guides/code-splitting format #1796

Merged
merged 1 commit into from
Jul 20, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 41 additions & 40 deletions src/content/guides/code-splitting.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ contributors:
- atesgoral
- snitin315
- artem-malko
- Yucohny
translators:
- QC-L
- jacob-lcs
Expand All @@ -48,19 +49,19 @@ related:
url: https://developer.mozilla.org/en-US/docs/Web/HTML/Preloading_content
---

T> 本指南继续沿用 [起步](/guides/getting-started) 中的示例代码。请确保你已熟悉这些指南中提供的示例以及[输出管理](/guides/output-management/)章节。
T> 本指南继续沿用 [起步](/guides/getting-started) 中的示例代码。请确保你已熟悉这些指南中提供的示例以及 [管理输出](/guides/output-management/) 章节。

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle、控制资源加载优先级,如果使用合理,会极大影响加载时间。

常用的代码分离方法有三种:

- **入口起点**:使用 [`entry`](/configuration/entry-context) 配置手动地分离代码。
- **防止重复**:使用 [Entry dependencies](/configuration/entry-context/#dependencies) 或者 [`SplitChunksPlugin`](/plugins/split-chunks-plugin) 去重和分离 chunk。
- **动态导入**:通过模块的内联函数调用来分离代码。

## 入口起点(entry point) $#entry-points$
## 入口起点 $#entry-points$

这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些隐患,我们将会解决这些问题。先来看看如何从 main bundle 中分离 another module(另一个模块)
这是迄今为止最简单直观的分离代码的方式。不过,这种方式手动配置较多,并有一些我们将会解决的隐患。先来看看如何从 main bundle 中分离另一个模块

**project**

Expand Down Expand Up @@ -104,7 +105,7 @@ console.log(_.join(['Another', 'module', 'loaded!'], ' '));
};
```

这将生成如下构建结果
构建后结果如下

```bash
...
Expand All @@ -124,13 +125,13 @@ webpack 5.4.0 compiled successfully in 245 ms
- 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个 bundle 中。
- 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。

以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 `./src/index.js` 中也引入过 `lodash`,这样就在两个 bundle 中造成重复引用。在下一章节会移除重复的模块
以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 `./src/index.js` 中也引入过 `lodash`,这样就在两个 bundle 中造成重复引用。在下一章节会介绍如何移除重复的模块

## 防止重复(prevent duplication) $#prevent-duplication$
## 防止重复 $#prevent-duplication$

### 入口依赖 $#entry-dependencies$

配置 [`dependOn` option](/configuration/entry-context/#dependencies) 选项,这样可以在多个 chunk 之间共享模块:
配置 [`dependOn`](/configuration/entry-context/#dependencies) 选项,这样可以在多个 chunk 之间共享模块:

**webpack.config.js**

Expand Down Expand Up @@ -159,7 +160,7 @@ webpack 5.4.0 compiled successfully in 245 ms
};
```

如果我们要在一个 HTML 页面上使用多个入口时,还需设置 `optimization.runtimeChunk: 'single'`,否则还会遇到[这里](https://bundlers.tooling.report/code-splitting/multi-entry/)所述的麻烦。
如果我们要在一个 HTML 页面上使用多个入口时,还需设置 `optimization.runtimeChunk: 'single'`,否则还会遇到 [这里](https://bundlers.tooling.report/code-splitting/multi-entry/) 所述的麻烦。

**webpack.config.js**

Expand Down Expand Up @@ -240,7 +241,7 @@ webpack 5.4.0 compiled successfully in 249 ms
};
```

使用 [`optimization.splitChunks`](/plugins/split-chunks-plugin/#optimization-splitchunks) 配置选项之后,现在应该可以看出,`index.bundle.js``another.bundle.js` 中已经移除了重复的依赖模块。需要注意的是,插件将 `lodash` 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。执行 `npm run build` 查看效果:
使用 [`optimization.splitChunks`](/plugins/split-chunks-plugin/#optimization-splitchunks) 配置选项之后,将会发现 `index.bundle.js``another.bundle.js` 中已经移除了重复的依赖模块。需要注意的是,插件将 `lodash` 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了 bundle 大小。执行 `npm run build` 查看效果:

```bash
...
Expand All @@ -260,13 +261,13 @@ webpack 5.4.0 compiled successfully in 241 ms

以下是由社区提供,一些对于代码分离很有帮助的 plugin 和 loader:

- [`mini-css-extract-plugin`](plugins/mini-css-extract-plugin): 用于将 CSS 从主应用程序中分离。
- [`mini-css-extract-plugin`](plugins/mini-css-extract-plugin)用于将 CSS 从主应用程序中分离。

## 动态导入(dynamic import) $#dynamic-imports$
## 动态导入 $#dynamic-imports$

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式是,使用符合 [ECMAScript 提案](https://github.com/tc39/proposal-dynamic-import)[`import()` 语法](/api/module-methods/#import-1) 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 [`require.ensure`](/api/module-methods/#requireensure)。让我们先尝试使用第一种……
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式,使用符合 [ECMAScript 提案](https://github.com/tc39/proposal-dynamic-import)[`import()` 语法](/api/module-methods/#import-1) 实现动态导入。第二种则是 webpack 的遗留功能,使用 webpack 特定的 [`require.ensure`](/api/module-methods/#requireensure)。让我们先尝试使用第一种……

W> `import()` 调用会在内部用到 [promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。如果在旧版本浏览器中(例如,IE 11)使用 `import()`,记得使用一个 polyfill 库(例如 [es6-promise](https://github.com/stefanpenner/es6-promise)[promise-polyfill](https://github.com/taylorhakes/promise-polyfill)来 shim `Promise`
W> `import()` 调用会在内部使用到 [promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)。如果在旧版本浏览器中(例如,IE 11)使用 `import()`,记得使用一个 polyfill 库(例如 [es6-promise](https://github.com/stefanpenner/es6-promise)[promise-polyfill](https://github.com/taylorhakes/promise-polyfill))来 shim `Promise`

在我们开始之前,先从上述示例的配置中移除掉多余的 [`entry`](/concepts/entry-points/)[`optimization.splitChunks`](/plugins/split-chunks-plugin/#optimization-splitchunks),因为接下来的演示中并不需要它们:

Expand Down Expand Up @@ -309,7 +310,7 @@ webpack-demo
|- /node_modules
```

现在,我们不再使用 statically import(静态导入) `lodash`而是通过 dynamic import(动态导入) 来分离出一个 chunk:
现在,我们不再静态导入 `lodash`而是通过动态导入来分离出一个 chunk:

**src/index.js**

Expand All @@ -320,7 +321,7 @@ webpack-demo
+function getComponent() {
- const element = document.createElement('div');

- // Lodash, now imported by this script
- // lodash 现在使用 import 引入
- element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ return import('lodash')
+ .then(({ default: _ }) => {
Expand Down Expand Up @@ -356,7 +357,7 @@ cacheable modules 530 KiB
webpack 5.4.0 compiled successfully in 268 ms
```

由于 `import()` 会返回一个 promise,因此它可以和 [`async` 函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function)一起使用。下面是如何通过 async 函数简化代码
由于 `import()` 会返回 promise,因此它可以和 [`async` 函数](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function) 一起使用。下面是使用 `async` 简化后的代码

**src/index.js**

Expand Down Expand Up @@ -384,18 +385,18 @@ webpack 5.4.0 compiled successfully in 268 ms
});
```

T> 在稍后示例中,可能会根据计算后的变量(computed variable)导入特定模块时,可以通过向 `import()` 传入一个 [动态表达式](/api/module-methods/#dynamic-expressions-in-import)
T> 在稍后示例中,当需要根据计算后的变量导入特定模块时,可以通过向 `import()` 传入一个 [动态表达式](/api/module-methods/#dynamic-expressions-in-import) 实现

## 预获取/预加载模块(prefetch/preload module) $#prefetchingpreloading-modules$
## 预获取/预加载模块 $#prefetchingpreloading-modules$

Webpack v4.6.0+ 增加了对预获取和预加载的支持
Webpack v4.6.0+ 增加了对预获取(prefetch)和预加载(preload)的支持

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resource hint(资源提示)",来告知浏览器:
在声明 `import` 时,使用下面这些内置指令,可以让 webpack 输出resource hint,来告知浏览器:

- **prefetch**(预获取):将来某些导航下可能需要的资源
- **preload**(预加载):当前导航下可能需要资源
- **prefetch**预获取:将来某些导航下可能需要的资源
- **preload**预加载:当前导航下可能需要资源

下面这个 prefetch 的简单示例中,有一个 `HomePage` 组件,其内部渲染一个 `LoginButton` 组件,然后在点击后按需加载 `LoginModal` 组件。
下面这个预获取的简单示例中,有一个 `HomePage` 组件,其内部渲染一个 `LoginButton` 组件,然后在点击后按需加载 `LoginModal` 组件。

**LoginButton.js**

Expand All @@ -406,18 +407,18 @@ import(/* webpackPrefetch: true */ './path/to/LoginModal.js');

这会生成 `<link rel="prefetch" href="login-modal-chunk.js">` 并追加到页面头部,指示着浏览器在闲置时间预取 `login-modal-chunk.js` 文件。

T> 只要父 chunk 完成加载,webpack 就会添加 prefetch hint(预取提示)
T> 只要父 chunk 完成加载,webpack 就会添加预获取提示

与 prefetch 指令相比,preload 指令有许多不同之处
与预获取指令相比,预加载指令有许多不同之处

- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 预加载 chunk 会在父 chunk 加载时,以并行方式开始加载。预加载 chunk 会在父 chunk 加载结束后开始加载。
- 预加载 chunk 具有中等优先级,并立即下载。预加载 chunk 在浏览器闲置时下载。
- 预加载 chunk 会在父 chunk 中立即请求,用于当下时刻。预加载 chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。

下面这个简单的 preload 示例中,有一个 `Component`依赖于一个较大的 library,所以应该将其分离到一个独立的 chunk 中。
下面这个简单的预加载示例中,有一个 `Component`依赖于一个较大的库,所以应该将其分离到一个独立的 chunk 中。

我们假想这里的图表组件 `ChartComponent` 组件需要依赖一个体积巨大的 `ChartingLibrary` 库。它会在渲染时显示一个 `LoadingIndicator(加载进度条)` 组件,然后立即按需导入 `ChartingLibrary`
假想这里的图表组件 `ChartComponent` 组件需要依赖一个体积巨大的 `ChartingLibrary` 库。它会在渲染时显示一个 `LoadingIndicator` 组件,然后立即按需导入 `ChartingLibrary`

**ChartComponent.js**

Expand All @@ -426,7 +427,7 @@ T> 只要父 chunk 完成加载,webpack 就会添加 prefetch hint(预取提
import(/* webpackPreload: true */ 'ChartingLibrary');
```

在页面中使用 `ChartComponent` 时,在请求 ChartComponent.js 的同时,还会通过 `<link rel="preload">` 请求 charting-library-chunk。假定 page-chunk 体积比 charting-library-chunk 更小,也更快地被加载完成,页面此时就会显示 `LoadingIndicator(加载进度条)` ,等到 `charting-library-chunk` 请求完成,LoadingIndicator 组件才消失。这将会使得加载时间能够更短一点,因为只进行单次往返,而不是两次往返尤其是在高延迟环境下。
在页面中使用 `ChartComponent` 时,在请求 `ChartComponent.js` 的同时,还会通过 `<link rel="preload">` 请求 `charting-library-chunk`。假定 page-chunk 体积比 `charting-library-chunk` 更小,也更快地被加载完成,页面此时就会显示 `LoadingIndicator` ,等到 `charting-library-chunk` 请求完成,`LoadingIndicator` 组件才消失。这将会使得加载时间能够更短一点,因为只进行单次往返,而不是两次往返尤其是在高延迟环境下。

T> 不正确地使用 `webpackPreload` 会有损性能,请谨慎使用。

Expand All @@ -439,7 +440,7 @@ const lazyComp = () =>
// 例如,我们可以在网络错误的情况下重试请求
});
```
如果在 webpack 开始加载该脚本之前脚本加载失败(如果该脚本不在页面上,webpack 只是创建一个 script 标签来加载其代码),则该 catch 处理程序将不会启动,直到 [chunkLoadTimeout](/configuration/output/#outputchunkloadtimeout) 未通过。此行为可能是意料之外的。但这是可以解释的 - webpack 不能抛出任何错误,因为 webpack 不知道那个脚本失败了。Webpack 将在错误发生后立即将 onerror 处理脚本添加到 script 中。
如果在 webpack 开始加载该脚本之前脚本加载失败(如果该脚本不在页面上,webpack 只是创建一个 script 标签来加载其代码),则该 catch 处理程序将不会启动,直到 [chunkLoadTimeout](/configuration/output/#outputchunkloadtimeout) 未通过。此行为可能是意料之外的。但这是可以解释的 —— webpack 不能抛出任何错误,因为 webpack 不知道那个脚本失败了。webpack 将在错误发生后立即将 onerror 处理脚本添加到 script 中。

为了避免上述问题,你可以添加自己的 onerror 处理脚本,将会在错误发生时移除该 script。

Expand All @@ -451,18 +452,18 @@ const lazyComp = () =>
></script>
```

在这种情况下,错误的 script 将被删除。Webpack 将创建自己的 script,并且任何错误都将被处理而没有任何超时。
在这种情况下,错误的 script 将被删除。webpack 将创建自己的 script,并且任何错误都将被处理而没有任何超时。

## bundle 分析(bundle analysis) $#bundle-analysis$
## bundle 分析 $#bundle-analysis$

一旦开始分离代码,一件很有帮助的事情是,分析输出结果来检查模块在何处结束。 [官方分析工具](https://github.com/webpack/analyse) 是一个不错的开始。还有一些其他社区支持的可选项:
一旦开始分离代码,一件很有帮助的事情是,分析输出结果来检查模块在何处结束。[官方分析工具](https://github.com/webpack/analyse) 是一个不错的开始。还有一些其他社区支持的可选项:

- [webpack-chart](https://alexkuz.github.io/webpack-chart/): webpack stats 可交互饼图。
- [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/): 可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
- [webpack-chart](https://alexkuz.github.io/webpack-chart/)webpack stats 可交互饼图。
- [webpack-visualizer](https://chrisbateman.github.io/webpack-visualizer/):分析并可视化 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
- [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer):一个 plugin 和 CLI 工具,它将 bundle 内容展示为一个便捷的、交互式、可缩放的树状图形式。
- [webpack bundle optimize helper](https://webpack.jakoblind.no/optimize)这个工具会分析你的 bundle,并提供可操作的改进措施,以减少 bundle 的大小。
- [webpack bundle optimize helper](https://webpack.jakoblind.no/optimize)这个工具会分析 bundle,并提供可操作的改进措施,以减少 bundle 的大小。
- [bundle-stats](https://github.com/bundle-stats/bundle-stats):生成一个 bundle 报告(bundle 大小、资源、模块),并比较不同构建之间的结果。

## 下一步 $#next-steps$

接下来,查看 [延迟加载](/guides/lazy-loading/) 来学习如何在实际一个真实应用程序中使用 `import()` 的具体示例,以及查看 [缓存](/guides/caching/) 来学习如何有效地分离代码。
接下来,查看 [延迟加载](/guides/lazy-loading/) 来学习如何在真实的应用程序中使用 `import()` 的具体示例,以及查看 [缓存](/guides/caching/) 来学习如何有效地分离代码。
Loading