利用 Webpack 来优化 Web 性能属于_加载性能优化_的一部分: ☛ Web Performance Optimization with webpack
二、使用长期缓存
1、文件名输出
缓存包(bundle
),并通过更改包名称(bundle name)来区分版本,将文件名替换成[name].[chunkname].js
[hash]
替换:可以用于在文件名中包含一个构建相关(build-specific)的 hash; [chunkhash]
替换:在文件名中包含一个 chunk 相关(chunk-specific)的哈希,比[hash]
替换更好; [contenthash]
替换:会根据资源的内容添加一个唯一的 hash,当资源内容不变时,[contenthash]
就不会变。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
- entry: './index.js',
+ entry: {
+ main: './index.js',
+ },
output: {
- filename: 'bundle.js',
+ filename: '[name].[contenthash].js', // / → bundle.8e0d62a03.js
path: path.resolve(__dirname, 'dist')
}
plugins: [
new HtmlWebpackPlugin({
- title: 'Output Management'
+ title: 'Caching'
})
],
};
➹ Hash vs chunkhash vs ContentHash
2、提取第三方库和样板代码
将bundle
拆分成程序代码(app
)、第三方库代码(vendor
)和运行时代码(runtime
)。
- 开启智能 code splitting
在 webpack 4 中添加以下的代码,当第三方库代码大于 30 kb 时(未压缩和未gzip前),webpack 能够自动提取 vendor
代码,并且如果你在路由层面使用了代码分割的话,它也能够提取公共代码。
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
}
},
};
这样,每次打包都会生成两个文件:main.[chunkhash].js
和 vendors~main.[chunkhash].js
(for webpack 4). 在 webpack 4 中, 当第三方库依赖很小的时候,vendor 包可能不会被生成,但也没关系。
- webpack 运行时代码
Webpack 在入口 chunk 中,包含了其运行时的引导代码: runtime
,以及伴随的 manifest 数据,runtime
是用来管理模块交互的一小片段代码。当你将代码分割成多个文件时,这段代码包含了 chunk id 和模块文件之间的映射,包括浏览器中的已加载模块的连接,以及懒加载模块的执行逻辑。
Webpack 会将这个运行时包含到最后生成的 chunk 中,即 vendor
。每次有任何块发生变化时,这段代码也会发生变化,导致 vendor
bundle 发生变化。
【解决方法】:设置 runtimeChunk
为 true
来为所有 chunks 创建一个单一的运行时包:
// webpack.config.js (for webpack 4)
module.exports = {
optimization: {
runtimeChunk: true,
},
};
webpack 运行时代码很小,内联它,可以减少 HTTP 请求。
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const InlineSourcePlugin = require('html-webpack-inline-source-plugin');
module.exports = {
plugins: [
new HtmlWebpackPlugin({
// Inline all files which names start with “runtime~” and end with “.js”.
// That’s the default naming of runtime chunks
inlineSource: 'runtime~.+\\.js',
}),
// This plugin enables the “inlineSource” option
new InlineSourcePlugin(),
],
};
3、代码懒加载
单页应用中,使用
import
对不关键的代码进行懒加载。
// videoPlayer.js
export function renderVideoPlayer() { … }
// comments.js
export function renderComments() { … }
// index.js
import {renderVideoPlayer} from './videoPlayer';
renderVideoPlayer();
// …Custom event listener
onShowCommentsClick(() => {
import('./comments').then((comments) => {
comments.renderComments();
});
});
import()
表示你想要动态加载特定模块,当 webpack 看到 import('./module.js')
时,它会自动把该模块从 chunk 中移除,只有在执行的时候才会被下载。
这会使 main
模块更小,能够减少初始加载时间,并且也能很好的提高缓存,如果你在 main chunk 中改了代码,懒加载的模块不会被影响。
按路由/页面分割代码(Code Splitting),以避免加载不必要的内容。
单页应用中,除了通过 import()
进行懒加载,还可以通过框架层面的手段来进行。
React 应用懒加载——> Code Splitting(react-router) 或者 React.lazy(react doc)。
➹ WebpackGuides-Caching
➹ WebpackConcepts-The Manifest
4、模块标识符
使模块标识符更稳定
在 webpack 构建时,每个 module.id
会基于默认的解析顺序(resolve order)进行增量,也就是说,当解析顺序发生变化,ID 也会随之改变。如:当新增一个模块的时候,它可能会出现在模块列表的中间,那么它之后的模块 ID 就会发生变化。
如果在业务代码里新引入一个模块,则:
-
main
bundle 会随着自身的新增内容的修改,而发生变化 ——> 符合预期 -
vendor
bundle 会随着自身的module.id
的修改,而发生变化 ——> 【不符合预期】 -
runtime
bundle 会因为当前包含一个新模块的引用,而发生变化 ——> 符合预期
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin()
],
};
为了解决这个问题,模块 ID 通过 HashedModuleIdsPlugin
来进行计算,它会把基于数字增量的 ID 替换成模块自身的 hash。这样的话,一个模块的 ID 只会在重命名或者移除的时候才会改变,新模块不会影响到它的 ID 变化。
[3IRH] ./index.js 29 kB {1} [built]
[DuR2] (webpack)/buildin/global.js 488 bytes {2} [built]
[JkW7] (webpack)/buildin/module.js 495 bytes {2} [built]
[LbCc] ./webPlayer.js 24 kB {1} [built]
[lebJ] ./comments.js 58 kB {0} [built]
[02Tr] ./ads.js 74 kB {1} [built]
+ 1 hidden module