webpack常用配置

一、安装与运行

1
npm i webpack webpack-cli -D

配置文件:webpack.config.js

运行命令:配置**package.json**文件

1
2
3
4
5
{
"scripts": {
"build": "webpack --config webpack.config.js"
}
}

二、基础配置

1
2
3
4
5
6
7
8
9
10
11
12
const {resolve} = require("path")

module.exports = {
mode: 'development', // 环境
entry: resolve(__dirname, 'src/index.js'), // 入口文件
output: { // 输出文件
path: resolve(__dirname, 'dist'),
filename: 'js/bundle.js',
},
module: { rules: [] }, // loader
plugins: [] // 插件
}

三、开发配置

1. 处理CSS/Less/Sass

1
2
3
4
5
npm i style-loader css-loader -D # CSS

npm i style-loader css-loader less less-loader -D # Less

npm i style-loader css-loader sass-loader sass node-sass -D # Sass
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader'
]
},
{
test: /\.scss$/,
use: [
'style-loader',
'css-loader',
'sass-loader'
]
},
]
}
}

2. 处理HTML

html-webpack-plugin:指定默认处理的html文件,自动引入打包后的CSS,JS,同时可以压缩打包后的html

1
npm i html-webpack-plugin -D
1
2
3
4
5
6
7
8
9
10
const HtmlWebpackPlugin = require("html-webpack-plugin")

module.exports = {
plugins: [
new HtmlWebpackPlugin({
// html模板文件位置
template: resolve(__dirname,"public/index.html"),
})
]
}

3. 图片/字体处理

1
npm i file-loader url-loader html-loader -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
module.exports = {
module: {
rules: [
{
test: /\.(png|svg|jpe?g|gif)$/i,
loader: 'url-loader',
options: {
limit: 4096, // 超过4096,转为base64
name: 'img/[name].[hash:8].[ext]'
}
},
{
test: /\.html$/,
loader: 'html-loader' // 解决html中src路径问题
},
{
test: /\.(eot|ttf|woff2?)$/,
loader: 'file-loader',
options: {
name: 'fonts/[name].[hash:8].[ext]'
}
}
]
}
}

4. 自动编译构建

4.1 watch

自动监听并构建,需要手动刷新浏览器。

原理:轮询判断文件的最后编辑时间是否发生变化,某个文件发生变化,先缓存起来,等aggregateTimeout之后再构建。

1
2
3
4
5
6
7
8
module.exports = {
watch: true,
watchOptions: {
poll: 1000,
aggregateTimeout: 500,
ignored: /node_modules/
}
}

4.2 devServer

开发服务器。自动编译(只在内存中编译打包,不会有任何文件输出)、自动打开浏览器、自动刷新浏览器等功能。

1
npm i webpack-dev-server -D 
1
2
3
4
5
{
"script": {
"dev": "webpack-dev-server --config webpack.config.js"
}
}
1
2
3
4
5
6
7
8
9
module.exports = {
devServer: {
contentBase: resolve(__dirname, 'dist'), // 指定静态资源目录
host: 'localhost',
port: 3000,
open: true, // 自动打开浏览器
compress: true, // 开启gzip压缩
}
}

四、生产配置

1. CSS

1.1 提取CSS到单独文件中

1
npm i mini-css-extract-plugin -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../' // 解决background: url()的路径问题
}
},
'css-loader'
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css',
chunkFilename: 'css/[name].[contenthash:8].css',
}),
]
}

1.2 CSS兼容性处理

1
npm i postcss-loader postcss-preset-env -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* webpack.config.js
*/

module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath: '../' // 解决background: url()的路径问题
}
},
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [ "postcss-preset-env" ]
}
}
}
]
}
]
}
}

修改package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"browserslist": {
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
],
"production": [
">0.2%",
"not dead",
"not op_mini all"
]
}
}

1.3 CSS压缩

1
npm i optimize-css-assets-webpack-plugin -D
1
2
3
4
5
6
7
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')

module.exports = {
plugins: [
new OptimizeCssAssetsWebpackPlugin()
]
}

2. JS兼容性处理

1
npm i babel-loader @babel/core @babel/preset-env -D

2.1 兼容基础语法

两种配置方式写法相同。

(1) 配置到babel.config.js单独文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* babel.config.js
*/

module.exports = {
presets: [
"@babel/preset-env", // 只能处理基础语法,如es6模块化,箭头函数等
]
}

/**
* webpack.config.js
*/

module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
}
]
}
}
(2) 配置到webpack.config.js中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env', // 只能处理基础语法,如es6模块化,箭头函数等
]
}
}
]
}
}

2.2 兼容ES6,ES7等

(1) 全局兼容处理(polyfill)
1
npm i @babel/polyfill -D

相当于把所有不兼容的JS方法等都重新定义(打包后,体积大)。

1
2
3
4
5
/**
* xxx.js
*/

import "@babel/polyfill"
(2) 按需加载(core-js)
1
npm i core-js -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* babel.config.js
*/

module.exports = {
presets: [
[
"@babel/preset-env", // 只能处理基础语法,如es6模块化,箭头函数等
{ // 处理ES6,ES7...
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '60',
ie: '9',
safari: '10',
edge: '17'
}
}
]
]
}

3. JS压缩

production模式下,自动压缩

4. HTML压缩

使用html-webpack-plugin提供的压缩功能

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: resolve(__dirname, 'public/index.html'),
minify: {
removeComments: true,
collapseWhitespace: true,
minifyCSS: true,
minifyJS: true
}
})
]
}

5. 辅助插件清空构建目录

1
npm i clean-webpack-plugin -D
1
2
3
4
5
6
7
const { CleanWebpackPlugin } = require("clean-webpack-plugin")

module.exports = {
plugins: [
new CleanWebpackPlugin()
]
}

五、性能优化

1. 开发优化

1.1 HMR

HMR:一个模块发生了变化,只会重新构建这个模块,而不是所有模块,提高构建速度

1
2
3
4
5
6
7
8
module.exports = {
devServer: {
hot: true // 开启HMR
},
plugins: [
new HotModuleReplacementPlugin()
]
}
  • 样式文件:可以通过 style-loader 使用HMR

  • js文件:默认不能使用HMR, 可以通过module.hot 监听JS文件HMR

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /**
    * xxx.js中
    */

    if (module.hot) {
    module.hot.accept('./js/common.js', function () {
    test()
    })
    }
  • html文件:默认不能使用HMR,html文件只有一个,所以不需要使用HMR

1.2 source-map

一个信息文件,存储着位置信息,可以定位到源代码,用于代码调试。

[inline- | hidden- | eval-][nosources-][cheap- [module-]]source-map

  • inline:内联,保存在打包后的js文件中,只生成一个最终的source-map信息,构建速度快。能定位到源代码的行列。
  • hidden:外部map文件。不能定位到源代码。
  • eval:内联,保存在打包后的js文件中,每个模块对应一个eval包裹的source-map信息。能定位到源代码的行列。
  • nosources:外部map文件。不能定位到源代码。
  • cheap:外部map文件。只能定位到源代码的行。
  • module:外部map文件,包含loader。只能定位到源代码的行。
  • source-map:外部map文件。能定位到源代码的行列。

开发环境:eval-source-map(速度快,调试友好)

生产环境:source-map / nosources-source-map(全部隐藏) / hidden-source-map(只隐藏源代码)

2. 生产优化

2.1 oneOf

只会匹配一个

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
module: {
rules: [
{ test: /\.js$/ },
{
oneOf: [
{ test: /\.js$/ },
{ test: /\.css$/ },
]
}
]
}
}

2.2 缓存

(1) babel-loader缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
// 缓存转换过的JS语法,让第二次构建速度更快
cacheDirectory: true
}
}
]
}
}
(2) 文件资源缓存(文件指纹)
  • hash:和整个项目的构建有关,只要项目文件有改动,整个项目构建的hash就会变
  • chunkhash:和webpack打包的chunk有关,不同的entry会生成不同的chunkhash
  • contenthash:根据文件内容定义hash,内容不变,则ContentHash不变

JS设置文件指纹:[name].[chunkhash:8].js

CSS设置文件指纹:[name].[contenthash:8].css

图片/字体设置文件指纹:[name]. [hash:8].[ext]

2.3 tree shaking

去除无用代码。

开启条件:

  1. 使用ES6模块化
  2. mode: production

在package.json中添加sideEffects: false,表示所有代码都是没有副作用的(即都可以tree shaking),此时import css文件可能会导致tree shaking。

可以指定哪些文件有副作用,如sideEffects: ['*.css']

2.4 多进程打包

1
npm i thread-loader -D

多进程打包:进程启动大概消耗600ms,进程通信也有开销,只有工作时间较长,才需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
'thread-loader',
'babel-loader'
]
}
]
}
}

2.5 code split

代码分割。

(1) 多入口文件分割

打包后自动分割,一个entry对应一个代码文件。

1
2
3
4
5
6
module.exports = {
entry: {
index: 'src/index.js',
test: 'src/test.js'
}
}
(2) 分割node_modules中公共依赖
1
2
3
4
5
6
7
module.exports = {
optimization: {
splitChunks: {
chunks: 'all' // 所有引入的库进行分离
}
}
}
(3) 分割指定文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* xxx.js中
*/

import(
/* webpackChunkName: 'test' */
'x.js'
).then() // 单独打包x.js

/**
* webpack.config.js
*/

module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
},
runtimeChunk: {
// 解决修改a文件导致b文件的contenthash变化的问题
name: entrypoint => `runtime~${entrypoint.name}`
}
}
}

2.6 JS懒加载和预加载

正常加载:并行加载

懒加载:使用时才加载

预加载:等其他资源加载完,浏览器利用空闲时间加载资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* xxx.js
*/

div.onclick = function(){
import(
/* webpackChunkName: 'test' */
'x.js'
).then() // 懒加载
}

div.onclick = function(){
import(
/* webpackChunkName: 'test' */
/* webpackPrefetch: true */
'x.js'
).then() // 预加载
}

2.7 PWA

PWA:渐进式网络应用程序,即离线可访问。主要技术是service worker + cache。

service worker 必须运行在服务器上

1
npm i workbox-webpack-plugin -D
1
2
3
4
5
6
7
8
9
10
11
module.exports = {
plugins: [
// 1. 帮助 service worker 快速启动
// 2. 删除旧的 service worker
// 3. 生成一个service workder配置文件
new WorkboxWebpackPlugin.GenerateSW({
skipWaiting: true,
clientsClaim: true
})
]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* xxx.js中
*/

if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker
.register('service-worker.js')
.then(() => {
console.log('sw注册成功')
})
.catch(() => {
console.log('sw注册失败')
})
})
}

2.8 externals

当某些资源不要打包并且需要通过CDN引入时,可通过externals排除某些包

1
2
3
4
5
module.exports = {
externals: {
jquery: 'jQuery' // 库名: npm包名
}
}

2.9 DLL(动态链接库)

单独打包库文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* webpack.dll.js
*/

const { resolve } = require('path')
const webpack = require('webpack')

module.exports = {
mode: 'production',
entry: {
jquery: ['jquery']
},
output: {
path: resolve(__dirname, 'dll'),
filename: '[name].js',
library: '[name]_[hash]' // 向外暴露的名称
},
plugins: [
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库暴露的名称
path: resolve(__dirname, 'dll/manifest.json') // 映射文件
})
]
}
1
2
3
4
5
{
"script": {
"dll": "webpack --config webpack.dll.js"
}
}
1
npm i add-asset-html-webpack-plugin -D
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* webpack.config.js
*/

const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin')

module.exports = {
plugins: [
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js'),
outputPath: 'libs',
publicPath: './libs'
})
]
}

六、其他配置

1. entry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
* 1: string
*/

module.exports = {
entry: 'index.js'
}

/**
* 2: array
*/

module.exports = {
// 将index2.js和index.js打包到一个文件中,默认使用index.js的名称
entry: ['index.js', 'index2.js']
}

/**
* 3: object
*/

module.exports = {
entry: {
index: 'index.js',
index2: ['index2.js', 'index3.js']
}
}

2. output

  • publicPath:所有资源引入的公共路径前缀
  • chunkFilename:非入口chunk的名称(code split 分割出来的文件命名)
  • library:整个文件向外暴露的变量名
  • libraryTarget:变量名添加到那个对象上,‘window’ | ‘global’ | ‘commonjs’…

3. module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports = {
module: {
rules: [
{
test: /\./,
use: [], // 多个loader
loader: '', // 单个loader
options: {}, // loader配置选项
exclude: /node_modules/, // 排除文件
include: 'src', // 只检查哪些文件
enforce: 'pre' | 'post', // 优先执行 | 延后执行,
oneOf: []
}
]
}
}

4. resolve

1
2
3
4
5
6
7
8
9
module.exports = {
resolve: { // 解析模块的规则
alias: { // 配置解析模块路径别名
@: 'src'
},
extensions: ['.js'], // 配置省略文件路径的后缀名
modules: ['node_modules'] // 告诉webpack解析模块去哪找
}
}

5. resolveLoader

1
2
3
4
5
module.exports = {
resolveLoader: { // loader解析路径规则
modules: ['node_modules']
}
}

6. devServer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module.exports = {
devServer: {
proxy: { // 代理
before(app) { // 用于mock数据
app.get("/user", (req, res) => {})
},
"/admin": "http://localhost:3000",
"/api": { // devServer服务器接收到/api/xxx的请求,就会把请求转发到target
target: "http://localhost:3000",
pathRewrite: {
"^/api": "" // 发送请求时,将/api/xxx -> /xxx
}
}
},
watchContentBase: true, // 监视contentBase目录下的文件,一变化就reload
watchOptions: {
ignored: /node_modules/ // 忽略文件
},
clientLogLevel: 'none', // 日志信息
quiet: true, // 除了基本信息,其他都不要显示
overlay: false, // 如果出错,不要全屏提示
}
}

7. optimization

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
module.exports = {
optimization: {
splitChunks: {
chunks: 'all',
minSize: 30 * 1024, // 分割的chunk最小为30kb
maxSize: 0, // 没有最大限制
minChunks: 1, // 要提取的chunk最少被引用1次
cacheGroups: { // 分割chunk组
vendors: {
name: 'chunk-vendors',
test: /[\\/]node_modules[\\/]/,
priority: -10,
chunks: 'initial'
},
common: {
name: 'chunk-common',
minChunks: 2,
priority: -20,
chunks: 'initial',
reuseExistingChunk: true // 解决重复打包问题
}
}
},
minimizer: [ // 配置生产环境的js和css压缩方案
new TerserWebpackPlugin({
cache: true,
paraller: true,
sourceMa: true
})
]
}
}