构建
将源代码转化为分发代码的过程称为构建。
- 源代码:用于书写和编辑的代码
- 分发代码:在构建过程中,经过最小化和优化后产生的输出结果,最终将在浏览器中加载
打包
根据依赖图将多个模块的代码合并到一个或者几个 chunk 中的过程称为打包。
chunk 组
module.exports = {
entry: {
home: './home.js',
about: './about.js',
},
};
- 每个键值对就是一个 chunk 组。以上配置包括两个 chunk 组:home 组 about 组。
- 一个 chunk 组中可能有多个 chunk。例如,SplitChunksPlugin 会将一个 chunk 组拆分为一个或多个 chunk。
- 每个 chunk 组至少包括一个 initial chunk,以及零个或者多个 non-initial chunk。
chunk
chunk: 一个导出的 js 文件就是一个 chunk,一个 chunk 中的代码可以包括多个模块。
导出的 chunk 可以分为三类:
- initial chunk
- async chunk
- split chunk
其中 async chunk 和 split chunk 统称为 non-initial chunk
initial chunk
根据入口模块的依赖图打包形成的 chunk 称为 initial chunk。
-
entry
中一个键值对代表一个入口模块,有几个入口模块就生成几个 initial chunk -
entry
中键值对的 key 指定 initial chunk 的 chunk name -
entry
中键值对的 value 指定入口模块的路径 -
output.filename
指定 initial chunk 的全名,以及从 initial chunk 中分离出来的 split chunk (runtime, vendor, common) 的全名:filename: [name].bundle.js
async chunk
import() 导入的模块,根据该模块的依赖图打包形成一个 async chunk。
- 有几个 import() 语句就生成几个 async chunk
- 通过注释指定 async chunk 的 chunk name:
import(/* webpackChunkName: 'foo' */'./foo')
-
output.chunkFilename
指定 async chunk 的全名,以及从 async chunk 中分离出来的 split chunk (vendor, common) 的全名:chunkFilename: [name].chunk.js
以下三种方式都会生成两个 async chunk,第一种方式并发加载,后两种方式继发加载。
/* index.js */
import('./a.js');
import('./b.js');
/* index.js */
import('./a.js/').then(() => {
import('./b.js');
});
/* index.js */
import('./a.js');
/* a.js */
import('./b.js');
split chunk
通过 SplitChunksPlugin 插件从一个 initial chunk 或者一个 async chunk 中分离出的 chunk 叫做 split chunk,例如 runtime vendor common 等 split chunk。
-
output.filename
指定从 initial chunk 中分离出来的 split chunk (runtime, vendor, common) 的全名 -
output.chunkFilename
指定从 async chunk 中分离出来的 split chunk (vendor, common) 的全名
同步加载 chunk
最终打包生成的 html 文件里使用 <script> 同步加载 initial chunk 以及从 initial chunk 中分离出去的 split chunk。
<html>
<head>
<script src="runtime.bundle.js"></script>
<script src="vendor.bundle.js"></script>
<script src="index.bundle.js"></script>
</head>
</html>
异步加载 chunk
async chunk 以及从 async chunk 中分离出去的 split chunk 会按需异步加载:
- 按需加载:代码执行到
import()
语句的时候才加载对应的 async chunk,只要没执行到import()
语句,就不会加载对应的 async chunk。 - 异步加载:执行
import()
会开启一个加载线程,并返回 promise 对象,然后继续执行当前 Eventloop 中剩余的同步代码。async chunk 加载完毕,并且当前同步代码已经执行完了,便开始执行 async chunk 中的代码。执行完 async 中的代码,promise 对象状态变为 resolved,再执行 then 中的 resolve 函数。
webpack 的实现:webpack runtime 代码将 import(url)
语句替换为动态创建一个 <script src="url">
标签,引入 async chunk,将 then() 语句替换为 onload 回调函数。动态加载外部脚本相当于<script src="url" async>
脚本,立即下载,下载结束之后立即执行 (需要等到当前同步代码都执行完毕之后),异步脚本下载并执行结束之后执行 onload 回调函数。
/* app.js */
console.log('1')
import('./foo.js').then(() => console.log('4'))
console.log('2')
/* foo.js */
console.log('3')
/* 输出结果 */
1
2
3
4
网友评论