不论是 react 还是 vue 脚手架,都依赖 webpack 进行构建和打包。这一篇主要是我之前在入门 webpack 4 的时候记的一点小笔记,详细内容还得参见官方文档
简单打个包
初始化项目,会生成一个 package.json
npm init
// 快速 init
// 1、默认都 yes
npm init -y
// 2、设置 init 模板
npm config set init.author.name "JacksonZhou"
npm init -y // 或者 -f
安装 webpack 和 webpack-cli
npm install webpack -D
npm install webpack-cli -D
我们随意创建一个 src 目录下的 index.js 文件,然后在 package.json 修改 scripts
"build": "webapck --mode production"
npm run build
这时会生成一个 dist 文件夹,这个里面就是 webpack 打包好的文件
创建配置文件
创建一个 webpack.config.js
,执行 webpack
命令时会去执行这个配置文件。若是自定义配置文件的名字,则应该使用命令 webpack config 文件名
entry
入口,webpack 会从这里开始解析依赖。默认会按 entry 来划分 chunk
entry: './src/index.js'
output
webpack 最终构建出来的静态文件,可配置输出结果的文件名、路径等
const path = require('path') // 本质上webpack配置文件就是nodejs脚本,所以可以使用nodejs的模块
output: {
publicPath: '', // 输出资源的网址前缀
path: path.resolve(__dirname, 'dist'),
filename: '[name].js' // 原文件名
}
关于 filename 的 hash 命名中 hash、chunkhash、contenthash 的区别
hash
是跟整个项目的构建相关,只要项目里有文件更改,整个项目构建的 hash 值都会更改,并且全部文件都共用相同的 hash 值chunkhash
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值,但是 chunkhash 下,引用其他文件的文件一旦修改,也会改变其他文件的 hash 值,即使其他文件没动过。contenthash
保证文件内容必须要修改后,hash 值才会变化
loader
通过使用不同的 loader 来解析处理不同类型的文件。webpack 默认支持打包 js 文件,其他格式的文件需要借助对应的 loader 进行转换
换句话说,当 webpack 遇到其他文件的时候,会去配置文件的 module 选项中去找相应的规则。比如在配置文件中我们可以规定当 webpack 遇到图片文件的时候,就使用 file-loader 来帮我们进行打包文件
常见 loader
- babel-loader 将 es6/es7 转换成 es5(待确认)
npm i babel-loader @babel/core @babel/preset-env @babel/polyfill -D
npx babel src --out-dir lib // 测试 babel
module: {
rules: [
{
// 命中 js 文件
test: '/\.js$/',
// 使用 babel-loader 来解析 js 文件
loader: 'babel-loader',
// 只命中 src 目录下的 js 文件,加快 webpack 的编译速度
include: path.resolve(__dirname, 'src'),
// 排除 node_modules,加快 webpack 的编译速度
exclude: /node_modules/,
}
]
},
这样还不能发挥 Babel 的作用。在项目根目录下创建一个 .babelrc 文件,添加代码:
{
"presets": [
"@babel/preset-env"
]
}
- 构建 CSS
npm i css-loader style-loader -D
module: {
rules: [
{
test: /\.css/,
include: [
path.resolve(__dirname, 'public/style'),
],
use: ['style-loader','css-loader']
// loader 的执行顺序是从下到上,从右到左
}
]
}
// 在index.js里引入
import '../public/style/index.css'
css-loader
负责解析 CSS 代码,主要是为了处理 CSS 中的依赖,帮我们分析出几个 css 文件之间的关系,并最终把这些 css 文件合并成一段 css,例如解析 @import 和 url() 等引用外部文件的声明;
style-loader
会将 css-loader 解析的结果转变成 JS 代码,运行时动态插入 style 标签来让 CSS 代码生效。
经由上述两个 loader 的处理后,CSS 代码会转变为 JS,和 index.js 一起打包了。
- 处理预处理语言
npm i less-loader -D
module: {
rules: [
{
test: /\.less$/,
use: ExtractTextPlugin.extract({
fallback: 'style-loader',
use: [
'css-loader',
'less-loader',
],
}),
},
],
},
- 处理图片
file-loader
可以用于处理很多类型的文件,它的主要作用是直接输出文件,把构建后的文件路径返回
url-loader
会将引入的图片编码,生成 dataURl 并将其打包到文件中。但是如果图片较大,编码会消耗性能。因此 url-loader 提供了一个 limit 参数,小于 limit 字节的文件会被转为 DataURl,大于 limit 的还会使用 file-loader 进行 copy
image-webpack-loader
可用于压缩图片
{
test: '/\.(png|gif|jpe?g)$/i',
use: [
{
loader: 'url-loader',
options: {
limit: 8192 // 单位是 Byte,一般当文件小于 8KB 时作为 DataURL 处理
}
}
]
},
{
test: '/.*\.(gif|png|jpe?g|svg|webp)$/i',
use: [
{
loader: 'file-loader',
options: {}
},
{
loader: 'image-webpack-loader',
options: {
mozjpeg: { // 压缩 jpeg 的配置
progressive: true,
quality: 65
},
optipng: { // 使用 imagemin-optipng 压缩 png,enable: false 为关闭
enabled: false,
},
pngquant: { // 使用 imagemin-pngquant 压缩 png
quality: '65-90',
speed: 4
},
gifsicle: { // 压缩 gif 的配置
interlaced: false,
},
webp: { // 开启 webp,会把 jpg 和 png 图片压缩为 webp 格式
quality: 75
},
}
},
]
}
使用
html-withimg-loader
进行处理 img 标签引入的图片
npm i html-withimg-loader --save-dev
{
test:/\.(html|htm)$/,
use:'html-withimg-loader'
}
- 打包图标字体
其他常用 loader
- px2rem-loader。移动端 css px 自动转换为 rem 的 loader
- postcss-loader + autoprefixer。在 css 属性上加上相应的浏览器的厂商前缀,如-webkit、-ms、-moz 等
// postcss.config.js
module.exports = {
plugins: [
require('autoprefixer')
]
}
// webpack.config.js
{
test: /\.less$/,
use: [
'style-loader',
'css-loader',
'less-loader',
'postcss-loader',
]
}
- ts-loader。转换 ts 代码
- thread-loader。多进程打包资源
- raw-loader。将文件以字符串的形式导入
- …
plugin
在 webpack 的构建流程中,plugin 用于处理更多其他的一些构建任务。比如有以下这些常见的使用场景
清除 dist 文件
npm i clean-webpack-plugin -D
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
plugins: [new CleanWebpackPlugin()];
关联 HTML
- webpack 从入口开始构建 JS ,而通常一个 spa 项目都是从一个 html 页面出发的,这时候需要使用 script 标签引用构建好的 JS 文件。
- 但是问题来了,这些构建好的 JS 文件名可能会变,比如用了 hash 值作为文件名,所以我们需要通过插件来自动关联 html 文件
npm i -D html-webpack-plugin
// webpack.config.js
...
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', // 输出文件
template: './public/index.html' // 模板文件
})
]
之后打包npm run build
,dist 目录就有了 index.html 了
其他 plugin
- ProvidePlugin:设置全局变量
- mini-css-extract-plugin:将 css 从 bundle 文件中提取成一个独立的 css 文件
- terser-webpack-plugin:压缩 js 的插件,支持压缩 es6 代码
- copy-webpack-plugin:将文件或者文件夹拷贝到构建的输出目录
- …
mode
开启不同的环境,webpack 4 会根据这个参数做不同的处理和优化,具体处理和优化可以参见 https://webpack.js.org/configuration/mode/#mode-development
resolve
该配置用于扩展模块的解析路径,我们可以通过它配置一些 alias
module.exports = {
...
resolve: {
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
'src': path.resolve(__dirname, '../src'),
},
},
};
optimization
在构建后期负责压缩和优化代码,常见的代码分割现在是通过这个配置来控制。optimization.splitChunks
默认是不用设置的。如果 mode
是 production
,那 Webpack 4 就会开启 Code Splitting
,不过只会对按需加载的代码做分割。如果我们需要配置初始加载的代码也加入到代码分割中,可以设置 splitChunks.chunks
为 'all'
Webpack 4 的 Code Splitting 最大的特点就是配置简单,和基于内置规则自动拆分。内置的代码切分的规则是这样的:
- 新 bundle 被两个及以上模块引用,或者来自 node_modules
- 新 bundle 大于 30kb (压缩之前)
- 异步加载并发加载的 bundle 数不能大于 5 个
- 初始加载的 bundle 数不能大于 3 个
简单的说,Webpack 会把代码中的公共模块自动抽出来,变成一个包,前提是这个包大于 30kb,不然 Webpack 是不会抽出公共代码的,因为增加一次请求的成本是不能忽视的。
一般用默认的配置就行了。如果有特殊的需求,也可以通过下面的 optimization.splitChunks API 定制。
splitChunks
用法示例
chunks: 'all',
name: true,
cacheGroups: {
// 缓存组,会继承和覆盖splitChunks的配置
default: {
// 模块缓存规则,设置为false,默认缓存组将禁用
minChunks: 2, // 模块被引用>=2次,拆分至vendors公共模块
priority: -20, // 优先级
reuseExistingChunk: true, // 默认使用已有的模块
},
vendors: {
test: /[\\/]node_modules[\\/]/, // 表示默认拆分node_modules中的模块
priority: -10,
},
antd: {
name: 'antd', // 单独将 antd 拆包
priority: 15, // 权重需大于其它缓存组
test: /[\/]node_modules[\/](antd|@ant-design)[\/]/,
},
bizcharts: {
name: 'bizcharts', // 单独将 bizcharts 拆包
priority: 10,
test: /[\/]node_modules[\/]bizcharts[\/]/,
},
antv: {
name: 'antv', // 单独将 antv 拆包
priority: 12,
test: /[\/]node_modules[\/]@antv[\/]/,
},
},
sourceMap
sourceMap 其实就是源码和打包文件的映射关系,可以帮助我们快速定位一些代码问题。webpack 4 默认会开启,关键配置为 devtool
,下面是它的一些配置对比:
- inline。直接会将 .map 文件直接打包到对应的 js 中去,从而加快相应的速度。使用这个我们会发现,打包出来的文件没有 .map 文件了,而是以 base64 的形式放入了打包的文件中了。
- cheap。map 文件只会帮你定为到具体的 某一行,并不会把代码定位到 具体的 某一行 某一列,从而加快速度;cheap 还有一个作用,就是这个选项只使针对业务代码,也就是说只能定位到业务代码里面的错误,并不能定位到我们引用的第三方文件(比如说 loader,第三方模块)的错误。
- module。不仅会帮我们定位 自己的业务代码中的错误,还会同时帮我们定位第三方模块的错误。
- eval。使用 eval 包裹模块代码,并且存在 //@sourceURL,这个是打包速度最快,性能最好的的一种方式,但是有的时候,对于代码比较复杂的情况,它提示出来的错误可能不够全面。
建议配置
开发环境:cheap-module-eval-source-map
生产环境不建议开启,如果需要定位错误,可以尝试用cheap-module-source-map