webpack高级概念
1.Tree shaking
tree shaking是一个术语来的,通常用于描述移除JavaScript上下文中的未引用的代码。比如某个js文件中导出的某一个方法没有使用,在production模式下打包就会把这个方法移除掉。
但是得注意:这个Tree shaking有时候会滥杀无辜,把我们一些需要的,但未应用得模块给移除掉的话,就会导致我们的项目会发生bug。例如:我们导入css模块的时候,import './style/index.css'
,由于这种无法引用的,如果我们配置Tree shaking,就会导致css样式失效。配置内容如下:
package.json文件:
添加"sideEffects"属性:当值为“false”的时候:表示对所有的模块都使用Tree shaking。如果我们向某个模块或者css不使用Tree shaking,就配置一个数组:“[ "*.css", "./src/header.js", "jquery"]”
。这样就所有css文件都不适用tree shaking,和src目录下的header.js也不使用tree shaking,下载的jquery也不使用。
"sideEffects": "false"
webpack.config.js文件:
添加optimization选项:
optimization: {
usedExports: true
}
注:tree shaking在development模式下是不会移除掉未引用的代码的,只会在打包完的代码那里,添加未引用的注释,只有在production模式下才会真正的移除掉。
2.development和production模式的区分打包
由于development模式和production模式的配置信息不一样,我们就把development模式的配置信息写在webpack.dev.js
,production模式的配置信息写在webpack.prod.js
文件,然后抽离两个模式下的共同的配置信息,写在webpack.common.js
文件下。然后使用webpack-merge
插件的merge
方法将配置信息合并。
webpack.dev.js文件基本结构:
const webpack_dev_config = {
mode: 'development'
....
}
module.exports = webpack_dev_config
webpack.prod.js文件基本结构:
const webpack_prod_config = {
mode: 'production'
....
}
module.exports = webpack_prod_config
webpack.common.js文件结构:
const {merge} = require('webpack-merge')
const webpack_prod_config = require('./webpack.prod.js')
const webpack_dev_config = require('./webpack.dev.js')
const webpack_common_config = {
entry: {
main: './src/index.js'
},
output: {
path: './dist',
filename: 'bundle.js'
}
}
if(env && env.production) {
module.exports = merge(webpack_common_config,webpack_prod_config)
} else {
module.exports = merge(webpack_common_config,webpack_dev_config)
}
去package.json文件设置配置文件,和环境变量env.production:
"scripts": {
"build": "webpack --env.config --config ./build/webpack.common.js",
"dev-build": "webpack --config ./build/webpack.common.js",
"dev": "webpack-dev-server --config ./build/webpack.common.js"
}
env.prodction不设置,默认是true
3.Code Spliting(代码分割)
code spliting:将一些库的代码和自己写的一些业务代码分开打包,分开打包的好处:我们的业务有时候需要修改,但是我们引用的某些库的代码是不会被修改的,由于浏览器有缓存的机制,加入我们自己写的代码有1mb,引用的库的代码也有1mb,在第一次加载的时候,需要加载2mb的代码,使不使用code spliting都不会影响第一次加载的速度;但是我们使用code spliting,如果我们修改了自己写的业务代码,第二次就只用加载我们自己写的业务代码,不用再加载第三方库的代码(因为有缓存)。
code Spliting的两种方式:
第一种:使用import xx from 'xx’的导入方式,然后在webpack.config.js中配置:
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
第二种:使用import(‘xx’).then()这种动态导入形式,不需要任何配置(webpack有默认配置):
import(/*webpackChunkName: 'lodash'*/'lodash').then(({default: _}) => {
console.log(_)
})
注:{default: _}这里是对象解构的用法。_
是lodash导入的对象。/*webpackChunkName: 'loadsh'*/
是起打包完后的文件名,这个教魔法注释,后面我们会在webpack中也会用到。
4.配置文件中splitChunks配置参数详解
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
};
chunks:
- async:表示只从异步加载模块import()里面进行拆分,即对import()和import()里面的所有导入模块进行拆分,包裹import xx from 'xx’这种
- initial:表示只从入口模块进行拆分
- all:表示两者都包括
minSize:表示模块要大于多少才能进行代码分割。单位是byte
minChunks:表示模块要导入大于多少次才能进行代码分割
maxAsyncRequests:用来限制拆分数量的。限制异步模块内部的并行最大请求数,就是每个import()它里面的最大并行请求数量 - import()算一个请求
- js以外的公共资源不算,比如css
- 如果有两个模块满足cacheGroup规则要进行拆分,但是maxAsyncRequests的值只允许拆分一个模块,那就拆分最大的那个模块
maxInitialRequests:用来限制拆分数量的。它表示允许入口并行加载的最大请求数量。 - 入口文件本身算一个请求
- 如果入口里面有动态模块这个不算在内
- 通过runtimeChunk拆分的runtime不算在内
- js以外的公共资源不算,比如css
- 如果有两个模块满足cacheGroup规则要进行拆分,但是maxInitialRequests的值只允许拆分一个模块,那就拆分最大的那个模块
automaticNameDelimiter:用来连接文件名称的,例如:vendors~lodash.js
cacheGroups:当模块都满足上面的条件,就进入第二轮进行,看是否满足cacheGroups中的条件。
reuseExistingChunk:true:如果一个模块已经被打包过了,再打包的时候就忽略这个模块,直接使用被打包的那个模块。
priority: 优先级,越大,优先级越高。选择那个组的条件。
5.lazy loading懒加载
使用import()来实现懒加载:
function func() {
return import(/*webpackChunkName: 'lodash'*/'lodash').then(({default: _}) => {
return _
})
}
document.onclick = () => {
func().then((lodash)=> {
console.log(lodash)
})
}
这样的话,只有点击浏览器,才会加载lodash这个模块。第一次加载不用加载lodash,加快打开网页速度。
6.强化代码利用率
再浏览器控制台中,使用ctrl + shift + p,然后输入show coverage,就可以查看当夜网站的代码利用率。
可以优化的代码:
document.onclick = () => {
const div = document.createElement('div')
div.innerHTML = 'hell world'
document.body.appendChild(div)
}
优化:
document.onclick = () => {
import('./handle.js').then((default: func) => {
func()
})
}
创建一个handle.js文件:
export default function func() {
const div = document.createElement('div')
div.innerHTML = 'hell world'
document.body.appendChild(div)
}
优化性能,就得多写异步代码,增加代码利用率!
7.使用prefetch来利用空闲带宽加载资源
使用魔法注释:/*webpackPrefetch: true*/
document.onclick = () => {
import(/*webpackPrefetch: true*/'./handle.js').then((default: func) => {
func()
})
}
创建一个handle.js文件:
export default function func() {
const div = document.createElement('div')
div.innerHTML = 'hell world'
document.body.appendChild(div)
}
注:preload的用法和prefetch的都是通过魔法注释开启,preload的开启:webpackPreload: true
。但是preload是和页面核心代码一起加载的,所以没什么优化。prefetch是等核心代码加载完成,带宽空闲才加载的。
8.css代码分割
css代码分割:webpack默认是会把css打包在js中的,这就是我们所说的css in js
概念。我们想要把css从js中剥离出来的话,需要借助webpack的插件:mini-css-extract-plugin
;css代码分割出来之后,我们可以借助插件:optimize-css-assets-webpack-plugin
。如果webpack版本更新,建议去参考配置。以下是webpack4的配置:
1.安装mini-css-extract-plugin
npm i mini-css-extract-plugin
2.在index.js中导入css文件
import './style.css'
3.在webpack.config.js中配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
plugins: [new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
chunkFilename: '[name].[contenthash].chunk.css'
})],
module: {
rules: [
{
test: /\.(sc|c)ss$/,
use: [MiniCssExtractPlugin.loader, {
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'postcss-loader',
'sass-loader']
}
]
}
}
这样就可以对css进行代码分割了。
注:如果没有生成css文件,可能你使用tree shaking功能,由于import 进来的css文件没有被引用,就被摇晃掉了,这时候需要去package.json中配置:"sideEffects": [ "*.css", "*.scss" ]
优化:在production模式使用optimize-css-assets-webpack-plugin来压缩合并css代码
1.安装optimize-css-assets-webpack-plugin
和terser-webpack-plugin
npm install --save-dev optimize-css-assets-webpack-plugin terser-webpack-plugin
2.在webpack.config.js中配置
const TerserJSPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
module.exports = {
optimization: {
minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})],
splitChunks: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
}
}
这样就可以实现css代码合并和压缩了。
版本更新的导致配置失效或报错,建议去看官方最新文档
webpack官方文档:点我
9.webpack与浏览器缓存
我们在第一次打开页面的时候,浏览器会帮我们把一些文件给缓存下来。下次进入这个页面的时候,它会对比文件名是否和上次的一致,一致就走缓存,不一致就重新请求。所以有时候我们修改了js代码的时候,页面没有效果,原因是浏览器没有请求我们最新编写的js文件,这个时候我们需要ctrl + f5 强制刷新页面才能看到最新效果。
为了阻止以上缺点,webpack为我们提供了一个很好的解决方法,为打包好的文件的文件名添加contenthash值,这样就能让浏览器及时请求最新的代码了
webpack.config.js文件:
module.exports = {
output: {
filename: '[name].[contenthash].js',
path: path.resolve(__dirname, './dist')
}
}
注:contenthash的值是随着内容的改变而改变,如果内容不变,无论打包多少次contenthash的值是不会变的。如果你webpack版本比较低,可能会出现内容不变,但contenthash值发生了变化,那就要增加以下配置:
optimization: {
runtimeChunk: {
name: 'runtime'
}
}
这样就会打包多一个runtiem.xxxx.js
的文件。这个文件的内容就是业务代码和你用到的库的一些联系。
10.Shimming的使用
shimming:大概是电片的意思,用来申明一些**全局变量(所有模块都能用的变量)或者改变一些全局变量(所有模块都能用的变量)**的指向。
申明全局变量:
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
_join: ["lodash", 'join']
})
]
}
_join: ['lodash', 'join']
是吧lodash中的join方法赋值给_join
改变全局变量的指向(将this指向window):
npm i imports-loader
module.exports = {
module: {
rules: [
{
test: /.js$/,
use: [
{
loader: 'babel-loader'
},
{
loader: 'imports-loader?this=>window'
}],
},
],
},
}