前言
前端开发时一般都会使用框架(React、Vue)、ES6模块化语法、Less或者Sass等 css预处现器等语法。这样的代码要想在浏览器运行,必须要编译成浏览器能够识别的 JS、Css等语法。而打包工具的作用就是压缩代码、兼容性处理、提升代码性能、代码编译等。
常见的打包工具包括Grunt、Gulp、Parcel、Webpack、Rollup、Vite等等,目前市面上最常用的是webpack。
第一章 Webpack的基本配置
1. 基本使用
Webpack 是一个静态资源打包工具。它会以一个或多个文件作为打包的入口,将我们整个项目所有文件编译组合成一个或多个文件输出。输出的文件就是编译好的文件bundle,可以在浏览器端运行。
Webpack本身的功能是有限的:
- 开发模式:只能编译JS中的ES Module语法
- 生产模式:不仅可以编译JS中的ES Module语法,还可以压缩JS代码
- 直接使用JS代码
index.html主文件
未打包的main.js文件
控制台报错
- 使用打包后的JS代码
第一步:初始化一个package.json文件npm init -y

第二步:下载webpack npm i webpack webpack-cli -D
第三步:打包指定目录文件npx webpack ./src/main.js --mode=development
development模式
production模式
控制台正常输出
2. 核心概念
- Entry:入口文件,webpack编译的起点,即从哪个文件开始打包
- output:输出,webpack打包完的文件从哪里输出。其中output.filename对应initial chunk文件名称,output.chunkFilename对应non-initial chunk文件名称
- Loader:加载器,webpack本身只能处理JS、json资源文件,其他资源需要借助loader处理
- Plugin:插件,扩展webpack功能
- mode:模式,分为development开发模式和production生产模式
3. 其他概念
- Compiler:编译管理器,webpack启动后会创建compiler对象,该对象一直存活到编译结束
- Compilation:单次编译过程的管理器,每次触发重新编译时,都会创建一个新的compilation对象
- Dependence:依赖对象,webpack基于该类型记录模块间依赖关系
- Module:webpack内部所有资源都会以"module"对象形式存在,所有关于资源的操作、转译、合并都是以 "module" 为基本单位进行的
- Chunk:编译完成准备输出时,webpack会将module按特定的规则组织成一个一个的chunk,这些chunk某种程度上跟最终输出一一对应
4. Webpack基本配置
在根目录下创建一个Webpack.config.js文件并完成基础配置
java
const path = require("path");
module.exports = {
// 入口,相对路径
entry: "./src/main.js",
// 输出,绝对路径
output: {
path: path.resolve(__dirname,"dist"), // 路径
filename: "main.js",// 文件名
},
// 加载器
module: {rules: []},
// 插件
plugins:[],
// 模式
mode: "development"
}
可以在output中添加clean
配置,自动清除上一次的打包资源。
lua
output: {
path: path.resolve(__dirname,"dist"), // 路径
filename: "main.js",// 文件名
clean: true, // 在生成文件之前清空output目录
},
第二章 资源文件的处理
1. 处理样式资源
1.1 style-loader
作用:把CSS插入到DOM中,推荐将style-loader
与css-loader
一起使用
javascript
// 下载
npm install --save-dev style-loader
// 使用加载器
module: {
rules: [
{
test: /\.css$/i, // 正则表达式匹配文件
use: ["style-loader", "css-loader"], // 从右到左依次使用loader处理
}
]
},
1.2 css-loader
css-loader
会对@import
和url()
进行处理,就像js解析import/require()
一样。
javascript
// 下载
npm install --save-dev css-loader
// 使用加载器
module: {
rules: [
{
test: /\.css$/i, // 正则表达式匹配文件
use: ["style-loader", "css-loader"], // 从右到左依次使用loader处理
}
]
},

1.3 less-loader
作用:将Less编译为CSS的loader
css
// 下载
npm install less less-loader --save-dev
// 使用加载器
module: {
rules: [
{
test: /\.less$/i,
use: ['style-loader','css-loader','less-loader'],
},
]
},

1.4 sass-loader
作用:加载Sass/SCSS文件并将他们编译为CSS。
javascript
// 下载
npm install sass-loader sass --save-dev
// 使用加载器
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
// 将JS字符串生成为style节点
'style-loader',
// 将CSS转化成CommonJS模块
'css-loader',
// 将Sass编译成CSS
'sass-loader',
],
},
]
},

1.5 stylus-loader
作用:将Stylus文件编译为CSS
css
// 下载
npm install stylus stylus-loader --save-dev
// 使用加载器
module: {
rules: [
{
test: /.styl$/,
loader: "stylus-loader",
},
]
},
2. 处理图片资源
2.1 简单配置
Webpack4处理图片资源时需要使用file-loader和url-loader两个加载器,而Webpack5已经内置了两个Loader,使用时不需要单独下载安装,只需要简单配置即可。
bash
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/i,
type: "asset",
}
]
},
图片作为背景图片,通过url引入

2.2 资源模块
资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外loader。包括以下内容:
- raw-loader:将文件导入为字符串
- url-loader:将文件作为data URI内联到bundle中
- file-loader:将文件发送到输出目录
一般情况下webpack将按照默认条件,自动地在resource和inline之间进行选择:小于8kb的文件,将会视为inline模块类型,否则会被视为resource模块类型。可以通过在webpack配置的module rule层级中,设置Rule.parser.dataUrlCondition.maxSize选项来修改此条件。
- resource资源:直接发送到输出目录,其路径会被被注入到bundle中
- inline资源:文件会作为data URI注入到bundle中,格式为base64,可以减少网络请求
yaml
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 100 * 1024 // 小于100kb转化为inline资源
}
}
}
]
},

🤔:url-loader和file-loader的区别是什么?
🙋:大致总结如下
首先概念不同:file-loader可以指定要复制和放置资源文件的位置,以及如何使用版本哈希命名以获得更好的缓存。url-loader允许你有条件地将文件转换为内联的base-64 URL (当文件小于给定的阈值),这会减少小文件的HTTP请求数。如果文件大于该阈值,会自动的交给file-loader处理。
处理图片资源方式不同:file-loader将文件上的import
和require
解析为url,并将该文件发射到输出目录中。url-loader可以识别图片的大小,并把图片转换成base64,从而减少代码的体积,如果图片超过设定的限制,就会继续用file-loader处理。
2.3 自定义输出文件
默认情况下,asset/resource模块以[hash][ext][query]
文件名发送到输出目录中,可以通过在webpack配置中设置output.assetModuleFilename
来修改此模板字符串。
特点:不能对asset/resource模块下的内容进行区分
arduino
output: {
// 所有输出文件的路径
path: path.resolve(__dirname,"dist"),
// 入口文件对应的输出文件名称
filename: "main.js",
// asset/resource模块的输出路径和名称配置
assetModuleFilename: 'images/[hash][ext][query]'
},
也可以通过generator.filename
单独设置某个resource模块,输出到指定目录下。
yaml
{
test: /\.(png|jpe?g|gif|webp)$/i,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 100 * 1024 // 100kb
}
},
// 将图片资源输出到dist/static/images目录中
// 文件名为前8位hash值 + 文件扩展名 + 其他扩展字段
generator: {
filename: 'static/images/[hash:8][ext][query]'
}
}

3. 处理字体图标资源
字体图标资源也属于资源模块,但是不需要转化为base-64格式,所以需要使用Resource。
bash
{
test: /\.(ttf|Woff2?)$/i,
type:"asset/resource",
generator: {
filename: 'static/media/[hash:8][ext][query]'
}
}
4. 处理其他资源
例如音频、视频等标资源也属于资源模块,同样也不需要转化为base-64格式,所以需要使用Resource。
bash
{
test: /\.(map3|map4|avi)$/i,
type:"asset/resource",
generator: {
filename: 'static/media/[hash:8][ext][query]'
}
}
5. 处理JS资源
Webpack对JS的处理是有限的,只能编译JS中ES模块化语法,不能编译其他语法,导致JS不能在IE等浏览器中运行,所以需要做一些兼容性处理。
- Babel:JS兼容性处理
- Eslint:代码格式校验
需要先完成Eslint检测代码格式无误后,再由Babel做代码兼容性处理。
5.1 Eslint
作用:用来检测js和jsx语法的工具,可以扩展各种功能
- 配置文件
- .eslintrc.*:新建位于项目根目录的文件,可以是.js或者.json格式
- package.json中eslintConfig:直接在package文件中添加配置,Eslint会自动查找和读取对应配置规则
- 使用
scss
// 下载
npm install eslint-webpack-plugin eslint --save-dev
java
// 添加配置文件.eslintrc.js
module.exports = {
// 继承eslint规则
extends:["eslint:recommended"],
env:{
node:true, // 启用node中的全局变量
browser:true, // 启用浏览器中的全局变量
},
parserOptions:{
ecmaVersion: 6,
sourceType: "module"
},
rules:{
"no-var": 2,
}
}
arduino
// 添加eslint忽略文件.eslintignore,忽略打包后的dist文件夹
dist

5.2 babel
作用:将ES6语法转换为向后兼容的JS代码,以便能够运行在当前和旧版本的浏览器中
- 配置文件
- babel.config.*:新建位于根目录的文件,格式为.js或者.json
- .babelrc.*:新建位于根目录的文件,格式为.js或者.json
- package.json的babel:直接在package文件中添加配置
- 使用
typescript
// 下载
npm install -D babel-loader @babel/core @babel/preset-env
// 使用加载器
{
test: /\.m?js$/,
exclude: /(node_modules)/, // 忽略node包
loader: 'babel-loader',
}
java
// 添加外部的babel.config.js文件,配置预设规则
module.exports = {
presets: ['@babel/preset-env'] // 智能预设
}
6. 处理html资源
作用:自动生成一个HTML5文件, 在body中使用script标签引入所有webpack生成的bundle
css
// 下载
npm install --save-dev html-webpack-plugin
// 使用插件
plugins:[
new ESLintPlugin({
context: path.resolve(__dirname,"src")
}),
new HtmlWebpackPlugin({
// 配置模版,生成的html文件中会自动保留模板格式
template: path.resolve(__dirname,"public/index.html")
})
],
第三章 搭建开发服务器
1. 自动化打包
作用:自动化编译代码,取消手动输入npx webpack指令操作
arduino
// 下载
npm install --save-dev webpack-dev-server
// 添加配置项
module.esports = {
// 开发服务器配置
devServer:{
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true // 是否自动打开浏览器
},
}
// 使用
npx webpack server
浏览器自动弹出3000窗口
2. 生产模式
生产模式即开发完部署上线,生产模式需要对代码进行优化,让其运行性能更好。优化主要从两个角度出发:
- 优化代码运行性能
- 优化代码打包迪度
一般在项目中会拆分生产模式和开发模式的配置文件,并在package.json中通过不同的指令分别启动。

3. 生产模式优化配置
3.1 提取输出CSS文件
插件:MiniCssExtractPlugin
作用:将CSS提取到单独的文件中,为每个包含CSS的JS文件创建一个CSS文件,并且支持CSS和SourceMaps的按需加载。
- MiniCssExtractPlugin.loader:将css文件以link方式引入
- style-loader:将css样式放到style内联样式中
scss
// 下载
npm install --save-dev mini-css-extract-plugin
css
// 使用 MiniCssExtractPlugin.loader代替style-loader,并在plugin中引入
module: {
rules: [
{
test: /\.css$/i, // 正则表达式匹配文件
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
]
},
plugins:[
new MiniCssExtractPlugin({
filename: "css/main.css"
})
],
MiniCssExtractPlugin-loader style-loader

3.2 压缩CSS文件
生产模式默认开启了js和html压缩,针对css,需要使用插件对其进行压缩。
插件:ss-minimizer-webpack-plugin
scss
// 下载
npm install css-minimizer-webpack-plugin --save-dev
// 使用
plugins:[
new CssMinimizerPlugin()
]
第四章 Webpack优化设置
1. 提升开发体验
1.1 SourceMap
SourceMap是一个源代码映射的系统,可以生成源代码与构建后代码一一映射的文件。SourceMap会生成一个xxx.map文件,里面包含源代码和构建后代码在每一行、每一列的映射关系,当构建后代码出错了,会通过xxx.map文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快的找到错误根源。
-
开发模式:cheap-module-source-map
优点: 打包编译速度快,只包含行映射
缺点: 没有列映射
vbnet
mode: "production",
devtool: "cheap-module-source-map"
-
生产模式:source-map
优点:既包含行映射,又包含列映射
缺点:打包编译速度慢
vbnet
mode: "production",
devtool: "source-map"
1.2 模块热替换
模块热替换(HMR)功能会在应用程序运行过程中替换、添加或删除模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态
- 只更新变更内容,从而节省开发时间
- 在源代码中CSS/JS产生修改时,会立刻在浏览器中进行更新,相当于在浏览器devtools中直接更改样式
arduino
devServer:{
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
hot: true, // 是否开启HMR热模块替换功能,Webpack5默认开启
},
2. 提升打包构建速度
2.1 OneOf
作用:规定一个文件只能被一个loader处理,提升打包速度
css
module: {
rules: [{
oneOf: [
{
test: /\.css$/i,
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.less$/i,
use: [MiniCssExtractPlugin.loader, 'css-loader', 'less-loader'],
},
]
}]
}
2.2 Include/Exclude
针对JS文件做处理,提升打包编译速度。
- include:包含,只处理xx文件
- exclude:排除,除了xxx文件以外其他文件都需要处理
javascript
{
test: /\.m?js$/,
exclude: /(node_modules)/, // 忽略node包
loader: 'babel-loader',
}
{
test: /\.m?js$/,
include: path.resolve(__dirname,"./src"),
loader: 'babel-loader',
}
new ESLintPlugin({
context: path.resolve(__dirname, "src"),
exclude: "node_modules", // exclude的默认值
}),
3. 减少代码体积
3.1 Cache
每次打包时js文件都要经过Eslint检查和Babel编译,速度比较慢。我们可以缓存之前的Eslint检查和Babel编译的结果,这样第二次打包时速度就会更快了。
javascript
// 缓存babel编译
{
test: /\.m?js$/,
include: path.resolve(__dirname,"./src"),
loader: 'babel-loader',
options:{
cacheDirectory: true, // 开启babel缓存
cacheCompression: false // 关闭缓存文件压缩
}
}
// 缓存eslint检查
plugins: [
new ESLintPlugin({
context: path.resolve(__dirname, "src"),
exclude: "node_modules", // exclude的默认值
cache: true, // 开启缓存
cacheLocation: path.resolve(__dirname, "./node_modules/.cache/eslintcache") // 缓存路径
}),
]
3.2 Thead
当项目越来越庞大时,打包速度就会越来越慢,影响最严重的就是JS的打包速度。而对js文件处理主要就是eslint、babel、Terser三个工具,所以要想提升js文件的运行速度,可以开启多进程同时处理js文件,从而提升打包速度。
多进程打包指的是开启电脑的多个进程同时干一件事,速度更快。
⚠️:请在特别耗时的操作中使用,因为每个进程启动就有大约600ms左右开销。而启动进程的数量不得大于CPU的核数
1)获取CPU核数
ini
const os = require("os");
const threads = os.cpus().length;
2)babel解析:开启多进程,设置进程数量
yaml
{
test: /\.m?js$/,
include: path.resolve(__dirname, "./src"),
use: [
{
loader: 'thread-loader', // 开启多进程
options: {
works: threads // 进程数量
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel缓存
cacheCompression: false // 关闭缓存文件压缩
}
},
],
}
3)eslint校验:开启多进程,设置进程数量
css
optimization: {
minimizer: [
new CssMinimizerPlugin(), // 压缩css
new TerserWebpackPlugin({ // 开启多进程、设置进程数量
parallel: threads
})
]
}
3.3 Tree Shaking
用于描述移除JavaScript中的没有使用的代码,前提是必须依赖ES Hodule模块化。
Webpack5目前已经内置了Tree Shaking,无需过多的配置。
3.4 Babel文件体积优化
Babel为编译的每个文件都插入了辅助代码,如公共方法的辅助代码_extend。但是有一些辅助代码会被重复添加到每一个需要它的文件中,造成文件体积过大。通过将捕助代码作为一个独立模块,从而避免重复引入问题。
@babel/plugin-transform-runtime:禁用了Babel自动对每个文件的 runtime注入,改为引入@babel/plugin-transform-runtin内的所有辅助代码。通过减少定义和重复引入,从而减少文件体积。
java
// 下载
npm i @babel/plugin-transform-runtime -D
// 使用
{
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
}
}
3.5 图片压缩
image-minimizer-webpack-plugin插件,可以对本地静态图片进行压缩处理,从而减少代码体积。
压缩图片的模式分为有损压缩和无损压缩两种:
- 无损压缩:imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo
- 有损压缩:imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo
css
// 下载
npm i image-minimizer-webpack-plugin imagemin -D
// 使用-无损压缩
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
['gifsicle',{interlaced: true}],
['jpegtran',{progressive: true}],
['optipng',{optimizationLevel: 5}],
[ 'svgo', { plugins: [ 'preset-default', 'prefixIds', { name: 'sortAttrs', params: {xmlnsOrder: 'alphabetical'} } ]
}
]
]
}
}
})
4. 优化代码运行性能
4.1 Code Split
Code Split,通过将代码分割打包,从而可以按需加载,优化加载速度。
代码分调的作用:
- 分剩文件:将打包生成的文件进行分割,生成多个js文件
- 按需加载:需要哪个文件就加载哪个文件
1)多入口打包
css
entry:{
app: "./src/app.js",
main: "./src/main.js"
}
2)多入口提取公共模块
css
optimization: {
splitChunks: {
chunks: "all"
}
}
3)多入口按需加载
javascript
// import()动态加载语法,返回值为promise对象
import(./count.js).then((res)=>{
...
}).catch((err)=>{
...
})
4)模块命名
java
// 对某个引入模块命名
import(/* webpackChunkName:"math" */'./math.js')
// 打包输出文件名称使用
module.exorts = {
output:{
chunkFilename:"static/js/[name].js"
}
}
4.2 Preload/Prefetch
1)含义
- Preload:立即加载资源
- Prefetch:在浏览器空闲时才开始加载资源
2)共同点
- 只加载资源,不会执行资源
- 可以缓存资源
3)区别
- Preload加载优先级高,Prefetch加载优先级低
- Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面使用到的资源,也可以加载下一个页面使用到的资源
4)适用场景
- 当前页面优先级高的资源用Preload加载
- 下一个页面需要使用的资源用Prefetch加载
4.3 Core-js
core-js是用来做ES6以及以上API的polyfill的工具。polyfill翻译过来叫做垫片/补丁,就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上使用该新特性。
core-js一般会和babel一起使用,为ES6及以上语法生成对应的兼容性实现方案,并且会在dist文件夹下面生成一个新的打包文件。
java
// 下载
npm i core-js
// 配合babel.config.js使用
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: "usage", // 按需加载自动引入
coreJs: 3
}
]
]
}
4.4 PWA
浙进式网络应用程序(progressive web application PWA)是一种可以提供类似于native app(原生应用程序)体验的Web App的技术。其中最重要的是在离线(offline) 时,应用程序能够继续运行功能。
其内部是通过Service Workers技术实现的。
javascript
// 下载
npm i workbox-webpack-plugin --save-dev
// 添加配置
plugins: [
new WorkboxPlugin({
clientsClaim: true,
skipWaiting: true
})
]
// 使用-main.js
if("servicelorker" in navigator{
window.addEventListener("load", ()=>{
navigator.serviceWorker
.register("/service-worker.js")
.then((registration)=>{console.lor("sW registered: ",registration)})
.catch((registrationError)=>{console.log("sW registration failed: ", reristrationError)})
5. 性能优化概述
1)提升开发体验
- source Map:开发或上线时代码报错能有更加准确的错误提示
2)提升打包构建速度
- HMR:开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快
- oneOf:资源文件一旦被某个loader处理了,就不会继续遍历其他loader,打包速度更快
- Include/Exclude:排除或只检测某些文件,处理的文件更少,速度更快
- Cache:对eslint和babel处理的结果进行缓存,让第二次打包速度更快
- Thead:多进程处理esint和babel任务,速度更快(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效)
3)减少代码体积
- Tree shaking:移除没有使用的多余代码,让代码体积更小
- babel/plugin-transform-runtime:对babel进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小
- Image hininizer:对项目中图片进行压缩,体积更小,请求速度更快。(本地项目静态图片才需要进行压缩)
4)优化代码运行性能
- code Split:对代码进行分割成多个js文件,从而使单个文件体积更小,并行加载速度更快
- import():动态导入语法,按需加载
- Preload/Prefetch:对代码进行提前加载,提升用户体验
- Network cache:对输出资源文件进行更好的命名,利于缓存处理,提升用户体验
- core-js:对js进行兼容性处理,使代码能运行在低版本浏览器中
- PWA:实现代码离线访问功能,提升用户体验
第五章 webpack核心模块
1. Loader
Loader是帮助webpack将不同类型的文件转换为webpack可识别的模块。
1)Loader优先级分类
- pre:前置loader
- normal:普通loader
- inline:内联loader
- post:后置loader
2)不同优先级Loader的执行顺序
- 不同优先级loader:pre > normal >inline > post
- 相同优先级loader:从右到左,从下到上
3)配置Loader优先级
- 配置方式:在webpack.config.js文件中指定loader为pre、normal、post loader中一种,不添加任何指定时,默认为normal loader
- 内联方式:在每个import语句中显式指定loader为inline loader
javascript
// pre loader
{
enforce: "pre"
test: /\.js$/,
loader: "loader1"
},
// normal loader
{
test: /\.js$/,
loader: "loader2"
}
// post loader
{
enforce: "post"
test: /\.js$/,
loader: "loader3"
}
javascript
// inline loader
// 使用style-loader和css-loader处理styles.css文件
import Styles from 'style-loader!css-loader?modules!./styles.css";
// 前面添加一个"!",跳过normal loader
import Styles from '!style-loader!css-loader?modules!./styles.css";
// 前面添加一个"-!",跳过 pre、normal loader
import Styles from '-!style-loader!css-loader?modules!./styles.css";
// 前面添加一个"!!",跳过pre、normal、postloader
import Styles from '!!style-loader!css-loader?modules!./styles.css";
4)Loader底层原理
loader在底层就是一个函数,当webpack解祈资源时,会调用相应的loader方法处理。loader()接收三个参数:
- content:文件内容
- map:与SourceMap相关数据
- meta:其他loader传递的数据
javascript
module.exports = function(content, map, meta){
... ....
return content;
}
5)Loader分类
- 同步loader
javascript
// 只有一个loader时
module.exports = function(content, map, meta){
// 不需要向下传递参数和source-map
return content;
}
// 有多个loader连用时
module.exports = function(content, map,meta){
// err:代表是否有错误
// content:处理后的内容
// source-map:继续向下传递source-map
// meta:给下一个loader传递的参数
this.callback(null,content,map,meta);
}
- 异步loader
javascript
module.exports = function(content , map, meta){
// 获取异步的回调函数
const callback = this.async();
setTimeout(() => {
// 参数和同步回调函数一致
callback(null, content, map, meta);
}, 1000);
}
- Raw loader raw loader表示具有raw属性的loader,属性值为布尔值。可以是同步也可以是异步的loader,区别是其接收到的content是Buffer格式的流数据
ini
function mayLoader(content){
// Buffer流,一般用于操作图片、图标等资源文件
return content;
}
mayLoader.raw = true;
module.exports = mayLoader;
- pitch loader
pitch loader表示具有pitch属性的loader,属性值为函数。可以是同步也可以是异步的loader,特点是执行顺序会早于其他loader。
如连用三个loader处理文件资源时,会从左到右依次执行每个loader的pitch方法,然后在从右到左依次执行每个loader方法。
正常的pitch函数无返回值,如果在执行过程中某个pitch有返回值,则会中断执行顺序,转而执行前一个pitch所在的loader方法。
lua
module.exports = function(content){
console.log('normal loader');
return content;
}
module.exports.pitch = function(){
consel.log("pitch loader");
}
pitch无返回值时的执行顺序
pitch2有返回值时的执行顺序
6)Loader常用API
方法名 | 含义 | 使用 |
---|---|---|
this.async | 异步回调loader,返回this.callback | const callback = this.async() |
this.callback | 同步或异步调用的、可以返回多个结果的函数 | this.callback(err,content,sourceMap?,meta?) |
this.getOptions(schema) | 获取loader的options配置,schema为验证规则 | this.getOptions(schema) |
this.emitFile | 生成一个文件 | thisemitFile(name,content,sourceMap) |
this.utils.contextify | 返回一个相对路径 | this.utils.contextify(context,request) |
this.utils.absolutify | 返回一个绝对路径 | this.utils.absolutify(context,request) |
7)创建Loader
scss
// 清除console.log
module.exports = function (content){
return content.replace(/consolel.log\(.* );?/g,"");
}
// 创建适配低版本浏览器的babel-loader
const babel = require( @babel/core");
const schema = require("./schema.json");
module.exports = function (content) {
// 异步loader
const callback = this.async();
const options = this.getOptions(schema);
// 使用babe1对代码进行编译
babel.transform(content, options, function(err, result){
if (err){
callback(err);
}else{
callback(null, result.code);
}
});
}
2. Plugin
plugin插件可以扩展webpack,加入自定义的构建行为,使webpack可以执行更广泛的任务,拥有更强的构建能力。
webpack就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。这条生产线上的每个处理流程的职责都是单一的,多个流程之间存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。插件就像是一个插入到生产线中的功能,在特定的时间对生产线上的资源做处理,webpack通过Tapable来组织这条复杂的生产线,webpack在运行过程中广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。webpack的事件流机制保证了插件的有序性,使得整个系统扩展性很好------------[深入浅出 Webpack]
站在代码逻辑的角度思考plugin的工作原理:webpack在编译的过程中,会触发一系列Tapable钩子事件,插件的作用就是找到对应的钩子,往钩子中挂载任务,即注册事件。当webpack构建的时候,插件注册的事件就会随着钩子的触发而执行。
1)钩子
钩子的本质就是事件,为了方便开发者直接介入和控制编译过程,webpack把编译过程中触发的各类关键事件封装成事件接口暴露了出来,这些接口被称为hooks(钩子)
2)Tapable
Tapable为webpack提供了统一的插件接口(钩子)类型定义,它是webpack的核心功能库。webpack中目前有十种hooks:

Tapable提供了三个方法给插件,用于注入不同类型的自定义构建行为:
- top:可以注册同步钩子和异步钩子
- topAsync:回调方式注册异步钩子
- tapPromise:Promise方式注册异步钩子
3)Compiler对象
compiler对象中保存着完整的Webpack环境配置,每次启动webpack构建时,都会创建一个独一无二的compiler,它有以下主要属性:
- compiler.options:访问本次启动webpack时所有的配置文件,包括但不限于 loaders、entry、output、plugin等等完整配置信息。
- compiler.inputFileSystem、compiler.outputFileSysten:进行文件操作,相当于Nodejs中fs
- compiler.hooks:注册tapable的不同种类Hook,从而可以在compiler生命周期中植入不同的逻辑
4)Compilation
compilation对象代表一次资源的构建,compilation实例能够访问所有的模块和它们的依赖。一个compilation对象会对构建依赖图中所有横块进行编译,在编译阶段横块会被加载(load)、封存(seal)、优化(optimize)、分块(chunk)、哈希(hash)和重新创建(restore)。
它有以下主要属性:
- compilation.modules:访问所有横块,打包的每一个文件都是一个横块
- compilation.chunks:chunk即是多个modules组成而来的一个代码块,入口文件引入的资源组成一个chunk,通过代码分割的模块又是另外的chunk
- compilation.assets:访问本次打包生成所有文件的结果
- compilation.hooks:注册tapable的不同种类Hook,用于在compilation编译模块阶段进行逻辑添加以及修改
5)plugin生命周期

6)创建插件
ruby
/*
1. 加载webpack.config.js中所有配置,调用new TestPlugin(),执行插件的constructor
2. 创建compiler对象
3. 遍历所有plugins中的插件,调用插件的apply方法
4. 执行所有编译流程 (触发hooks事件)
class TestPlugin{
constructor(){}
apply(compiler){
// 挂载钩子
compiler.hooks.environmenttap("TestPlugin",()=>{})
compiler.hooks.emit.tap("TestPlugin",(compilation)=>{}
compiler.hooks.emit.tapAsync("Testplugin",(compilation, callback) => {
setTimeout(()=>{
callback();
}, 2000);
})
}
}
module.exports = TestPlugin;
compiler对象结构
compilation对象结构
第六章 Webpack原理
1. 核心功能
webpack最核心的功能:内容转化+资源合并
- 初始化
- 初始化参数:参数 = 配置文件、配置对象、Shell 参数 + 默认配置
- 创建编译器对象:通过参数创建Compiler对象
- 初始化编译环境:注入内置插件、注册模块工厂、初始化RuleSet集合、加载配置的插件
- 开始编译:执行compiler对象的run方法
- 确定入口:根据entry找出所有入口文件
- 转换入口:调用compilition.addEntry将入口文件转换为dependence对象
- 构建
- 编译模块(make):根据dependence对象创建module对象,调用loader将模块转译为标准JS内容,调用JS解释器将内容转换为AST对象,从中找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理
- 完成模块编译:上一步递归处理所有能触达到的模块后,得到module 集合以及 module之间的依赖关系图
- 生成
- 输出资源(seal):根据入口和模块之间的依赖关系,组装成一个个包含多个模块的Chunk,再把每个Chunk转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会
- 写入文件系统(emitAssets):在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统
2. 初始化阶段
- 整合参数:process.args + webpack.config.js
- 校验参数:validateSchema
- 合并参数:getNormalizedWebpackOptions + applyWebpackOptionsBaseDefaults
- 基于参数创建compiler对象:new Compiler
- 插入外部plugin插件:遍历plugins集合,执行插件的apply方法
- 加载内置plugin插件:new WebpackOptionsApply().process

3. 构建阶段
- 构建module子类:根据文件类型调用handleModuleCreate 构建 module 子类
- 转义module内容:调用runLoaders将各类资源转译为 JavaScript 文本
- 解析JS文本:调用 acorn 将 JS 文本解析为AST
- 遍历AST,处理依赖
module => AST => dependences => module
🤔:webpack与babel区别?
🙋:相同点:webpack构建阶段会读取源码,解析为AST集合,babel解析阶段会读取源码解析为AST集合。不同点:Webpack只遍历AST集合,babel会对AST做等价转换
🤔:webpack如何识别资源依赖?
🙋:遍历AST集合,通过识别require/import之类的导入语句,确定依赖关系



4. 生成阶段
- 构建chunkGroup对象
- 将module分配给chunk:遍历compilation.modules集合,将module按entry/动态引入的规则分配给不同的Chunk对象
- 记录assets输出规则:遍历module/chunk ,调用compilation.emitAssets方法将assets信息记录到 compilation.assets对象中
- 将assets写入文件系统
- 控制流回转到compiler对象
entry及entry触达到的模块,组合成一个chunk;
使用动态引入语句引入的模块,各自组合成一个chunk;
5. 资源形态流转

1)compiler.make
- entry文件以dependence对象形式加入compilation的依赖列表,dependence对象记录entry的类型、路径等信息
- 根据dependence调用对应的工厂函数创建module对象,之后读入 module 对应的文件内容,调用 loader-runner 对内容做转化,转化结果若有其它依赖则继续读入依赖资源,重复此过程直到所有依赖均被转化为module
2)compilation.seal
- 遍历module集合,根据entry配置及引入资源的方式,将module分配到不同的chunk
- 遍历 chunk 集合,调用compilation.emitAsset方法标记chunk的输出规则,即转化为assets集合
3)compiler.emitAssets:将assets写入文件系统