webpack概述:
webpack是一款常见的前端构建工具。它通过分析项目中的模块依赖关系,将各类资源视为模块。对于这些资源,一部分是像 Html,Css,js等浏览器认识的资源,另一部分是 jsx,ts,vue等浏览器不认识的资源,这时候就需要用到 webpack 这种构建工具去打包,去将这些不认识的代码转为浏览器认识的代码。webpack使用的是js代码进行开发,基于Node平台去运行。
通过本篇文章能学到什么:
-
webpack是怎么工作的
-
webpack用到什么工具去做这些构建打包工作的
-
常见优化性能的实现原理详解
webpack构建流程:
webpack的构建过程分为三部分:初始化,编译,输出。
初始化:
-
读取配置:通过配置文件(webpack.config.js)或合并命令行参数(--mode)读取配置
-
创建Compiler对象:负责构建整个生命周期(启动,监听,关闭)
-
加载插件:调用每个插件的apply方法,将插件挂载到Compiler的生命周期上的钩子上。
-
初始化默认配置:根据mode(development/production)设置默认优化规则
编译:
-
从入口文件开始构建依赖图:根据entry找到入口文件
-
解析模块:
- 调用Loader:对模块内容进行翻译,转换
- 生成AST:将代码转为抽象语法树,分析模块的依赖关系(如import,require)
- 递归处理依赖
-
生成模块记录:保存每个模块的信息(如源码,依赖路径,转换后的代码)
输出:
- 生成chunk:根据依赖图和配置的代码分割(splitChunks),将模块组合成 Chunk
- 优化chunk:执行插件定义的优化操作(如
TerserPlugin
压缩代码,SplitChunksPlugin
拆分公共代码) - 生成最终文件:
将 Chunk 转为浏览器可执行的代码
根据output配置,将文件写入磁盘
总结:
-
初始化:启动构建,读取并合并配置参数,加载plugin,实例化compiler
-
编译:从入口文件触发,找到每个module串行调用对应的loader,再找到该模块依赖的模块,递归地进行编译处理
-
输出:将编译后地module组合成chunk,将chunk转换为文件,输出到文件系统
webpack打包流程:
首先,到这大家肯定有一个疑问,构建工程和打包过程不是一个东西吗???

严格来说,构建过程强调的是从源码到产物的完整处理过程,而打包过程更侧重于最终生成文件的结果阶段
构建过程和打包过程的区别:
流程 | 定义 | 阶段覆盖范围 |
---|---|---|
构建过程 | 从读取配置到生成最终产物的完整过程,包括初始化、编译、优化等所有步骤。 | 包含从启动到输出的全部阶段(广义)。 |
打包流程 | 通常指将处理后的模块(Module)组合成 Chunk,并输出为文件(Bundle)的具体过程。 | 更侧重于编译后的输出阶段(狭义)。 |
打包流程:
-
从 Module 到 Chunk
-
根据入口文件 和代码分割规则 (如
import()
动态导入或splitChunks
配置),将关联的模块分组为 Chunk。 -
每个 Chunk 包含:
- 入口模块及其依赖的所有模块。
- 运行时代码(Webpack 自执行的胶水代码,用于模块加载和管理)。
-
-
优化 Chunk
- Tree Shaking :删除未被使用的代码(仅
production
模式生效)。 - 代码压缩 :通过
TerserPlugin
压缩 JS,CssMinimizerPlugin
压缩 CSS。 - 作用域 提升(Scope Hoisting) :合并模块作用域,减少闭包代码体积。
- 公共代码提取 :通过
SplitChunksPlugin
拆分重复依赖(如node_modules
中的库)。
- Tree Shaking :删除未被使用的代码(仅
-
生成 Bundle 文件
-
将 Chunk 转换为最终的可执行文件(Bundle),包括:
- JS Bundle:包含模块代码和 Webpack 运行时逻辑。
- CSS Bundle :若使用
MiniCssExtractPlugin
,CSS 会被提取为独立文件。 - 资源文件 :如图片、字体通过
file-loader
输出到指定目录。
-
写入文件系统
- 根据
output
配置,将 Bundle 和资源文件写入磁盘(如dist/
目录)。 - 生成辅助文件(如
manifest.json
、index.html
或 SourceMap)。
- 根据
webpack Loader
webpack默认值只认识 js 文件,需要 Loader 去充当"翻译官"的角色,去识别其他不认识的文件
Css-Loader
在我们模块化开发时,需要将 css 文件和 js 文件分隔开
我们若想让浏览器识别 css 文件,则需要两个步骤:
- 首先使用 css-loader 进行依赖图中 css 文件的识别,进行 css 文件的解析
- 再使用 style-loader 将 css 链接到 index.html 文件中,让 css 发挥出真正的效果
java
module.exports={
module:{
rules:[
{
test:/.css$/, //解释见[1]
use:[ //记录需使用的loader,注意这里是从后往前使用,即先使用css-loader再使用style---loader
{loader:style-loader}, //将css文件链接至index.html
{loader:css-loader} //解析css文件
]
}
]
}
}
//[1]这里使用的是正则表达式,/ /代表正则表达式的开始与结束,.代表.(.在正则中有特定含义,需要用\转义),$表示需要以.css结尾。
Less-Loader
less-loader是用来解析 less 文件,分为三步:
- 首先使用 less-loader 将 less 文件转为 css 文件(需要借助 less 库进行转化)
- 再使用 css-loader 进行依赖图中 css 文件的识别,进行 css 文件的解析
- 最后使用 style-loader 将 css 链接到 index.html 文件中,让 css 发挥出真正的效果
css
module.exports={
module:{
rules:[
{
test:"/.less$/",
use:[
{loader:"style-loader"},
{laoder:"css-loader"},
{loader:"less-loader"}
]
}
]
}
}
Postcss-Loader
postcss是一个样式转换工具,对 css 进行转化和适配,比如加一些浏览器前缀以适配不同浏览器,将 px 转为 rem 或 vh。但完成上述功能需要 postcss 对应不同插件的支持:
- Autoprefixer 和 PostCSS-preset-env 的区别
特性 | Autoprefixer | PostCSS-preset-env |
---|---|---|
功能范围 | 仅添加前缀 | 前缀+未来 Css 语法转换 |
Css 新特性支持 | 不支持 | 支持 |
配置复杂度 | 简单 | 较复杂 |
独立性 | 可单独使用 | 内置Autoprefixer |
- 二者适用场景
如果只需处理浏览器前缀,用 Autoprefixer(更轻量)。
如果需要使用 CSS 新特性(如嵌套、逻辑属性等),用 PostCSS-preset-env。
两者通常一起使用:PostCSS-preset-env 负责语法转换,Autoprefixer 处理前缀(即使 preset-env 内置了 Autoprefixer,也可单独配置)。
Babel-Loader
Babel 是负责在低版本的浏览器环境中依然能运行 es6+ 这种新特性语法,但每个新特性都需要对应插件的支持。
perl
module.exports={
test:/.js$/,
use:[
{
loader:"babel-loader",
options:{
plugins:[ //babel使用的插件
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-transform-block-scoping"
]
}
}
],
}
然而这些插件又多又难记,因此实际开发中一般给 webpack 提供一个预设preset,webpack 根据预设来加载对应插件列表并传递给Babel:
javascript
module.exports={
test:/.js$/,
use:[
{
loader:"babel-loader",
options:{
presets:["@babel/preset-env"] //注意这里是预设preset配置项,已不再是plugins
}
}
],
}
webpack plugin
Plugin 概述:
loader代表加载器,只能完成不同模块类型的解析,plugin代表插件,可以做更加广泛的任务,贯穿于Webpack整个生命周期。
Plugin 的作用:
-
打包优化,将样式单独抽取为一个文件并在index.html中用link方式引用,而不是用style标签直接嵌入到html页面中。
-
资源管理,二次打包前先删除上次打包后的文件夹,可以使得二次打包不需要的文件(如图片)自动删除。
-
环境变量注入,使得变量一处定义,处处可用。
常见 plugin:
资源管理 clean 插件: clean-webpack-plugin 可以使得二次打包前先删除打包后的文件夹,即二次打包不需要的文件删除
java
import {CleanWebpackPlugin} from "clean-webpack-plugin"
module.exports={
plugin:[new CleanWebpackPlugin()]
}
Html 插件: 默认情况下,打包后只有一个 js 文件,然后需要手动添加一个 html 文件引入 js 文件。html-webpack-plugin 就是帮我们自动生成 html 文件的
java
import {CleanWebpackPlugin} from "clean-webpack-plugin"
module.exports={
plugin:[new CleanWebpackPlugin()]
}
DefinePlugin:define-plugin插件用于环境变量注入,使得变量一处定义,处处使用。该插件是 webpack 内置的插件,直接从 webpack 库中引入即可:
javascript
const {DefinePlugin} from "webpack"
module.exports={
plugins:[
new DefinePlugin({
"BASE_URL":"./" //配置BASE_URL变量的值为"./"
})
]
}
自定义 plugin:
- 目的: 自定义 plugin可以让 webpack 做自己定制的需求,执行特定的任务,有更强的构建能力
- 工作原理:
Webpack 就像一条生产线,插件就像插入生产线的一个功能
Webpack 在编译代码过程中,会触发一系列钩子事件,插件做的就是找到对应钩子,往上面挂上自己的任务,也就是注册事件。
当 webpack 构建的时候,插件就会找到对应钩子触发执行了
- 理解 compiler 和 compilation:
compiler是贯穿了整个 webpack 生命周期的一个实例,代表了完整的 webpack 的环境配置
compilation是每次新的资源构建时创建的一个实例,代表一次单独的版本构建
- 怎么实现自定义 plugin:
搭建插件的基本结构:需要 apply 方法,接受 compiler 对象作为参数
在 apply 方法中注册生命周期钩子
生成版本文件 用 node 在 debugger 行设置断点进行调试
javascript
//需求:给打包输出文件添加注释
const { compilation } = require("webpack");
//开发思路:
//触发时机:生成资源到输出目录之前 emit钩子
//如何获取打包输出的资源?compilation可以获取所有的即将输出的资源文件
class BannerWebpackPlugin{
constructor(){}
apply(compiler){
compiler.hooks.emit.tap('BannerWebpackPlugin',(compilation)=>{
debugger;
const extensions=['css','js'];
//过滤只保留这两种文件
const assets=Object.keys(compilation.assets).filter((assetpath)=>{
const splitted=assetpath.split('.');
const extension=splitted[splitted.length-1];
return extensions.includes(extension)
})
const prefix = '/* Built by BannerWebpackPlugin */\n';
assets.forEach((asset)=>{
const source=compilation.assets[asset].source();//原来内容
const content=prefix+source; //拼接上注释
//修改资源
compilation.assets[asset]={
source(){
return content; //资源内容
},
size(){
return content.length
}
}
})
})
}
}
module.exports=BannerWebpackPlugin
plugin和loader的区别:
-
webpack只认识JavaScript,Loader就是充当一个翻译官的角色,对其他类型的资源做转译的预处理工作
-
Plugin就是插件,基于事件流框架Tapable,扩展Webpack的功能,贯穿于Webpack的整个生命周期
-
Loader在module.rules中配置,类型为数组,每一项都是Object
-
Plugin在plugins中单独配置,类型为数组,每一项都是一个plugin的实例,参数通过构造函数传入
webpack的常见优化手段整理:
- 并行构建优化:
多进程多实例构建:thread-loader
并行压缩:terser-webpack-plugin
- 缩小构建范围:
精确指定 loader 的作用范围
优化模块解析
排除无用模块
- 缓存利用:
持久化缓存:cache属性
Loader 缓存:babel-loader开启缓存
HardSourceWebpackPlugin插件
- 代码拆分与按需加载:
- Tree shaking 与 scope hosting
- 图片压缩:image-webpack-plugin
- 按需加载 polyfill
热更新HMR
- HMR 概述: 它能够让你在不刷新页面的前提下,自动更新修改的部分,同时还能保留页面状态
- HMR的核心就是客户端向服务器拉取更新后的文件,即 chunk diff
- 实际上 webpack-dev-server 与浏览器上维护了一个websocket,当本地资源变化后,wds会立刻向浏览器推送更新,并带上构建时的hash,与上一次的构建做对比。
- 客户端对比出差异后,会向WDS发送AJAX请求来获取这些更改内容,后续客户端就可以借助这些信息发送JSONP请求获取 chunk 的增量更新。

树摇 Tree shaking
Tree shaking 基于ESM的静态结构特性,通过分析模块间的导入导出关系,精确地识别并移除未被引用的代码
实现原理:
- 在编辑阶段,提取所有导出变量并记录到模块依赖图的结构中,构建完整的模块关系图并标记未使用的导出
- 随后,在优化阶段,Terser等压缩工具会识别并物理移除未使用的代码,最终生成精简的产物文件
为什么CommonJS 不支持 Tree shaking?
- CommonJS 模块系统允许高度动态的导入模式
- Cjs 导出的是完整的模块对象
- Cjs 无法在编译时确定使用了哪些具体导出项,依赖解析发生在执行阶段,模块依赖图只有在代码执行时才能完全确定
- 静态分析工具无法可靠地预测所有可能的模块加载路径
代码分割
-
代码分割就是将一个大的bundle拆分成多个小的chunks
-
代码分割的本质是:将代码拆解为更小的,可独立加载的模块单元,在全量加载和零散请求之间找到性能最优解,将可控的复杂度换取用户体验与工程效率的平衡
-
要抽离的代码:
- 公共模块的代码不需要重复打包,单独抽离成一个文件,直接引用即可
- 第三方模块的代码,单独抽离成一个文件,然后比如lodash
-
核心拆解:
- 以一个电商网站的长页面为例,
- 要首先做首屏加载
- 再做交互时加载,后续分批按需加载
-
意义:这样做既不会让浏览器内存超载,又可以逐步扩展功能
Source Map:
- Source Map 概述: Source Map 是一种用来还原源码位置的技术,记录了打包压缩后代码和源码之间的映射关系,让你在浏览器调试时看到真正的代码。
- 常见的环境搭配:

- 工作原理:
当在webpack的配置文件中配置了 Source Map,打包文件会产生一个魔法注释。这个魔法注释告诉浏览器,这份文件有 Source Map。浏览器看到这行文件后,会请求对应的 Map 文件,然后使用该 .map文件中的信息,进行还原,映射和调试。
其他常见优化场景:
开发环境优化
- 增量构建
- HMR
- Source Map
- 多进程多实例构建: Thread Loader
生产环境优化
- 利用CDN
- Tree shaking
- 代码分割,拆包优化
构建服务端渲染SSR
- 将客户端代码和服务端代码分开,减轻服务器负担
- 什么是SSR:指的是在服务端产生HTML页面,然后将完整的HTML页面发送到浏览器进行展示的一种渲染方式
- 与客户端渲染CSR相比,SSR在服务器上运行JS代码生成页面,而不是依赖浏览器在客户端运行代码生成页面
- SSR的核心流程:
暂时无法在飞书文档外展示此内容
Babel 原理:
-
Babel是什么:
- Babel就是允许开发者在低版本环境下使用最新的js语法
- babel就是可扩展的插件系统,用来添加,删除,或转换规则
-
Babel的三种存在方式:
- 使用单体文件
- 命令行:就是在package.json的scripts命令中的某条命令
- 构建工具的插件:webpack的babel-loader
-
运行方式和插件:
- Babel总共分为三个阶段:解析,转换,生成
- Babel本身不具有任何转换功能,他把一个个转换的功能都分解到一个个plugin中,因此当我们不配置任何插件时,经过Babel的代码和输入是相同的
- 在解析阶段:把源码转换成结构化的AST,在转换阶段:遍历AST转换为目标字符串
-
preset:一个功能一个插件,那岂不是要安装很多插件,设立预设就是提供了一组插件的集合
-
Babel原理:
- 解析:将代码转换成AST
- 词法分析:将代码分割成tokens流
- 语法分析:分析token数组转换成AST树
- 转换:遍历AST并生成新的AST
- 生成:以新的AST为基础生成代码