兼容 Vite 插件!新版打包算法!Rust Web 构建引擎 Farm v0.13 版本发布

Farm 是一个基于 Rust 语言编写的构建引擎,如果还不了解 Farm 可以参考 :比 Vite 快 5 倍? 2ms HMR?Farm:基于 Rust 的极速构建引擎!

仓库地址:GitHub - farm-fe/farm

从 Farm 第一次发布 v0.3 版本(2023 年 3 月 6 日),已经过去半年多的时间,期间 Farm 一直在持续开发,并且得到了很多社区开发者的贡献和支持,目前 Farm 已经实现了构建引擎需要的所有能力(包括生产环境优化如 tree shake,压缩,拆包,语法降级和 polyfill 等),基本达到生产环境可用的状态。

如果希望体验或者接入 Farm,可以参考官方文档:Farm Documentation | Farm

本次 v0.13 版本 Farm 更新了两个重磅功能:

  1. Vite/Rollup 插件兼容:Vite 插件可以直接在 Farm 中使用,目前 @vitejs/plugin-vue,vite-plugin-solid 等等插件都可以直接在 Farm 中使用!并且在完全使用 Vite 插件的情况下,Farm 依然比 Vite 快数倍。
  2. 新版局部打包算法: 实现了一套全新的打包算法,可以自动根据依赖关系、大小等因素,将每次资源加载需要的模块打包成 25 个左右的文件,实现确保最优加载性能的同时,进一步提升缓存命中率。

对于新版打包算法的技术细节,可以参考 RFC-003 Partial Bundling

一、Vite/Rollup 插件兼容

从 V0.13 版本开始,Farm 计划在 JS 侧直接复用 Vite/Rollup 的现有生态,对于现有功能 JS 插件直接使用 Vite 即可,Farm 后续将专注 Rust 化,即使用 Rust 语言重写常用工具或者开发新工具,而不在 JS 生态上再重复开发。

1.1 兼容 Vite 插件后,Farm 还有何优势?

一些开发者可能会提出疑问,既然已经兼容 Vite 生态,为何不直接使用 Vite?用 Farm 有什么优势呢?这个问题涉及到 Vite 和 Farm 基本理念,在 比 Vite 快 5 倍? 2ms HMR?Farm:基于 Rust 的极速构建引擎! 中有提及。

简单来说,笔者认为,Vite 目前并不完美,存在以下几个问题:

  1. 开发环境的巨量请求:对于一个较大型的项目,开发环境的首屏请求数量可能达到数千,冷启动刷新一次页面需要数十秒,热启动后刷新也需要 10s,极大影响开发体验
  2. 开发环境和生产的不一致:Vite 的开发环境和生产环境存在很大的不一致,从机制到底层使用的工具链都完全不同,比较割裂,一旦出现开发生产不一致问题,排查起来非常痛苦
  3. 拆包算法不够灵活:Vite 目前复用的 rollup 的拆包算法,而 rollup 的拆包算法配置起来非常不灵活,比如希望限制产物最小大小、最大大小、控制并发请求数量等实现起来非常困难。

而 Farm 可以完美解决上述问题,Farm 在开发生产采用完全一致的策略,同时有一套非常灵活且细粒度的打包算法。并且,在 Farm 中使用 Vite 插件后,依然比 Vite 数倍!

我们迁移了一个后台 Vite 模板项目(github.com/farm-fe/far...)到 Farm,在几乎完全复用 Vite 插件的基础上,Farm 仍然比 Vite 在开发环境快 2 倍,生产环境快 6 倍!性能对比如下:

Dev 冷启动(Server 启动 + 页面渲染) 生产构建
Farm 3.5s 4s
Vite 7.5s 24.1s
对比 Farm 快 2 倍 Farm is 快 6 倍

1.2 为什么选择兼容 Vite 生态?

Farm 选择兼容 Vite 生态有如下几点原因:

  1. 上面已经提到,我们计划减少 Farm 在 JS 生态上的持续投入,尽可能复用现有 JS 生态,从而提升后续 Farm 在 Rust 化上面的投入
  2. Vite 生态当前最活跃,新技术、框架都支持 Vite,兼容 Vite 也有助于 Farm 生态的长远发展
  3. Farm 采用了一套和 Vite/Rollup 类似的插件系统,兼容起来成本比较低

1.3 如何在 Farm 中使用 Vite 插件

首先安装 Vite 插件到项目中,以 plugin vue 为例:

bash 复制代码
pnpm add -D @vitejs/plugin-vue vite

然后在 farm.config.ts 中配置即可:

ts 复制代码
import type { UserConfig } from '@farmfe/core';
import vue from '@vitejs/plugin-vue';

function defineConfig(config: UserConfig) {
  return config;
}

export default defineConfig({
  vitePlugins: [vue()]
});

添加插件后,Vue SFC 就可以直接在 Farm 项目中使用了!

目前大部分常用的 vite transformer 插件已经能直接在 Farm 中使用了。不过目前版本(v0.13) ,还只支持 build stage 的钩子,后续 Farm 会兼容 generate stage 的钩子。 如果遇到不兼容的 Vite 插件,Farm 会报错提示。

二、局部打包算法

局部打包即 Partial Bundling 。局部打包的技术细节暂不在此文讨论,后续单独分析,感兴趣可以参考:RFC-003 Partial Bundling

2.1 什么是局部打包?

Partial Bundling 是 Farm 用于打包模块的策略,与其他 bundler 的做法类似,但 Farm 的 Partial Bundling 的理念有所不同。

与其他 bundler 不同,Farm 不会尝试将所有内容打包在一起,然后使用 splitChunks 等优化将它们分开,相反,Farm 会将项目直接打包到多个输出文件中。 例如,如果启动一个 html 页面需要数百个模块,Farm 将尝试直接将它们打包到 20-30 个输出文件中。 Farm 将这种行为称为 Partial Bundling

Farm Partial Bundling 的目标是:

  1. 减少请求数量和请求层次:使数百或数千个模块请求减少到20-30个请求,并避免由于依赖层次而逐个加载模块,这将使资源加载更快。
  2. 提高缓存命中率:当模块更改时,确保只有少数输出文件受到影响,对于线上项目尽可能复用更多缓存。

对于传统的 bundler,我们可能很难配置复杂的splitChunksmanualChunks来同时实现上述两个目标,但在 Farm 中,通过Partial Bundling原生支持。

2.2 局部打包规则

在本节中,我们将通过示例介绍Partial Bundling使用的基本规则。

首先我们研究一个基本的 React 项目示例。 对于像下面这样的基本 react 项目,我们在入口脚本中导入 react 和 react-dom:

tsx 复制代码
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.scss';

const container = document.querySelector('#root');
const root = createRoot(container);

root.render(
 <>
 <div>Index page</div>
 </>
);

打包结果将如下所示:

bash 复制代码
./dist/
├── index_9c07.49b83356.js    # 包含react-dom
├── index_a35f.0ac21082.js    # 包含./index.tsx
├── index_b7e0.7ab9ca2d.js    # 包含react及其依赖项
├── index_ce26.7f833381.css   # 包含./index.scss
└── index.html                # 包含./index.html

默认情况下,Farm 会将项目打包为 5 个产物文件:

  • 2 个 js 文件来自 node_modules,包含 react、react-dom 及其依赖项。
  • 1 个js文件来自./index.tsx
  • 1 个 css 文件来自./index.scss;
  • 1个html文件来自./index.html;

以上结果来源于 Farm 使用的下述规则:

  1. 可变和不可变模块应始终位于不同的输出文件中:默认情况下,Farm 认为 node_modules 下的所有模块都是不可变的,否则是可变的。 所以 ./index.tsx 位于一个单独的文件中,因为它是一个可变模块,所以它永远不会与 react 和 react-dom 位于同一个输出文件中。
  2. 不同类型的模块始终位于不同的输出文件中:因此 ./index.scss 位于单独的文件中。
  3. 同一包中的模块应该位于同一个输出文件中:因此所有react模块始终位于同一个输出文件中,react-dom也是如此。
  4. 默认情况下,资源加载的目标并发请求数应在 20-30 之间:因此有 3 个 js 输出文件,而不是 1 个 js 包。
  5. 输出文件大小应相似,默认最小资源大小应大于20KB:因为react-dom最大,超过100KB,所以它在一个单独的文件中,而 react 的依赖项小于20KB,被合并到同一个输出文件中。

因此通过配置 可变不可变请求数量资源大小 等因素,就可以非常灵活的控制 Farm 的默认打包行为。同时,也可以通过 gourps 等配置来指定打包行为。

2.3 局部打包配置

局部打包支持大量的配置项,详情请参考文档:Partial Bundling | Farm,本文仅仅简要讨论常用配置的用法。

通过 groups 配置可以实现将指定模块打包到一起,示例配置如下:

ts 复制代码
export default defineConfig({
  compilation: {
    partialBundling: {
      groups: [
        {
          name: 'vendor-react',
          test: ['node_modules/react', 'node_modules/react-dom'],
        }
      ]
    },
  },
});

上述配置将 react 和 react-dom 分组到一起,在输出产物时,他们会尽可能在同一个产物文件中。不过请注意,groups并不强制打包所有与该组匹配的模块,一个group会生成多个产物文件,因为以下规则的限制:

  1. 可变和不可变模块始终位于不同的输出文件中。 当可变模块和不可变模块都命中这个组时,它们将处于不同的输出文件中。
  2. 当涉及多页面应用程序或 dynamic import 时,可能存在共享模块,这些入口不同的共享模块会始终位于不同的输出文件中。

如果需要强制打包指定的模块到一个文件中,可以使用 enforceResources 配置,详情可以参考文档。

三、总结

Farm 经过近一年紧锣密鼓的开发,以及众多社区开发者的贡献和努力,已经距离 1.0 版本越来越近了,当前也已经基本达到生产可用的状态,并且有真实的线上项目在使用 Farm!我们计划在 2023 年底前发布 1.0 版本,目前距离 1.0 版本,还缺少如下两个特性:

  • 持久缓存:缓存构建产物到磁盘,并在多次构建中复用,进一步提速项目冷启动
  • 90% 的 Vite 插件兼容:兼容 generate stage 钩子,90% 的 Vite 插件都能无缝接入 Farm

在此也再次感谢众多社区贡献者对 Farm 的贡献和投入,也欢迎更多贡献者加入 Farm 兴趣团队。我们的目标是做更好的工具,提供更加优质的开发体验!

相关推荐
轻口味31 分钟前
【每日学点鸿蒙知识】AVCodec、SmartPerf工具、web组件加载、监听键盘的显示隐藏、Asset Store Kit
前端·华为·harmonyos
alikami34 分钟前
【若依】用 post 请求传 json 格式的数据下载文件
前端·javascript·json
吃杠碰小鸡1 小时前
lodash常用函数
前端·javascript
vvw&1 小时前
如何在 Ubuntu 22.04 上安装 Ansible 教程
linux·运维·服务器·ubuntu·开源·ansible·devops
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
泰伦闲鱼1 小时前
nestjs:GET REQUEST 缓存问题
服务器·前端·缓存·node.js·nestjs
m0_748250031 小时前
Web 第一次作业 初探html 使用VSCode工具开发
前端·html
一个处女座的程序猿O(∩_∩)O2 小时前
vue3 如何使用 mounted
前端·javascript·vue.js
m0_748235952 小时前
web复习(三)
前端
AiFlutter2 小时前
Flutter-底部分享弹窗(showModalBottomSheet)
java·前端·flutter