webpack快速配置篇
在讲这篇文章前,如果你以前并没自己配置过webpack
或者对webpack
并不是太了解的,可以先去看看我的另一篇文章:Webpack与Babel基本配置
webpack基本配置
配置拆分和merge
在配置前我们可以规范一下文件,先建好一个build
文件夹,下面建新建文件,另外需要提醒本篇文章基于webpack5
构建:
paths.js
:放我们经常会用到的一些文件路径webpack.common.js
:webpack开发环境和生产环境的公共配置webpack.dev.js
:webpack用于开发环境的配置webpack.prod.js
:webpack用于生产环境的配置
当然你也可以按照自己的想法来进行建立文件,建好文件后,我们需要到package.json
添加脚本命令方便后面运行:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev-without-dev-server": "webpack --config build/webpack.dev.js",
"dev": "webpack serve --config build/webpack.dev.js",
"build": "webpack --config build/webpack.prod.js"
},
完成这些准备工作后我们开始正式的配置。
我这里的paths.js
定义了一个入口文件夹路径,一个打包文件夹:
// 常用文件夹
const path = require('path')
const srcPath = path.join(__dirname, '..', 'src')
const distPath = path.join(__dirname, '..', 'dist')
module.exports = {
srcPath,
distPath
}
首先在公共配置webpack.common.js
中定义一下入口和一个HtmlWebpackPlugin
插件:
// 公共配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath } = require('./paths')
module.exports = {
// 单文件打包入口
entry: path.join(srcPath, 'index'),
// 插件
plugins: [
// 单文件
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html'
})
]
}
因为我们把配置文件拆分成了公共配置、开发配置和生产配置,为的就是减少代码冗余,那么我们后面写详细配置就需要用到webpack-merge
的merge
方法。
在**webpack.dev.js
**中,我们需要定义好devServer
和ENV
:
// 开发环境配置
const path = require('path')
const webpack = require('webpack')
const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')
const { distPath } = require('./paths')
// 合并webpackCommonConf
module.exports = merge(webpackCommonConf, {
mode: 'development',
devServer: {
port: 8080,
static: distPath, // 根目录
open: true, // 自动打开浏览器
hot: true,
compress: true, // 启动gzip压缩
// 设置代理
// proxy: {
// // 将本地 /api/xxx 代理到 localhost:3000/api/xxx
// '/api': 'http://localhost:3000',
// // 将本地 /api2/xxx 代理到 localhost:3000/xxx
// '/api2': {
// target: 'http://localhost:3000',
// pathRewrite: {
// '/api2': ''
// }
// }
// }
},
plugins: [
new webpack.DefinePlugin({
// window.ENV = 'development'
ENV: JSON.stringify('development')
})
]
})
在**webpack.prod.js
**中,我们则需要加上打包出口:
const path = require('path')
const webpack = require('webpack')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const webpackCommonConf = require('./webpack.common.js')
const { merge } = require('webpack-merge')
const { srcPath, distPath } = require('./paths')
module.exports = merge(webpackCommonConf, {
mode: 'production',
output: {
// 打包带上hash戳
filename: 'bundle.[contenthash:8].js',
path: distPath
},
plugins: [
new CleanWebpackPlugin(), // 会默认清空 output文件夹
new webpack.DefinePlugin({
ENV: JSON.stringify('production')
})
]
})
这样一个初步架构就已经搭好了。
处理ES6+文件
很多时候因为浏览器不支持高语法,我们需要把js文件都转到es5语法,这时我们就需要用到babel-loader
,可以在wbpack.common.js
中加上(后面配置可自行根据项目需求加在公共配置、开发配置和生产配置):
module: {
rules: [
// es6以上编译到es5
{
test: /\.js$/,
use: ['babel-loader'],
include: srcPath
}
]
},
这里我们就需要安装上babel-loader
相关依赖了,并且需要在项目目录新建.babelrc
文件
{
"presets": ["@babel/preset-env"],
"plugins": []
}
注配置中相关依赖均需自行安装好,不清除的可以到文末拿到
package.json
安装
处理样式文件
module: {
rules: [
{
test: /\.css$/,
// loader执行顺序是: 从后往前
// postcss-loader兼容浏览器
// css-loader编译css
// style-loader添加到html头部style标签里面(不抽离)
use: ['style-loader', 'css-loader', 'postcss-loader'],
include: srcPath
},
{
test: /\.less$/,
// 增加'less-loader'
use: ['style-loader', 'css-loader', 'less-loader']
}
]
},
注意上面我们在编译样式时是先进行了浏览器兼容处理,我们需要额外在项目目录新建postcss.config.js
文件。
module.exports = {
plugins: [require('autoprefixer')]
}
处理图片
这里我们可以分开发环境和生产环境配置,因为开发环境可以直接使用图片url更快,生产环境可以让小文件就不用加载多次,生成base64
编码反而更适合,当然这里只是推荐,自行选择:
webpack.dev.js
:
// 开发环境直接引入图片url
{
test: /\.(png|jpg|jpeg|gif)$/,
use: 'file-loader'
},
webpack.prod.js
:
// img考虑base64编码
{
test: /\.(png|jpg|jpeg|gif)$/,
use: {
loader: 'url-loader',
options: {
// 当大小小于5kb 用base64产出, 否则直接file-loader产出
limit: 5 * 1024,
// 打包输出目录
outputPath: '/img1/'
}
}
},
webpack进阶配置
上面我们讲的都是基于单文件的打包,css、js
文件也未进行抽离,接下来我们重点说到这些配置方法。
多入口配置
这时我们需要在webpack.common.js
进行多入口配置:
// 公共配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { srcPath } = require('./paths')
module.exports = {
// 多文件打包入口
entry: {
index: path.join(srcPath, 'index.js'),
other: path.join(srcPath, 'other.js')
},
module: {
rules: [
// es6以上编译到es5
{
test: /\.js$/,
use: ['babel-loader'],
include: srcPath
}
]
},
plugins: [
// 多入口时 index.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'index.html'),
filename: 'index.html',
// chunks表示该页面要引用哪些 chunks 上面entry包含的js
chunks: ['index']
}),
// other.html
new HtmlWebpackPlugin({
template: path.join(srcPath, 'other.html'),
filename: 'other.html',
chunks: ['other']
})
]
}
抽离压缩CSS
文件
我们可以思考一下,前面的css
打包后并未抽离出css
文件,而是用style-loader
会放到html头部的style
标签里,当我们项目复杂起来,这显然是不靠谱的。
这里我们就需要用到mini-css-extract-plugin
插件实现了。具体操作如下:
// ...
const MixCssExtractPlugin = require('mini-css-extract-plugin')
// ...
module: {
rules: [
// 抽离css
{
test: /\.css$/,
use: [
MixCssExtractPlugin.loader,
'css-loader',
'postcss-loader'
]
},
// 抽离less
{
test: /\.less$/,
use: [
MixCssExtractPlugin.loader,
'css-loader',
'less-loader',
'postcss-loader'
]
}
]
},
plugins: [
// 抽离css文件
new MixCssExtractPlugin({
filename: 'css/[name].[contenthash:8].css'
})
]
在上面的基础上实现css
文件的压缩,我们用到css-minimizer-webpack-plugin
// js压缩
const TerserJSPlugin = require('terser-webpack-plugin')
// css压缩
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
// ...
// 加上optimization配置
optimization: {
// 压缩js和css
minimizer: [new TerserJSPlugin({}), new CssMinimizerPlugin({})],
}
抽离公共代码和第三方库
一个工具文件,我们可能在多个地方重复引入使用,打包后却是把功能的工具文件重复编译到了用到它的地方,这就导致了代码冗余,当代码量大时,无疑增加了打包体积,我们需要考虑抽离出去,第三方库也不应该和我们自己写的逻辑代码打包到一起,需要打包到一个单独文件。
optimization: {
// 分割代码块
splitChunks: {
/**
* initial 入口chunk, 对于异步导入的文件不处理
* async 异步chunk, 只对异步导入文件处理
* all 全部chunk
*/
chunks: 'all',
// 缓存分组
cacheGroups: {
// 第三方模块
vendor: {
name: 'vender', // chunk名称
priority: 1, // 权限更高, 优先抽离
test: /node_modules/,
minSize: 0, // 大小限制 超过才抽离
minChunks: 1 // 最少复用过次数, 超过才打包
},
// 公共模块
common: {
name: 'common',
priority: 0,
minSize: 0,
minChunks: 2
}
}
}
}
懒加载
也会单独创建一个chunk
,打包出一个文件。
// 引入动态数据 - 懒加载
setTimeout(() => {
import('./data.js').then(res => {
console.log(res.default.message)
})
}, 3000)
module
、chunk
、bundle
的区别
可能看前面的代码注释会看到很多关于moudle
、chunk
、bundle
的说法,这里来解释一下区别:
moudle
可以理解为各个源码文件,模块化,webpack中一切皆模块chunk
可以理解为有一个或moudle
模块组成,最后打包出的一个js
文件就是由一个·chunk
来的,如entry
、splitChunk
、import()懒加载
都会产生chunk
bundle
就是最终输出的文件
webpack
性能优化(重点)
优化打包构建速度--开发体验和效率
优化babel-loader
- 使用
cacheDirectory
,开启缓存,代码未改变下次就可以不用打包 - 灵活使用
include
|exclude
rules: [
// es6以上编译到es5
{
test: /\.js$/,
use: ['babel-loader?cacheDirectory'],
include: srcPath
}
]
IgnorePlugin
- 使用第三方模块不直接引入整个模块,按需引入
- 避免了无用模块的引入,优化打包编译速率
- 同时优化产出代码大小
// 以moment.js语言包为例子
plugins: [
// 忽略 moment 下的 /locale 目录
new webpack.IgnorePlugin(/\.\/locale/, /moment/),
]
import moment from 'moment'
// 在需要的文件下手动引入 单独模块
import 'moment/locale/zh-cn'
coment.locale('zh-cn')
noParse
避免重复打包
// 忽略已经打包好的文件
module: {
noParse: [/react\.min\.js$/],
}
happyPack
- js单线程,开启多进程打包
- 提高构建速度
const HappyPack = require('happypack')
module: {
rules: [
// js
{
test: /\.js$/,
// 把对 .js 文件的处理转交给 id 为 babel 的 HappyPack 实例
use: ['happypack/loader?id=babel'],
include: srcPath,
// exclude: /node_modules/
}
]
},
plugins: [
// happyPack 开启多进程打包
new HappyPack({
// 用唯一的标识符 id 来代表当前的 HappyPack 是用来处理一类特定的文件
id: 'babel',
// 如何处理 .js 文件,用法和 Loader 配置中一样
loaders: ['babel-loader?cacheDirectory']
}),
]
ParallelUglifyPlugin
- 多进程压缩
- webpack内置
Uglify
工具压缩js - 和
happyPack
同理 - 放生产环境
- 项目较大,打包较慢,开启多进程能提高速度
- 项目较小,打包很快,开启多进程反而会降低速度(进程也需要开销)
const ParallelUglifyPlugin = require('webpack-parallel-uglify-plugin')
plugins: [
// 使用 ParallelUglifyPlugin 并行压缩输出的 JS 代码
new ParallelUglifyPlugin({
// 传递给 UglifyJS 的参数
// (还是使用 UglifyJS 压缩,只不过帮助开启了多进程)
uglifyJS: {
output: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
},
compress: {
// 删除所有的 `console` 语句,可以兼容ie浏览器
drop_console: true,
// 内嵌定义了但是只用到一次的变量
collapse_vars: true,
// 提取出出现多次但是没有定义成变量去引用的静态值
reduce_vars: true,
}
}
})
]
自动刷新
devServer
默认有,一般不用配置
watch: true, // 开启监听,默认为 false
watchOptions: {
ignored: /node_modules/, // 忽略哪些
// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// 默认为 300ms
aggregateTimeout: 300,
// 判断文件是否发生变化是通过不停的去询问系统指定文件有没有变化实现的
// 默认每隔1000毫秒询问一次
poll: 1000
}
热更新
- 自动刷新:整个网页全部刷新。速度较慢,状态会丢失
- 热更新:新代码生效,网页不刷新,状态不丢失
- 但是需要自行配置监听模块
if (module.hot) {
// 监听模块
module.hot.accept(['./math'], () => {
// 不会丢失状态
const sumRes = sum(10, 30)
console.log('sumRes in hot', sumRes)
})
}
DllPlugin
- 一般框架体积大,构建慢,但是较稳定,不常升级版本
- 同一版本只构建一次即可,不用每次都重新构建
- webpack内置DllPlugin支持,打包出dll
- 通过
DllReferencePlugin
使用dll文件
优化产出代码 -- 产品性能
- 小图片通过
base64
编码 bundle
加hash
- 懒加载
- 提取公共代码
IngorePlugin
按需引入需要的代码模块- 使用CDN加速
Scope Hosting
这里单独说说Scope Hosting
,它可以处理:
- 优化代码,代码体积更小
- 创建函数作用域更少
- 代码可读性更好
// 开启方式
const ModuleConcatenationPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin')
resolve: {
// 针对npm中第三方模块优先采用 jsnext:main中指向的es6模块化语法文件
mianFields: ['jsnext:main', 'brower', 'main']
},
plugins: [
new ModuleConcatenationPlugin()
]
使用production
模式,也会有很多自带优化:
- 会自动压缩js代码
- vue、react框架等会自动删除调试代码(如开发环境的warning)
- 会启动
Tree-Shaking
(用到的模块才打包,没用的模块不打包,但是只有ES6 module才能让其生效
,commonjs模块
就不行)
ES6 Module
和Commonjs
区别
ES6 Module
静态引入,编译时引入Commonjs
动态引入,执行时引入ES6 Module
可以静态分析,实现Tree-Shaking
ES6 Module
导出和引入
export const sum = (a, b) => {
return a + b
}
export const muti = (a, b) => {
return a * b
}
// 对象导出
// export {
// sum,
// muti
// }
import { sum } from './util'
const sumRes = sum(10, 20)
Commonjs
导出和引入
const sum = (a, b) => {
return a + b
}
const muti = (a, b) => {
return a * b
}
module.exports.sum = sum
module.exports.muti = muti
const util = require('./util')
const sumRes = util.sum(10, 20)
Babel
环境搭建和基本配置
presets: 作为babel插件的组合
plugins: 当presets不能满足需求,需要额外插件时使用
babel-polyfill
Polyfill补丁 babel单独不能解析的语法
core-js和regenerator兼容语法es6 7
babel-polyfill即是两者的集合,7.4后被弃用,推荐直接使用core-js和regenerator
只解析语法,不解析模块
babel-ployfill文件较大,只用一部分功能,按需引入
问题
- 会污染全局环境