☰
farm 的 React 编译插件 @farmfe/plugin-react:从 CHANGELOG 追溯演进路线与实现原理
2026/10/13 6:48:28 网站建设 项目流程
  • 前端构建
  • 构建工具
  • 开发工具

【免费下载链接】farm

Extremely fast Vite-compatible web build tool written in Rust

项目地址:https://gitcode.com/gh_mirrors/fa/farm
点击查看免费下载

本文以 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 的联动改造:

  1. 在 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已不再支持该参数。

  2. 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" }
  3. 修复@farmfe/plugin-react的build:publish脚本指向正确的 Cargo 包:-p farmfe_plugin_react即 Cargo.toml 中声明的name = "farmfe_plugin_react",避免在多 crate 的 workspace 里误构建其他包。

  4. 同步更新 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.0a5364b5: Extract plugin react into a single pluginReact 支持从 core 中拆出为独立插件,即当前rust-plugins/react/目录的由来
0.1.2Republish @farmfe/plugin-react as it misses optionalDependencies field平台二进制包以 optionalDependencies 形式按需安装(见下文"二进制解析策略")
0.1.3Do not resolve browser when targetEnv is node与 src/lib.rs 中"非 browser target 禁用 refresh"同属 target env 适配逻辑
0.1.5 / 0.3.3Fix 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.10Fix that css HMR will always reload the whole pageCSS HMR 误整页刷新的修复
0.2.0d604b5e: Support React SSRReact SSR 支持,示例见 examples/react-ssr/(server 端farm.config.mjs+ 中间件渲染 HTML 模板)
0.3.056f235c: Upgrade swc crates and support emotion升级 swc 并支持 emotion(仓库中亦有 examples/emotion/ 示例)
0.3.2Fix that vite plugin is not compatible with Farm's lazy compilation与 Farm 懒编译(lazy compilation)的兼容修复
0.4.0Support persistent cache and incremental building;Refactor render pot renders and optimize sourcemap generation持久缓存与增量构建;渲染产物重构
0.4.1 / 0.4.5Fix arm64 arch resolving/support linux-x64-musl平台解析修复与 musl 支持
0.5.0Bump version版本基线
0.6.07fc2a650: 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 的解析逻辑,各选项的实际行为如下:

选项类型 / 默认值源码行为说明
pragmastring,默认React.createElement编译 JSX 表达式时替换的工厂函数(classic runtime)
pragmaFragstring,默认React.Fragment编译 JSX Fragment 使用的组件
throwIfNamespaceboolean遇到 XML 命名空间标签名(如<f:image />)时是否抛错;React 的 JSX 不支持命名空间,故默认禁用
developmentboolean,默认false为 JSX 元素添加__self/__source调试 props,供 React Developer Tools 使用
useBuiltinsboolean,已标记@deprecated用Object.assign()代替_extends
refreshboolean,默认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导入
importSourcestringruntime: "automatic"时jsx/jsxs工厂函数的模块来源
useAbsolutePathboolean,默认falsetrue时 swc source map 的文件名使用相对config.root的绝对路径(resolved_path_with_query),1.2.3 引入、1.2.4 修复 panic

两个隐含的运行时规则值得注意(均由源码确认):

  1. 非浏览器目标自动关闭 refresh:src/lib.rs 中if !config.output.target_env.is_browser() { options_obj.insert("refresh", false) },即 Node 端(SSR 编译)不会注入 Fast Refresh 代码;
  2. useAbsolutePath会在传给 swc 的 options 中被剔除(src/lib.rs 中options_obj.remove("useAbsolutePath")),因为它只影响插件自身生成的文件名字符串,不属于 swc 的配置项。

Fast Refresh 注入:三段式代码改写

dev 模式下开启 refresh 时,插件对每个Jsx | Tsx模块执行 inject_react_refresh,分三步改写 AST:

  1. 注入运行时导入(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!加载的原因。
  2. 注入 PRE_CODE:保存并覆盖window.$RefreshReg$/window.$RefreshSig$,使RefreshRuntime.register的 type id 前缀为当前模块 id({FARM_MODULE}.id + " " + id)——这解释了为什么 Farm 中 HMR 更新能精确定位到"哪个模块里的哪个组件"。
  3. 注入 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),其解析策略为双通道:

  1. 本地存在:existsSync(join(currentDir, '../npm/<platform>-<arch>/index.farm'))——monorepo 内工作区(如本仓库examples/*通过workspace:*引用)直接使用源码树内的产物;
  2. 按需安装:否则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

项目地址:https://gitcode.com/gh_mirrors/fa/farm
点击查看免费下载

相关推荐

上一篇:Enzyme ShallowWrapper `.get(index)` 方法详解:按索引直接获取 ReactElement 节点
下一篇:web3-eth-personal 4.x 版本演进全解析:从 CHANGELOG 看 Ethereum 账户管理模块的架构重构与最佳实践

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询