ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Alchemy Cloudflare Worker 构建实录:用 `strictExecutionOrder` 修复 Drizzle 模式分块导致的 ScriptStartupError

Alchemy Cloudflare Worker 构建实录:用 `strictExecutionOrder` 修复 Drizzle 模式分块导致的 ScriptStartupError Alchemy Cloudflare Worker 构建实录用strictExecutionOrder修复 Drizzle 模式分块导致的 ScriptStartupError【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code导读本文围绕 Alchemy 开源仓库.repos/alchemy-effect即 alchemy-effect monorepo中针对 issue #749 设计的回归测试夹具drizzle-schema-chunks展开剖析一个非常典型的 Edge Runtime 构建陷阱当 Drizzle ORM 的顶层pgTable(...)模式模块被代码分割code-splitting到与drizzle-orm不同的 chunk 中时workerd 在 Worker 启动阶段会以错误的顺序执行跨 chunk 模块从而抛出ScriptStartupError。读完本文你将理解该问题的成因、Alchemy 通过 RolldownstrictExecutionOrder: true修复它的原理以及这套夹具 正反测试如何把一次真实的部署事故固化为可持续验证的回归防线。一、问题背景issue #749 与 Worker 启动期 TDZ1.1 现象在 alchemy 仓库的 issue #749 中用户报告了如下现象Alchemy/Rolldown 打包 Cloudflare Worker 后如果 Drizzle 的 schema 模块在模块顶层执行pgTable(...)被代码分割到与drizzle-orm相互独立的 chunk 中上传部署时 Cloudflare 会在脚本启动校验阶段直接报错经典报错一PgSerialBuilder is not a constructor经典报错二Cannot access minified before initializationTDZ暂时性死区其根因在于跨 chunk 求值顺序ESM 语义要求被依赖模块先于使用方执行但分割后的 chunk 图是循环的worker.js - auth-*.js - worker.js而 workerd 对跨 chunk 绑定的初始化时机并不总能保证 ESM 语义导致读取方schema 模块先于其导入的类绑定drizzle-orm内部类被求值。1.2 为什么顶层pgTable是导火索在 fixtures/drizzle-schema-chunks/schema/users.ts 中可以看到典型的触发形态import { pgTable, serial, text, timestamp } from drizzle-orm/pg-core; // Top-level pgTable(...) — evaluated at module init. export const users pgTable(users, { id: serial(id).primaryKey(), email: text(email).notNull(), createdAt: timestamp(created_at).defaultNow(), });pgTable在模块初始化阶段就同步执行且表与表之间还存在跨模块引用如 schema/sessions.ts 中的references(() users.id)。一旦这个模块所在 chunk 先于drizzle-orm所在 chunk 执行pgTable、serial等类绑定尚未初始化立即触发 TDZ 错误。二、夹具布局忠实还原用户的 monorepo 结构drizzle-schema-chunks夹具位于 fixtures/drizzle-schema-chunks/其布局刻意镜像了报告者的 monorepo 形状drizzle-schema-chunks/ ├── schema/ # db 包表定义pgTable 在模块顶层执行 │ ├── users.ts │ ├── sessions.ts │ └── waitlist.ts ├── auth/ # auth 包表定义交叉导入 db schema │ ├── workspace.ts │ └── invitations.ts └── worker.ts # Worker 入口导入完整依赖图schema/*扮演 monorepo 中独立的 db 包三张表各自顶层初始化pgTable且sessions、waitlist都通过references(() users.id)引用users见 sessions.ts 与 waitlist.ts。auth/*扮演 auth 包跨包导入 db schemaimport { users } from ../schema/users.ts并在模块顶层构造表见 auth/workspace.ts 与 auth/invitations.ts。worker.ts入口模块一次性导入完整依赖图fetch处理器把所有表收集进数组并返回 JSON用于验证 Worker 部署后真的能跑起来见 worker.ts。这个布局的关键点在于auth模块与schema模块存在双向依赖链auth 引用 users同时 auth 内部 workspace 又引用 invitations为下面强制制造循环 chunk 图提供了天然素材。三、测试如何钉死这个缺陷强制分割 循环图3.1 默认配置下小依赖图不会复现夹具 README 明确指出在 Alchemy 默认 Worker 打包器下小的依赖图会保持单 chunk因此缺陷不会自然出现。要在测试中稳定复现必须人为强制出问题中的 chunk 布局。3.2DrizzleSchemaChunks.test.ts的强制分割测试位于 test/Cloudflare/Workers/DrizzleSchemaChunks.test.ts通过Cloudflare.Worker的build选项强制分块const chunking { preserveEntrySignatures: allow-extension, output: { cleanDir: true, codeSplitting: { groups: [ { name: auth, test: drizzle-schema-chunks/(schema|auth)/, includeDependenciesRecursively: false, }, ], }, }, } satisfies WorkerBuildOptions;其要点分组匹配drizzle-schema-chunks/(schema|auth)/把schema与auth目录下的所有模块归入名为auth的代码分割组includeDependenciesRecursively: false分组只捕获这些 schema 模块本身不递归包含其依赖于是drizzle-orm留在入口 chunk 中最终生成循环图worker.js - auth-*.js - worker.jsESM 求值便会在 drizzle 类绑定初始化之前先执行 schema chunk——TDZ 被精确复现。3.3 为什么循环是必要条件夹具 README 特别强调循环是关键。如果只是无环分割例如 drizzle 独立成 chunk、由 schema chunk 导入普通 import 顺序即可正确求值无论是否开启strictExecutionOrder都不会触发 bug。换言之只有出现谁先谁后说不清的循环依赖workerd 的启动校验才会踩中时序问题。3.4 Cloudflare 上传即断言由于 Cloudflare 的 script-startup 校验恰好在上传时对分割后的 chunk 执行整个部署动作本身就是回归断言正向用例部署成功后测试通过 HTTP 客户端反复拉取 worker 地址直到响应体包含ok:true见DrizzleSchemaChunks.test.ts中带指数退避重试的test块验证 Worker 正常服务该正向测试还带 180 秒超时覆盖 workers.dev 新 URL 在传播期间的占位页干扰。四、反向对照关闭strictExecutionOrder必然复现为了让测试真正钉死问题而不是因为分块方式改变而悄悄变成无环布局测试文件还提供了一个反向对照组test.provider块build: { ...chunking, output: { ...chunking.output, strictExecutionOrder: false }, },同样的夹具、同样的强制分块仅把strictExecutionOrder关闭Cloudflare 就会在上传时抛出 issue 中的原始错误ScriptStartupError: Uncaught ReferenceError: Cannot access a before initialization at auth-BFaahPAe.js:1:110测试断言error._tag ScriptStartupError即关闭修复开关 → 一定失败。这个设计非常干净Cloudflare 本身充当 oracle测试文件无需断言 chunk 文件名或发射代码的细节——正反两用例共同证明部署能成功完全归功于strictExecutionOrder而不是分块方式的变化。五、修复原理WorkerBundle默认开启strictExecutionOrder5.1 源码中的修复点修复落在 Worker 打包器的 Rolldown 输出选项中。在 src/Cloudflare/Workers/Sources/Rolldown.ts 中WorkerBundle构造输出选项时显式写入const outputOptions: rolldown.OutputOptions { format: esm, sourcemap: hidden, minify: true, keepNames: true, // Rolldowns default chunking can split top-level initializer modules // (e.g. Drizzle pgTable schemas) away from the classes they read, // and workerd then evaluates a reader before its imported binding is // initialized — the script fails Cloudflare startup validation with // ScriptStartupError: Cannot access minified before // initialization (#749). strictExecutionOrder wraps cross-chunk // modules so evaluation follows ESM semantics regardless of how the // graph was chunked. See DrizzleSchemaChunks.test.ts. strictExecutionOrder: true, dir: .alchemy/bundles/${options.id}, ...options.extraOptions?.output, };几点值得注意strictExecutionOrder: true会包装跨 chunk 模块使求值顺序遵循 ESM 语义无论依赖图如何被切分它是默认值用户不需要任何额外配置即可获得修复...options.extraOptions?.output允许用户覆盖这正是反向测试把其置为false的入口该输出选项的其余部分format: esm、sourcemap: hidden、minify: true、keepNames: true是 Worker 产物一贯的 ESM 构建基线dir: .alchemy/bundles/${options.id}明确了产物输出目录。5.2 修复的影响范围从源码结构看strictExecutionOrder不仅用于 Cloudflare Worker同样的开关也出现在 Fly、Railway、Hetzner 的托管部署构建路径中可以推断这是 Alchemy 各边缘/托管运行时统一采用的跨 chunk 求值安全策略。5.3 用户侧 workaround 已被取代issue 中曾建议通过用户侧的advancedChunks分组来规避此问题在 Alchemy 默认开启strictExecutionOrder之后这个 workaround 不再必要——用户依然可以自定义codeSplitting.groups就像测试那样但不必再担心跨 chunk 求值顺序。六、从本案例可复用的工程经验顶层副作用初始化 代码分割 启动期风险任何在模块顶层执行的初始化DrizzlepgTable、类实例、单例等在 chunk 化后都存在跨 chunk 求值顺序风险若涉及循环依赖风险更高。用部署本身做断言Cloudflare 的 script-startup 校验发生在上传时天然是最真实的回归门禁比在本地断言产物文件名更可靠。正反用例成对出现正向用例证明修复后能部署反向用例关闭修复开关证明同样的输入依然会失败两者共同锁定根因防止测试因分块策略漂移而假通过。构建工具默认值也是产品决策把strictExecutionOrder放进WorkerBundle默认输出选项而非文档里建议用户手工配置从根本上消灭了一整类用户侧的踩坑空间。延伸阅读夹具本体fixtures/drizzle-schema-chunks/schema/*、auth/*、worker.ts回归测试test/Cloudflare/Workers/DrizzleSchemaChunks.test.ts修复实现src/Cloudflare/Workers/Sources/Rolldown.ts【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表