☰
Farm 实战:Rust Worker 插件动态入口 URL 不一致导致的 E2E 失败排查与修复
2026/10/14 8:48:03 网站建设 项目流程
  • 前端构建
  • 构建工具
  • 开发工具

【免费下载链接】farm

Extremely fast Vite-compatible web build tool written in Rust

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

本文围绕 Farm 仓库中 docs/experiences/rust-plugin-worker-e2e-url-mismatch.md 记录的调试经历展开,完整还原examples/rust-plugin-worker示例在浏览器端 E2E 失败的现象、排查步骤、根因定位与修复方案。读完本文,你将掌握「构建成功但浏览器运行失败」这类隐性缺陷的排查套路,理解 Farm Worker 插件动态入口命名与模块图去重的底层机制,并学会用单元测试与 E2E 双重手段验证修复。

问题背景:构建成功,浏览器却报错

examples/rust-plugin-worker是 Farm 仓库中用于演示 Rust Worker 插件(@farmfe/plugin-worker)的示例工程。它的 E2E 失败有一个非常迷惑的特点:Farm 的构建与 dev server 都能正常启动、编译产物完整,但页面在浏览器中运行 Worker 用例时整体失败。

该失败最初在 Windows 与 macOS 的 CI 上通过pnpm run test-e2e观测到,且呈平台相关特征。由于 Worker 动态入口名在多种 Worker 语法引用同一份源文件时可能被选择得不同,这类问题往往只在特定平台、特定引用方式下暴露,本地 Linux 环境难以复现,排查成本较高。

典型症状与关键信号

E2E runner 的默认冒烟测试(由 scripts/test-e2e-worker.mjs 驱动)会报告来自浏览器控制台的错误:

[rust-plugin-worker] rust-plugin-worker › start Browser console error: Worker case failures [Object, Object, Object, Object, Object, Object, Object]

这条报错有三个值得注意的信号:

  1. Farm 构建或 dev server 已成功启动——错误不是编译期问题,而是运行期问题;
  2. 失败来自页面自身的console.error("Worker case failures", results)——这是 examples/rust-plugin-worker/src/index.tsx 中runWorkerCases()收尾处主动打出的日志,说明至少有一个 Worker 用例在浏览器里抛了异常;
  3. 浏览器日志只显示[Object]——失败用例的名称、错误详情被隐藏,必须直接检查页面或产物才能定位。

从 E2E 机制看,e2e/farm-runner.mjs 的visitPage会监听页面console事件,任何msg.type() === 'error'的控制台错误都会触发Browser console error: ...并让测试失败。因此页面代码里那行console.error一出现,整个用例就被判定为失败。

逐步排查清单

1. 先审查 Worker 用例源码

排查的第一步是回到用例源头。examples/rust-plugin-worker/src/index.tsx 针对同一份vue.worker.ts源文件,同时使用了四种不同的引用语法:

// new URL 绝对路径(以 / 开头) new Worker(new URL("/src/worker/vue.worker.ts", import.meta.url)) // new URL 相对路径 new Worker(new URL("./worker/vue.worker.ts", import.meta.url)) // ?worker 查询参数,导入 Worker 构造函数 import WorkerCtor from "./worker/vue.worker.ts?worker" // ?worker&url 查询参数,导入 worker 脚本 URL import workerUrl from "./worker/vue.worker.ts?worker&url"

关键疑点就在这里:如果同一份源文件被多种语法引用,理论上应该产生多个公开的 Worker URL,但 Farm 是按真实源ModuleId追踪动态入口的,因此同一源文件只会保留一个动态入口名。若插件生成的各 wrapper 指向的 URL 不一致,就会产生「产物只发出一个、引用却指向多个」的错位。

2. 构建示例并对比 wrapper URL 与产物文件

执行构建:

cd examples/rust-plugin-worker npm run build

然后检查dist/index.*.js中生成的 wrapper 代码,以及dist/目录下实际产出的 Worker 文件。一个典型的坏构建长这样:

function WorkerWrapper(options) { return new Worker("/vue.63ddc497.worker.js", options); } var vue_worker_ts_default = "/vue.c1e83ed7.worker.js";

而输出目录里只有:

dist/vue.63ddc497.worker.js

对比即可发现:?worker&url导出的vue.c1e83ed7.worker.js指向了一个根本未被产出的文件。这就是「构建成功、运行时失败」的直接证据——构建阶段两个 wrapper 都成功生成,但运行时只有被模块图选中产出的那一个 URL 是真实存在的。

3. 不要轻信 preview 返回的 200

排查时最容易踩的坑是:Farm preview 服务器对缺失的 Worker URL 可能返回index.html兜底内容,因此请求即使返回状态码200,拿到的也可能是 HTML 而不是 Worker 脚本。检查响应体,而不是只检查response.ok。

以 PowerShell 为例,直接查看响应内容的前 120 个字符:

$r = Invoke-WebRequest -Uri http://localhost:1911/vue.c1e83ed7.worker.js -UseBasicParsing $r.Content.Substring(0, [Math]::Min(120, $r.Content.Length))

如果响应以<!doctype html>开头,说明该 Worker URL 确实缺失,浏览器实际上把 HTML 当作 Worker 脚本加载了。这也是排查文档中反复强调的经验:只断言fetch(url).ok的 E2E 检查,在 preview 服务器存在 HTML 兜底时会漏掉缺失资源的缺陷。

4. 修改 Rust 插件后必须先重建平台二进制

@farmfe/plugin-worker是 Rust 实现的 NAPI 插件,rust-plugins/worker/scripts/index.js 会按平台、架构从npm/<platform>/index.farm加载对应的本地二进制。因此修改rust-plugins/worker/src/*.rs后,必须先重新构建插件,再运行示例,否则farm build仍会加载旧的本地index.farm二进制,改动不会生效:

pnpm --filter @farmfe/plugin-worker build pnpm run test-e2e -- --example rust-plugin-worker

其中pnpm run test-e2e -- --example rust-plugin-worker由 scripts/test-e2e.mjs 解析,--example指定只跑单个示例并禁用并行。

根因:动态入口名与模块图去重的错位

Worker 入口名是如何被计算的(源码证据)

从当前已修复的源码看,Worker 插件的核心逻辑集中在 rust-plugins/worker/src/lib.rs 与 rust-plugins/worker/src/codegen.rs:

  • load钩子:识别带?worker/?sharedworker/?worker&inline的虚拟模块,返回 URL 型 wrapper 模块或 inline 占位模块;
  • transform钩子:用 constants.rs 中定义的WORKER_IMPORT_META_URL_RE正则匹配new Worker(new URL("...", import.meta.url)),将其改写为确定性的输出 URL,并把(importer, worker 源路径, 入口名)记录到transform_worker_deps;
  • analyze_deps钩子:为上述两类发现注入ResolveKind::DynamicEntry { name: entry_name, ... }依赖,让 Farm 把 Worker 源编译为独立 chunk。

问题出在入口名的哈希输入。修复前的实现用「Worker 源文件名 + 导入时带查询参数的module_id字节」作为哈希输入,导致同一个源文件因查询参数不同而算出不同的入口名:

src/worker/vue.worker.ts?worker -> vue.63ddc497.worker.js src/worker/vue.worker.ts?worker&url -> vue.c1e83ed7.worker.js

模块图按真实源模块去重

Farm 存储动态入口时按解析后的源模块进行去重。上面这些导入最终都解析到同一个vue.worker.ts源文件,因此模块图对这份源只能保留一个动态入口名。错位由此产生:

  • wrapper 模块在生成阶段立即创建,不同语法可能指向不同 URL;
  • 模块图只对解析后的源发出一个 Worker 资源;
  • 任何指向未被选中 URL 的 wrapper,运行时会 404 / 加载到 HTML,导致用例失败。

此外还有一个平台敏感的细节:解析出的路径在 Windows 上可能包含\,在类 Unix 上使用/,不同 resolve 分支也可能产生差异。若路径字符串未规范化就参与哈希,等价源文件在不同平台或不同调用点仍会算出不同入口名。

修复:让入口名只依赖真实的 Worker 源路径

修复的核心思路是:让 Worker 入口名对真实的 Worker 源文件保持稳定。对应实现见 codegen.rs 的compute_worker_entry_name:

  • 以解析后的 Worker 源路径(resolved_path)作为哈希输入,而不是带查询参数的虚拟module_id(函数签名中module_id已改为_module_id,不再参与计算);
  • 哈希前规范化路径分隔符:resolved_path.replace('\\', "/"),把 Windows 反斜杠统一为/;
  • 从规范化源路径中取文件名 stem(去掉扩展名),作为入口名的可读前缀。

入口名随后经output.assets_filename模板(如asserts/[resourceName].[hash].[ext])生成完整文件名并剥离扩展名,得到vue.xxxx.worker这样的裸入口名;codegen.rs 的compute_worker_url再套用output.entry_filename与public_path得到最终可访问的 URL。示例工程的配置见 examples/rust-plugin-worker/farm.config.ts,其中插件选项里设置了assetsFilename: 'asserts/[resourceName].[hash].[ext]'。

回归覆盖需要包含两类场景:

  • 同一源、不同查询参数:?worker与?worker&url必须算出同一个入口名;
  • 同一源、不同路径分隔符:C:\project\...与C:/project/...必须算出同一个入口名。

这两条已固化为 codegen.rs 中的单元测试:

  • worker_entry_name_is_stable_for_same_source_with_different_queries:用"C:\project\src\worker\vue.worker.ts"分别以?worker、?worker&url两种 module_id 调用,断言入口名相等;
  • worker_entry_name_is_stable_for_same_source_with_different_path_separators:对比反斜杠路径与正斜杠路径,断言入口名相等。

验证与回归

修复后的最小验证流程如下:

cargo test -p farmfe_plugin_worker worker_entry_name_is_stable pnpm --filter @farmfe/plugin-worker build pnpm run test-e2e -- --example rust-plugin-worker

期望结果:

cargo test ... 2 passed rust-plugin-worker › start passed rust-plugin-worker › preview passed

最后再检查重建后的dist/index.*.js:所有对同一份vue.worker.ts源的引用(?worker、?worker&url、两种new URL写法)都应指向同一个产出的vue.*.worker.js文件,不再出现「wrapper 指向多个、产物只发一个」的错位。

经验总结

本次排查沉淀下来四条可复用的工程经验:

  1. 插件在编译器产出资源之前就创建 URL wrapper 时,wrapper 预测的 URL 必须与编译器动态入口产出路径使用完全相同的身份标识,否则任何一处不一致都会在运行时爆发;
  2. 如果模块图按解析后的源模块去重,Worker 源的身份标识就不应包含查询参数,?worker、?worker&url这类语法差异必须被归一化;
  3. 只断言fetch(url).ok的 E2E 检查可能漏掉缺失资源缺陷——preview 服务器对缺失脚本可能返回 HTML 兜底并给出 200;排查 Worker 失败时务必校验生成 URL 的响应内容(如检查是否以<!doctype html>开头);
  4. 修改 Rust 插件后,验证示例前必须重新构建平台插件二进制,避免farm build继续加载旧的index.farm本地产物,导致「改了没生效」的假象。
  • 前端构建
  • 构建工具
  • 开发工具

【免费下载链接】farm

Extremely fast Vite-compatible web build tool written in Rust

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

相关推荐

上一篇:Krita AI Diffusion插件SD3模型CLIP文件缺失问题的完整解决方案指南
下一篇:PaddleX 时序预测产线实战:基于 Electricity 数据集完成用电量长期预测、调优与部署

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

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

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

立即咨询