文章第一句话为“这是我参与「第四届青训营 」笔记创作活动的第3天
webpack
webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源,用于展示你的内容。
我们结合官网的banner图,用大白话来解读这段定义:
- webpack是一个打包工具,一个资源中转站。它接收一些文件,经过内部处理后,输出对应的静态资源
- 它从入口文件出发,找到入口依赖的模块,再递归寻找依赖的依赖,直到所有模块被加载进来
- 它把开发者使用方便但浏览器不能直接识别的资源(如less,scss,ES Next等),处理成浏览器可直接识别的资源(css,js等)
使用webpack
webpack的使用方法基本都围绕“配置”展开,这些配置大致可划分为两类: 流程类:作用于流程中某个or若干个环节,直接影响打包效果的配置项 工具类:主流程之外,提供更多工程化能力的配置项
流程类配置
配置总览
核心概念
接下来介绍webpack的四个核心概念:入口(entry),输出(output),loader,插件(plugins)
entry——入口
入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
我们现在大多数开发的是单页面应用,这里我们以单入口为例,具体配置可去文档中查看
我们修改目录结构,新增webpack.config.js文件(注意该文件必须与src同级,且不能更改命名。后续我们可以通过更改script脚本命令来打破这些限制。)与add.js文件。
├─ src
│ ├─ add.js
│ └─ index.js
├─ .gitignore
└─ README.md
└─ webpack.config.js
└─ package.json
webpack.config.js文件配置如下,entry配置的是相对路径:
module.exports = {
entry: './src/index.js'
}
add.js内容如下:
const add = (a,b) => {
return a + b
}
export default add
index.js引入这个add函数并输出:
import add from './add.js'
const hello = () => {
console.log('hello webpack')
}
hello()
console.log(add(2,3))
接下来打开终端输入webpack回车看看它是如何打包文件的。
可以看到刚刚引入的add函数也被打包输出。注意这个add函数输出的形式,webpack帮我们直接输出了函数的运算结果。
只要资源被其他模块引入并使用,webpack就会帮我们打包这些资源。
output——输出
可以通过配置 output 选项,告知 webpack 如何向硬盘写入编译文件。注意,即使可以存在多个 entry 起点,但只能指定一个 output 配置。
我们更改webpack.config.js的配置如下:
const path = require('path')
const resolvePath = _path => path.resolve(__dirname, _path)
module.exports = {
entry: './src/index.js',
output: {
path: resolvePath('./dist'),
filename: 'scripts/[name].js'
}
}
这里讲解一下output中这2个属性:
- path: 文件输出的路径,必须是个绝对路径
- filename: 文件输出的名称。可以在名称前指定一个输出路径,让文件输出在这个路径下。同时也可以使用占位符
[name]指定不同的文件名称
终端输入webpack打包,我们查看打包后的程序
新的main.js被打包到我们在filname中指定的scripts文件夹下。
这里有个新的问题待我们解决,之前被打包的main.js没有被清除,我们希望每次打包都能生成最新的文件,并清除上一次的打包结果。此时在output中配置clean:true即可解决问题,更改output配置如下:
output: {
path: resolvePath('./dist'),
clean: true,
filename: 'scripts/[name].js'
}
再次打包:
效果实现,之前的文件已被清除。这里需要注意,webpack 5.0以下的版本,需要另外引入插件clean-webpack-plugin进行配置,过程较为复杂,不展开赘述,了解即可。webpack 5.0以上仅需在output中配置clean的值为true即可实现同样的效果。
loader
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
用大白话翻译一下就是:loader是一个翻译,把webpack不能直接处理的资源,翻译成能直接处理的。
那什么是webpack不能直接处理的资源呢?我们尝试打包一个css资源,调整目录结构如下:
├─ src
│ ├─ css
│ │ └─ index.css
│ └─ add.js
│ └─ index.js
├─ .gitignore
└─ README.md
└─ webpack.config.js
└─ package.json
index.css中添加如下样式:
body {
background-color: red;
}
index.js引入index.css后,进行打包,查看结果如下:
报错提示: webpack不识别样式的写法,我们需要一个合适的loader来处理这种类型的文件。
这就是webpack不能直接处理的资源。也就是说除了webpack开箱自带支持的JS,JSON文件,其余文件都不能被webpack直接处理,此时就需要我们引入loader帮助webpack翻译翻译。
接下来我将围绕样式资源,图片资源等跟大家讲解loader的具体使用方法。
样式loader
加载css文件——css-loader,style-loader
- 步骤1: 识别基础css资源需要安装2个loader
yarn add css-loader style-loader -D
loader作用如下:
css-loader:将css文件处理成commonJs模块放入js中
style-loader:创建style标签将js中的css插入到html中
- 步骤2: 下载完成后,配置
webpack.config.js使用loader
module.exports = {
// ...
module: {
rules:[{
test: /.css$/,
use:[
'style-loader',
'css-loader'
]
}]
}
}
这里我们新增了module配置项,它用来配置不同loader的使用规则。module.rules 允许你在 webpack 配置中指定多个 loader。
rules中每一个对象即为处理一种类型文件的方式,具体字段释义如下:
test: loader匹配的规则
use:对应文件使用的loader
这里的配置是为所有css文件匹配使用style-loader和css-loader。注意loader的执行顺序是从下到上,从右到左。先执行css-loader处理css,再执行style-loader将css通过style标签插入页面。
- 步骤3: 执行webpack查看结果:
编译成功,我们在dist根目录下新建index.html,并引入打包后的main.js查看结果:
打包后的js和样式全部生效了,我们看看样式插入的方式:
样式通过style标签被插入页面。到这里基础的样式loader使用就已经成功。但实际开发中我们多以less,scss开发为主,处理这些样式文件需要使用对应的less-loader和sass-loader,这里拿less举例。
加载less文件——less-loader
我们调整src/css文件结构,新增一个less文件,如下所示:
├─ css
│ ├─ index.css
│ └─ rectangle.less
less文件样式如下:
@blue: #4285f4;
.webpack-rectangle{
width: 100px;
height: 60px;
background-color: @blue;
}
src/index.js引入less文件,进行打包
import add from './add.js'
import './css/index.css'
import './css/rectangle.less'
查看结果,发现报错:
报错信息: 不支持less文件的打包,需要一个loader处理该类文件。
与css报错的信息基本一致,这时请出我们的less-loader。
- 步骤1: 安装less,less-loader
yarn add less less-loader -D
- 步骤2: 配置
webpack.config.js
// ...
module.exports = {
// ...
module: {
rules:[{
test: /.css$/,
use:[
'style-loader',
'css-loader'
]
},{
// 匹配less文件
test: /.less$/,
// loader的使用顺序 less-loader,css-loader,style-loader
use:[
'style-loader',
'css-loader',
'less-loader'
]
}]
},
mode:'development'
}
这里新增mode:'development',防止webpack报一些提示信息的错误。然后在rules数组中新增处理less文件的loader对象。
这里的处理顺序如下所示:
- less-loader将less文件转义成css文件
- css-loader将css文件处理成commonJs模块
- style-loader将css通过style标签插入页面中
- 步骤3: 在dist根目录下再次新建一个index.html引入main.js查看刚刚写的less是否生效,这里为了观察方便我们把原来的红色背景改成灰色背景。
<body>
<div class="webpack-rectangle"></div>
<script src="./scripts/main.js"></script>
</body>
资源loader
处理图片资源
webpack4中处理图片使用file-loader和url-loader,webpack5中,这2个loader已被内置到webpack里,我们只需激活配置即可。
- 步骤1: 调整src结构
├─ src
│ ├─ assets
│ │ └─ img.png
│ │ └─ iron-man.gif
│ │ └─ mk50.jpeg
│ ├─ css
│ │ └─ index.scss
│ └─ add.js
│ └─ index.js
- 步骤2: 配置
webpack.config.js的rules
rules:[
// ...
{
test: /.(jpe?g|png|gif|webp|svg)$/,
type: 'asset',
generator: {
filename: 'assets/img/[hash:10][ext]'
}
}]
- 步骤3: 修改index.scss
$baseCls:"webpack-img";
.#{$baseCls} {
display: flex;
justify-content: space-between;
div{
width: 230px;
height: 230px;
&.box1{
background: url(../assets/img/img.png) no-repeat center /contain;
}
&.box2{
background: url(../assets/img/iron-man.gif) no-repeat center /contain;
}
&.box3{
background: url(../assets/img/mk50.jpeg) no-repeat center /contain;
}
}
}
- 步骤4: 执行webpack,dist根目录下创建index.html引入main.js查看样式
<body>
<div class="webpack-img">
<div class="box1"></div>
<div class="box2"></div>
<div class="box3"></div>
</div>
<script src="./scripts/main.js"></script>
</body>
查看结果:
成功运行,我们看看dist目录生成的文件
新生成了assets文件夹,里面包含hash名称的图片资源,再看看刚刚的loader中有这样的配置。
generator: {
filename: 'assets/img/[hash:10][ext]'
}
这下应该明白了,filename可以指定图片资源的输出路径,其他字段释义如下:
[hash:10]:取hash的前10位为文件名称
[ext]:文件扩展名,之前是什么值,打包后仍然是什么值
可以看出,打包前和打包后,文件扩展名是未发生修改的。到这里图片资源的处理就完成了。
babel-loader
将 ES6+ 语法编写的代码转义为向下兼容的js代码,使低版本浏览器能正常运行程序。
在使用babel-loader前,调整目录结构如下:
├─ src
│ └─ index.js
├─ .gitignore
└─ README.md
└─ webpack.config.js
└─ package.json
在index.js中编写一段ES6代码:
class GirlFriend {
constructor(name, age) {
this.name = name
this.age = age
}
say () {
console.log(`我叫${this.name},我今年${this.age}岁。很高兴认识你`)
}
}
const girl = new GirlFriend('Alice',22)
girl.say()
使用webpack打包查看结果:
可以发现,ES6的代码被原封不动地打包了,这样的打包结果,对低版本浏览器并不友好。所以我们需要引入babel-loader解决这个问题,下面我们开始配置babel-loader。
- 步骤1: 安装依赖
yarn add babel-loader @babel/core @babel/preset-env -D
@babel/core :babel核心库
@babel/preset-env: 智能预设,允许开发者使用最新的js
- 步骤2: 配置
webpack.config.js的rules:
rules: [
// ...
{
test: /.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}]
当然我们也可以在项目根目录中新增.babelrc或babel.config.js等文件,设置一些babel的使用规则。关于babel更详细地使用这里不多做展开,请查阅官方文档了解。
- 步骤3: 执行webpack查看打包结果
可以看到我们的ES6代码已被转义为ES5代码。
plugin——插件
plugins 选项用于以各种方式自定义 webpack 构建过程。它监听webpack的打包过程,执行对应的生命周期回调,拓展webpack的功能。
啥是插件?为什么要这么设计?
这是一个特别复杂的过程,所以:1.新人需要了解整个流程细节,上手成本高,功能迭代成本高,牵一发而动全身 2.功能僵化,作为开源项目而言缺乏成长性
所以:心智成本高 => 可维护性低 => 生命力弱
插口架构设计精髓:对扩展开放,对修改封闭
打包html页面——HtmlWebpackPlugin
之前我们打包js,css后,总要在dist根目录下新建一个html,引入打包的js并进行一些设置,才能在页面中看到打包后的效果。这种方式非常不方便,因此我们通过HtmlWebpackPlugin这一插件,让webpack帮我们自动打包出一个index.html。
这里我们提一个需求,我们希望webpack能打包一个html页面,这个页面生成时带有这样的内容<h2>Hello CengFan!</h2>,并引入打包后的js资源。
需求有了接下来我们一步步实现
- 步骤1: src文件夹下新建一个html,内容如下
<body>
<h2>Hello Cengfan!</h2>
</body>
- 步骤2: 安装依赖
yarn add html-webpack-plugin -D
- 步骤2: 配置
webpack.config.js
const path = require('path')
// 引入插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
const resolvePath = _path => path.resolve(__dirname, _path)
module.exports = {
// ...
plugins: [
new HtmlWebpackPlugin({
template: resolvePath('./src/index.html'),
})
],
mode: 'development'
}
template指定以哪个html为入口文件,值是一个绝对路径。
plugin都要遵循先引用,然后使用new关键字进行实例化调用的原则。
- 步骤3: 执行webpack查看打包结果
符合预期,js也正常运行
mode模式
mode主要分为开发/生产两种环境,不同环境打包出来的代码不同。
development:开发环境
production:生产环境
首先看看开发环境下打包的js代码:
再看看生产环境下打包的js代码:
生产环境下我们还可以配合其他插件,压缩生产环境的代码。
In the end
至此webpack常用的配置已列举完毕,如果你也跟着一路看下来相信基本的配置应该不成问题了。受限于篇幅,更全的配置还是推荐大家去官网查看。
最后贴一张学习路线图