webpack进阶
treeshaking
作用是在打包时只引入我们依赖的代码,只能作用于import这种静态引入的代码。
我们在package.json文件中,加入"sideEffects": false
这样可以告知对所有文件开启treeshaking,如果有些文件我们不想使用treeshaking我们可以传入一个数组来忽略这些文件,如果想删除代码我们只需要将mode改为production会自动帮我们删除没用的代码
代码分割
我们经常会遇到在一个模块中引入一个第三方包多次,比如loadsh,如果我们不做任何配置,默认会在每个引入的文件都将loadsh打进来,造成体积比较大,这时候就要代码分割登场了。提到代码分割,在一般场景下我们会有以下2中场景
- 同步代码分割
在我们正常写的js文件中,需要做的就是这种分割,我们在entry中,加入另一个文件入口,这样就会将不同的逻辑代码打成2份文件,但这样会引入另一个问题,比如2个文件都会打进第三方依赖的loadsh,我们还要进一步优化配置,我们加入splitChunks来讲相同的依赖打入另一个文件中,可以看到我们的业务文件体积减少了很多
module.exports = {
entry: {
main: path.join(__dirname, './src/index.js'),
anthor: path.join(__dirname, './src/anthor.js'),
},
optimization: {
splitChunks:{
chunks: 'all'
}
},
}
- 异步代码分割
我们可以直接使用webpack提供的import语法来实现异步代码分割,我们修改index.js
function getComponent() {
return import(/* webpackChunkName: "lodash" */ 'lodash').then(({ default: _ }) => {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}).catch(error => 'An error occurred while loading the component');
}
getComponent().then(component => {
document.body.appendChild(component);
})
为什么我们不做任何配置,异步代码可以执行代码分割,是因为如果我们不做任何配置,webpack会使用默认配置,我在注释中写了这些参数的意思,我们将chunks设置为all就会将同步异步代码都做代码分割
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // 默认对异步代码进行分割
minSize: 30000, // 如果文件超过30kb才会分割
maxSize: 0, // 一般不做配置
minChunks: 1, // 当重复引用几次时做分割
maxAsyncRequests: 5, // 同时异步请求的连接数
maxInitialRequests: 3, // 初始化加载时页面的请求书
automaticNameDelimiter: '~', // 命名时使用的连接符
automaticNameMaxLength: 30,
name: true,
cacheGroups: { // 将提取出来的chunk加入缓存组,扫描所有文件后在分组生成chunk
vendors: {
test: /[\\/]node_modules[\\/]/, // 匹配node_modules下边的所有模块
priority: -10 // 分组时的优先级,比如2个chunk都匹配到打包到优先级高的模块里去
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 将会检查是否有重复引用的问题
}
}
}
}
};
懒加载
我们在上边介绍的异步加载,实际上就实现了懒加载,比如还是之前的例子。我们重启项目,可以看到network直到我们点击页面时才会触发加载loadsh生成的chunk
document.addEventListener('click', () => {
getComponent().then(component => {
document.body.appendChild(component);
})
})
function getComponent() {
return import(/* webpackChunkName: "lodash" */ 'lodash').then(({ default: _ }) => {
const element = document.createElement('div');
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
return element;
}).catch(error => 'An error occurred while loading the component');
}
预加载
在我们访问页面时我们可以使用懒加载来将一些交互的代码提出来做异步加载,但这同时➡又引出一个问题,就是如果文件比较大,可能会出现反应比较慢的情况这时就要预加载登场了,webpack4的最新版本支持了这个功能
在import中加入import(/*webpackPrefetch: true*/'./click.js')
,这样webpack会我们做预加载将文件放到缓存中,其实使用的时link标签rel的prefetch功能
分离css
webpack默认会将css和js代码生成在一起,这从前端渲染上是不好的,我们在生产环境需要分离css先安装npm install --save-dev mini-css-extract-plugin
缓存
在webpack生成的文件我们可以对output中的文件名加入contenthash来保证文件没有改变,生成文件的名一样,这样既可以有效的利用缓存,也可以在文件改变时及时更新,对于html文件常见的做法将http的max-age设为0这样防止了html文件的缓存
打包第三方库
我们开发自己的库时也可以使用webpack打包,只需要将output中增加两个参数,umd表示可以支持commonjs,AMD,和es6module的模块化规范,library表示可以将库挂载到library全局对象上
module.exports = {
output: {
library: 'library',
libraryTarget: 'umd'
}
}
Dll加速打包
我们想提高打包速度,有一个方法是我们将依赖的第三方模块都打包好不用每次都从新分析打包,webpack提供了现成了DllPlugin,下边我们看一个例子,
我们新建一个webpack.dll.js文件写入以下内容,我们将react和react-dom都一起打包到dll.js文件中,我们还要生成mainfest文件,以便我们使用打包好的js来寻找依赖,我们还配置了library来讲react和react-dom都暴露到dll的全局变量上,使用我们打包好的dll文件也很简单,我们直接在打包的文件中加入dll的引用就可以,在plugins中加入DllReferencePlugin插件,并配置引用mainfest的地址,每次打包时遇到像react,react-dom就可以直接引用dll.js中已经分析好的文件。我们也需要将dll.js也引入我们的html中这里需要一个插件add-asset-html-webpack-plugin
,这样就可以看到我们实现了一样的效果但速度减少了一半
// webpack.dll.js
const webpack = require('webpack');
const path = require("path");
module.exports = {
entry: {
dll: ['react', 'react-dom']
},
output: {
path: path.resolve(__dirname, './dist'),
filename: '[name].dll.js',
library: '[name]'
},
plugins: [
new webpack.DllPlugin({
name: '[name]',
path: path.resolve(__dirname, './dist', '[name].mainfest.json'),
})
]
}
// webpack.prod.js
new webpack.DllReferencePlugin({
manifest: path.resolve(__dirname, './dist/dll.mainfest.json')
}),
new AddAssetHtmlPlugin([{
filepath: path.resolve(__dirname, './dist/vendor.dll.js')
}])