Webpack5 高级配置4:如何优化代码运行性能
优化代码运行性能 optimize performance
Code Split
问题背景
打包代码时会将所有 js 文件打包到一个文件中,体积太大了。如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。
所以需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。
解决方案
代码分割(Code Split)主要做了两件事:
- 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
- 按需加载:需要哪个文件就加载哪个文件。
使用方法
代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,创建了新的文件来演示和加深学习印象
1. 多入口
- 新创建的文件目录如下
1 |
|
- 下载包
1 |
|
- 新建文件,观察打包输出的结果
- app.js
1 |
|
- main.js
1 |
|
- 配置
1 |
|
- 运行指令
npx webpack
此时在 dist 目录能看到输出了两个 js 文件。多入口打包,配置了几个入口,至少输出几个 js 文件。
2. 提取重复代码
如果多入口文件中都引用同一份代码,这份代码会被打包多次,导致代码重复,体积更大,所以只打包生成一个 js 文件,其他文件引用它就好。
- 修改文件
- app.js
1 |
|
- main.js
1 |
|
- math.js
1 |
|
- 修改配置文件
1 |
|
- 运行指令
npx webpack
此时会发现生成了 3 个 js 文件,其中有一个是提取的公共模块。
3. 按需加载,动态导入
想要实现按需加载,动态导入模块。还需要额外配置:
- 修改文件
- main.js
1 |
|
- app.js
1 |
|
- public/index.html
1 |
|
- 运行指令
npx webpack
可以发现,一旦通过 import 动态导入语法导入模块,模块就被代码分割,同时也能按需加载了。
4. 单入口
开发时可能是单页面应用(SPA),只有一个入口(单入口)。那么需要这样配置:
1 |
|
5. 更新配置
最终会使用单入口+代码分割+动态导入方式来进行配置。更新之前的配置文件。
1 |
|
6. 给动态导入文件取名称
- 修改文件
- main.js
1 |
|
- eslint 配置
下载包
npm i eslint-plugin-import -D
配置
1 |
|
- 统一命名配置
1 |
|
- 运行指令
npx webpack
观察打包输出 js 文件名称。
Preload / Prefetch
问题背景
前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(也叫懒加载,比如路由懒加载就是这样实现的)。但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。如果想在浏览器空闲时间,加载后续需要使用的资源,就需要用上 Preload
或 Prefetch
技术。
解决方案
Preload
:告诉浏览器立即加载资源。Prefetch
:告诉浏览器在空闲时才开始加载资源。
它们共同点是:都只会加载资源,并不执行;都有缓存。
它们区别:
Preload
加载优先级高,Prefetch
加载优先级低。Preload
只能加载当前页面需要使用的资源,Prefetch
可以加载当前页面资源,也可以加载下一个页面需要使用的资源。
所以当前页面优先级高的资源用 Preload
加载,下一个页面需要使用的资源用 Prefetch
加载。
它们的问题:兼容性较差,Preload
相对于 Prefetch
兼容性好一点。
- Can I Use 网站查询 API 的兼容性问题。
怎么用
下载包
npm i @vue/preload-webpack-plugin -D
配置 webpack.prod.js
1 |
|
Network Cache
问题背景
将来开发时对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。
但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。
所以从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了。
解决方案
它们都会生成一个唯一的 hash 值。
- fullhash(webpack4 是 hash)
每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。
- chunkhash
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。 js 和 css 是同一个引入,会共享一个 hash 值。
- contenthash
根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。
使用方法
1 |
|
- 问题:
当修改 math.js 文件再重新打包的时候,因为 contenthash 原因,math.js 文件 hash 值发生了变化(这是正常的)。但是 main.js 文件的 hash 值也发生了变化,这会导致 main.js 的缓存失效
原因:main.js 引用的 math.yyy.js, 文件名发生了变化,间接导致 main.js 也发生了变化
解决:
将 hash 值单独保管在一个 runtime 文件中。最终输出三个文件:main、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,main 不变。runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。
1 |
|
Core-js 彻底解决 js 兼容性问题
问题背景
过去使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env
智能预设来处理兼容性问题。它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。
所以此时 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。那么怎么将 js 兼容性问题彻底解决呢?
解决方案
core-js
是专门用来做 ES6 以及以上 API 的 polyfill
。
polyfill
翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让在不兼容某些新特性的浏览器上,使用该新特性。
使用方法
- 修改 main.js
1 |
|
此时 Eslint 会对 Promise 报错。
- 修改配置文件
下载包
npm i @babel/eslint-parser -D
.eslintrc.js
1 |
|
- 运行指令
npm run build
此时观察打包输出的 js 文件,发现 Promise 语法并没有编译转换,所以需要使用 core-js
来进行 polyfill
。
- 使用
core-js
下载包
npm i core-js
手动全部引入
1 |
|
这样引入会将所有兼容性代码全部引入,体积太大了。只想引入 promise 的 polyfill
。
- 手动按需引入
1 |
|
只引入打包 promise 的 polyfill
,打包体积更小。但是将来如果还想使用其他语法,我需要手动引入库很麻烦。
自动按需引入
- main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./styl/index.styl";
const result1 = count(2, 1);
console.log(result1);
const result2 = sum(1, 2, 3, 4);
console.log(result2);
// 添加promise代码
const promise = Promise.resolve();
promise.then(() => {
console.log("hello promise");
});- babel.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16module.exports = {
// 智能预设:能够编译ES6语法
presets: [
[
"@babel/preset-env",
// 按需加载core-js的polyfill
{
useBuiltIns: "usage",
corejs: {
version: "3",
proposals: true
}
},
],
],
};
此时就会自动根据代码中使用的语法,来按需加载相应的 polyfill
了。
PWA - progressive web application
问题背景
开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。如果希望给项目提供离线体验,该怎么做呢?
解决方案
渐进式网络应用程序是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。它的内部通过 Service Workers 技术实现的。
使用方法
下载包
npm i workbox-webpack-plugin -D
修改配置文件
1 |
|
- 在 main.js 中注册 serviceWorker
1 |
|
- 运行指令
npm run build
此时如果直接通过 VSCode 访问打包后页面,在浏览器控制台会发现 SW registration failed
。
因为打开的访问路径是:http://127.0.0.1:5500/dist/index.html
。此时页面会去请求 service-worker.js
文件,请求路径是:http://127.0.0.1:5500/service-worker.js
,这样找不到会 404。
实际 service-worker.js
文件路径是:http://127.0.0.1:5500/dist/service-worker.js
。
- 解决路径问题:要保证运行项目的根目录还是dist
- 下载一个专门用来部署静态资源服务器的库
npm i serve -g
serve 也是用来启动开发服务器来部署代码查看效果的。
- 运行指令,serve + 要部署的目录
serve dist
,此时通过 serve 启动的服务器 service-worker 就能注册成功了。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!