众所周知,掌握一个打包工具是很重要的,在大厂面试中,常有面试官抓着打包工具拷打,那么,是时候展示!真正的实力辣!
1、Vite 和 Webpack 对比
Vite特点&优势
- 开发启动速度极快。Vite 在开发模式下,利用浏览器原生支持 ES Modules 的特性,是基于浏览器的 module import,在请求的时候对模块做下编译,实现了即时的模块加载。(使用vite运行项目时,首先会用
esbuild进行预构建,将所有模块转换为es module,不需要对我们整个项目进行编译打包,而是在浏览器需要加载某个模块时,拦截浏览器发出的请求,根据请求进行**按需编译**,然后返回给浏览器。) - 热模块更新(HMR)性能出色,能够快速地将更新的模块推送到浏览器,实现局部模块的实时更新。
- 配置简单(vite对我们常用功能都做了内置,比如:
css 预处理器、html 预处理器、hash 命名、异步加载、分包、压缩、HMR等等,我们可以很轻松的通过配置项去配置。)
Webpack特点&优势
- 模块打包能力强,能够处理各种各样的模块类型,包括 JavaScript、CSS、图片、字体等,并将它们有效地打包在一起。
- 得益于其插件生态丰富、loader可以处理各种类型的文件、其支持处理多种模块系统的代码以及其配置的灵活性可拓展性(可以配置入口文件输出路径模块规则等),Webpack可以满足任何复杂的构建需求。
- 可定制性强,通过复杂但强大的配置选项,Webpack 可以实现高度定制化的构建流程(Vite插件系统较新生态较小,且Vite配置文件相对简洁),满足各种特殊的项目需求。
Vite的局限性
-
生产环境的优化仍有待完善
- 在生产环境中,Vite 可能不如 Webpack 那样在代码分割、压缩和缓存等方面表现出色。例如,对于大型项目,Vite 生成的打包文件可能不够紧凑,导致加载时间较长。(Vite 是基于 ES 模块进行构建的,它会将代码转换为浏览器能够理解的格式,但可能没有像 Webpack 那样深入地分析模块之间的依赖关系来进行极致的优化)
- 插件系统较新生态较小
-
对旧版本浏览器的支持不足
- 由于依赖浏览器原生的 ES Modules 支持,对于一些老旧浏览器,如 IE11 ,Vite 无法直接运行。
- 需要通过额外的工具如
polyfill.io来提供必要的支持,增加了项目的复杂性。(在需要的情况下Webpack和Vite都需要借助额外工具)
Webpack局限性
-
开发启动速度较慢
- 在开发模式下,Webpack 需要进行全量的模块打包,这导致启动速度相对较慢。
- 特别是当项目规模较大,模块数量众多时,启动时间可能会让人感到不耐烦。假设项目中有数百个模块,Webpack 在启动时需要遍历和处理所有这些模块,导致启动时间延长。
-
配置复杂
- 复杂的配置对于新手开发者来说是一个较大的挑战。例如,要实现复杂的代码分割、优化和加载器配置,可能需要深入理解 Webpack 的内部机制,容易出现配置错误。
除此之外,还有grunt、gulp等构建工具
“工具的选择不在于谁更流行,而在于它是否适合你的项目需求。”
2、webpack常见loader
-babel-loader:将es6转译为es5
-image-webpack-loader: 加载并压缩图片资源
-awesome-typescirpt-loader: 将typescript转换为javaScript
-sass-loader: 将SCSS/SASS代码转换为CSS
-css-loader: 加载CSS代码 支持模块化、压缩、文件导入等功能特性
-style-loader: 把CSS代码注入到js中,通过DOM 操作去加载CSS代码
-source-map-loader: 加载额外的Source Map文件
-eslint-loader: 通过ESlint 检查js代码
-cache-loader: 可以在一些开销较大的Loader之前添加可以将结果缓存到磁盘中,提高构建的效率
-thread-loader: 多线程打包,加快打包速度。
3、webapck常见plugin
-define-plugin: 定义环境变量(webpack4之后可以通过指定mode:production/development实现同样效果)
-web-webpack-plugin:为单页面应用输出HTML 性能优于html-webpack-plugin
-clean-webpack-plugin: 每次打包时删除上次打包的产物, 保证打包目录下的文件都是最新的
-webpack-merge: 用来合并公共配置文件,常用(例如分别配置webpack.common.config.js/ webpack.dev.config.js/webpack.production.config.js并将其合并)
-ignore-plugin: 忽略指定的文件,可以加快构建速度
-terser-webpack-plugin:压缩ES6的代码(tree-shaking)
-uglifyjs-webpack-plugin: 压缩js代码
-mini-css-extract-plugin: 将CSS提取为独立文件,支持按需加载
-css-minimize-webpack-plugin:压缩CSS代码(先使用mini-css-extract-plugin将css代码抽离成单独文件,之后使用css-minimize-webpack-plugin对css代码进行压缩)
-serviceworker-webpack-plugin: 为离线应用增加离线缓存功能
-ModuleconcatenationPlugin: 开启Scope Hositing 用于合并提升作用域, 减小代码体积
-copy-webpack-plugin: 在构建的时候,复制静态资源到打包目录。
-compression-webpack-plugin: 生产环境采用gzip压缩JS和CSS
-ParalleUglifyPlugin: 多进程并行压缩js
-webpack-bundle-analyzer: 可视化webpack输出文件大小的根据
-speed-measure-webpack-plugin: 用于分析各个loader和plugin的耗时,可用于性能分析
-webpack-dashboard: 可以更友好地展示打包相关信息
-splitChunkPlugin:用于代码分割
-HotModuleReplacementPlugin:支持模块热替换
-UnusedWebpackPlugin: 反向查找项目中没被用到的文件,日常工作经常用到,可在重构或者性能分析时使用
-webpack-dashboard: webpack-dashboard 是一个命令行可视化工具,能够在编译过程中实时展示编译进度、模块分布、产物信息等相关信息,性能分析时很有用。
-Webpack Analysis:Webpack Analysis 是 webpack 官方提供的可视化分析工具。
-BundleAnalyzerPlugin:性能分析插件,可以在运行后查看是否包含重复模块/不必要模块等
4、Loader-Plugin
Loader:
为了处理非JS模块并支持不同的模块规范和语言,我们需要loader。Loader负责代码模块的转换,即对接收到的内容进行转换后将转换后的结果返回 配置Loader通过在modules.rules中以数组的形式配置
Plugin:
Plugin本质上是一个带有apply(compiler)的函数,基于tapable这个事件流框架来监听webpack构建/打包过程中发布的hooks来通过自定义的逻辑和功能来改变输出结果。(Tapable 提供了多种类型的钩子(hooks),如SyncHook(同步钩子)、AsyncSeriesHook(异步串行钩子)、AsyncParallelHook(异步并行钩子)等。在 Webpack 构建过程的不同阶段,会发布(emit)相应的钩子事件。插件通过compiler.hooks对象来监听这些钩子。)
Loader主要负责将将各种类型的文件(如 CSS 文件、图片文件、JSON 文件等)转换为浏览器可以读取的格式,而 Plugin 更多的是负责通过接入webpack 构建过程来影响构建过程以及产物的输出,Loader的职责相对比较单一简单,而Plugin更为丰富多样。
5、Loader执行顺序
可以通过enforce来强制控制Loader的执行顺序 (pre 表示在所有正常的loader执行之前执行,post则表示在之后执行)
Loader的执行有以下两个阶段:
- Pitching 阶段: loader 上的 pitch 方法,按照
后置(post)、行内(inline)、普通(normal)、前置(pre)的顺序调用。- Normal 阶段: loader 上的 常规方法,按照
前置(pre)、普通(normal)、行内(inline)、后置(post)的顺序调用。模块源码的转换, 发生在这个阶段。
6、HMR热更新原理
HMR(Hot Module Replacement)即热模块替换,是一种在开发环境下用于提高开发效率的技术。它允许在应用程序运行过程中,替换、添加或删除模块,而无需进行完整的页面刷新。
如何开启HMR: 通过设置devServer: {hot: true} 开启 开启后便可以在发生改变后局部刷新改变的部分
// webpack.config.js
module.exports = {
devServer: {
// 必须设置 devServer.hot = true,启动 HMR 功能
hot: true
}
};
import component from "./component";
let demoComponent = component();
document.body.appendChild(demoComponent);
// HMR interface
if (module.hot) {
// Capture hot update
module.hot.accept("./component", () => {
const nextComponent = component();
// Replace old content with the hot loaded one
document.body.replaceChild(nextComponent, demoComponent);
demoComponent = nextComponent;
});
}
原理:
-
使用
webpack-dev-server(WDS)托管静态资源 同时以Runtime方式注入HMR客户端代码 -
浏览器加载页面后 与WDS建立
WebSocket连接 -
webpack监听到文件变化后 增量构建发生变更的模块 并通过WebSocket发送
hash事件-
文件变化监听与模块识别
- 文件系统监听器:Webpack 使用文件系统监听器来监控项目中的源文件。当文件发生变化时,监听器会捕获到这个事件,并将变化信息(如文件路径、修改时间等)传递给 Webpack。
- 模块映射与定位:Webpack 内部维护着一个从文件路径到模块的映射关系。通过这个映射,当监听到文件变化时,它可以快速确定是哪个模块对应的文件发生了变化。例如,如果一个.js文件发生变化,Webpack 可以根据文件路径在模块映射中找到对应的 JavaScript 模块。
-
增量构建模块的流程
- 模块依赖分析:一旦确定了发生变化的模块,Webpack 会分析该模块的依赖关系。它参考之前构建过程中生成的模块依赖图,找出所有直接或间接依赖于这个变化模块的其他模块。这是通过遍历依赖图来实现的,从变化模块开始,沿着依赖关系链向上和向下查找相关模块。
- 选择性构建:在找出相关模块后,Webpack 只会重新构建这些受影响的模块,而不是整个项目。
-
通过 WebSocket 发送 Hash 事件实现更新通知
- Hash 生成与模块标识:Webpack 会为每个构建生成一个唯一的 Hash 值。这个 Hash 值是基于构建内容(如模块代码、配置信息等)计算得到的。对于每个重新构建的模块,也会有相应的子 Hash 值或者标识符。这些 Hash 值用于在客户端(浏览器)准确地识别哪些模块发生了更新。
- WebSocket 通信机制:Webpack 使用 WebSocket 协议与浏览器建立通信通道。当增量构建完成后,它会通过 WebSocket 发送一个包含 Hash 事件的消息。这个消息中包含了更新模块的 Hash 值、模块名称、路径等信息。浏览器端的运行时环境(如 Webpack 的运行时脚本)接收到这个消息后,就可以根据 Hash 值和模块信息来确定需要更新的模块内容。
- 客户端更新模块:在浏览器端,根据接收到的 Hash 事件,Webpack 运行时会比较本地已加载模块的 Hash 值和接收到的更新模块的 Hash 值。如果两者不匹配,就说明模块需要更新。 然后,它会使用接收到的模块更新内容(通过之前建立的通信机制获取)来替换本地的旧模块。这种方式确保了浏览器中的模块能够与服务器端(Webpack 构建环境)的模块保持同步更新。
-
-
浏览器接收到
hash事件后 请求manifest资源文件 确认增量变更范围 -
浏览器加载发生变更的增量模块
-
webpack运行时触发变更模块的
module.hot.accept回调 执行代码变更逻辑 -
done:构建完成,更新变化
总结就是webpack将静态资源托管在 WDS 上,而 WDS 又和浏览器通过webSocket 建立联系,而当webpack监听到文件变化时,就会向浏览器推送更新并携带新的hash与之前的hash进行对比,浏览器接收到hash事件后变化加载变更的增量模块并触发变更模块的 module.hot.accept回调执行变更逻辑。
8、开发环境优化(重点在于提供快速的开发体验和方便的调试功能。)
- 增量构建: 通过
cache: { type: 'systemfile'}|module: {noParse: /jquery|lodash/}减少重复编译模块,开启缓存构建可以加快二次构建的效率
- 通过HMR热更新可以做到局部更新变化,提高开发效率
- 根据设置
devtool,用于配置源映射(source maps),它影响代码在开发者工具中的调试体验和构建速度。不同的devtool配置有不同的性能和功能差异,主要影响调试时的精度、构建速度和最终的输出体积。
- 使用Thread-loader以多进程的方式运行资源加载逻辑,使用时,需将此 loader 放置在其他 loader 之前。
- 通过 stats、webpack bundle analyzer来分析性能做优化
9、生产环境优化(重点在于性能优化)
-
利用CDN:可以使用CDN来加快对静态资源的访问,提高用户的使用体验。适合不太变动的静态资源,例如UI组件库。
- 通过将静态资源(如 HTML、CSS、JavaScript、图片、字体等)分发到全球多个服务器节点,以减少加载时间、降低延迟并减轻源服务器的负担。浏览器在加载页面时会从离用户物理位置最近的服务器获取所需库,而不是从本地服务器加载。这不仅可以加快加载速度,还可以减少本地服务器的资源占用。
- 配置
externals: -
module.exports = { externals: { react: 'React', 'react-dom': 'ReactDOM', }, }; - 配合 HTML 文件直接引入:
-
<script src="https://unpkg.com/react@17/umd/react.production.min.js"></script> <script src="https://unpkg.com/react-dom@17/umd/react-dom.production.min.js"></script>
-
Tree Shaking: 删除没用到的代码(动态导入在某种程度上会影响树摇的效果,因为它们的导入是在运行时发生的,而不是在静态分析阶段确定的。树摇的关键特性是在编译时(静态分析阶段)识别未使用的代码并将其删除,但动态
import的模块加载是在运行时进行的,这意味着编译器无法确定哪些模块会被加载和执行。)- 这是 Webpack 5 的默认功能之一,并且在 Webpack 5 中大大简化了配置,不需要像以前那样显式启用或进行特殊配置。只要将
mode设置为production,Tree Shaking 就会自动启用。Webpack 会自动分析模块依赖并删除未使用的代码。
- 这是 Webpack 5 的默认功能之一,并且在 Webpack 5 中大大简化了配置,不需要像以前那样显式启用或进行特殊配置。只要将
-
使用SplitChunksPlugin插件来进行公共模块抽取/代码分割/拆包优化
- 把更新频率低的代码和内容频繁变动的代码分离,把共用率较高的资源也拆出来,最大限度利用浏览器缓存。
- 减少 http 请求次数的同时避免单个文件太大以免拖垮响应速度,也就是拆包时尽量实现文件个数更少、单个文件体积更小。
- 比如一些第三方插件,更新频率其实很低,单个体积通常又较小,就很适合打包在一个文件里。而 UI 组件库更新少的同时体积却比较大,就可以单独打成一个包(也有直接用 CDN 外链的)。还有程序员自己写的公共组件,一般写完后修改也不多,适合拎出来放一个文件。
-
使用TerserWebpackPlugin代码压缩
-
TerserWebpackPlugin 是一个 Webpack 插件,用于通过 Terser 压缩和优化 JavaScript 代码。Terser 是一个用于 ES6+ 代码的 JavaScript 压缩工具,它基于 UglifyJS,但添加了对现代 JavaScript 特性的支持。TerserWebpackPlugin 利用 Terser 的强大功能,帮助开发者生成更小、更高效的 JavaScript 文件,从而提升网页加载速度和用户体验。
-
UglifyJsPlugin、ParallelUglifyPlugin功能相似,but,这俩只支持ES5 适用Webpack 4,压缩效果都不如TetserWebpackPlugin。
-
9、业务场景
1. 单页应用 (SPA)
通过代码分割、懒加载和按需加载来优化资源加载,减少初始加载的包大小,提升用户体验。
2. 一个项目管理多个单页面(Multi-page App)
企业门户网站的多个页面可以按需打包,每个页面根据访问量和功能差异配置独立的打包策略,避免不必要的打包和冗余。
3. 代码分隔优化
可以引入 splitChunks 插件来实现常见的代码分割策略:将公共模块提取到一个文件中,减少页面间的冗余代码;某些情况下,通过 Webpack 的 async 异步加载模块,首次访问网站时只加载必需资源,减少加载时间,其他部分通过懒加载或用户交互后再加载(例如,用户点击某个按钮后才加载特定模块)。
4. 构建服务端渲染 (SSR)
将客户端代码与服务器端代码分开,通过合理配置 webpack.config.js 实现服务端代码的构建,避免不必要的资源打包,减轻服务端负担。同时通过代码分割与懒加载提高性能。
什么是 SSR?
服务端渲染(Server-Side Rendering, SSR)是指在服务器端生成 HTML 页面,然后将完整的 HTML 页面发送到客户端浏览器显示的一种渲染方式。与客户端渲染(CSR)相比,SSR 在服务器上运行 JavaScript 代码生成页面,而不是依赖浏览器在客户端运行代码生成页面。
SSR 的核心流程
- 请求到达服务器:用户通过浏览器访问一个 URL。
- 服务端生成 HTML:服务器上的代码(比如 React、Vue 等框架的代码)会运行并渲染成 HTML 页面。
- 发送给客户端:生成的 HTML 页面返回给浏览器,浏览器直接显示内容。
- 客户端接管:React 或 Vue 的 JavaScript 文件在加载后会“接管”页面,使其变成一个动态的应用。
5. 热更新(HMR)
在 React 或 Vue 开发过程中,修改代码时,Webpack 能够通过热更新快速反映改动,避免页面重新加载,提升开发效率。
6. 优化开发编译、生产环境打包速度
通过 webpack-dev-server 配合 HMR 提升开发体验,如何通过 TerserPlugin 等工具优化生产环境的打包速度。
7. 优化首屏速度
通过代码分割、懒加载、图片压缩和 CSS 提取,有效优化 Web 应用的首屏加载速度。
总结
从vite跟Webpack的对比切入,介绍了常见的loader、plugins,webpack在开发跟生产环境中如何优化等,希望对大噶有帮助😇😇😇