初识Webpack | 配置 (万字解析)
一、Webpack 基础
第一部分,先从简单的“会配置”的要求出发,先了解 Webpack 简单配置以及简单配置会涉及到的面试题。
1. 简单配置
该部分需要掌握:
- Webpack 常规配置项有哪些?
- 常用 Loader 有哪些?如何配置?
- 常用插件(Plugin)有哪些?如何的配置?
- Babel 的如何配置?Babel 插件如何使用?
1.1 安装依赖
毫无疑问,先本地安装一下 webpack
以及 webpack-cli
$ npm install webpack webpack-cli -D # 安装到本地依赖
复制代码
安装完成 ✅
+ webpack-cli@4.7.2
+ webpack@5.44.0
复制代码
1.2 工作模式
webpack 在 4 以后就支持 0 配置打包,我们可以测试一下
- 新建
./src/index.js
文件,写一段简单的代码
const a = 'Hello ITEM'
console.log(a)
module.exports = a;
复制代码
此时目录结构
webpack_work
├─ src
│ └─ index.js
└─ package.json
复制代码
- 直接运行
npx webpack
,启动打包
打包完成,我们看到日志上面有一段提示:The 'mode' option has not been set,...
意思就是,我们没有配置 mode(模式),这里提醒我们配置一下
模式: 供 mode 配置选项,告知 webpack 使用相应模式的内置优化,默认值为
production
,另外还有development
、none
,他们的区别如下
选项 | 描述 |
---|---|
development | 开发模式,打包更加快速,省了代码优化步骤 |
production | 生产模式,打包比较慢,会开启 tree-shaking 和 压缩代码 |
none | 不使用任何默认优化选项 |
怎么配置呢?很简单
- 只需在配置对象中提供 mode 选项:
module.exports = {
mode: 'development',
};
复制代码
- 从 CLI 参数中传递:
$ webpack --mode=development
复制代码
1.3 配置文件
虽然有 0 配置打包,但是实际工作中,我们还是需要使用配置文件的方式,来满足不同项目的需求
-
根路径下新建一个配置文件
webpack.config.js
-
新增基本配置信息
const path = require('path')//nodejs环境下require
module.exports = {
mode: 'development', // 模式
entry: './src/index.js', // 打包入口地址
output: {
filename: 'bundle.js', // 输出文件名
path: path.join(__dirname, 'dist') // 输出文件目录, /dist可加可不加
}
}
注:__dirname指的是当前文件所在文件夹的绝对路径。
1.4 Loader
这里我们把入口改成 CSS 文件,可能打包结果会如何
1.新增 ./src/main.css
body {
margin: 0 auto;
padding: 0 20px;
max-width: 800px;
background: #f4f8fb;
}
复制代码
- 修改 entry 配置
const path = require('path')
module.exports = {
mode: 'development', // 模式
entry: './src/main.css', // 打包入口地址
output: {
filename: 'bundle.css', // 输出文件名
path: path.join(__dirname, 'dist') // 输出文件目录
}
}
复制代码
3.运行打包命令:npx webpack
这里就报错了!
这是因为:webpack 默认支持处理 JS 与 JSON 文件,其他类型都处理不了,这里必须借助 Loader 来对不同类型的文件的进行处理。
。
- 安装
css-loader
来处理 CSS
npn install css-loader -D
复制代码
- 配置资源加载模块
const path = require('path')
module.exports = {
mode: 'development', // 模式
entry: './src/main.css', // 打包入口地址
output: {
filename: 'bundle.css', // 输出文件名
path: path.join(__dirname, 'dist') // 输出文件目录
},
module: {
rules: [ // 转换规则
{
test: /\.css$/, //匹配所有的 css 文件
use: 'css-loader' // use: 对应的 Loader 名称
}
]
}
}
复制代码
- 重新运行打包命令
npx webpack
dist
└─ bundle.css # 打包得到的结果
复制代码
这里这是尝试,入口文件还是需要改回 ./src/index.js
这里我们可以得到一个结论:Loader 就是将 Webpack 不认识的内容转化为认识的内容
1.5 插件(plugin)
与 Loader 用于转换特定类型的文件不同,插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务
下面来看一个使用的列子:
1.新建 ./src/index.html
文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ITEM</title>
</head>
<body>
</body>
</html>
复制代码
如果我想打包后的资源文件,例如:js 或者 css 文件可以自动引入到 Html 中,就需要使用插件 html-webpack-plugin来帮助你完成这个操作
2.本地安装 html-webpack-plugin
npm install html-webpack-plugin -D
复制代码
3.配置插件
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development', // 模式
entry: './src/index.js', // 打包入口地址
output: {
filename: 'bundle.js', // 输出文件名
path: path.join(__dirname, 'dist') // 输出文件目录
},
module: {
rules: [
{
test: /\.css$/, //匹配所有的 css 文件
use: 'css-loader' // use: 对应的 Loader 名称
}
]
},
plugins:[ // 配置插件
new HtmlWebpackPlugin({
template: './src/index.html'//将打包好的文件引入此html模板中
filename: 'app.html' //模板输出在dist的文件的名字
inject: 'body' //注入位置,默认为head
})
]
}
复制代码
运行一下打包,打开 dist 目录下生成的 index.html 文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ITEM</title>
<script defer src="bundle.js"></script></head>
<body>
</body>
</html>
复制代码
可以看到它自动的引入了打包好的 bundle.js ,非常方便实用
1.6 自动清空打包目录
每次打包的时候,打包目录都会遗留上次打包的文件,为了保持打包目录的纯净,我们需要在打包前将打包目录清空
这里我们可以使用插件 clean-webpack-plugin 来实现
- 安装
$ npm install clean-webpack-plugin -D
复制代码
- 配置
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 引入插件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
// ...
plugins:[ // 配置插件
new HtmlWebpackPlugin({
template: './src/index.html'
}),
new CleanWebpackPlugin() // 引入插件
]
}
复制代码
在webpack5中不需要安装插件,在output中配置clean即可:
1.7 区分环境
本地开发和部署线上,肯定是有不同的需求
本地环境:
- 需要更快的构建速度
- 需要打印 debug 信息
- 需要 live reload 或 hot reload 功能
- 需要 sourcemap 方便定位问题
- ...
生产环境:
- 需要更小的包体积,代码压缩+tree-shaking
- 需要进行代码分割
- 需要压缩图片体积
- ...
针对不同的需求,首先要做的就是做好环境的区分
方法1:不用安装cross-env
通过--env配置环境变量
package.json
"scripts": {
"start": "webpack serve -c ./config/webpack.config.js --env development", //-c为指定打包的config文件
"build": "webpack -c ./config/webpack.config.js --env production"
}
module.exports = (env) => {
switch(true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
defult:
return new Error('No matching configuration was found')
}
}
方法2:
- 本地安装 cross-env [文档地址]
npm install cross-env -D
复制代码
- 配置启动命令
打开 ./package.json
"scripts": {
"dev": "cross-env NODE_ENV=dev webpack serve --mode development",
"test": "cross-env NODE_ENV=test webpack --mode production",
"build": "cross-env NODE_ENV=prod webpack --mode production"
},
复制代码
npm run dev 相当于
webpack --config build/webpack.config.js
- 在 Webpack 配置文件中获取环境变量
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
console.log('process.env.NODE_ENV=', process.env.NODE_ENV) // 打印环境变量
const config = {
entry: './src/index.js', // 打包入口地址
output: {
filename: 'bundle.js', // 输出文件名
path: path.join(__dirname, 'dist') // 输出文件目录
},
module: {
rules: [
{
test: /\.css$/, //匹配所有的 css 文件
use: 'css-loader' // use: 对应的 Loader 名称
}
]
},
plugins:[ // 配置插件
new HtmlWebpackPlugin({
template: './src/index.html'
})
]
}
module.exports = (env, argv) => {
console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
// 这里可以通过不同的模式修改 config 配置
return config;
}
复制代码
4.测试一下看看
- 执行
npm run build
process.env.NODE_ENV= prod
argv.mode= production
复制代码
- 执行
npm run test
process.env.NODE_ENV= test
argv.mode= production
复制代码
- 执行
npm run dev
process.env.NODE_ENV= dev
argv.mode= development
复制代码
这样我们就可以不同的环境来动态修改 Webpack 的配置
1.8 启动 devServer
1.安装 webpack-dev-server
npm install webpack-dev-server -D
复制代码
⚠️注意:当使用的
webpack-dev-server
版本是3.11.2
,用devServer.contentBase
配置项。,当版本version >= 4.0.0
时,需要使用 devServer.static 进行配置
2.配置本地服务
// webpack.config.js
const config = {
// ...
devServer: {
//默认配置static:true 静态文件目录默认指向public
compress: true, //是否启动压缩 gzip
port: 8081, // 端口号
open:true // 是否自动打开浏览器
},
// ...
}
module.exports = (env, argv) => {
console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
// 这里可以通过不同的模式修改 config 配置
return config;
}
复制代码
为什么要配置 contentBase ?
因为 webpack 在进行打包的时候,对静态文件的处理,例如图片,都是直接 copy 到 dist 目录下面。但是对于本地开发来说,这个过程太费时,也没有必要,所以在设置 static:true(contentBase) 之后,就直接到对应的静态目录(默认为public)下面去读取文件,而不需对文件做任何移动,节省了时间和性能开销。
- 启动本地服务
$ npm run dev
为了看到效果,我在 html 中添加了一段文字,并在 public 下面放入了一张图片 logo.png
public
└─ logo.png
复制代码
接着访问 http://localhost:8080/logo.png
1.9 引入 CSS
上面,我们在 Loader 里面讲到了使用 css-loader 来处理 css,但是单靠 css-loader 是没有办法将样式加载到页面上。这个时候,我们需要再安装一个 style-loader 来完成这个功能
style-loader 就是将通过css-loader处理好的 css 通过 style 标签的形式添加到页面上
- 安装
style-loader
[文档地址]
npm install style-loader -D
复制代码
- 配置 Loader
const config = {
// ...
module: {
rules: [
{
test: /\.css$/, //匹配所有的 css 文件
use: ['style-loader','css-loader']
}
]
},
// ...
}
复制代码
⚠️注意: Loader 的执行顺序是固定从后往前,即按
css-loader --> style-loader
的顺序执行
- 引用样式文件
在入口文件 ./src/index.js
引入样式文件 ./src/main.css
// ./src/index.js
import './main.css';
const a = 'Hello ITEM'
console.log(a)
module.exports = a;
复制代码
/* ./src/main.css */
body {
margin: 10px auto;
background: cyan;
max-width: 800px;
}
复制代码
- 重启一下本地服务,访问
http://localhost:8080/
css-loader(让webpack识别css文件) --> style-loader(将css文件转为style标签样式并插入head)
style-loader 核心逻辑相当于:
const content = `${样式内容}`
const style = document.createElement('style');
style.innerHTML = content;
document.head.appendChild(style);
复制代码
通过动态添加 style 标签的方式,将样式引入页面
1.10 CSS 兼容性
使用 postcss-loader,自动添加 CSS3 部分属性的浏览器前缀
上面我们用到的 transform: translateX(-50%);
,需要加上不同的浏览器前缀,这个我们可以使用 postcss-loader 来帮助我们完成
⚠️ 这里有个很大的坑点:参考文档配置好后,运行的时候会报错
😁
npm install postcss postcss-loader postcss-preset-env -D
复制代码
添加 postcss-loader 加载器
const config = {
// ...
module: {
rules: [
{
test: /\.css$/, //匹配所有的 css 文件
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}
]
},
// ...
}
复制代码
创建 postcss 配置文件 postcss.config.js
// postcss.config.js
module.exports = {
plugins: [require('postcss-preset-env')]
}
复制代码
创建 postcss-preset-env 配置文件 .browserslistrc
# 换行相当于 and
last 2 versions # 回退两个浏览器版本
> 0.5% # 全球超过0.5%人使用的浏览器,可以通过 caniuse.com 查看不同浏览器不同版本占有率
IE 10 # 兼容IE 10
再尝试运行一下
前缀自动加上了 👏
1.11 引入 Less 或者 Sass
less 和 sass 同样是 Webpack 无法识别的,需要使用对应的 Loader 来处理一下
文件类型 | loader |
---|---|
Less | less-loader |
Sass | sass-loader node-sass 或 dart-sass |
Less 处理相对比较简单,直接添加对应的 Loader 就好了
Sass 不光需要安装 sass-loader
还得搭配一个 node-sass
,这里 node-sass
建议用淘宝镜像来安装,npm 安装成功的概率太小了 🤣
这里我们就使用 Sass 来做案例
- 安装
$ npm install sass-loader -D
# 淘宝镜像
$ npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
复制代码
- 新建
./src/sass.scss
Sass 文件的后缀可以是 .scss(常用)
或者 .sass
$color: rgb(190, 23, 168);
body {
p {
background-color: $color;
width: 300px;
height: 300px;
display: block;
text-align: center;
line-height: 300px;
}
}
复制代码
- 引入 Sass 文件
import './main.css';
import './sass.scss' // 引入 Sass 文件
const a = 'Hello ITEM'
console.log(a)
module.exports = a;
复制代码
- 修改配置
const config = {
// ...
rules: [
{
test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
use: [
'style-loader',
'css-loader',
'postcss-loader',
'sass-loader',
]
},
]
},
// ...
}
复制代码
来看一下执行结果
1.12 分离样式文件
前面,我们都是依赖 style-loader
将样式通过 style 标签的形式添加到页面上
但是,更多时候,我们都希望可以通过 CSS 文件的形式<link href=...>引入到页面上
$ npm install mini-css-extract-plugin -D
复制代码
- 修改
webpack.config.js
配置
// ...
// 引入插件
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const config = {
// ...
module: {
rules: [
// ...
{
test: /\.(s[ac]|c)ss$/i, //匹配所有的 sass/scss/css 文件
use: [
// 'style-loader',
MiniCssExtractPlugin.loader, // 添加 loader
'css-loader',
'postcss-loader',
'sass-loader',
]
},
]
},
// ...
plugins:[ // 配置插件
// ...
new MiniCssExtractPlugin({ // 添加插件
filename: '[name].[hash:8].css' //指定打包的css文件名 可styles/[name].[hash:8].css
}),
// ...
]
}
// ...
复制代码
- 查看打包结果
dist
├─ avatar.d4d42d52.png
├─ bundle.js
├─ index.html
├─ logo.56482c77.png
└─ main.3bcbae64.css # 生成的样式文件
复制代码
官方文档:
推荐 production
环境的构建将 CSS 从你的 bundle 中分离出来,这样可以使用 CSS/JS 文件的并行加载。 这可以通过使用 mini-css-extract-plugin
来实现,因为它可以创建单独的 CSS 文件。 对于 development
模式(包括 webpack-dev-server
),你可以使用 style-loader,因为它可以使用多个 标签将 CSS 插入到 DOM 中,并且反应会更快。
i 不要同时使用
style-loader
与mini-css-extract-plugin
。
webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== "production";
module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
devMode ? "style-loader" : MiniCssExtractPlugin.loader,
"css-loader",
"postcss-loader",
"sass-loader",
],
},
],
},
plugins: [].concat(devMode ? [] : [new MiniCssExtractPlugin()]),
};
1.13 图片和字体文件
虽然上面在配置开发环境的时候,我们可以通过设置 contentBase
去直接读取图片类的静态文件,看一下下面这两种图片使用情况
- 页面直接引入
<!-- 本地可以访问,生产环境会找不到图片 -->
<img src="/logo.png" alt="">
11.jpg:1 Failed to load resource: the server responded with a status of 404 (Not Found)
- 背景图引入
<div id="imgBox"></div>
复制代码
/* ./src/main.js*/
...import logo from '../public/11.jpg'
复制代码
直接会报错
所以实际上,Webpack 无法识别图片文件,需要在打包的时候处理一下
在Webpack4中常用的处理图片文件的 Loader 包含:
Loader | 说明 |
---|---|
file-loader | 解决图片引入问题,并将图片 copy 到指定目录,默认为 dist |
url-loader | 解依赖 file-loader,当图片或字体小于 limit 值的时候,会将图片转为 base64 编码,大于 limit 值的时候依然是使用 file-loader 进行拷贝 |
img-loader | 压缩图片 |
- 安装
file-loader
npm install file-loader -D
复制代码
- 修改配置
const config = {
//...
module: {
rules: [
{
// ...
},
{
test: /\.(jpe?g|png|gif)$/i, // 匹配图片文件
use:[
'file-loader' // 使用 file-loader
]
}
]
},
// ...
}
复制代码
3.引入图片
<!-- ./src/index.html -->
<!DOCTYPE html>
<html lang="en">
...
<body>
<p></p>
<div id="imgBox"></div>
</body>
</html>
复制代码
样式文件中引入
/* ./src/sass.scss */
$color: rgb(190, 23, 168);
body {
p {
width: 300px;
height: 300px;
display: block;
text-align: center;
line-height: 300px;
background: url('../public/logo.png');
background-size: contain;
}
}
我们可以看到图片文件的名字都已经变了,并且带上了 hash 值,然后我看一下打包目录
dist
├─ 56482c77280b3c4ad2f083b727dfcbf9.png
├─ bundle.js
├─ d4d42d529da4b5120ac85878f6f69694.png
└─ index.html
复制代码
dist 目录下面多了两个文件,这正是 file-loader 拷贝过来的
- 安装
url-loader
$ npm install url-loader -D
复制代码
- 配置
url-loader
配置和 file-loader 类似,多了一个 limit 的配置
const config = {
//...
module: {
rules: [
{
// ...
},
{
test: /\.(jpe?g|png|gif)$/i,
use:[
{
loader: 'url-loader',
options: {
name: '[name][hash:8].[ext]',
// 文件小于 50k 会转换为 base64,大于则拷贝文件
limit: 50 * 1024
}
}
]
},
]
},
// ...
}
复制代码
看一下,我们两个图片文件的体积
public
├─ avatar.png # 167kb
└─ logo.png # 43kb
复制代码
我们打包看一下效果
很明显可以看到 logo.png 文件已经转为 base64 ,而大于50kb的则直接拷贝到dist文件下
参考文档进行配置即可
https://v4.webpack.docschina.org/loaders/file-loader
1.14 资源模块的使用
在 webpack 5 之前,通常使用:
raw-loader
将文件导入为字符串url-loader
将文件作为 data URI 内联到 bundle 中,若小于限定的则转为base64内联到bundle,否则拷贝到distfile-loader
将文件发送到输出目录
webpack5 新增资源模块(asset module),允许使用资源文件(字体,图标等)而无需配置额外的 loader。
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
asset/resource
将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能,可在dist中找到56482c77280b3c4ad2f083b727dfcbf9.png
.asset/inline
将资源导出为 dataUrl (base64)的形式,类似之前的 url-loader 的小于 limit 参数时功能,在dist文件内找不到.asset/source
将资源导出为源码(source code). 类似的 raw-loader 功能.在dist中找不到asset
会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource
件
// ./src/index.js
import logo from '../public/avatar.png'
const img = new Image()
img.src = logo
document.getElementById('imgBox').appendChild(img)
根据类型的不同,img.src不同,上面的为inline(打包为base64),下面为resource(在dist中存在)
注:output{assetModuleFilename:"[name][hash:8][ext]"}跟generator{filename}功能一样,但后者的优先级更高
文档地址:https://webpack.docschina.org/guides/asset-modules
贴一下修改后的完整代码
// ./src/index.js
const config = {
//...
output{assetModuleFilename:"[name][hash:8][ext]"}// ...
module: {
rules: [
// ...
{
test: /\.(jpe?g|png|gif)$/i,
type: 'asset', //asset/inline
generator: {
// 输出文件位置以及文件名
// [ext]为扩展名,自动补全 自带 "." 这个与 url-loader 配置不同
filename: "[name][hash:8][ext]"
},
parser: {
dataUrlCondition: {
maxSize: 50 * 1024 //超过50kb不转 base64
}
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/i,
type: 'asset',
generator: {
// 输出文件位置以及文件名
filename: "[name][hash:8][ext]"//如果是/fonts[hash:8][ext]则打包到dist并生成fonts
},
parser: { //定义解析器
dataUrlCondition: {
maxSize: 10 * 1024 // 超过100kb不转 base64
}
}
},
]
},
// ...
}
module.exports = (env, argv) => {
console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
// 这里可以通过不同的模式修改 config 配置
return config;
}
复制代码
执行打包,结果和之前一样
1.15 JS 兼容性(Babel)
在开发中我们想使用最新的 Js 特性,但是有些新特性的浏览器支持并不是很好,所以 Js 也需要做兼容处理,常见的就是将 ES6 语法转化为 ES5。
这里将登场的“全场最靓的仔” -- Babel
- 未配置 Babel
我们写点 ES6 的东西
// ./src/index.js
import './main.css';
import './sass.scss'
import logo from '../public/avatar.png'
import './fonts/iconfont.css'
// ...
class Author {
name = 'ITEM'
age = 18
email = 'lxp_work@163.com'
info = () => {
return {
name: this.name,
age: this.age,
email: this.email
}
}
}
module.exports = Author
复制代码
为了方便看源码,我们把 mode 换成 development
接着执行打包命令
打包完成之后,打开 bundle.js
查看打包后的结果
虽然我们可以找到我们的代码,但是阅读起来比较不直观,我们先设置 mode 为 none
,以最原始的形式打包,再看一下打包结果
打包后的代码变化不大,只是对图片地址做了替换,还是ES6的语法,部分旧版本浏览器不兼容,接下来看看配置 babel 后的打包结果会有什么变化
- 安装依赖
$ npm install babel-loader @babel/core @babel/preset-env -D
复制代码
babel-loader
使用 Babel 加载 ES2015+ 代码并将其转换为 ES5@babel/core
Babel 编译的核心包@babel/preset-env
Babel 编译的预设,可以理解为 一组Babel 插件的集合
- 配置 Babel 预设
// webpack.config.js
// ...
const config = {
entry: './src/index.js', // 打包入口地址
output: {
filename: 'bundle.js', // 输出文件名
path: path.join(__dirname, 'dist'), // 输出文件目录
},
module: {
rules: [
{
test: /\.js$/i,
exclude:/node_modules/, //node_modules里的js不需要转换成ES5
use: [
{
loader: 'babel-loader',
options: {
presets: [//预设
'@babel/preset-env'
],
}
}
]
},
// ...
]
},
//...
}
// ...
复制代码
配置完成之后执行一下打包
刚才写的 ES6 class 写法 已经转换为了 ES5 的构造函数形式
尽然是做兼容处理,我们自然也可以指定到底要兼容哪些浏览器
为了避免 webpack.config.js
太臃肿,建议将 Babel 配置文件提取出来
根目录下新增 .babelrc.js
// ./babelrc.js
module.exports = {
presets: [
[
"@babel/preset-env",
{
// useBuiltIns: false 默认值,无视浏览器兼容配置,引入所有 polyfill
// useBuiltIns: entry 根据配置的浏览器兼容,引入浏览器不兼容的 polyfill
// useBuiltIns: usage 会根据配置的浏览器兼容,以及你代码中用到的 API 来进行 polyfill,实现了按需添加
useBuiltIns: "entry",
corejs: "3.9.1", // 是 core-js 版本号
targets: {
chrome: "58",
ie: "11",
},
},
],
],
};
复制代码
好了,这里一个简单的 Babel 预设就配置完了
- 配置 Babel 插件
对于正在提案中,还未进入 ECMA 规范中的新特性,Babel 是无法进行处理的,必须要安装对应的插件,例如:async、await, 现在进入ECMA规范了。。。
// 新增装饰器的使用 @log('hi') class MyClass { }
Babel 提供了对应的插件:
@babel/plugin-proposal-decorators
@babel/plugin-proposal-class-properties
安装一下:
$ npm install babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D复制代码
打开 .babelrc.js
加上插件的配置
module.exports = {
presets: [
[
"@babel/preset-env",
{
useBuiltIns: "entry",
corejs: "3.9.1",
targets: {
chrome: "58",
ie: "11",
},
},
],
],
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
]
};
复制代码
这样就可以打包了,在 bundle.js
中已经转化为浏览器支持的 Js 代码
1. 16 代码分离与动态导入
运行时优化的核心就是提升首屏的加载速度,主要的方式就是
- 降低首屏加载文件体积,首屏不需要的文件进行预加载或者按需加载
- 把多个模块共享的代码抽离出去,减少入口文件的大小
1.0 代码分离
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的
bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的
bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。
常用的代码分离方法有三种:
入口起点:使用 entry 配置手动地分离代码。
防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离
chunk。
动态导入:通过模块的内联函数调用来分离代码。
1.1 入口点分割
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
output: {
filename: '[name].bundle.js'
},
}`
```
这种方式的确存在一些隐患:
- 如果入口 chunk 之间包含一些重复的模块,那些重复模块都会被引入到各个bundle 中。
- 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。
以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在
./src/index.js 中也引入过 lodash ,这样就在两个 bundle 中造成重复引用。
1.2 splitChunks 分包配置
optimization.splitChunks 是基于 SplitChunksPlugin 插件实现的
SplitChunksPlugin 插件可以将公共的依赖模块提取到已有的入口 chunk
中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重
复的 lodash 模块去除:
//...
optimization: {
//...
splitChunks: {
chunks: 'all',
}
},
}
1.3 代码懒加载
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把
你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用
或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体
体积,因为某些代码块可能永远不会被加载
针对首屏加载不太需要的一些资源,我们可以通过懒加载的方式去实现,下面看一个小🌰
- 需求:点击图片给图片加一个描述
1. 新建图片描述信息
desc.js
const ele = document.createElement('div')
ele.innerHTML = '我是图片描述'
module.exports = ele
复制代码
2. 点击图片引入描述
index.js
import './main.css';
import './sass.scss'
import logo from '../public/avatar.png'
import '@/fonts/iconfont.css'
const a = 'Hello ITEM'
console.log(a)
const img = new Image()
img.src = logo
document.getElementById('imgBox').appendChild(img)
// 按需加载
img.addEventListener('click', () => {
import(/* webpackChunkName: '685' */ './desc').then(({ default: element }) => {
console.log(element)
document.body.appendChild(element)
})
})
这里有句注释,我们把它称为 webpack 魔法注释: webpackChunkName: 'math' ,
告诉webpack打包生成的文件名为685。
3. 查看效果
- 点击前
- 点击后
1.4 prefetch 与 preload
上面我们使用异步加载的方式引入图片的描述,但是如果需要异步加载的文件比较大时,在点击的时候去加载也会影响到我们的体验,这个时候我们就可以考虑使用 prefetch 来进行预拉取
prefetch(预获取):将来某些导航下可能需要的资源
preload(预加载):当前导航下可能需要资源
1.4.1 prefetch
- prefetch (预获取):浏览器空闲的时候进行资源的拉取
改造一下上面的代码
// 按需加载
img.addEventListener('click', () => {
import( /* webpackPrefetch: true */ './math').then(({ default: element }) => {
console.log(element)
document.body.appendChild(element)
})
})
复制代码
添加第二句魔法注释: webpackPrefetch: true
告诉 webpack 执行预获取。这会生成 <link rel="prefetch" href="math.js">
并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件.
运行打包后:
我们发现,在还没有点击按钮时, math.bundle.js 就已经下载下来了。同时,在
app.html 里webpack自动添加了一句:<link rel="prefetch" href="math.js">
1.4.2 preload
- preload (预加载):提前加载后面会用到的关键资源
- ⚠️ 因为会提前拉取资源,如果不是特殊需要,谨慎使用
官网示例:
import(/* webpackPreload: true */ 'ChartingLibrary');
与 prefetch 指令相比,preload 指令有许多不同之处:
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
- 浏览器支持程度不同。
1.17 CSS压缩
css-minimizer-webpack-plugin
:
$ npm install css-minimizer-webpack-plugin --save-dev
接着在 webpack
配置中加入该插件。示例:
webpack.config.js
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
module.exports = {
module: {
rules: [
{
test: /.s?css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
],
},
optimization: {
minimizer: [
// 在 webpack@5 中,你可以使用 `...` 语法来扩展现有的 minimizer(即 `terser-webpack-plugin`),将下一行取消注释
// `...`,
new CssMinimizerPlugin(),
],
},
plugins: [new MiniCssExtractPlugin()],
};
1.18 缓存
1.18.1输出文件的文件名
如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。
可以通过替换 output.filename 中的 substitutions 设置,来定义输出文件的名称。webpack 提供了一种使用称为 substitution(可替换模板字符串) 的方式,其中, [contenthash] substitution 将根据资源内容创建出唯一 hash。
1.18.2 缓存第三方库
将第三方库(library)(例如 lodash )提取到单独的 vendor chunk 文件中,是比较
推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。
配置输出文件名
output: {//出口配置
filename: 'scripts/[name].[contenthash].js',//文件名,随机生成hash,如index.ashdoadosd.js,每次打包文件名不同,刷新缓存
},
第三方库
splitChunks: {//代码分离
cacheGroups: {//缓存
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',//对于modules很少修改的模块,名字固定即可
chunks: 'all'
}
}
}
1.19 拆分开发环境和生产环境配置
1.19.1公共路径
publicPath 配置选项在各种场景中都非常有用。你可以通过它来指定应用程序中所 有资源的基础路径。
output: {
.../
publicPath: 'http://localhost:8080/'
}
//在打包好的html文件中标签为<link href='http://localhost:8080/styles/46sdfsw9wf13w.css'>
1.19.2环境变量
webpack 命令行 环境配置 的 --env
参数,可以允许你传入任意数量的环境变量。而在 webpack.config.js
中可以访问到这些环境变量。例如,--env production
或 --env goal=local
。
npx webpack --env goal=local --env production --progress
复制代码
对于我们的 webpack 配置,有一个必须要修改之处。通常,module.exports
指向配置对象。要使用 env
变量,你必须将 module.exports
转换成一个函数:
module.exports = (env) => {
return {
//...
// 根据命令行参数 env 来设置不同环境的 mode
mode: env.production ? 'production' : 'development',
//...
}
}
注:terser-webpack-plugin为webpack5开箱即用的插件,用来压缩js,但在前面optimization中配置了CSS压缩后,要重新引入terser并在optimization中配置才可压缩js代码。
先上总体代码webpack.config.js:
const path = require('path')//路径包
const HtmlWebpackPlugin = require('html-webpack-plugin')//打包后的文件自动引入html模板重
const MiniCssExtractPlugin = require('mini-css-extract-plugin')//分离样式文件
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')//CSS压缩
const TerserPlugin = require('terser-webpack-plugin')//js压缩
const toml = require('toml')//数据处理
const yaml = require('yaml')//数据处理
const json5 = require('json5')//处理json5数据
module.exports = {
entry: {//入口配置,多点打包入口
index: './src/index.js',
another: './src/another-module.js'
},
output: {//出口配置
filename: 'scripts/[name].[contenthash].js',//文件名,随机生成hash,如index.ashdoadosd.js,每次打包文件名不同,刷新缓存
path: path.resolve(__dirname, './dist'),//输出路径,__dirname为当前目录下的绝对路径
clean: true,//自动清理dist文件,webpack4中是用插件clean-webpack-plugin
assetModuleFilename: 'images/[contenthash][ext]',//资源管理模块的输出文件名
publicPath: 'http://localhost:8080/'
},
mode: env.production ? 'production' : 'development',//开发模式
devtool: 'inline-source-map',//sourcemap
plugins: [
new HtmlWebpackPlugin({
template: './index.html',//插入的html模板
filename: 'app.html',//输出到dist的html文件名
inject: 'body'//插入位置
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'//分离样式文件输出的名字
})
],
devServer: {//默认配置 port:8080 open:true compress:true
static: './dist'//静态文件的处理输出的路径
},
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',//资源管理模块,直接打包到dist
generator: {
filename: 'images/[contenthash][ext]'
}
},
{
test: /\.svg$/,//图标
type: 'asset/inline'//base64格式
},
{
test: /\.txt$/,
type: 'asset/source'//打包源码
},
{
test: /\.jpg$/,
type: 'asset',//动态匹配inline或resource
parser: {
dataUrlCondition: {
maxSize: 4 * 1024 * 1024//限制大小
}
}
},
{
test: /\.(css|less)$/,//处理less->css->样式分离引入
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,//字体
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /\.xml$/,
use: 'xml-loader'
},
{
test: /\.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
},
{
test: /\.yaml$/,
type: 'json',
parser: {
parse: yaml.parse
}
},
{
test: /\.json5$/,
type: 'json',
parser: {
parse: json5.parse
}
},
{
test: /\.js$/,
exclude: /node_modules/,//对modules中的js文件不转化
use: {
loader: 'babel-loader',//ES6+转ES5
options: {
presets: ['@babel/preset-env'],//预处理
plugins: [//插件,主要用来对未纳入ECM规范的语法进行转译
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
},
optimization: {//优化
minimizer: [
new CssMinimizerPlugin(),//CSS压缩
new TerserPlugin()//js压缩
],
splitChunks: {//代码分离
cacheGroups: {//缓存
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',//对于modules很少修改的模块,名字固定即可
chunks: 'all'
}
}
}
}
}
2.0 拆分文件与合并
目前,生产环境和开发环境使用的是一个配置文件,我们需要将这两个文件单独放到不同的配置文件中。如webpack.config.dev.js
(开发环境配置)和 webpack.config.prod.js
(生产环境配置)。在项目根目录下创建一个配置文件夹 config
来存放他们。
配置 npm 脚本来简化命令行的输入,这时可以省略 npx
:
package.json
{
"scripts": {
"start": "webpack serve -c ./config/webpack.config.dev.js",
"build": "webpack -c ./config/webpack.config.prod.js"
}
}
注:-c是指定要打包使用的config文件
创建 webpack.config.common.js
,配置公共的内容:
webpack.config.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
output: {
// 注意这个dist的路径设置成上一级
path: path.resolve(__dirname, '../dist'),
clean: true,
assetModuleFilename: 'images/[contenthash][ext]',
},
plugins: [
new HtmlWebpackPlugin({
template: './index.html',
filename: 'app.html',
inject: 'body'
}),
new MiniCssExtractPlugin({
filename: 'styles/[contenthash].css'
})
],
module: {
rules: [
{
test: /\.png$/,
type: 'asset/resource',
generator: {
filename: 'images/[contenthash][ext]'
}
},
{
test: /\.svg$/,
type: 'asset/inline'
},
{
test: /\.txt$/,
type: 'asset/source'
},
{
test: /\.jpg$/,
type: 'asset',
parser: {
dataUrlCondition: {
maxSize: 4 * 1024
}
}
},
{
test: /\.(css|less)$/,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader']
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/,
type: 'asset/resource'
},
{
test: /\.(csv|tsv)$/,
use: 'csv-loader'
},
{
test: /\.xml$/,
use: 'xml-loader'
},
{
test: /\.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
},
{
test: /\.yaml$/,
type: 'json',
parser: {
parse: yaml.parse
}
},
{
test: /\.json5$/,
type: 'json',
parser: {
parse: json5.parse
}
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: [
[
'@babel/plugin-transform-runtime'
]
]
}
}
}
]
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
},
//关闭 webpack 的性能提示
performance: {
hints:false
}
}
复制代码
webpack.config.dev.js
module.exports = {
// 开发环境不需要配置缓存
output: {
filename: 'scripts/[name].js',
},
// 开发模式
mode: 'development',
// 配置 source-map
devtool: 'inline-source-map',
// 本地服务配置
devServer: {
static: './dist'
}
}
webpack.config.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
module.exports = {
// 生产环境需要缓存
output: {
filename: 'scripts/[name].[contenthash].js',
publicPath: 'http://localhost:8080/'
},
// 生产环境模式
mode: 'production',
// 生产环境 css 压缩
optimization: {
minimizer: [
new CssMinimizerPlugin()
]
}
}
如何保证配置合并没用问题呢?webpack-merge 这个工具可以完美解决这个问题。
安装 webpack-merge
:
npm install webpack-merge -D
创建 webpack.config.js
,合并代码:
const { merge } = require('webpack-merge')
const commonConfig = require('./webpack.config.common.js')
const productionConfig = require('./webpack.config.prod.js')
const developmentConfig = require('./webpack.config.dev')
module.exports = (env) => {
switch(true) {
case env.development:
return merge(commonConfig, developmentConfig)
case env.production:
return merge(commonConfig, productionConfig)
default:
throw new Error('No matching configuration was found!');
}
}
建议先看完视频再看第二篇文章,更好理解
参考: