Skip to content

让你的 Vite 应用运行更快一点

make-your-vite-applications-run-a-little-faster

背景

虽然 Vite 默认运行速度很快,但随着项目需求的增长,性能问题可能会悄然出现。这篇文章希望从多个角度出发,介绍不同的优化方式,让你的 Vite 应用运行更快一点,解决服务器启动慢、页面加载慢、构建慢等问题。

浏览器

插件

大部分情况下,我们都在浏览器中查看我们的 Vite 应用并开发调试。某些浏览器插件(如去广告插件 uBlock、adGuard)可能会干涉请求,导致启动和刷新速度降低。在这种情况下,建议使用不含插件、专门用于开发的配置,也建议使用无痕模式获取更快的速度,见 Chrome隐私模式网页加载速度更快

开发者工具

此外,Vite 开发服务器对预打包依赖项进行了强缓存,同时对源代码实现快速的 304 响应,如果在开发者工具打开的情况下禁用缓存,可能会大幅影响启动速度和全页刷新的时间。因此,建议关闭“禁用缓存”功能,以确保启动速度和全页刷新速度。

disable-cache

Vite 配置

插件

Vite 官方插件一直在优化性能,比如 vitejs/vite-plugin-react 就通过动态导入大型依赖来减少 Node.js 的启动时间。

社区插件对于性能可能没有那么关注,进而影响开发者的体验。

举一个例子来说,很多社区文章至今仍然在推荐 vite-plugin-eslint,但截至目前为止,它已经两年没有更新了,对于 Vite 和 ESLint 新版本支持一般。

vite-plugin-eslint-2-years-ago-updated

另一方面,它强制在 buildStarttransform 钩子运行 ESLint 校验,在 buildStart 中校验会导致开发服务器启动期间等待时间过长,延迟可以在浏览器中访问站点的时间,而在 transform 钩子中校验会导致一些文件加载速度比其他文件慢,加载站点时在浏览器中的请求瀑布图就会越明显,在开发时能感受到的卡顿也就会越明显。

尽管达到了 ESLint 校验的目的,但是开发体验下降了,开发速度降低了,值不值得呢?这是一个很值得考量的问题。

有很多种方法可以确认这个性能问题,比如使用 vite --debug plugin-transformvite-plugin-inspect 来检查,也可以运行 vite --profile 后访问站点,在终端中按 p + enter 记录一个 .cpuprofile,接着使用像 speedscope 这样的工具来检查配置文件并识别瓶颈。

确认到性能问题后,可以考虑分叉并改进相应的插件,或者直接使用替代品。针对上面的例子,你可以考虑将它换成 vite-plugin-checker@nabla/vite-plugin-eslint 或者 vite-plugin-eslint2,它们都支持异步的 ESLint 校验,这样就不会特别影响性能和开发体验,同时也能达到 ESLint 校验的目的。

利益相关:我是 vite-plugin-eslint2 的作者,欢迎沟通讨论。

工具链

精简你的工具链是一个提速的好办法。

举一个例子来说,很多人使用 SCSS 主要是为了变量和嵌套,但是实际上 CSS 变量嵌套 都已经正式落地了。

css-variables

css-nesting

如果生产环境要支持稍微老一点、不支持嵌套的版本的浏览器,完全可以在生产构建时使用 PostCSS 来处理嵌套,这样就不会影响 Vite 开发服务器的速度。非常非常古老的版本的浏览器既不安全,也不快速,体验也很差,应该逐步引导淘汰这部分内容以提高用户体验和开发体验,感兴趣可以看看 browser-update

而使用更原生化的工具链也是一个提速的好办法。SWC 官网显示它的速度是 Babel 的 20 到 70 倍,在复杂的实际应用中也有大量的速度优势,这足以证明原生化对提速的帮助重大。你可以使用 @vitejs/plugin-react-swc 来替代 vite-plugin-react,用 LightningCSS 来替代 PostCSS,用 SWCesbuild 来替代 Babel,等等等等,以此来实现更好的性能。

swc-speed

lightningcss-speed

依赖预构建

Vite 会将以 CommonJS 或 UMD 形式提供的依赖项转换为 ES 模块,还将具有许多内部模块的 ESM 依赖项转换为单个模块,这就是 Vite 的依赖预构建。

前者是出于兼容性考虑,我们这里不做详细讨论。而后者是出于性能考虑,如果没有这一步,当我们执行 import { debounce } from 'lodash-es' 时,浏览器会同时发出 600 多个 HTTP 请求,对应 lodash-es 600 多个模块!即使服务器能够轻松处理它们,但大量请求会导致浏览器端的网络拥塞,使页面加载变得明显缓慢。

这也就是为什么我们需要依赖预构建。Vite 本身会自动、透明地进行这一步,我们也可以通过配置来明确指定需要预构建的依赖。

ts
// vite.config.ts
import { defineConfig } from 'vite';
import pkg from './package.json';

export default defineConfig({
  optimizeDeps: {
    include: Object.keys(pkg.dependencies),
  },
});

预热

预热是常常被忽视的优化,我在一些介绍 Vite 的文章里也几乎没有看见,但它却能有效地提升开发体验,减少等待时间。

默认地,Vite 开发服务器只按需转换浏览器请求的文件,这使得它能够快速启动,并且只对使用的文件执行转换。

然而这种做法可能让 Vite 开发服务器闲置,导致切换页面加载需要等待,导致开发体验不佳。如果预计某些文件将被短时间内请求,可以提前转换和缓存,提高页面加载速度。这种做法就是预热。

可以通过运行 vite --debug transform 并检查日志来找到频繁使用的文件,然后把它添加到 Vite 配置里面。

sh
vite:transform 28.72ms /@vite/client +1ms
vite:transform 62.95ms /src/components/BigComponent.vue +1ms
vite:transform 102.54ms /src/utils/big-utils.js +1ms
ts
// vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
  server: {
    warmup: {
      clientFiles: [
        './src/components/BigComponent.vue',
        './src/utils/big-utils.js',
      ],
    },
  },
});

需要注意的是,只应该预热频繁使用的文件,这是为了避免过载 Vite 开发服务器。但我还是喜欢直接全部预热,主打一个反骨。🤪

Nuxt 最新版本已经配置了该功能,你也可以查看 Vite / 配置 / server.warmup 以获取更多信息。

对于 4.3 <= Vite < 5,需要使用 vite-plugin-warmup,更低版本没有相关支持。

项目

路径解析

大家可能都难以预料解析导入路径操作的昂贵程度。

当你尝试使用 import './Component' 导入 ./Component.jsx 时,Vite 将运行以下步骤来解析:

  • 检查 ./Component 是否存在,不存在。
  • 检查 ./Component.mjs 是否存在,不存在。
  • 检查 ./Component.js 是否存在,不存在。
  • 检查 ./Component.mts 是否存在,不存在。
  • 检查 ./Component.ts 是否存在,不存在。
  • 检查 ./Component.jsx 是否存在,存在!

解析一个简单的导入路径就进行了 6 次文件系统检查!隐式导入越多,解析路径所需的时间就越多。

因此,通常最好明确导入路径,比如 import './Component.jsx',你也可以缩小 resolve.extensions 的列表以减少一般的文件系统检查,但必须确保它也适用于 node_modules 中的文件。

如果想要使用 ESLint 来强制明确导入路径,可以配置 import/extensionsimport-x/extensions 规则,下面是一个示意配置。

txt
"import/extensions": ["warn", "ignorePackages"]
"import-x/extensions": ["warn", "ignorePackages"]

桶文件

桶文件(barrel files)是重新导出同一目录下其他文件 API 的文件。比如:

js
// src/utils/index.js
export * from './color.js';
export * from './dom.js';
export * from './slash.js';

当你只导入一个单独的 API,例如 import { slash } from './utils',仍然需要获取和转换桶文件中的所有文件,因为它们可能包含 slash API,也可能包含在初始化时运行的其他副作用。这意味着在初始页面加载时,你加载的文件比所需的要更多,进而导致页面加载速度变慢。当模块越来越多时,加载时间也越来越多,测试表明加载时间随模块数量的增加呈现出接近指数级或类指数级的增长。

js-tools-module-cost

如果可能的话,你应该尽量避免使用桶文件,直接导入单独的 API。你可以阅读 Speeding up the JavaScript ecosystem - The barrel file debacle 了解更多细节与数据,上面的图片正是来源于这篇文章。

总结

本文参考了 Vite 官方文档 / 性能 写成,在此对 Vite 团队表示由衷的感谢。🙏

本文从浏览器、Vite 配置以及项目三个方面出发,介绍了不同的优化方式,目的就是让 Vite 应用运行更快一点,解决服务器启动慢、页面加载慢、构建慢等问题。

但必须注意,这些方法不是万能的,它们不会影响生产环境下的性能。要优化生产环境性能,你需要考虑分割代码、优化静态资源、引入 SSR、引入 CDN、预加载、预取、优化 CSS、缓存、Web Worker、减少 DOM 操作等等,这些就不在这里详细展开了,感兴趣可以留言叫我写一写。

值得一提的是,Vite 也不是万能的,正如 Farm 文档 中所说,开发和生产之间的不一致、拆包优化困难等等问题仍然需要解决,这一切都需要我们耐心等待,或是积极参与其中。如果实在等不及了,推荐看看 Farm 或者 Rsbuild 吧。

希望本文能带给你一点启发和帮助!

欢迎关注我的 个人站、我的公众号 程序员想退休、我的掘金号 狗鸽 和我的知乎号 狗鸽,获取更多分享。

Released under the MIT License.