- 前端构建
- 构建工具
- 开发工具
【免费下载链接】farm
Extremely fast Vite-compatible web build tool written in Rust
本文以 rust-plugins/react/CHANGELOG.md 为主线,完整梳理 farm 构建工具中官方 React 插件(npm 包名@farmfe/plugin-react)从 0.x 到 2.0.0-beta.1 的全部版本演进:JSX 转换与 React Fast Refresh 如何生效、各配置项的默认值与底层行为、以及 Rust 插件跨平台构建与发布流水线在多份 changelog 记录背后对应到的真实源码改动。读完后你可以准确判断某个 bug 修复发生在哪个版本、插件的refresh/useAbsolutePath等选项在源码中如何被解析,并能复用仓库中的构建脚本自行编译或扩展该 Rust 插件。
插件定位:一个基于 napi-rs 的 Rust 原生插件
@farmfe/plugin-react是 farm 官方用于支持React JSX/TSX 编译与React Refresh(热更新保留组件状态)的插件。与纯 JS 插件不同,它是一个 Rust 编写的原生插件,通过 napi-rs 编译为各平台的动态库(index.farm)后加载进 Farm 编译器进程。其源码位于 rust-plugins/react/,核心结构如下:
- rust-plugins/react/src/lib.rs:插件主体
FarmPluginReact,实现Plugintrait 的resolve、load、process_module、analyze_deps四个钩子; - rust-plugins/react/src/react_refresh.rs:React Fast Refresh 代码注入逻辑;
- rust-plugins/react/src/is_react_refresh_boundary.ts:虚拟模块,负责刷新边界校验;
- rust-plugins/react/scripts/:发布给 npm 的 JS 入口,负责按平台/架构解析原生二进制;
- rust-plugins/react/npm/:9 个平台子包(darwin/linux/win32 × x64/arm64/ia32,linux 再分 gnu/musl),每个目录对应一个独立的 npm 二进制包,例如 rust-plugins/react/npm/linux-x64-gnu/README.md 声明其为x86_64-unknown-linux-gnu二进制。
Rust crate 定义中 crate 名为farmfe_plugin_react,crate-type = ["cdylib", "rlib"]:cdylib产出平台动态库供 Farm 加载,rlib则供 dev-dependencies 中的 farmfe_testing_helpers 做集成测试。
npm 包描述(当前版本2.0.0-beta.1)中的napi.targets明确列出了 9 个构建目标:
"napi": { "binaryName": "farm-plugin-react", "targets": [ "x86_64-unknown-linux-gnu", "x86_64-pc-windows-msvc", "x86_64-apple-darwin", "aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "aarch64-unknown-linux-musl", "x86_64-unknown-linux-musl", "i686-pc-windows-msvc", "aarch64-pc-windows-msvc" ] }这正是 changelog 早期多条记录("support linux-x64-musl"、"downgrade glibc and support win32-ia32 and win32-arm64"、"Fix arm64 arch resolving" 等)的落点——每一次新平台支持,本质上都对应targets列表与 scripts/index.js 中一段switch (platform) / switch (arch)分支的增加。
版本演进全记录:CHANGELOG 逐段解读
CHANGELOG 采用 changesets 格式(Major/Minor/Patch Changes+ commit 短哈希)。下面按时间由近到远逐段解读,并尽量把每条记录映射到当前仓库中可验证的源码位置。
2.0.0-beta.1:Rust 插件构建工具链兼容性修复
这是该 changelog 中信息量最大的一条 Patch 记录,涉及 farm 所有 Rust 插件的构建脚本与 packages/plugin-tools 的联动改造:
在 plugin-tools 中为
@napi-rs/cli增加--cargo-name向后兼容。对应源码 packages/plugin-tools/src/index.ts 第 68 行:removeArg(args, '--cargo-name')——即在把所有参数透传给 napi-rs CLI 之前,先把旧版参数--cargo-name(含--cargo-name=xxx形式,见该文件removeArg实现第 25–46 行)从参数表中剥离,因为新版@napi-rs/cli已不再支持该参数。Rust 插件构建脚本改用
-p <cargo-package>指定包。当前 rust-plugins/react/package.json 中即为:"scripts": { "build": "farm-plugin-tools build --platform -p farmfe_plugin_react --release", "build:publish": "cross-env CARGO_PROFILE_RELEASE_LTO=fat CARGO_PROFILE_RELEASE_STRIP=symbols CARGO_PROFILE_RELEASE_PANIC=abort CARGO_PROFILE_RELEASE_OPT_LEVEL=z farm-plugin-tools build --platform -p farmfe_plugin_react --release", "prepublishOnly": "farm-plugin-tools prepublish" }修复
@farmfe/plugin-react的build:publish脚本指向正确的 Cargo 包:-p farmfe_plugin_react即 Cargo.toml 中声明的name = "farmfe_plugin_react",避免在多 crate 的 workspace 里误构建其他包。同步更新 create-farm-plugin 的 Rust 模板与文档示例,使新创建的插件默认生成修正后的构建命令(模板仓库位于 crates/create-farm-rs/templates/)。
2.0.0-beta.0 与各 nightly 版本
2.0.0-beta.0:Major,"Release Farm v2.0.0",随 farm 核心 v2 整体发版;2.0.0-nightly-20250827162746("Chore: release v2 nightly")、2.0.0-nightly-20250411100807("bump nightly version")、2.0.0-nightly-20241022124925("update nightly tag"):nightly 渠道的版本占位记录,说明该插件跟随 core 的 nightly 标签走自动发版。
1.2.x 系列:HMR 修复与 useAbsolutePath 选项
这一小段是 changelog 中与用户行为最直接相关的部分:
- 1.2.6 —
fix: match swc priority。可验证的落地点是 src/lib.rs 中fn priority(&self) -> i32 { 99 }:插件通过声明较高的 priority,确保其process_module(JSX 转换)在 SWC 相关插件之间处于预期顺序。该记录表明 1.2.6 之前插件的 priority 与 swc 侧的约定不匹配,1.2.6 将其对齐。 - 1.2.4 — 两条修复:
Fix(plugin-react) useAbsolutePath is panic:useAbsolutePath选项(1.2.3 引入,见下)在异常路径下会 panic;fix class component hmr:类组件无法参与 React Refresh 的问题。结合 rust-plugins/react/src/react_refresh.rs 的注入逻辑(见下文"Fast Refresh 注入"一节),refresh 注册依赖registerExportsForReactRefresh对module.exports逐键检测isLikelyComponentType,函数组件与类组件均可识别;1.2.2 与 1.2.4 连续两次修复说明类组件刷新在模块合并(module concatenation)场景下经历了迭代。
- 1.2.3 —
fix #1755 and support useAbsolutePath for plugin-react。useAbsolutePath允许用户显式指定是否使用绝对路径作为 swc 的文件名标识,当前源码支持如下:src/lib.rs 中,true时通过param.module_id.resolved_path_with_query(&context.config.root)生成绝对路径用于create_swc_source_map,否则用原始 module id;默认值为false(react_options.use_absolute_path.unwrap_or(false))。仓库中也有真实使用示例:examples/react/farm.config.js 中plugins: [react({ useAbsolutePath: true })]。 - 1.2.2 —
fix class component cannot be hot-updated:与 1.2.4 的fix class component hmr呼应,类组件热更新问题跨两个版本修复。 - 1.2.1 —
Optimize dynamic resources map size and fix minify.moduleDecls cyclic dependencies issues:该条是 core 侧对资源表与minify.moduleDecls的处理优化,插件随版本 bump 跟进(此类 "Bump version cause core changed" 的模式贯穿整个 changelog,原因是 Rust 插件与farmfe_core的 ABI 必须严格对齐)。
1.2.0:Rust 插件与 JS 插件用法归一化
1.2.0包含 Minor 记录Bump version cause core changed与 Patch 记录feat: normalize usage of rust plugins and js plugins。归一化前后的典型差异可以从仓库两个示例看出:
- 字符串式(归一后兼容的旧写法):examples/react-ssr/farm.config.mjs 中直接
plugins: ['@farmfe/plugin-react', '@farmfe/plugin-sass']; - 函数式:examples/react/farm.config.js 中
react({ useAbsolutePath: true })。
函数式写法之所以能拿到配置,得益于 npm 包入口 scripts/func.js 的极简实现——export default (options)=>[binPath, options]:调用react(options)返回一个二元组,第一个元素是解析出的平台原生二进制路径,第二个元素是原样透传给 Rust 侧的 options 对象(以 JSON 序列化后由SwcTransformReactOptions反序列化)。
1.1.0 / 1.0.x:jsx runtime 默认值与 minify 粒度
- 1.1.0 —
assign 'runtime' default value is 'automatic'。即未显式配置runtime时,JSX 编译默认采用 automatic runtime(import { jsx } from 'react/jsx-runtime')而非 classic runtime(React.createElement),与 scripts/index.d.ts 中runtime?: "automatic" | "classic"的声明一致,并可通过importSource自定义jsx/jsxs工厂的导入来源。 - 1.0.1 —
minify modules instead of resource pots:farm 在 1.0 时代引入了 resource pot(资源桶)概念,压缩粒度从"整个 pot"细化到"模块",这直接影响了插件产物在最终压缩阶段的处理边界,属于 core 能力升级的被动跟进。 - 1.0.0 / 1.0.0-beta.1 / 1.0.0-beta.0:1.0 正式发布线;
56dbe2c0: Fix prepublishOnly script对应 package.json 中至今保留的"prepublishOnly": "farm-plugin-tools prepublish"——发布主包前先把各平台二进制子包发出去。
0.x 系列:从"从 core 中拆出"到多平台全覆盖
changelog 前半段(以及文末按 Keep a Changelog 格式维护的 0.0.1 汇总)记录了插件的诞生与早期演进,关键节点:
| 版本 | 记录 | 含义与可验证依据 |
|---|---|---|
| 0.1.0 | a5364b5: Extract plugin react into a single plugin | React 支持从 core 中拆出为独立插件,即当前rust-plugins/react/目录的由来 |
| 0.1.2 | Republish @farmfe/plugin-react as it misses optionalDependencies field | 平台二进制包以 optionalDependencies 形式按需安装(见下文"二进制解析策略") |
| 0.1.3 | Do not resolve browser when targetEnv is node | 与 src/lib.rs 中"非 browser target 禁用 refresh"同属 target env 适配逻辑 |
| 0.1.5 / 0.3.3 | Fix that dev code presents in prod/fix that production build contains react dev code #618 | 生产构建曾泄漏 React dev 代码,跨版本两次修复;当前实现中swc_transform_react接收mode: context.config.mode(src/lib.rs),由 mode 决定 dev 行为(如development调试 props) |
| 0.1.10 | Fix that css HMR will always reload the whole page | CSS HMR 误整页刷新的修复 |
| 0.2.0 | d604b5e: Support React SSR | React SSR 支持,示例见 examples/react-ssr/(server 端farm.config.mjs+ 中间件渲染 HTML 模板) |
| 0.3.0 | 56f235c: Upgrade swc crates and support emotion | 升级 swc 并支持 emotion(仓库中亦有 examples/emotion/ 示例) |
| 0.3.2 | Fix that vite plugin is not compatible with Farm's lazy compilation | 与 Farm 懒编译(lazy compilation)的兼容修复 |
| 0.4.0 | Support persistent cache and incremental building;Refactor render pot renders and optimize sourcemap generation | 持久缓存与增量构建;渲染产物重构 |
| 0.4.1 / 0.4.5 | Fix arm64 arch resolving/support linux-x64-musl | 平台解析修复与 musl 支持 |
| 0.5.0 | Bump version | 版本基线 |
| 0.6.0 | 7fc2a650: Support preserving comments for Js/Ts/Jsx/Tsx modules | 注释保留能力;插件侧在process_module中把comments从模块 meta 取出并传入swc_transform_react(src/lib.rs 中let comments = param.meta.as_script().comments.clone().into();) |
changelog 末尾的 0.0.1 汇总段落还列出了早期若干跨插件性修复,可作背景参考:linux arm64 原生插件解析修复("linux arm64 native plugin resolving")、win32-ia32 CI 构建修复、css modules sourcemap 生成失败修复、以及Refactor Rust plugin system(Rust 插件系统重构,即当前farmfe_macro_plugin/#[farm_plugin]宏体系的起点)。
配置选项(ReactConfig):默认值与源码行为
scripts/index.d.ts 声明了完整的ReactConfig接口。结合 src/lib.rs 的解析逻辑,各选项的实际行为如下:
| 选项 | 类型 / 默认值 | 源码行为说明 |
|---|---|---|
pragma | string,默认React.createElement | 编译 JSX 表达式时替换的工厂函数(classic runtime) |
pragmaFrag | string,默认React.Fragment | 编译 JSX Fragment 使用的组件 |
throwIfNamespace | boolean | 遇到 XML 命名空间标签名(如<f:image />)时是否抛错;React 的 JSX 不支持命名空间,故默认禁用 |
development | boolean,默认false | 为 JSX 元素添加__self/__source调试 props,供 React Developer Tools 使用 |
useBuiltins | boolean,已标记@deprecated | 用Object.assign()代替_extends |
refresh | boolean,默认true | 开启 React Fast Refresh;注意 src/lib.rs 中生效条件是is_dev && refresh.unwrap_or(true)——仅 development 模式下默认开启,生产构建不注入 refresh |
runtime | "automatic" \| "classic",1.1.0 起默认"automatic" | 决定 JSX 编译策略;automatic时自动注入jsx/jsxs导入 |
importSource | string | runtime: "automatic"时jsx/jsxs工厂函数的模块来源 |
useAbsolutePath | boolean,默认false | true时 swc source map 的文件名使用相对config.root的绝对路径(resolved_path_with_query),1.2.3 引入、1.2.4 修复 panic |
两个隐含的运行时规则值得注意(均由源码确认):
- 非浏览器目标自动关闭 refresh:src/lib.rs 中
if !config.output.target_env.is_browser() { options_obj.insert("refresh", false) },即 Node 端(SSR 编译)不会注入 Fast Refresh 代码; useAbsolutePath会在传给 swc 的 options 中被剔除(src/lib.rs 中options_obj.remove("useAbsolutePath")),因为它只影响插件自身生成的文件名字符串,不属于 swc 的配置项。
Fast Refresh 注入:三段式代码改写
dev 模式下开启 refresh 时,插件对每个Jsx | Tsx模块执行 inject_react_refresh,分三步改写 AST:
- 注入运行时导入(inject_runtime_import):在模块最前面插入
import RefreshRuntime from 'react-refresh'与import * as ReactRefreshUtil from 'farmfe_plugin_react_is_react_refresh_boundary'。后者是一个虚拟模块 id,由插件的resolve+load钩子承接(src/lib.rs 的 resolve 返回该 id,load 钩子返回 is_react_refresh_boundary.ts 的源码),这也是该文件被直接放在src/下随 crate 编译、用include_str!加载的原因。 - 注入 PRE_CODE:保存并覆盖
window.$RefreshReg$/window.$RefreshSig$,使RefreshRuntime.register的 type id 前缀为当前模块 id({FARM_MODULE}.id + " " + id)——这解释了为什么 Farm 中 HMR 更新能精确定位到"哪个模块里的哪个组件"。 - 注入 POST_CODE(react_refresh.rs):恢复全局
$RefreshReg$/$RefreshSig$,并在import.meta.hot存在时于setTimeout中调用ReactRefreshUtil.registerExportsForReactRefresh(module.exports, module.id)注册导出,再import.meta.hot.accept处理下一个版本——用validateRefreshBoundaryAndEnqueueUpdate对比新旧导出,合法则enqueueUpdate()(30ms 防抖后触发Refresh.performReactRefresh()),不合法则import.meta.hot.invalidate(message)。源码注释解释了为何用setTimeout延迟注册:模块合并(module concatenation)后 exports 会被提升为带 getter 的访问器,同步注册时Bar` 之类的导出可能尚未初始化。
此外还有全局注入:analyze_deps 钩子 会在 refresh 开启时向每个 HTML 入口模块的依赖列表第 0 位插入虚拟模块farmfe_plugin_react_global_inject,其内容由load钩子提供(src/lib.rs)——确保页面加载时最先执行RefreshRuntime.injectIntoGlobalHook(window),且用window.__farm_refresh_runtime_injected_into_global_hook__标志防止重复注入。
刷新边界校验的实现细节见 is_react_refresh_boundary.ts:validateRefreshBoundaryAndEnqueueUpdate依次检查"导出被删除"、"新增导出"、"导出既非组件类型又发生变化"三类不兼容情况,返回中文语义的英文错误消息(如'Could not Fast Refresh (export removed)'),否则登记更新。registerExportsForReactRefresh遍历moduleExports,凡Refresh.isLikelyComponentType(exportValue)为真者以moduleID + ' exports ' + key为 typeID 注册——类组件同样会被isLikelyComponentType识别,这正是 1.2.2/1.2.4 两次修复"类组件无法热更新"所要保证的路径。
平台二进制解析:主包与平台子包的双通道
scripts/index.js 是 npm 主包的main入口(默认导出即binPath),其解析策略为双通道:
- 本地存在:
existsSync(join(currentDir, '../npm/<platform>-<arch>/index.farm'))——monorepo 内工作区(如本仓库examples/*通过workspace:*引用)直接使用源码树内的产物; - 按需安装:否则
require.resolve('@farmfe/plugin-react-<platform>-<arch>'),对应 npm 平台子包(如@farmfe/plugin-react-linux-x64-gnu),这类包正是prepublish流程(farm-plugin-tools prepublish)在主包发布前先行发布的产物,也解释了 0.1.2 版本"missing optionalDependencies field"重新发包的历史。
Linux 分支还包含 musl 判定:isMusl() 优先用process.report.getReport().header.glibcVersionRuntime(Node 10 以下则退化为读取/usr/bin/ldd是否含musl),从而在 glibc/musl 两种 libc 环境间选择正确的二进制——对应 changelog 中support linux-x64-musl(0.4.5 / #838)与linux arm64 native plugin resolving(#750)等记录。
构建、发布与测试:可操作的验证路径
- 构建:在仓库根执行
pnpm --filter @farmfe/plugin-react build即调用farm-plugin-tools build --platform -p farmfe_plugin_react --release。packages/plugin-tools/src/index.ts 中build命令的实际流程是:resolveNapiRsCli()定位 CLI → 透传参数(先剥离--cargo-name)→spawn('node', [cliPath, 'build', ...args])执行 napi-rs 构建 → 等待 500ms →copyArtifacts(abi)把产物复制并改名为index.farm放到npm/<platform>/下。 - 发布:
build:publish通过cross-env注入CARGO_PROFILE_RELEASE_LTO=fat、CARGO_PROFILE_RELEASE_STRIP=symbols、CARGO_PROFILE_RELEASE_PANIC=abort、CARGO_PROFILE_RELEASE_OPT_LEVEL=z,以体积优先的方式产出发布用二进制;随后prepublishOnly执行farm-plugin-tools prepublish先行发布平台子包。 - 底层转换入口:插件通过 farmfe_toolkit_plugin_types::swc_transforms 的
swc_transform_react以 FFI(libloading符号farm_swc_transform_react)调用 core 侧的 SWC React 转换,入参结构FarmSwcTransformReactOptions携带top_level_mark、unresolved_mark、mode、globals、comments与序列化后的 options JSON——这与 examples/react/farm.config.js 里开启persistentCache、runtime.isolate等 core 选项共同决定了最终产物行为。 - 集成测试:rust-plugins/react/tests/mod.rs 基于
farmfe_testing_helpers::fixture!宏以 tests/fixtures/button.tsx 为夹具,目前断言主体处于注释状态(保留了完整的 Compiler + 插件装配示例代码,可参考其external: ["^react-refresh$", "^react$", "^@swc/helpers"]的写法);端到端层面的验证则散落在 examples/react-ssr/e2e、examples/emotion/ 等示例的 e2e 用例中。
参考文档与延伸阅读
- 插件官方文档:website/docs/plugins/official-plugins/react.mdx(安装方式
npm install @farmfe/plugin-react react-refresh,以及指向 SWC Transform React Options 的完整选项文档); - Rust 插件开发指南:website/docs/plugins/writing-plugins/rust-plugin.mdx;
- 完整变更记录:rust-plugins/react/CHANGELOG.md;
- 实际配置示例:examples/react/farm.config.js(函数式 +
useAbsolutePath)、examples/react-ssr/farm.config.mjs(字符串式 + SSR)。
小结
从 rust-plugins/react/CHANGELOG.md 的 40 余条记录可以读出这条插件的完整生命轨迹:从 core 拆出独立插件(0.1.0)→ 补齐 SSR、多平台二进制(0.2.0~0.4.x)→ 注释保留、持久缓存等 core 能力跟进(0.4.0~0.6.0)→ 1.1.0 起 jsx runtime 默认automatic→ 1.2.x 集中修复类组件 HMR 并新增useAbsolutePath→ 2.0.0-beta 随 farm v2 发版并完成 napi-rs CLI 工具链兼容修复。每一条版本记录在当前仓库中都能找到对应的源码落点:refresh的 dev-only 默认值、priority = 99、-p farmfe_plugin_react构建脚本、removeArg(args, '--cargo-name')兼容层等,均与 changelog 逐条吻合。
- 前端构建
- 构建工具
- 开发工具
【免费下载链接】farm
Extremely fast Vite-compatible web build tool written in Rust
相关推荐
Portia 终极问题解答清单:AJAX 网站、登录页面与 Crawlera 代理一网打尽
Portia 终极问题解答清单:AJAX 网站、登录页面与 Crawlera 代理一网打尽 🧭 快速导航 1. Portia 是什么 portia 是什么30
前端构建构建工具开发工具Farm 的 @farmfe/plugin-react-components:用 Rust 插件实现 React 组件按需自动导入
Farm 的 @farmfe/plugin react components:用 Rust 插件实现 React 组件按需自动导入 本文以 farm(一个用 R
前端构建构建工具开发工具PySpark 如何锁定 PyArrow `to_pandas()` 默认转换行为:golden 测试矩阵全景解读
PySpark 如何锁定 PyArrow to_pandas 默认转换行为:golden 测试矩阵全景解读 本文以 Apache Spark 仓库中 PyArr
前端构建构建工具开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考