从 Parcel 到 Vite: 一个 10 万行代码迁移的小故事
我们将三个前端项目从 Parcel 迁移到了 Vite,过程非常顺利。
背景故事
我们在 Logto 有三个主要的前端项目:登录体验、控制台,以及实时预览。这些项目都是基于 TypeScript,React 和 SASS 模块构建的;总共有大约 10 万行代码。
我们喜欢 Parcel 的简单性和零配置开发。我还记得第一次用 Parcel 创建新项目时的震惊,你只需运行 parcel index.html
然后咔一声,所有的依赖自动安装并且项目启动起来了。如果你是一个"经验丰富的"开发者,你可能会和我一样有同样的感受,尤其是和以前使用 Gulp 和 Webpack 的日子相比。Parcel 就像一根魔杖。
Parcel 的简单性是我们长期坚持使用它的主要原因,尽管它有时候有点"任性"。例如:
- Parcel 有时无法打包项目,因为它找不到一些实际上存在的块文件。
- 在我们的 monorepo 设置下,它需要一些“曲折”的配置才能正常工作。
- 它不原生支持 MDX 3,因此我们不得不为其创建自定义转换器。
- 它不支持手动分块(截至本文撰写时,手动分块功能仍处于实验阶段),这在大多数情况下没问题,但有时你会需要它。
那么我们为什么决定迁移到其他东西呢?
- 我们卡在了 Parcel 2.9.3,这个版本于 2023 年 6 月发布。此后每次有新版本发布时,我们都尝试升级,但总是出现构建错误。
- Parcel 的最新版本是 2.12.0,于 2024 年 2 月发布。尽管它几乎每天有提交更新,但从那以后就没有发布新版本。
有人甚至打开了一个讨论,问Parcel 是否已经死了。官方的回答是否定的,Parcel 仍然活着,但它处于一种"我们正在进行大型重构,没有时间发布小版本"的状态。对我们来说,它就像一只"鸭死":我们能用的最新版本已经是一年前的东西了,而且我们不知道下一个版本何时发布。它看起来像是死了,行为像是死了,因此对我们来说它就死了。
为什么选择 Vite?
我们通过 Vitest 认识了 Vite。几个月前,我们对 Jest 的 ESM 支持(在测试中)感到厌倦,想试试点新东西。Vitest 凭借其原生 ESM 支持和 Jest 兼容性赢得了我们的心。它有着出色的开发者体验,而且由 Vite 驱动。
现状
你可能在项目中有不同的设置,但通常你会发现插件替代品,因为 Vite 生态系统正在蓬勃发展。这是我们迁移时的一些配置:
- **Monorepo:**我们使用 PNPM (v9) 工作区管理我们的 Monorepo。
- **模块:**我们所有的项目都使用 ESM 模块。
- **TypeScript:**我们使用 TypeScript (v5.5.3) 进行所有的项目开发,并使用路径别名。
- **React:**我们的所有前端项目都使用 React (v18.3.1)。
- **样式:**我们使用 SASS 模块来进行样式处理。
- **SVG:**我们将 SVG 用作 React 组件。
- **MDX:**我们在 MDX 中支持 GitHub 风格的 Markdown 和 Mermaid。
- **懒加载:**我们需要懒加载某些页面和组件。
- **压缩:**我们为生产版本生成压缩后的资源(gzip 和 brotli)。
迁移过程
我们开始迁移时,通过创建一个新的 Vite 项目并尝试它的工作原理来熟悉它。过程很顺利,实际的迁移只花了几天时间。
开箱即用的支持
Vite 对 Monorepo,ESM,TypeScript,React 和 SASS 有开箱即用的支持。我们只需要安装必要的插件和配置来使其工作。
路径别名
Vite 对路径别名有内置支持,例如,在我们的 tsconfig.json
中:
我们只需要在 vite.config.ts
中添加相同的解析:
需要注意的是,替换路径应该是绝对路径,而它是相对于项目根目录的。或者,你可以使用 vite-tsconfig-paths 插件从 tsconfig.json
读取路径别名。
React 快速刷新和 HMR
尽管 Vite 内置了 HMR 支持,但仍需安装一个插件来启用 React 快速刷新。我们使用了由 Vite 团队提供的 @vitejs/plugin-react 插件,它对 React 功能有很好的支持,如快速刷新:
作为 React 组件的 SVG
我们使用 vite-plugin-svgr 插件将 SVG 转换为 React 组件。只需在 Vite 配置中添加插件:
然而,我们并没有指定在什么条件下 SVG 应该被转换为 React 组件,因此所有的导入都被转换了。插件提供了一个更好的默认配置:仅转换导入了.svg?react
后缀的 SVG。我们相应地更新了导入。
SASS 模块
虽然 Vite 对 SASS 模块提供了开箱即用的支持,但有一点需要注意:类名的格式。这可能会对用户和我们的集成测试造成困扰,如果类名没有一致地格式化。在 vite.config.ts
中的一行配置可以解决这个问题:
顺便说一句,Parcel 和 Vite 在导入 SASS 文件时有不同的做法:
* as
语法虽然在 Vite 中也能用,但当你使用动态键访问 styles
对象时,它会导致模块化类名的丢失。例如:
MDX 支持
由于 Vite 在内部使用的是 Rollup,因此我们可以使用官方的 @mdx-js/rollup 插件来支持 MDX 及其插件。配置如下:
remarkGfm
插件用于支持 GitHub 风格的 Markdown,rehypeMdxCodeProps
插件用于将属性传递到 MDX 文件中的代码块,就像 Docusaurus 做的那样。
MDX 中的 Mermaid 支持
我们希望在 MDX 文件中使用 Mermaid 图表,像其他编程语言一样。用法应与其他代码块一样简单:
应该渲染为:
由于我们的应用程序支持浅色和深色主题,我们编写了一些代码来让 Mermaid 图表在深色主题下也能正常工作。创建了一个 React 组件:
useTheme
是一个自定义 hook,用于从上下文中获取当前主题。mermaid
库异步导入,以减少初次页面加载时的加载大小。
对于 MDX 文件中的代码块,我们有一个统一的组件来处理这些工作:
最后我们定义了 MDX 提供者如下:
懒加载
这不是一个 Vite 特有的事情,但仍值得一提的是我们在迁移过程中更新了页面以使用懒加载,迁移后什么也没有坏掉。
React 内置了 React.lazy
函数用于懒加载组件。然而,当你进行快速迭代时,它可能会引发一些问题。我们制作了一个小库叫做 react-safe-lazy 来解决这些问题。它是 React.lazy
的即插即用替代品,详细解释可以在这个 博客帖子 中找到。
压缩
有一个很棒的插件叫做 vite-plugin-compression 可以生成压缩资产。它支持 gzip 和 brotli 压缩。配置非常简单:
手动分块
Vite(或其底层的 Rollup)的一大亮点是手动分块。虽然 React.lazy
是懒加载组件的使用方法,但我们可以通过指定手动分块来更好地控制分块,决定哪些组件或模块应该打包在一起。
例如,我们可以先使用 vite-bundle-visualizer 来分析包大小和依赖关系。然后我们可以编写一个合适的函数来分离这些块:
开发服务器
与生产构建不同,Vite 不会在开发模式下打包你的源码(包括在同一个 monorepo 中链接的依赖),而是将每个模块都当作一个文件。对我们来说,浏览器第一次加载时可能会加载数百个模块,这看起来很疯狂,但在大多数情况下实际上没什么问题。你可以在 这里 看到相关讨论。
如果你对此有所顾虑,一个不完美但可行的解决方案是将链接的依赖列在 vite.config.ts
的 optimizeDeps
选项中:
这会"预打包"链接的依赖,使开发服务器更快。但是这个方法的缺点是 HMR 可能不会对这些链接依赖如预期那样工作。
另外,我们使用了一个代理,它在生产中提供静态文件并在开发环境中代理到 Vitest 服务器。我们配置了一些特定的端口以避免冲突,这在 vite.config.ts
中也很容易设置:
环境变量
与 Parcel 不同,Vite 使用一种现代化的方法处理环境变量,即使用 import.meta.env
。它会自动加载 .env
文件并替换代码中的变量。然而,它要求所有环境变量都以 VITE_
为前缀(可配置)。
当我们使用 Parcel 时,它只是简单地替换 process.env
变量而不检查前缀。因此,我们提出了一个使用 define
字段的变通方案来使迁移更容易:
这让我们可以逐步将环境变量添加前缀,并删除 define
字段。
结论
就是这些!我们已经成功将三个前端项目从 Parcel 迁移到了 Vite,希望这个小故事能帮助你进行迁移。最终配置如下: