工程化
Vite 和 Webpack 的区别?
前端常见打包工具:webpack,rollup,parcel,esbuild 等,它们都能对代码进行压缩、合并、转换、分割和打包。 vite 是新一代的前端构建工具,开发环境下使用 esbuild 进行预构建,生产环境使用 rollup 进行打包,充分利用了现代浏览器的特性,例如 ES Module 和 http2,vite 更像是 webpack 和 webpack-dev-server 的结合物。
从两者的使用成本来看,webpack 有更重的心智负担,它需要配置比较多的东西,例如跨域、代码压缩、代码分割、css 预处理、样式兼容、压缩、热更新、ts 转换等等,并且需要了解各种 loader 和 plugin 的使用,并且根据具体的使用场景进行优化调整。而 vite 则对常用的功能都做了内置,可以很轻松地配置,降低了学习成本和提高了开发体验。
为什么 Vite 比 Webpack 快?
- 原理: vite 是 no bundle ,webpack 是 bundle。webpack 在开发环境启动项目时会对所有模块进行全量编译和构建,然后生成一个 bundle.js 文件交给浏览器使用,而 vite 在开发环境启动项目时不会进行全量编译和构建,而是利用 ES Module 的特性,使用 esbuild 进行预构建,将所有模块都转换为 ES Module,在浏览器加载某个模块时拦截请求后按需编译,最后响应给浏览器。这样带来的优势就是项目的冷启动比 webpack 更快。
- 构建方式:webpack 是基于 Nodejs 运行的,而 js 是单线程的,无法充分利用多核 CPU 的优势。vite 的预构建和按需编译是使用 esbuild,而 esbuild 是使用 go 开发的,可以充分利用多核 CPU 的优势,因此开发环境下预构建和按需编译的速度是非常快的。
- http2: vite 充分利用了 http2 的并发优势。使用 http1 时,浏览器对同一个域名的请求是有并发限制的,一般为 6 个,如果超出 6 个就会出现阻塞,而 http2 则没有并发限制,这时候将打包产物分为多个小模块并行去加载时速度会更快。
- 热更新机制: webpack 每次修改文件后都会对整个项目进行重新打包,虽然它实现了缓存机制,但是在项目体量达到一个量级后依旧无法解决构建速度慢的问题。vite 项目中,监听到文件变化后会使用 webscoket 通知浏览器重新发起请求,vite 只对发生变化的模块进行编译构建然后响应,并且基于 ES Module 的特性,vite 利用浏览器的缓存策略,针对源码模块做了协商缓存,而对依赖模块(第三方库)做了强缓存,这使得热更新的速度更加快了。
Vite 为什么在生产环境下使用 rollup 打包而不是 esbuild ?
rollup 是一款 ES Module 打包器,它相比于 Webpack 更加小巧,并且打包生成的文件更小,而 vite 正是基于 ES Module 特性实现的,使用 rollup 更加合适。尽管 esbuild 的打包速度比 rollup 快,但是 vite 的插件 API 与使用 esbuild 作为打包器不兼容,而且 rollup 插件 API 和基础设施更加完善,使用 rollup 作为生产环境打包器更加稳定。这也导致了某些情况下使用 vite 时开发环境和生产环境打包结果的不一致。
Vite 做过哪些优化?
gzip 分包 打包体积分析
- rollup-plugin-visualizer 分析打包体积。
- vite-plugin-restart 监听 vite.config.js 和.env.development 文件的变化自动重启 vite。
- vite-plugin-compression 使用 gzip 或者 brotil 来压缩文件。
- vite-plugin-minipic 压缩图片。
- rollup-plugin-external-globals 启用 CDN 加速。
Vite 的热更新怎么实现的?和 Webpack 有没有区别?
TIP
HMR (Hot Module Replacement),也就是“热更新”。
vite 热更新基于 ESM HMR 规范,通过创建 websokect 建立开发服务器于客户端的通信,并使用 chokidar 监听项目文件的变化,在文件发生变化时通知客户端,客户端触发相应的 API 实现模块的更新。热更新的方法和属性都被定义在浏览器的内置对象 import.meta 上。vite 和 webpack 的热更新实现原理和方式类似,webpack 借助 webpack-dev-server 实现开发模式下的热更新。
实现原理:
- 项目启动时 vite 会创建模块依赖图来描述各个模块之间的关系,同时使用 websocket 建立开发服务器和客户端的通信。
- 同时通过 chokidar 监听各个模块的变化。
- 监听到变化后收集发生变化的模块信息通知客户端,客户端执收到信息后行相应的模块更新操作。
Webpack 打包时 hash 是如何生成的?
webpack 中有三种 hash 值,主要用于缓存和版本管理:
hash
:与项目整体构建有关,只要项目中任意一个文件发生了修改,整个项目的 hash 值就会更改。chunkhash
:与打包构建的 chunk 有关,不同的 entry 会生成不同的 chunkhash 值,每个 chunk 的修改自会影响到自身的 hash 值。contenthash
:与具体文件相关的 hash,根据文件内容计算出的 hash 值,如果文件内容发生变化,那么该文件的 hash 值就会改变。
生成方式: hash
和chunkhash
都是通过某种 hash 算法(默认为 md5)来对文件名或者 chunk 数据进行编码,而contenthash
一般是构建时通过 webpack 插件来生成,只针对特定的文件进行编码。
Webpack 一般有哪些优化手段?
Webpack 的优化可以分为两个方面,分别是构建时间优化和构建体积优化,现代前端项目的打包工具主要都是关注这两点。
构建时间优化: 尽量缩小项目代码的构建时间,这在大型项目中是非常必要的,优化手段如下:
缩小 loader 的匹配范围,可以避免对不必要的文件进行搜索,提高搜索效率,以此加速构建。
合理使用文件后缀自动匹配。在文件中如果导入语句中没有显示带有文件后缀时,webpack 会根据配置依次查找,自动补全,通过合理设置后缀匹配项的数量和顺序(使用频率高的放在前面)可以减少查找匹配的时间。
设置路径别名,减少引用文件的路径复杂度。
开启缓存,这将大大提升二次构建的速度。
并行构建。运行在 Node 环境中的 webpack 是单线程的,可以的使用
thread-loader
开启多线程打包。(只针对耗时 loader 开启使用,如果项目文件不多就不必使用,开启多线程也存在性能损耗)定向查到第三方模块。通过设置
modules
项指定 webpack 去哪寻找第三方模块。
构建体积优化: 主要针对 JavaScript 文件、CSS 文件、HTML 文件、静态资源等进行压缩处理。
使用
terser-webpack-plugin
插件压缩 JavasSript 文件。使用
css-minimizer-webpack-plugin
插件压缩 CSS。使用
html-webpack-plugin
插件压缩 HTML。使用
image-webpack-loader
压缩图片。使用按需加载,针对懒加载路由单独打包到一个 chunk 中,在页面路由跳转时动态引入。
代码分割。将不变的第三方库和公共模块打包到一个 chunk 中,这样在多个页面使用到它们时会优先采用浏览器缓存而不必重复加载。
使用
tree shaking
剔除冗余代码,生产模式下已默认开启。使用
compression-webpack-plugin
开启 GZIP 进一步压缩资源,然后部署在服务器,用户请求需要设置请求头acceot-encoding:gzip
,服务器响应时设置响应头content-encoding:gzip
。作用域提升(scope hoisting)可以让 webpack 打包出来的文件体积更小。开启后构建的代码会按照引入顺序放在一个函数作用域中,适当重命名以防止命名冲突,从而减少函数声明和内存开销。该功能需要分析某块之间的关系,所以只支持 ESM 语法。webpack 内置了该功能,只需要在 plugins 里面开启或者直接开启生产环境即可使用。
全局样式命名冲突和样式覆盖问题怎么解决
使用命名空间:给样式名添加前缀,以确保每个组件的样式类名不一样。例如
.componetA-button
,.componentB-button
等。使用BEM命名规范,这是一种常用的命名规范,将样式名分为块、元素、修饰符三个部分,以确保唯一性和可读性。例如
.button
代表一个块,.button_icon
代表一个元素,.button_icon--disabled
代表一个修改符。使用CSS预处理器,例如Less/SASS等,它们都提供了变量、嵌套规则和模块化等功能,可以便于管理和避免命名冲突。
使用CSS Module,它提供了在组件级别上限定样式作用域的能力,可以避免全局样式冲突。
使用css-in-js,通过将CSS样式写入JavaScript代码中与组件绑定来避免全局样式冲突。