三种方式:
-
入口点分割
-
动态导入和懒加载
入口点分割
module.exports = {
mode: 'development',
devtool: false,
entry: {
page1: './src/page1.js',
page2: './src/page2.js',
page3: './src/page3.js'
},
有3个entry,自然dist里也会有3个文件分别与之对应
多个page可能也对应多个HtmlWebpackPlugin插件的实例
这种方法的问题
如果入口 chunks 之间包含重复的模块(lodash),那些重复模块都会被引入到各个 bundle 中
不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码
动态导入和懒加载
用户当前需要用什么功能就只加载这个功能对应的代码,也就是所谓的按需加载 在给单页应用做按需加载优化时
一般采用以下原则:
对网站功能进行划分,每一类一个chunk
对于首次打开页面需要的功能直接加载,尽快展示给用户,某些依赖大量代码的功能点可以按需加载
被分割出去的代码需要一个按需加载的时机
这部分内容详见webpack优化 -> moduleId和chunkId的优化
preload
每个页面加载的资源有优先级
一个资源的加载的优先级被分为五个级别,分别是
Highest 最高
High 高
Medium 中等
Low 低
Lowest 最低
异步/延迟/插入的脚本(无论在什么位置)在网络优先级中是 Low
对于本页面要用到的关键资源,包括关键js、字体、css文件,preload将会把资源得下载顺序权重提高,使得关键数据提前下载好,优化页面打开速度
在资源上,动态调用import导入的地方,添加预先加载的注释,你指明该模块需要立即被使用
npm install --save-dev @vue/preload-webpack-plugin
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AssetPlugin = require('./asset-plugin');
+ const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
const path = require('path');
module.exports = {
mode: 'development',
devtool: false,
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
//初始(initial)chunk 文件的名称
filename: '[name].[chunkhash].js',
//此选项决定了非初始(non-initial)chunk 文件的名称
chunkFilename: '[name].[chunkhash].js',
},
plugins: [
+ new PreloadWebpackPlugin()
]
}
在打包之后,模板文件中会生成这样一行代码,来完成异步加载:
<link rel="preload" as="script" href="video.js">
document.getElementById('btn').addEventListener('click', () => {
import(
`./video.js`
/* webpackPreload: true */
/* webpackChunkName: "utils" */
).then(res => {
console.log(res.default)
})
})
video.js:
module.exports = '播放';
以上注释中,webpackChunkName就指定了异步加载的模块名为utils,而不是默认生成的路径名拼接起来的名字
在本案例中,video在preload预加载了之后并没有立即使用,而是在按钮点击的时候才使用,所以浏览器会有warning:
除了preload之外,还有prefetch这种加载策略
prefetch 跟 preload 不同,它的作用是告诉浏览器未来可能会使用到的某个资源,浏览器就会在闲时去加载对应的资源,若能预测到用户的行为,比如懒加载,点击到其它页面等则相当于提前预加载了需要的资源
<link rel="prefetch" href="utils.js" as="script">
button.addEventListener('click', () => {
import(
`./utils.js`
/* webpackPrefetch: true */
/* webpackChunkName: "utils" */
).then(result => {
result.default.log('hello');
})
});
preload vs prefetch
preload 是告诉浏览器页面必定需要的资源,浏览器一定会加载这些资源
而 prefetch 是告诉浏览器页面可能需要的资源,浏览器不一定会加载这些资源
所以建议:对于当前页面很有必要的资源使用 preload,对于可能在将来的页面中使用的资源使用 prefetch
splitChunks
基本概念
module:就是js的模块化webpack支持commonJS、ES6等模块化规范,简单来说就是你通过import语句引入的代码
chunk:chunk的三种形式
- 你的项目入口(entry)
- 通过import()动态引入的代码
- 通过splitChunks拆分出来的代码
\
bundle:bundle是webpack打包之后的各个文件,一般就是和chunk是一对一的关系,bundle就是对chunk进行编译压缩打包等处理之后的产出
以下面的模块关系为例,来学习分包:
各page模块代码如下:
page1.js:
let module1 = require('./module1');
let module2 = require('./module2');
let $ = require('jquery');
import( /* webpackChunkName: "asyncModule1" */'./asyncModule1');
page2.js:
let module1 = require('./module1');
let module2 = require('./module2');
let $ = require('jquery');
console.log(module1, module2, $);
page3.js:
let module1 = require('./module1');
let module3 = require('./module3');
let $ = require('jquery');
console.log(module1, module3, $);
module1、module2、module3都是各console出一条信息来
最初,chunks(以及内部导入的模块)是通过内部 webpack 图谱中的父子关系关联的。CommonsChunkPlugin 曾被用来避免他们之间的重复依赖,但是不可能再做进一步的优化。
从 webpack v4 开始,移除了 CommonsChunkPlugin,取而代之的是 optimization.splitChunks。
缓存组是用来指定代码块分割的条件,哪些模块应该被提取到哪些代码块中
缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项,例如下面的配置中default这个cacheGroups.chunk就指定:有2个代码块共享就可以放到这个chunk中,从而覆盖了splitChunks.minChunks的配置
optimization: {
splitChunks: {
//all=async+initial 表示哪些代码块需要分割,默认是async异步 all等于同步的initial加异步的async
chunks: 'all',
//生成 chunk 的最小体积(以 bytes 为单位) 分割出去的代码最小的体积是多少 0就是不限制
minSize: 0,
//拆分前必须共享模块的最小 chunks 数,比如module1被3个代码块引用, module2被 2个代码块引入
minChunks: 1,
//缓存组可以继承和/或覆盖来自 splitChunks.* 的任何选项
//缓存组是用来指定代码块分割的条件,哪些模块应该被 提取哪些代码块中
cacheGroups: {
//默认第三方缓存组
defaultVendors: {
//控制此缓存组选择的模块。省略它会选择所有模块
//它可以匹配绝对模块资源路径
//如果某个模块资源的绝对路径匹配此正则的话,那么这个模块就可以被提供到此代码块中
test: /[\/]node_modules[\/]/,
priority: -10
},
default: {
//指定拆分前模块被 多少个代码块共享 的话才会提取到此代码块中
minChunks: 2,
//一个模块可以属于多个缓存组 jquery
//优化将优先考虑具有更高 priority(优先级)的缓存组
//默认组的优先级为负,以允许自定义组获得更高的优先级(自定义组的默认值为 0)
priority: -10
}
}
对于目前的page1 page2 page3的案例来说,jquery被引用了3次,它既属于defaultVendors这个缓存组,有属于default这个缓存组,因此有了priority选项,会将模块打到priority值较高的chunk中去
利用这个配置打包后结果如下:
page1 page1.js
page2 page2.js
page3 page3.js
asyncModule1 import( /* webpackChunkName: "asyncModule1" */'./asyncModule1');
defaultVendors-node_modules_jquery_dist_jquery_js jquery
defaultVendors-node_modules_lodash_lodash_js lodash
default-src_module1_js
default-src_module2_js
default和defaultVendors是两个内置默认的缓存组,设置为false以后可以将它关掉