webpack是一个现代JavaScript应用程序的静态模块打包器。当webpack处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个bundle.
本文着重讲述webpack基础,先来绘制一个大概的知识体系,接下来会根据该知识体系进行讲述:
1. 简单配置
第一部分,我们先从“会配置”入手,去了解webpack简单配置以及简单配置所涉及到的知识点。该部分重点需要掌握以下几点:
:::info
1、webpack的常规配置项;
2、常用的Loader与其配置;
3、常用的Plugin与其配置;
4、Babel的配置与使用;
:::
1.1 安装
在本地安装webpack
以及webpack-cli
npm install webpack webpack-cli -D // 注意区分环境 -D -S
1.2 工作模式
webpack在4之后就支持0配置打包。我们在此处新建一个./src/index.js
文件,简单测试一下:
const a = 'btqf';
console.log('btqf');
module.exports = a
当我们直接运行npx webpack
,启动打包,会出现以下结果:
此时提示我们没有配置模式(mode),我们都知道mode
的作用是告知webpack使用相应模式的内置优化,其默认值为production
。
选项 | 描述 |
---|---|
development | 开发模式,打包更为快速,节省代码优化步骤 |
production | 生产模式,打包较慢,会开启tree-shaking 和压缩代码 |
none | 不使用任何默认的优化选项 |
因此对于上面的警告提示,我们可以采用以下两种方法:
在配置中提供
mode
选项module.exports = {
mode: 'production'
};
从
cli
参数中传递:webpack --mode=production
1.3 配置文件
虽然有0配置打包,但是在实际工作中。我们很多时候采用配置文件的方式来满足不同项目的需求,基本的配置步骤如下:
根路径下新建一个配置文件
webpack.config.js
- 新增基本配置信息 ```javascript const path = require(‘path’)
module.exports = { mode: ‘production’, // 模式 entry: ‘./src/index.js’, // 打包入口地址 output: { filename: ‘bundle.js’, // 输出文件名 path: path.join(__dirname, ‘dist’) // 输出文件目录 } }
<a name="uAPKu"></a>
### 1.4 Loader
首先我们需要了解,**webpack默认支持处理 JS 和 JSON 文件,无法处理其他类型的文件,所以这里我们可以使用Loader来将webpack不认识的内容转化为认识的内容。**<br />我们先创建`./src/main.css`文件,在把**打包入口修改为**`./src/main.css`后直接运行`npx webpack`.
```javascript
// ./src/main.css
body {
margin: 10px auto;
background: cyan;
max-width: 800px;
font-size: 46px;
font-weight: 600;
color: white;
position: fixed;
left: 50%;
transform: translateX(-50%);
}
此时我们来看看运行结果:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/28976061/1659770836342-fa313525-4156-48fa-a8a9-77678c7a46ce.png#clientId=ue5f6de47-6c08-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=132&id=uf0141290&margin=%5Bobject%20Object%5D&name=image.png&originHeight=165&originWidth=1475&originalType=binary&ratio=1&rotation=0&showTitle=false&size=23792&status=done&style=none&taskId=u112409e5-df51-46f5-8123-0750a1afeed&title=&width=1180)<br />如我们所料一般,根据提示我们需要安装`css-loader`来对 css 文件进行处理:
npm install css-loader -D
在安装过后,我们需要在`webpack.config.js`中配置资源加载模块:
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 名称
}
]
}
}
在修改过后就可以成功打包了:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/28976061/1659771384029-f02a5b6a-9f9c-4ee7-a35c-ac7a039a5b31.png#clientId=ue5f6de47-6c08-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=u6b344b64&margin=%5Bobject%20Object%5D&name=image.png&originHeight=139&originWidth=1115&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29580&status=done&style=none&taskId=u2293f109-ea41-4d41-a10f-9692cbe55d8&title=&width=892)
1.5 Plugins(插件)
与 Loader 用于转换特定类型的文件不同,插件可以贯穿webpack打包的生命周期,执行不同的任务。
如果想要使用一个插件,我们只需要require()
它,探后把它添加到plugins
数组中,多事插件可以通过选项(option
)自定义。这么说可能不太清楚,下面用一个例子来详细讲述:
第一步,我们新建一个./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`来帮助完成该操作。所以第二步为安装插件:
npm install html-webpack-plugin -D
第三步就是在`webpack.config.js`中配置插件即可:
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'
})
]
}
当我们运行后,打开`./dist/index.html`文件,可以看到它自动引入的打包好的`bundle.js`,非常方便:<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/28976061/1659772249255-e754ddde-7d33-4fc3-8272-3ee7718d8713.png#clientId=ue5f6de47-6c08-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=250&id=u7b9873fc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=313&originWidth=1091&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29985&status=done&style=none&taskId=u7f79d8f1-de8e-45d4-a84a-a725536e010&title=&width=872.8)
1.6 自动清空打包目录
在每次进行打包的时候,打包目录总是会遗留上次打包的文件,为了保持打包目录的纯净,我们需要在打包前将打包目录清空。这里我们可以使用插件clean-webpack-plugin
来实现。
1.安装
npm install clean-webpack-plugin -D
2.配置
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() // 引入插件
]
}
1.7 区分环境
我们都清楚,本地开发和部署线上,肯定有不同的需求。
本地环境
- 需要更快的构件速度
- 需要打印 debug 信息
- 需要 live reload 或 hot reload 功能
- 需要 sourceMap 方便定位问题
生产环境
- 需要更小的包体积、代码压缩+tree-shaking
- 需要进行代码分割
- 需要压缩图片体积
- …..
针对不同的需求,我们得要做好环境的区分。
本地安装 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"
},
在webpack配置文件中获取环境变量 ```javascript 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. 测试执行
```javascript
// 执行 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
1.8 启动 devServer
1.安装 webpack-dev-server
npm intall webpack-dev-server@3.11.2 -D
// 注意:当devServer版本 >= 4.0.0 时,需要使用devServer.static进行配置,不再有
// devServer.contentBase 配置项
2.配置本地服务
// webpack.config.js
const config = {
// ...
devServer: {
contentBase: path.resolve(__dirname, 'public'), // 静态文件目录
compress: true, //是否启动压缩 gzip
port: 8080, // 端口号
// open:true // 是否自动打开浏览器
},
// ...
}
module.exports = (env, argv) => {
console.log('argv.mode=',argv.mode) // 打印 mode(模式) 值
// 这里可以通过不同的模式修改 config 配置
return config;
}
在这里先说明一下,**为什么要配置 contentBase?**<br />原因在于webpack在进行打包的时候,对静态文件的处理,例如图片,都是直接copy到dist目录下,但对于本地开发来说,该过程费时也没有必要,所以在设置contentBase之后,就直接到对应的静态目录下面去读取文件,而不需要对文件进行任何改动,**节省了时间和性能开销。**<br />3.启动本地服务`npm run dev`,打开`http://localhost:8080/`即可看到效果
1.9 引入CSS
在上面的Loader中,我们通过使用css-Loader
来处理css,但是单靠css-Loader
是没有办法将样式加载到页面上的,此时我们可以再安装一个style-loader
来完成。style-loader
就是将处理好的css通过style标签的形式添加到页面上。值得注意的是,loader的执行顺序是固定从后往前的。
1.安装
npm install style-loader -D
2.配置
const config = {
// ...
module: {
rules: [
{
test: /\.css$/, //匹配所有的 css 文件
use: ['style-loader','css-loader']
}
]
},
// ...
}
3.引入样式文件:在入口文件`./src/index.js`中引入样式文件`./src/main.css`
// ./src/index.js
import './main.css';
const a = 'Hello ITEM'
console.log(a)
module.exports = a;
4.重启本地服务,访问`http://localhost:8080/`<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/28976061/1659855462545-39483908-4bb7-49f1-abbe-d1b4900cb89a.png#clientId=u2d87e882-4af5-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=347&id=u2ad42f8e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=434&originWidth=932&originalType=binary&ratio=1&rotation=0&showTitle=false&size=35859&status=done&style=none&taskId=ud22c5397-8a4a-401c-a5de-1040eaeb772&title=&width=745.6)
1.10 CSS兼容性
我们可以通过使用postcss-loader
,自动添加 css3 部分属性的浏览器前缀。
就像之前用到的transform: translateX(-50%)
,需要加上不同的浏览器前缀。
1.安装postcss-loader
npm install postcss postcss-loader postcss-preset-env -D
2.在webpack.config.js
文件中添加postcss-loader
3.创建postcss
配置文件postcss.config.js
// postcss.config.js
module.exports = {
plugins: [require('postcss-preset-env')]
}
4.创建 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 |
1.安装
npm install sass-loader -D
// 推荐用淘宝镜像下载,成功率比较高
npm i node-sass --sass_binary_site=https://npm.taobao.org/mirrors/node-sass/
2.新建`./src/sass.scss`
$color: rgb(190, 23, 168);
body {
p {
background-color: $color;
width: 300px;
height: 300px;
display: block;
text-align: center;
line-height: 300px;
}
}
3.在入口文件中引入Sass文件后,修改webpack.config.js配置
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
标签的形式添加到页面上,但是我们可以通过mini-css-extract-plugin
将css文件的形式引入到页面上。
1.安装
npm install mini-css-extract-plugin -D
2.修改配置
// ...
// 引入插件
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'
}),
// ...
]
}
3.查看打包结果<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/28976061/1659858692503-b4d71ef5-fa87-4834-9690-e0b98ffd1225.png#clientId=u9fc43456-4740-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=333&id=u70b369a2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=416&originWidth=928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=30868&status=done&style=none&taskId=u0dcc0d7b-afca-4637-b3ba-946fc2ff6c9&title=&width=742.4)
1.13 图片和字体文件
在开发环境中,我们可以通过设置contentBase
直接读取图片类的静态文件。但是当在生产环境中无法识别图片文件,所以可以用下面集中loader进行处理:
Loader | 说明 |
---|---|
file-loader | 解决图片引入问题,并将图片 copy 到指定目录,默认为 dist |
url-loader |
| 解依赖 file-loader,当图片小于 limit 值的时候,会将图片转为 base64 编码,大于 limit 值的时候依然是使用 file-loader 进行拷贝 | | img-loader | 压缩图片 |
1.安装file-loader。<br />值得一提的是,在webpack5中,内置了资源处理模块,`file-loader`和`url-loader`都可以不用安装。
npm install file-loader -D
2.修改配置
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/main.css */
...
#imgBox {
height: 400px;
width: 400px;
background: url('../public/logo.png');
background-size: contain;
}
4.刷新运行,结果如下:
至于url-loader
的配置基本与上述无异,只是多了一个 limit 的限制,这里就不再详细叙述了。
1.14 资源模块的使用
webpack5 新增资源模块,允许使用资源文件而不用配置额外的loader。资源模块支持以下四个配置: :::info
- asset/resource 将资源分割为单独的文件,并导出 url,类似之前的 file-loader 的功能.
- asset/inline 将资源导出为 dataUrl 的形式,类似之前的 url-loader 的小于 limit 参数时功能.
- asset/source 将资源导出为源码(source code). 类似的 raw-loader 功能.
- asset 会根据文件大小来选择使用哪种类型,当文件小于 8 KB(默认) 的时候会使用 asset/inline,否则会使用 asset/resource ::: 修改配置,执行打包后的结果一致。 ```javascript // ./src/index.js
const config = { // … module: { rules: [ // … { test: /.(jpe?g|png|gif)$/i, type: ‘asset’, 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]” }, parser: { dataUrlCondition: { maxSize: 10 * 1024 // 超过100kb不转 base64 } } }, ] }, // … }
<a name="iEr1a"></a>
### 1.15 JS兼容性(Babel)
在开发中,如果我们想要使用最新的 JS 特性,但有些新特性的兼容性支持并不是很好,所以我们需要做兼容处理,常见的就是将ES6语法转化为ES5。<br />1.在配置前,我们可以先写一点ES6语法。
```javascript
import './main.css'
class Author {
name = 'btqf'
age = 18
email = 'xxx@outlook.com'
info = () => {
return {
name: this.name,
age: this.age,
email: this.email
}
}
}
module.exports = Author
为了方便看源码,我们先将mode
设置为none
,以最原始的形式打包。打包后发现代码变化不大,只是对图片的地址进行了替换,接下来我们看看配置babel后的打包结果有啥子变化。
2.安装依赖
npm install babel-loader @babel/core @babel/preset-env -D
babel-loader
使用 Babel 加载 ES2015+ 代码并将其转换为 ES5@babel/core Babel
编译的核心包@babel/preset-env Babel
编译的预设,可以理解为 Babel 插件的超集
3.为了避免webpack.config.js
过于臃肿,新建文件.babelrc.js
配置Babel预设,
// ./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",
},
},
],
],
};
配置执行打包,发现ES6 class写法已经转换成 ES5 的构造函数形式。
4.对于还未进入 ECMA 规范中的新特性, Babel 是无法进行处理的,必须要安装对应的插件,例如:
@babel/plugin-proposal-decorators
@babel/plugin-proposal-class-properties
安装后打开.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代码。
2.SourceMap配置选择
SourceMap 是一种映射关系,当项目运行后,如果出现错误,我们可以利用 SourceMap 反向定位到源码位置。
2.1 devtool 配置
const config = {
entry: './src/index.js', // 打包入口地址
output: {
filename: 'bundle.js', // 输出文件名
path: path.join(__dirname, 'dist'), // 输出文件目录
},
devtool: 'source-map',
module: {
// ...
}
执行打包后,dist 目录下会生成以 .map 结尾的 SourceMap 文件。除了 source-map 这种类型之外,还有很多种类型可以用,例如:
eval
eval-source-map
cheap-source-map
inline-source-map
cheap-module-source-map
inline-cheap-source-map
cheap-module-eval-source-map
inline-cheap-module-source-map
hidden-source-map
nosources-source-map
2.2 配置项差异
这里就不一一就行分析了,直接上图:
devtool | build | rebuild | 显示代码 | SourceMap 文件 | 描述 |
---|---|---|---|---|---|
(none) | 很快 | 很快 | 无 | 无 | 无法定位错误 |
eval | 快 | 很快(cache) | 编译后 | 无 | 定位到文件 |
source-map | 很慢 | 很慢 | 源代码 | 有 | 定位到行列 |
eval-source-map | 很慢 | 一般(cache) | 编译后 | 有(dataUrl) | 定位到行列 |
eval-cheap-source-map | 一般 | 快(cache) | 编译后 | 有(dataUrl) | 定位到行 |
eval-cheap-module-source-map | 慢 | 快(cache) | 源代码 | 有(dataUrl) | 定位到行 |
inline-source-map | 很慢 | 很慢 | 源代码 | 有(dataUrl) | 定位到行列 |
hidden-source-map | 很慢 | 很慢 | 源代码 | 有 | 无法定位错误 |
nosource-source-map | 很慢 | 很慢 | 源代码 | 无 | 定位到文件 |
关键字 | 描述 |
---|---|
inline | 代码内通过 dataUrl 形式引入 SourceMap |
hidden | 生成 SourceMap 文件,但不使用 |
eval | eval(…) 形式执行代码,通过 dataUrl 形式引入 SourceMap |
nosources | 不生成 SourceMap |
cheap | 只需要定位到行信息,不需要列信息 |
module | 展示源代码中的错误位置 |
2.3 推荐配置
- 本地开发:
推荐:eval-cheap-module-source-map
理由:
- 本地开发首次打包慢点没关系,因为 eval 缓存的原因,rebuild 会很快
- 开发中,我们每行代码不会写的太长,只需要定位到行就行,所以加上 cheap
- 我们希望能够找到源代码的错误,而不是打包后的,所以需要加上 module
- 生产环境:
推荐:(none)
理由:
- 就是不想别人看到我的源代码
3. 三种 hash 值
Webpack 文件指纹策略是将文件名后面加上 hash 值。特别在使用 CDN 的时候,缓存是它的特点与优势,但如果打包的文件名,没有 hash 后缀的话将会很麻烦。
例如我们在基础配置中用到的:filename: "[name][hash:8][ext]
,这里里面 [ ] 包起来的,就叫占位符,它们都是什么意思呢?请看下表:
占位符 | 解释 |
---|---|
ext | 文件后缀名 |
name | 文件名 |
path | 文件相对路径 |
folder | 文件所在文件夹 |
hash | 每次构建生成的唯一 hash 值,任意文件改动,整个项目的 hash 都会改变 |
chunkhash | 根据 chunk 生成 hash 值,文件的改动只会影响其所在 chunk 的 hash 值 |
contenthash | 根据文件内容生成hash 值,文件的改动只会影响自身的 hash 值 |
好了,关于webpack的基础篇就暂时讲到这里了,如有错误,欢迎指出,谢谢!