前言介绍:
Webpack 是一个模块打包器。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
从图中我们可以看出,Webpack 可以将多种静态资源 js、css、less 转换成一个静态文件,减少了页面的请求。
Webpack的核心概念有哪些?
- Entry:入口,Webpack执行构建的第一步将从Entry开始,可抽象成输入。告诉Webpack要使用哪个模块作为构建项目的起点,默认为./src/index.js
- output:出口,告诉Webpack在哪里输出它打包好的代码以及如何命名,默认为./dist
- Module:模块,在Webpack里一切皆模块,一个模块对应着一个文件。Webpack会从配置的Entry开始递归找出所有依赖的模块。
- Chunk:代码块,一个Chunk由多个模块组合而成,用于代码合并与分割。
- Loader:模块转换器,用于把模块原内容按照需求转换成新内容。
- Plugin:扩展插件,在Webpack构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情。
Webpack的基本功能
- 代码转换:TypeScript编译成JavaScript、SCSS编译成CSS等等
- 文件优化:压缩JavaScript、CSS、html代码,压缩合并图片等
- 代码分割:提取多个页面的公共代码、提取首屏不需要执行部分的代码让其异步加载
- 模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
- 自动刷新:监听本地源代码的变化,自动构建,刷新浏览器
- 代码校验:在代码被提交到仓库前需要检测代码是否符合规范,以及单元测试是否通过
- 自动发布:更新完代码后,自动构建出线上发布代码并传输给发布系统。
1、介绍 Loader 和 Plugins
webpack 只能直接处理 javascript 格式的代码。任何非 js 文件都必须被预先处理转换为 js 代码,才可以参与打包。loader(加载器)就是这样一个代码转换器。它由 webpack 的 `loader runner` 执行调用,接收原始资源数据作为参数(当多个加载器联合使用时,上一个loader的结果会传入下一个loader),最终输出 javascript 代码(和可选的 source map)给 webpack 做进一步编译。
1.1、Loader 工作原理
webpack 只能直接处理 javascript 格式的代码。任何非 js 文件都必须被预先处理转换为 js 代码,才可以参与打包。loader(加载器)就是这样一个代码转换器。它由 webpack 的 `loader runner` 执行调用,接收原始资源数据作为参数(当多个加载器联合使用时,上一个loader的结果会传入下一个loader),最终输出 javascript 代码(和可选的 source map)给 webpack 做进一步编译。
常用loader
file-loader:用于处理文件类型资源,如jpg ,png等图片。返回值为publicPath 为准。
url-loader :url- loader也是处理图片类型资源,只不过它与file loader有一点不同,url-loader可以设置一个根据图片大小进行不同的操作,如果该图片大小大于指定的大小,则将图片进行打包资源,否则将图片转换为base64字符串合并到js文件里
css-loader:用途:用于识别.css 文件,处理css必须配合style- loader共同使用,只安装css-loader样式不会生效。
style-loader: 用于将css编译完成的样式,挂载到页面style标签上。需要注意loader执行顺序,style-loader放到第一位,因为loader都是从下往上执行,最后全部编译完成挂载到style上
scss-loader:自动将scss转换为CSS
less-loader:自动将less转换为CSS
sass-loader:CSS预处理器, Sass 文件转换为 CSS 文件
PostCSS-loader:PostCSS和sass/less不同, 它不是CSS预处理器(换个格式编写css)。PostCSS是一款使用插件去转换CSS的工具,PostCSS有许多非常好用的插件。用于补充css样式各种浏览器内核前缀
eslint-loader:用于检查代码是否符合规范,是否存在语法错误。
接下来简单为大家介绍 Webpack 的分包策略、代码分割优化、Tree Shaking。
1.2、Plugins工作原理
Plugins 插件是Webpack生态中不可或缺的一部分,它允许开发者在编译过程中插入自定义的任务,从而扩展Webpack的功能。
常用plugins
ignore-plugin:忽略部分文件
mini-css-extract-plugin:分离样式文件
HtmlWebpackPlugin:会在打包结束之后自动创建一个index.html, 并将打包好的JS自动引入到这个文件中。
clean-webpack-plugin:在打包之前将我们指定的文件夹清空。应用场景每次打包前将目录清空, 然后再存放新打包的内容, 避免新老混淆问题,非官方功能。
copy-webpack-plugin:打包相关的文档。除了JS/CSS/图片/字体图标等需要打包以外, 可能还有一些相关的文档也需要打包(word等)。文档内容是固定不变的, 我们只需要将对应的文件拷贝到打包目录中即可。
1.3、Loader和Plugin的区别?
- loader 是文件加载器,能够加载资源文件,并对这些文件进行一些处理,诸如编译、压缩等,最终一起打包到指定的文件中
- plugin 插件赋予了 webpack 各种灵活的功能,例如打包优化、资源管理、环境变量注入等,目的是解决 loader 无法实现的其他事
从整个运行时机上来看,如下图所示:
可以看到,两者在运行时机上的区别:
- Loader 运行在打包文件之前
- Plugin 在整个编译周期都起作用
作用不同:
- Loader:用于资源加载并处理各种语言的转换/编译(例如:将不同语言转换为 JavaScript)
- Plugin:用于资源加载以外的其他打包/压缩/文件处理等功能
加载器负责处理文件的加载与解析,而插件则负责在编译过程中执行各种自定义的操作。这种协同工作的模式使得开发者能够灵活地管理和维护大型代码库,同时保持代码的高度模块化、可扩展性以及复用性。
2、代码分包
代码分包是一种优化技术,用于将代码分割成多个独立的包(chunk),以便按需加载,减少初始加载时间,提高性能。
分包的目的是通过降低总体积和利用浏览器缓存,来优化Web应用的加载性能和资源利用率。
Webpack分包策略:
1、动态导入,通过 import() 语法实现按需加载。
2、使用SplitChunksPlugin。 是 Webpack 内置的插件,用于提取公共模块和第三方依赖,减少重复代码。
3、预加载和预获取。通过 webpackPrefetch 和 webpackPreload 指令,提前加载关键资源。
4、结合 CDN 优化
Webpack是一个强大的打包工具,但随着项目规模的增长,打包速度和体积可能会成为瓶颈。以下是一些优化Webpack打包的策略,以提高构建速度和减少打包体积。
1、使用多线程
由于Webpack运行在Node.js之上,默认是单线程的。可以使用thread-loader或HappyPack来开启多线程处理,从而加快打包速度。
2、使用缓存
开启缓存可以显著减少二次构建的时间。
3、分离开发和生产环境配置
在开发环境中使用webpack-dev-server和HMR(热模块替换),在生产环境中进行代码压缩、提取CSS等优化。
4、压缩代码
使用terser-webpack-plugin压缩JavaScript代码,使用optimize-css-assets-webpack-plugin压缩CSS代码。
5、可以移除未使用的代码
Tree Shaking可以移除未使用的代码。确保使用ES6模块语法,并在生产环境中启用。
6、代码分割
使用SplitChunksPlugin将代码分割成更小的块,以提高加载速度。
3、代码分割
代码分割是指将一个大文件分解成多个小文件的过程,使得只有当特定功能被请求时才加载相应的代码。
Webpack代码分割策略:
1、使用ES6的import()函数动态加载模块。
2、按需加载选择将一些代码块放在初始加载中,而将其他部分作为异步加载。
3、条件引入。通过 webpack 的 require.ensure() 方法
4、Tree Shaking
Tree Shaking 是一种优化技术,用于在打包过程中移除未使用的代码,从而减少最终的文件体积。
它的核心原理是通过静态分析代码,识别哪些模块或函数未被引用,并在打包时将其剔除。
作用:通过静态分析和压缩工具,帮助消除未使用的 ES6 模块代码,显著减小包体积。
Tree Shaking 的工作原理:
1、静态分析:Tree Shaking 依赖于 ES6 模块(ESM)的静态结构特性。通过解析 import 和 export 语法,Webpack 能够在编译时构建模块依赖图,确定哪些代码是未被引用的“死代码”(Dead Code)。
2、标记未使用的代码:Webpack 的 optimization.usedExports 配置会标记未被使用的导出内容为 unused。
3、移除未使用的代码:结合压缩工具(如 Terser),Webpack 会在打包阶段删除未使用的代码及其相关引用。
优化配置:
1、Tree Shaking 只能对 ES6 模块(即使用 import 和 export 的模块)进行优化。如果你使用的是传统的 CommonJS 或 AMD 模块,Webpack 就无法进行有效的 Tree Shaking。
2、要启用 Tree Shaking,我们需要将 Webpack 的 mode 设置为 production,因为只有在生产环境下,Tree Shaking 才会默认启用。
3、通过启用 usedExports 选项,Webpack 会标记出哪些模块或导出是被实际使用的,哪些没有被使用。
4、通过在 package.json 中配置 sideEffects 字段,Webpack 可以更智能地识别哪些文件包含副作用,避免错误地删除这些文件。
// webpack.config.js
module.exports = {
mode: 'production', // 生产模式下启用 Tree Shaking
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: __dirname + '/dist',
},
optimization: {
usedExports: true, // 标记未使用的导出
},
};
5、热更新
Webpack 热更新(Hot Module Replacement,HMR)是一种在不刷新整个页面的情况下,动态替换模块的技术。它通过局部更新代码,保留页面状态,极大地提升了开发效率和体验。过程:
- 通过
webpack-dev-server
创建两个服务器:提供静态资源的服务(express)和Socket服务- express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
- socket server 是一个 websocket 的长连接,双方可以通信
- 当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
- 通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
- 浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新
6、webpack打包流程
为什么要 webpack 打包?
因为在软件开发中,通常存在两种环境:开发环境和生产环境。
- 开发环境:用于开发者编写和调试代码的环境。它通常包含丰富的调试工具和实时反馈机制,以便开发者快速发现和解决问题。
- 生产环境:用于用户实际使用的环境。它要求代码高效、稳定且安全,通常会对代码进行优化和压缩,以提高性能和减少资源消耗。
前端构建工具(如Webpack、Vite等)用于将开发环境下的源代码转换为生产环境下的可执行代码。它们的主要功能包括:
- 模块打包:将多个模块文件打包成一个或多个文件,减少HTTP请求次数。
- 代码压缩:压缩代码体积,提高加载速度。
- 代码优化:优化代码结构,提高执行效率。
- 资源处理:处理图片、CSS、字体等静态资源,使其适应生产环境。
简单来说:通过对代码进行优化、压缩和转换,确保应用在生产环境中高效、稳定地运行。
为什么必须打包部署
1、性能优化
减少文件体积:通过代码压缩和优化,减少文件体积,提高加载速度。
减少HTTP请求:通过模块打包,将多个文件合并成一个文件,减少HTTP请求次数。
2、 兼容性
浏览器兼容:构建工具会对代码进行转译,确保在不同浏览器中都能正常运行。
模块化支持:构建工具会将ES6模块、CommonJS模块等转换为浏览器可识别的格式。
3、 安全性
代码混淆:通过代码混淆,增加代码的可读难度,防止代码被轻易篡改。
去除敏感信息:构建过程中可以去除开发环境中的敏感信息,如API密钥等。
4、 静态资源管理
资源优化:对图片、CSS等静态资源进行压缩和优化,提高加载速度。
缓存策略:通过配置缓存策略,提高资源加载效率。
1. 初始化参数 。Webpack 读取开发者配置的 webpack.config.js 文件或者是从 Shell 语句中读取与合并参数,得出最终的参数
2. 开始编译。 用上一步得到的参数初始Compiler对象,加载所有配置的插件,通过执行对象的run方法开始执行编译
3. 确定入口 。读取配置文件中的 Entry ,找出所有入口文件
4. 编译模块。 从入口文件出发,调用所有配置的 Loader 对模块进行编译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理(对所有的依赖文件进行编译,编译过程依赖 loaders, 不同类型的文件使用不同的 loader 进行解析,最终解析生成抽象语法树)
5. 完成模块编译 。在经过第4步使用 Loader 翻译完所有模块后, 得到了每个模块被编译后的最终内容及它们之间的依赖关系
6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再将每个 Chunk 转换成一个单独的文件加入输出列表中,这是可以修改输出内容的最后机会
7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,将文件的内容写入文件系统中
以上7个步骤,可以简单归纳为初始化阶段、编译阶段、输出阶段,三个过程
7、如何提高webpack的构建速度?
7.1、HotModuleReplacement
(HMR/热模块替换)
开发时我们修改了其中一个模块代码,Webpack
默认会将所有模块全部重新打包编译,速度很慢。所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。
module.exports = {
// 其他省略
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)
},
};
7.2、 缓存
每次打包时 js
文件都要经过Eslint
检查 和Babel
编译,速度比较慢。使用Cache
: 对Eslint
检查 和Babel
编译结果进行缓存。
在引入持久化缓存之前,Webpack 在每次运行时都需要对所有模块完整执行上述构建流程,Webpack5 的持久化缓存功能则尝试将构建结果保存到文件系统中,在下次编译时对比每一个文件的内容哈希或时间戳,未发生变化的文件跳过编译操作,直接使用缓存副本,减少重复计算;发生变更的模块则重新执行编译流程。
在缓存模块中提供了两种类型缓存能力:内存缓存、持久缓存:
- 内存缓存:在内存中维护一个map,通过get()和store()方法进行缓存的读取和写入。内存缓存仅在构建生命周期内有效。
- 持久化缓存:持久化缓存将构建结果存储到磁盘中,适用于跨构建进程的缓存需求。
module.exports = { cache: { type: 'filesystem' }, };
7.3、oneOf 正则提高loader匹配效率
打包时每个文件都会经过所有loader
处理,虽然因为 test
正则原因实际没有处理上,但是都要过一遍,比较慢,通过oneOf规则可以优化构建速度,使用oneOf确保文件匹配到第一条合适的规则后停止后续匹配。
用方法如下,匹配到一个loader后,后面的就不会再继续匹配了:
module:{
rules:[
{
test:/\.js$/,
exclude:/node_modules/,
// //优先执行
enforce:'pre',
loader:'eslint-loader',
options:{
fix:true
}
},
{
//下面的loader只会匹配一个,处理性能更好
oneOf:[
{
test:/\.css$/,
use:[...commenCssLoader]
},
{
test:/\.less$/,
use:[...commenCssLoader,'less-loader']
},
]
}
7.4、多进程处理
需要安装 thread-loader,它是实现多线程打包的关键插件。处理思路是将原有的 Webpack 对 loader
的执行过程从单一进程的形式扩展多进程模式,原本的流程保持不变,多个子进程并发的执行,子进程处理完后再把结果发送给主进程。