本文记录webpack的一些常用高级概念,需要对webapck基础有所了解,本文重点记录概念涉及到的配置,其他配置有所省略,更多内容参照官方文档。
webpack中文网
webpack英文网
Tree Shaking
作用是当引入一个模块时,不会引入模块全部代码,只引入需要的部分代码,Tree Shaking只支持ES Module,不支持CommonJS模块方式。
例如只引入match.js的add方法
// match.js
export function add(a, b) {
return a + b
}
export function minus(a, b) {
return a - b
}
// index.js
import { add } from './math'
webpack配置
// webpack.config.js 生产环境
module.exports = {
mode: 'production',
devtool: 'cheap-module-source-map',
}
// webpack.config.js 开发环境
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
optimization: {
usedExports: true
}
}
// package.json 如果某些文件不需要做Tree Shaking,需要增加如下配置
"sideEffects": false, // 没有要处理的文件
"sideEffects": ["*.css"], // 任何css文件都不做Tree Shaking
Develoment 和 Production 模式的区分打包
webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
entry: {
main: './src/index.js'
},
module: {
// 相关loader配置(此处省略)
rules: [ ... ]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
],
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../dist')
}
}
webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
optimization: {
usedExports: true
}
}
module.exports = merge(commonConfig, devConfig)
webpack.prod.js
const merge = require('webpack-merge')
const commonConfig = require('./webpack.common.js')
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map'
}
module.exports = merge(commonConfig, prodConfig)
Webpack 和 Code Splitting
- 代码分割,和webpack无关
- webpack中实现代码分割,两种方式
- 同步代码: 只需要在webpack.common.js中做optimization的配置即可
- 异步代码(import): 异步代码,无需做任何配置,会自动进行代码分割,放置到新的文件中
同步加载
// index.js
import _ from 'lodash'
console.log(_.join(['a', 'b'], '-'))
// webpack.common.js
module.exports = {
optimization: {
splitChunks: {
chunks: 'all'
}
}
}
异步加载
注意需要借助babel插件来支持该语法
npm install --save-dev @babel/plugin-syntax-dynamic-import
// .babelrc
{
plugins: ["@babel/plugin-syntax-dynamic-import"]
}
// index.js
function getComponent() {
return import(/* webpackChunkName:"lodash" */ 'lodash').then(({ default: _ }) => {
var element = document.createElement('div')
element.innerHTML = _.join(['a', 'b'], '-')
return element
})
}
getComponent().then(element => {
document.body.appendChild(element)
})
/* webpackChunkName:"lodash" */
webpack魔法注释的作用是对异步加载的lodash打包生成的文件进行重命名,即为vendors~lodash.js
,不设置的时候是0.js
SplitChunksPlugin详细配置参数
详细配置项详见webpack官方文档
module.exports = {
//...
optimization: {
splitChunks: {
chunks: 'async', // 对异步模块进行优化,结合cacheGroups生效,有效值是all、async(异步)和initial(同步)
minSize: 30000, // 引入的模块大于30kb才做代码分割
maxSize: 50000, // 如果分割的lodash大于50kb,则尝试进行二次分割(了解即可)
minChunks: 2, // 当一个模块至少用了2次的时候才进行代码分割
maxAsyncRequests: 5, // 同时只能加载五个请求,超过五个就不会做代码分割
maxInitialRequests: 3, // 入口文件引入的库如果做代码分割最多只能分割出3个文件
automaticNameDelimiter: '~', // 默认打包生成文件名的连接符
name: true, // cacheGroups中改变的名字生效
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/, // 是node_modules中引入的模块
priority: -10, // 优先级值越大优先级越高
filename: 'vendors.js' // 打包生成的文件名
},
default: {
priority: -20, // 优先级值越大优先级越高
reuseExistingChunk: true, // 如果模块已经被打包过,则忽略此模块,直接复用
filename: 'commin.js' // 打包生成的文件名
}
}
}
}
}
Lazy Loading 懒加载
懒加载其实就是上文 Code Splitting 中提到的异步加载模块,指的是需要的时候再去加载该模块
// index.js
async function getComponent() {
const { default: _ } = await import(/* webpackChunkName:"lodash" */ 'lodash')
const element = document.createElement('div')
element.innerHTML = _.join(['a', 'b'], '-')
return element
}
document.addEventListener('click', () =>{
getComponent().then(element => {
document.body.appendChild(element)
})
})
打包分析
暂不做详细介绍(后续补充),详细查看官方文档。
build命令需要增加 --profile 参数
// package.js
{
"scripts": {
"dev-build": "webpack --profile --json > stats.json --config ./build/webpack.dev.js"
}
}
Preloading,Prefetching
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
// click.js
function handleClick() {
const element = document.createElement('div')
element.innerHTML = 'hello world'
document.body.appendChild(element)
}
export default handleClick
// index.js
document.addEventListener('click', () =>{
import(/* webpackPrefetch: true */ './click.js').then(({default: func}) => {
func();
})
})
/* webpackPrefetch: true */
等有空闲带宽时加载此模块
CSS 文件的代码分割 MiniCssExtractPlugin
# 代码分割插件
npm install --save-dev mini-css-extract-plugin
# 代码压缩合并插件
npm install --save-dev optimize-css-assets-webpack-plugin
MiniCssExtractPlugin插件目前还不支持HMR support
热更新,一般在线上环境做打包时使用
webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js',
},
module: {
rules: [{
test: /.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}, {
test: /.(jpg|png|gif)$/,
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240
}
}
}, {
test: /.(eot|ttf|svg)$/,
use: {
loader: 'file-loader'
}
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist'], {
root: path.resolve(__dirname, '../')
})
],
optimization: {
usedExports: true, // 注意这里要对css文件不做Tree Shaking,具体在package.json中配置
splitChunks: {
chunks: 'all'
}
},
output: {
filename: '[name].js', // entry直接引入的(入口)文件走这里
chunkFilename: '[name].chunk.js', // 间接引入的文件走这里
path: path.resolve(__dirname, '../dist')
}
}
webpack.dev.js
const webpack = require('webpack');
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const devConfig = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true
},
module: {
rules: [{
test: /.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /.css$/,
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
}
module.exports = merge(commonConfig, devConfig);
webpack.prod.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
const merge = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
mode: 'production',
devtool: 'cheap-module-source-map',
module: {
rules:[{
test: /.scss$/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
}]
},
optimization: {
// 代码压缩合并插件的配置
minimizer: [new OptimizeCSSAssetsPlugin({})]
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css', // 直接引入的文件走这里
chunkFilename: '[name].chunk.css' // 间接引入的文件走这里
})
]
}
module.exports = merge(commonConfig, prodConfig);
package.json
// css文件不做Tree Shaking
{
"sideEffects": [
"*.css"
]
}
Webpack 与浏览器缓存
// webpack.common.js
module.exports = {
optimization: {
// 老版本webpack4需要runtimeChunk配置(处理没改变业务代码也会改变打包后的contenthash情况)
runtimeChunk: {
name: 'runtime'
},
usedExports: true,
splitChunks: {
chunks: 'all',
cacheGroups: {
vendors: {
test: /[\/]node_modules[\/]/,
priority: -10,
name: 'vendors',
}
}
}
},
performance: false, // 性能问题不做警告
output: {
path: path.resolve(__dirname, '../dist')
}
}
// webpack.prod.js
module.exports = {
output: {
// 文件名添加contenthash值
filename: '[name].[contenthash].js',
chunkFilename: '[name].[contenthash].js'
}
}
Shimming 的作用
index.js
import $ from 'jquery'
import _ from 'lodash'
import { ui } from './jquery.ui'
ui()
const dom = $('<div>')
dom.html(_.join(['a', 'b'], '-'))
$('body').append(dom)
jquery.ui.js
// 在另一个模块中直接使用jquery和lodash方法
export function ui() {
$('body').css('background', _join(['red'], ''))
}
webpack.common.js
const webpack = require('webpack')
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: 'jquery', // 在一个模块引入可以在其他模块直接用$
_join: ['lodash', 'join'] // _.join的另一种方式,可以直接用_join
})
]
}
环境变量的使用方法
在上文 Develoment 和 Production 模式的区分打包 中的区分打包可以改为使用环境变量的方式。即webpack.common.js
中通过变量判断是合并开发环境还是生产环境的配置,webpack.dev.js
和webpack.prod.js
只写各自配置,不需要单独合并引入。
webpack.common.js
const merge = require('webpack-merge')
const devConfig = require('./webpack.dev.js')
const prodConfig = require('./webpack.prod.js')
const commonConfig = {
...
}
module.exports = (env) => {
if(env && env.production) {
return merge(commonConfig, prodConfig);
} else {
return merge(commonConfig, devConfig);
}
}
webpack.dev.js或webpack.prod.js直接导出,不用合并webpack.common.js
const devConfig = {
...
}
module.exports = devConfig;
package.json
注意npm run build
中需要传入--env.production
变量,其他都是直接引入webpack.common.js
文件。
{
"scripts": {
"dev-build": "webpack --config ./build/webpack.common.js",
"dev": "webpack-dev-server --config ./build/webpack.common.js",
"build": "webpack --env.production --config ./build/webpack.common.js"
}
}