前言
这是我花了几个星期学习webpack4的学习笔记。内容不够细,因为一些相对比较简单的,就随意带过了。希望文章能给大家带来帮助。如有错误,希望及时指出。例子都在learn-webpack仓库上。如果你从中有所收获的话,希望你能给我的github
点个star
。
tree shaking
一个模块里会导出很多东西。把一个模块里没有被用到的东西都给去掉。不会把他打包到入口文件里。tree shaking只支持es6的方式引入(import
),使用require
无法使用tree shaking
。
webpack
的development
无法使用tree shaking
功能。除非在打包的配置里加上
// 开发环境需要加如下代码
optimization: {
usedExports: true
}
当你需要import某个模块,又不想tree shaking
把他给干掉,就需要在package.json里修改sideEffects
参数。比如当你import './console.js'
, import './index.css'
等没有export
(导出)模块的文件。又不想tree shaking
把它干掉。
// package.json
sideEffects: ['./console.js', './index.css']
// 反之
sideEffects: false
在development
环境即使你使用tree shaking
,它也不会把其他多余的代码给干掉。他只会在打包的文件里注明某段代码是不被使用的。
development
和 production
区别
development
代码不压缩,production
代码会压缩
省略…☺
webpack-merge
react
和vue
都会区分环境进行不同的webpack
配置,但是它们一定会有相同的部分。这个时候需要通过使用webpack-merge
进行抽离。
// webpack.base.config.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'production',
// mode: 'development',
entry: './index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
}
]
},
optimization: {
usedExports: true
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html'
})
]
}
// webpack.dev.config.js
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config')
const devConfig = {
mode: 'development',
}
module.exports = merge(baseConfig, devConfig)
这里就不重复把production
环境在配置出来了,主要介绍下webpack-merge
用法。
- 安装
npm i webpack-merge -D
- 新建一个公共的文件如:
webpack.base.config.js
- 将
development
和production
两个webpack
配置相同的抽离到webpack.base.config.js
文件中 -
在环境配置文件中(具体代码如上)
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.config.js')
module.exports = merge(baseConfig, devConfig)
code splitting
和splitChunks
当你把所有的代码都打包到一个文件的时候,每次改一个代码都需要重新打包。且用户都要重新加载下这个js文件。但是如果你把一些公共的代码或第三方库抽离并单独打包。通过缓存加载,会加快页面的加载速度。
- 异步加载的代码,webpack会单独打包到一个js文件中
- 同步加载的代码有两种方式
原始代码
import _ from 'lodash'
console.log(666)
打包后的文件:
main.js 551 KiB main [emitted] main
可以看到,webpack将业务代码跟lodash库打包到一个main.js文件了
方法一:
创建一个新文件
import _ from 'lodash'
window._ = _
将文件挂载到window
对象上,这样其他地方就可以直接使用了。
然后在webpack配置文件中的entry增加一个入口为该文件。让该文件单独打包。
Asset Size Chunks Chunk Names
lodash.js 551 KiB lodash [emitted] lodash
main.js 3.79 KiB main [emitted] main
方法二:
通过添加optimization
配置参数
optimization
: 会将诸如lodash
等库抽离成单独的chunk
,还会将多个模块公用的模块抽离成单独的chunk
optimization: {
splitChunks: {
chunks: 'all'
}
},
打包后文件:
Asset Size Chunks Chunk Names
main.js 6.78 KiB main [emitted] main
vendors~main.js 547 KiB vendors~main [emitted] vendors~main
可以看到,webpack将lodash抽成公共的chunk打包出来了。
splitChunks
里面还可以在添加个参数cacheGroups
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 下面的意思是:将从node_modules中引入的模块统一打包到一个vendors.js文件中
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
filename: 'vendors.js'
},
default: false
}
}
}
cacheGroups
中vendors
配置表示将从node_modules
中引入的模块统一打包到一个vendors.js文件中
splitChunks
的vendors
的default
参数:
根据上下文来解释,如上配置了vendors
,打包node_modules
文件夹中的模块,
那么default
将会打包自己编写的公共方法。
当不使用default
配置时。
Asset Size Chunks Chunk Names
main.js 315 KiB main [emitted] main
test.js 315 KiB test [emitted] test
添加如下配置之后:
optimization: {
splitChunks: {
chunks: 'all',
cacheGroups: {
// 下面的意思是:将从node_modules中引入的模块统一打包到一个vendors.js文件中
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
filename: 'vendors.js'
},
// 打包除上面vendors以外的公共模块
default: {
priority: -20,
reuseExistingChunks: true, // 如果该chunk已经被打包进其他模块了,这里就复用了,不打包进common.js了
filename: 'common.js'
}
}
}
}
打包后的文件体积为
Asset Size Chunks Chunk Names
common.js 308 KiB default~main~test [emitted] default~main~test
main.js 7.03 KiB main [emitted] main
test.js 7.02 KiB test [emitted] test
配置说明
splitChunks: {
chunk: 'all', // all(全部), async(异步的模块),initial(同步的模块)
minSize: 3000, // 表示文件大于3000k的时候才对他进行打包
maxSize: 0,
minChunks: 1, // 当某个模块满足minChunks引用次数时,才会被打包。例如,lodash只被一个文件import,那么lodash就不会被code splitting,lodash将会被打包进 被引入的那个文件中。如果满足minChunks引用次数,lodash会被单独抽离出来,打出一个chunk。
maxAsyncRequests: 5, // 在打包某个模块的时候,最多分成5个chunk,多余的会合到最后一个chunk中。这里分析下这个属性过大过小带来的问题。当设置的过大时,模块被拆的太细,造成并发请求太多。影响性能。当设置过小时,比如1,公共模块无法被抽离成公共的chunk。每个打包出来的模块都会有公共chunk
automaticNameDelimiter: '~', // 当vendors或者default中的filename不填时,打包出来的文件名就会带~
name: true,
cashGroups: {} // 如上
}
maxAsyncRequests
Lazy Loading
异步import
的包会被单独打成一个chunk
async function getComponent() {
const { default: _ } = await import(/* webpackChunkNanem:'lodash */ 'lodash')
const element = document.createElement('div')
element.innerHTML = _.join(['Dell', 'Lee'], '-')
return element
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element)
})
})
lazy loading
chunk
每一个js
文件都是一个chunk
chunk
是使用Webpack
过程中最重要的几个概念之一。在Webpack打包机制中,编译的文件包括entry(入口,可以是一个或者多个资源合并而成,由html通过script标签引入)和chunk(被entry所依赖的额外的代码块,同样可以包含一个或者多个文件)。从页面加速的角度来讲,我们应该尽可能将所有的js打包到一个bundle.js之中,但是总会有一些功能是使用过程中才会用到的。出于性能优化的需要,对于这部分资源我们可以做成按需加载。
打包分析
打包分析:
安装:npm install --save-dev webpack-bundle-analyzer
// package.json => scripts
"analyz": "NODE_ENV=production npm_config_report=true npm run build"
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
plugins: [
new BundleAnalyzerPlugin()
]
执行命令npm run analyz
浏览器就会自动打开localhost:8888
,分析图就会展现在你眼前
非常清晰直观的看出
CSS文件的代码分割
我们之前写的css文件都会被打包进js文件中,要想把css单独打包成一个css文件该怎么做呢?
这个时候就需要用到MiniCssExtractPlugin
开发环境用不到这个功能,一般都是用在生产环境中。
安装:npm install --save-dev mini-css-extract-plugin
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: MiniCssExtractPlugin.loader,
options: {
// 可以在此处指定publicPath
// 默认情况下,它在webpackoptions.output中使用publicPath
publicPath: '../',
// hmr: process.env.NODE_ENV === 'development',
},
}, 'css-loader']
}
]
},
plugins: [
new MiniCssExtractPlugin({
// 与webpackoptions.output中相同选项类似的选项
// 两个选项都是可选的
filename: '[name].css',
chunkFilename: '[id].css',
}),
]
// index.js
import './index.css';
console.log('haha')
// index.css
body {
background: green;
}
这样打包之后,css会被单独打包成一个css文件。
缓存
目前为止,我们每次修改内容,打包出去后的文件名都不变。线上环境的文件是有缓存的。所以当你文件名不变的话,更新内容打包上线。有缓存的电脑就无法获取到最新的代码。
这个时候我们就会用到contenthash
我们先记录配置contenthash
之前打包的文件名。
Asset Size Chunks Chunk Names
index.html 180 bytes [emitted]
main.js 3.81 KiB main [emitted] main
接下来我们来配置下contenthash
(就是根据你文件内容生成的hash值)
// webpack.config.js
output: {
path: path.resolve(__dirname, '../dist'),
filename: '[name][contenthash].js'
},
打包完之后会在main
后面接上hash
值。
Asset Size Chunks Chunk Names
index.html 200 bytes [emitted]
mainf5faa2d3d1e119256290.js 3.81 KiB main [emitted] main
当你不更新内容重新打包后,contenthash
还会维持不变。所以线上用户访问的时候就不会去服务器重新拿取代码,而是从缓存中取文件。
shimming
(预置依赖)
以jquery
为例,代码如下
// index.js
import $ from 'jquery'
$('body').html('HHAHAH')
import func from './test.js'
func()
// test.js
export default function func() {
$('body').append('2
')
}
当你不在test.js中引入import $ from 'jquery'
那么浏览器访问的时候,会报
test.js:5 Uncaught ReferenceError: $ is not defined
这个时候就需要使用垫片功能
const webpack = require('webpack')
plugins: [
new webpack.ProvidePlugin({
$: 'jquery'
})
]
当你加上这段代码后,模块在打包的时候,发现你使用了$
就会在你模块顶部自动加入import $ from 'jquery'
其他关于shimming
的内容参考webpack
官网 shimming