资讯详情

在 Cloudflare Workers 中运行时打包与动态执行 Worker:Dynamic Workers Playground 全解析

📅 2026/9/18 3:36:04 | 华诺云谱 👁 阅读
在 Cloudflare Workers 中运行时打包与动态执行 Worker:Dynamic Workers Playground 全解析
在 Cloudflare Workers 中运行时打包与动态执行 WorkerDynamic Workers Playground 全解析【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents导读Dynamic Workers Playground 是当前仓库Cloudflare Agents 示例集中一个极具代表性的示例工程它把cloudflare/worker-bundler的运行时打包能力与Dynamic Worker Loadersworker_loaders绑定的动态加载能力组合在一起实现在一个 Worker 内部接收用户提交的源码、现场完成依赖解析与打包、再以动态 Worker 的形式执行并实时回流日志与耗时。读完本文你将掌握运行时createWorker()的核心参数、worker_loaders绑定的配置方式、Tail Worker Durable Object 的日志管道设计以及一套可复用的源码 → 打包 → 加载 → 执行 → 观测完整链路。一、示例概览这个 Playground 做了什么Dynamic Workers Playground 位于仓库的 examples/dynamic-workers-playground 目录。它的核心定位在 README 中写得很清楚Write, bundle, and run Cloudflare Worker code at runtime—— 在运行时编写、打包并执行 Cloudflare Worker 代码。换言之它的宿主 Worker 不是一份写死的业务代码而是一个代码执行平台用户可以在浏览器里直接编辑 Worker 源码点击Run Worker后由服务端现场完成打包与执行。从源码结构看整个示例分为两层服务端Server-sidesrc/server.ts 承载运行时打包与动态执行src/logging.ts 承载日志采集管道。客户端Client-sidesrc/client.tsx 提供带文件标签页的编辑器、示例加载、GitHub 导入与实时结果面板。它演示的能力清单如下对应 README 的 What it demonstrates运行时打包使用cloudflare/worker-bundler在 Worker 内部解析 npm 依赖并打包源码动态执行通过worker_loaders绑定加载动态 Worker并在源码未变化时自动命中缓存日志采集管道一个 Tail WorkerDynamicWorkerTail把动态 Worker 的console.*输出转发到 Durable ObjectLogSession再实时流回调用方执行计时粒度化的 build / load / run 分段耗时并区分冷启动cold与热启动warm客户端能力带 Tab 键缩进的标签页式文件编辑器、加载内置示例或导入任意 GitHub 仓库、可透传的 bundle / minify 开关、实时输出响应体、控制台日志、耗时与打包信息。二、快速开始按 README 的指引从仓库根目录安装依赖再进入示例目录启动开发服务器npm install # 在仓库根目录执行 npm start # 在 examples/dynamic-workers-playground 目录执行npm start实际对应 package.json 中的脚本vite dev。示例同时提供了部署脚本scripts: { start: vite dev, deploy: vite build wrangler deploy, types: wrangler types env.d.ts --include-runtime false }本地开发时vite.config.ts 通过cloudflare/vite-plugin把 Vite 开发服务器与本地 Workers 运行时打通import { cloudflare } from cloudflare/vite-plugin; import react from vitejs/plugin-react; import tailwindcss from tailwindcss/vite; import { defineConfig } from vite; export default defineConfig({ plugins: [react(), cloudflare(), tailwindcss()] });客户端界面client.tsx左侧是源码面板文件标签页 编辑器 Run Worker / Format 按钮 Bundle / Minify 开关右侧是输出面板响应体、Console 日志、Timing 四格耗时、Bundle Info 模块清单并带有运行状态指示点与明暗主题切换。三、核心工作流从点击 Run Worker 到响应返回README 给出了整条链路的骨架代码这也是理解整个示例的钥匙。当用户点击Run Worker时宿主 Worker 收到源码文件调用cloudflare/worker-bundler的createWorker()在运行时完成打包const { mainModule, modules, wranglerConfig, warnings } await createWorker({ files: normalizedFiles, bundle: options?.bundle ?? true, minify: options?.minify ?? false }); const worker env.LOADER.get(workerId, async () ({ mainModule, modules, tails: [contextExports.DynamicWorkerTail({ props: { workerId } })] })); const response await worker.getEntrypoint().fetch(request);这一段对应 README 的 How it works其中包含三个关键概念createWorker()是打包期操作它接收files路径 → 内容映射解析依赖并产出mainModule入口模块路径、modules模块集合、wranglerConfig打包时解析出的配置与warningsenv.LOADER.get(workerId, factory)是加载期操作LOADER是worker_loaders绑定第二个参数是惰性工厂函数只有当该 workerId 对应的动态 Worker 尚未被缓存时才会执行这正是源码未变化时自动命中缓存的原理所在worker.getEntrypoint().fetch(request)是执行期操作拿到动态 Worker 的入口点后直接发起 fetch 调用。下面我们结合 src/server.ts 的完整实现逐段拆解。3.1 路由与请求校验宿主 Worker 的fetch处理两个 POST 接口server.ts/api/github交给handleGitHubImport导入 GitHub 仓库文件/api/run核心执行接口接收RunRequestBodyinterface RunRequestBody { files: Recordstring, string; // 源码文件路径 - 内容 version: number; // 客户端维护的版本号 pathname?: string; // 可选动态 Worker 内请求的路径 options?: { bundle?: boolean; // 默认 true minify?: boolean; // 默认 false }; }接口会先校验files非空否则返回 400At least one source file is required.然后进入normalizeFiles。3.2 源码规范化自动补全 package.jsonnormalizeFilesserver.ts会剔除空路径文件并在缺少package.json时自动生成一份用于告诉createWorker入口文件在哪里if (!normalized[package.json]) { const entryPoint normalized[src/index.ts] || normalized[src/index.js] ? Object.keys(normalized).find( (file) file src/index.ts || file src/index.js ) : Object.keys(normalized).find( (file) file.endsWith(.ts) || file.endsWith(.js) ); normalized[package.json] JSON.stringify( { name: dynamic-workers-playground-worker, main: entryPoint ?? src/index.ts }, null, 2 ); }可见入口解析的优先级是src/index.ts/src/index.js优先否则回退到第一个.ts或.js文件最后兜底src/index.ts。这正是createWorker的entryPoint未显式指定时package.json的main字段参与入口判定的实际落地参见packages/worker-bundler/src/types.ts中CreateWorkerOptions.entryPoint的注释入口可按wrangler.toml的main→package.json→ 默认路径顺序推断。3.3 稳定的 Worker ID内容寻址缓存动态执行要命中缓存前提是同样的源码对应同样的 ID。createWorkerIdserver.ts把排序后的文件列表连同bundle/minify选项序列化再做SHA-256 摘要并截取前 16 位十六进制const payload JSON.stringify({ files: sortedFiles, bundle: options?.bundle ?? true, minify: options?.minify ?? false }); const digest await crypto.subtle.digest( SHA-256, new TextEncoder().encode(payload) ); // ... return dynamic-workers-playground-worker-${hash};这里对files先按路径排序保证同样的内容集合无论以何种顺序提交都得到相同的 workerId。文件名前缀dynamic-workers-playground-worker-也避免了与其他示例的加载器命名空间冲突。从源码结构可以推断这个 ID 既是LOADER.get的缓存键也是LogSession.getByName日志会话的名字见下文让缓存命中与日志归位共用同一把钥匙。3.4 打包与加载createWorker LOADER.get核心执行逻辑server.tsconst worker env.LOADER.get(workerId, async () { const buildStart Date.now(); const { mainModule, modules, wranglerConfig, warnings } await createWorker({ files: normalizedFiles, bundle: options?.bundle ?? true, minify: options?.minify ?? false }); state.buildTime Date.now() - buildStart; state.bundleInfo { mainModule, modules: Object.keys(modules), warnings: warnings ?? [] }; return { mainModule, modules: modules as Recordstring, string, compatibilityDate: wranglerConfig?.compatibilityDate ?? 2026-01-01, compatibilityFlags: wranglerConfig?.compatibilityFlags ?? [], env: { API_KEY: sk-example-key-12345, DEBUG: true, WORKER_ID: workerId }, globalOutbound: null, tails: [ contextExports.DynamicWorkerTail({ props: { workerId } }) ] }; });几个值得注意的实现细节createWorker返回值mainModule入口路径、modules内容映射、wranglerConfig打包过程中解析出的compatibilityDate/compatibilityFlags、warnings。动态 Worker 的配置注入compatibilityDate与compatibilityFlags从打包产物的wranglerConfig继承缺失时回退到2026-01-01与空数组env展示了如何给动态 Worker 注入环境变量示例注入API_KEY、DEBUG、WORKER_IDglobalOutbound置为null。tails挂载为动态 Worker 挂上一个 Tail Worker 实例props携带workerId让日志管道知道日志归属哪个动态 Worker。state捕获由于LOADER.get的工厂函数是惰性的仅首次构建时执行示例用闭包把buildTime与bundleInfo写入state对象当缓存命中时工厂不再执行state保持初值buildTime: 0、bundleInfo: nullexecuteWorker里便用(cached)占位符标记未重新打包从而区分本次是构建还是缓存命中。关于worker_loaders绑定本身它需要在 wrangler.jsonc 中显式声明{ name: dynamic-workers-playground, main: src/server.ts, compatibility_date: 2026-06-11, compatibility_flags: [nodejs_compat], worker_loaders: [{ binding: LOADER }] }绑定名LOADER在运行时通过env.LOADER.get(id, factory)使用其类型由wrangler types生成到 env.d.tsinterface __BaseEnv_Env { LOADER: WorkerLoader; LogSession: DurableObjectNamespaceimport(./src/server).LogSession; }3.5 执行与响应冷/热启动计时与日志等待executeWorkerserver.ts负责真正执行动态 Worker并按阶段计时const entrypoint worker.getEntrypoint() as Fetcher { __warmup__?: () Promisevoid; }; const loadStart Date.now(); try { await entrypoint.__warmup__?.(); } catch { // Warmup intentionally calls a method that does not exist so the worker cold-starts. } const loadTime Date.now() - loadStart;这里有个巧妙设计代码主动调用一个不存在的方法__warmup__并吞掉异常。从注释可以确认意图——当动态 Worker 是冷启动时getEntrypoint()需要实例化运行时并解析模块这个故意调错方法的过程会触发完整的冷启动路径从而测得真实的加载耗时而当动态 Worker 已被缓存热启动时该调用接近零开销。这正好呼应 README 中cold vs. warm start detection的说明也解释了客户端在Timing (cold/warm)标题里用loadTime 0判定冷热的原因见 client.tsx。接着宿主通过runtimeExports.LogSession.getByName(workerId)拿到日志会话的 RPC stub调用waitForLogs()建立一个等待器然后才真正执行const logSessionStub runtimeExports.LogSession.getByName(workerId); const logWaiter await logSessionStub.waitForLogs(); const runStart Date.now(); const request new Request( https://example.com${pathname.startsWith(/) ? pathname : /${pathname}} );值得注意的是动态 Worker 收到的请求 URL 被规范化到https://example.com/pathname域名下默认/避免外部主机名干扰随后调用entrypoint.fetch(request)执行并捕获异常。对 5xx 响应、运行时异常分别记录workerError最终统一返回 JSONreturn Response.json({ bundleInfo: bundleInfo ?? { mainModule: (cached), modules: [], warnings: [] }, response: { status: workerResponse.status, headers, body: responseBody }, workerError, logs, timing: { buildTime, loadTime, runTime, totalTime: buildTime loadTime runTime } });这里logs来自await logWaiter.getLogs(1000)—— 最多等待 1000ms 收集动态 Worker 执行期间的日志超时则返回已收集的部分。响应体、响应头、状态码、日志与三段式耗时一次性回传给客户端。四、cloudflare/worker-bundler的createWorkerAPI 详解Playground 只用了createWorker的一小部分选项。该库类型定义位于 packages/worker-bundler/src/types.ts其入口导出见 packages/worker-bundler/src/index.tscreateWorker在调用时还会打印实验性 API 提示。完整参数如下可作为自行集成时的参考参数类型默认值说明filesFiles \| FileSystem必填输入文件键为相对项目根目录的路径值为文件内容entryPointstring自动推断入口文件路径未指定时按wrangler.toml的main→package.json→ 默认路径如src/index.ts依次推断bundlebooleantrue是否把所有依赖打包进单一产物externalsstring[][]不参与打包的外部模块注意cloudflare:*模块始终被视为外部targetstringes2022目标运行环境minifybooleanfalse是否压缩产物sourcemapbooleanfalse是否生成内联 source map仅bundle: true时生效便于调试与错误堆栈定位registrystringhttps://registry.npmjs.org拉取 npm 包的 registry 地址jsxtransform \| preserve \| automatic—传给 esbuild 的 JSX 转换模式automatic启用新 JSX runtime无需手动 import React仅bundle: true生效jsxImportSourcestring—jsx: automatic时 JSX runtime 的导入来源如react、preact、emotion/reactdefineRecordstring, string—打包期常量替换如{ process.env.NODE_ENV: production }仅bundle: true生效加载器覆盖见types.ts—按扩展名含前导点如.svg覆盖 loader.ts/.tsx/.js/.jsx/.json/.css的内置处理除非被覆盖否则保留从types.ts的注释可以确认在bundle: false仅转换不打包模式下sourcemap、jsx、define等选项不生效jsx与define还会被追加到返回结果的warnings数组中提示调用方。这与 Playground 把warnings原样展示在 Bundle Info 面板中的行为互相印证client.tsx。五、日志采集管道Tail Worker Durable ObjectREADME 强调的第三项核心能力是Log capture pipeline — a Tail Worker (DynamicWorkerTail) forwardsconsole.*output from dynamically loaded workers to a Durable Object (LogSession), streamed back to the caller in real time。实现位于 src/logging.ts包含三个角色5.1 DynamicWorkerTailTail Worker 事件转换DynamicWorkerTail继承WorkerEntrypoint并重写tail(events: TraceItem[])logging.ts。props.workerId由LOADER.get工厂里创建实例时传入。它把每条 Trace 事件转换成结构化事件并转发export class DynamicWorkerTail extends WorkerEntrypoint never, DynamicWorkerTailProps { override async tail(events: TraceItem[]) { const logSessionStub exports.LogSession.getByName( this.ctx.props.workerId ); for (const event of events) { const structuredEvents: StructuredDynamicWorkerEvent[] []; const requestSummary toRequestSummary(event, this.ctx.props.workerId); if (requestSummary) structuredEvents.push(requestSummary); structuredEvents.push(...toLogEvents(event, this.ctx.props.workerId)); structuredEvents.push(...toExceptionEvents(event, this.ctx.props.workerId)); // ... await logSessionStub.addLogs(toRealtimeLogEntries(structuredEvents)); } } }转换函数分工明确toRequestSummary利用TraceItemFetchEventInfo提取请求方法、URL path、响应状态码与 outcome生成形如GET / - 200 (ok)的请求摘要event.event上是否携带request字段用类型守卫isFetchTraceEvent判断toLogEvents把event.logs中的每条TraceLog变成{ level, message, timestamp }消息经由normalizeLogMessage统一为字符串数组元素逐个 join非字符串 JSON 序列化toExceptionEvents把event.exceptions中的异常名、消息与堆栈单独拆成kind: exception事件toRealtimeLogEntries过滤掉request类事件把日志与异常合并成面向界面的LogEntry[]异常带异常名前缀如TypeError: xxx。此外DynamicWorkerTail还会把每条结构化事件console.log到宿主 Worker 的日志便于在 Workers 控制台做整体观测logging.ts。5.2 LogSessionDurable Object 日志中转LogSession extends DurableObjectlogging.ts提供两个 RPC 方法export class LogSession extends DurableObject { private waiter: LogWaiter | null null; async addLogs(logs: LogEntry[]) { if (this.waiter) { this.waiter.addLogs(logs); } } async waitForLogs(): PromiseLogWaiter { this.waiter new LogWaiter(); return this.waiter; } }这里采用经典的消费者先就位生产者再投递的实时模型宿主在调用动态 Worker 前先waitForLogs()创建LogWaiter一个RpcTarget即跨隔离区可调用对象Tail Worker 稍后执行addLogs()时把日志直接推给等待者。LogWaiter.getLogs(timeoutMs)logging.ts在日志已到达时立即返回否则挂起最多timeoutMs毫秒被addLogs唤醒时清除定时器并 resolve。也就是说实时回流并不是通过 HTTP 长连接或流式响应实现而是通过RPC 握手 等待/唤醒语义执行完成后宿主一次性await logWaiter.getLogs(1000)收走这段时间内累积的所有日志。这也是 Dynamic Worker Loaders 生态中推荐的跨 Worker 数据交换姿势——利用 Durable Object 作为 RPC 枢纽。LogSession需要在 wrangler.jsonc 中注册并声明迁移durable_objects: { bindings: [{ name: LogSession, class_name: LogSession }] }, migrations: [{ tag: v1, new_classes: [LogSession] }]同时从 src/server.ts 可见宿主 Worker 直接export { DynamicWorkerTail, LogSession } from ./logging让加载器上下文能拿到这两个运行时导出contextExports.DynamicWorkerTail(...)与runtimeExports.LogSession.getByName(...)。六、GitHub 仓库导入把任意开源 Worker 拉进编辑器客户端支持从任意公开 GitHub 仓库导入源码服务端实现位于 src/github.ts对外暴露POST /api/github。parseGitHubUrl负责解析 URL只接受github.com主机名支持仓库根路径与tree分支路径两种形式如https://github.com/owner/repo与https://github.com/owner/repo/tree/branch/path分支默认mainif (parts.length 3 parts[2] tree parts[3]) { branch parts[3]; path parts.slice(4).join(/); }fetchGitHubDirectory递归调用 GitHub Contents API/repos/{owner}/{repo}/contents/{path}?ref{branch}请求头带Accept: application/vnd.github.v3json与自定义 User-Agent对目录继续下钻、对文件经download_url拉取原文最终产出相对路径 → 内容的映射并保留目录层级。导入完成后客户端client.tsx会检查结果是否包含package.json若没有则仿照服务端逻辑推断主入口并自动补一份{ name: imported-worker, main: 入口 }随后applyFiles把整个文件树载入编辑器标签页状态栏提示Imported N file(s)。七、客户端编辑器与实时结果面板客户端是典型的瘦前端 厚后端结构client.tsx组件与能力对应 README 的 Client-side 列表Tab 键缩进Textarea的onKeyDown拦截 Tab手动插入两个空格并恢复光标位置client.tsx文件管理Add file弹窗新增文件.json文件自动初始化为{}标签页上的×可删除文件最后一个文件禁止删除package.json不可删除内置示例EXAMPLES数组内置 5 个可直接运行的示例——Simple Worker最小 fetch handler、Multi-file Worker跨模块 import、JSON Configimport JSON 资源、With Env Bindings读取注入的API_KEY/DEBUG、API Router路径路由 404 响应覆盖多文件、资源导入、环境变量、路由等典型场景Bundle / Minify 开关勾选状态随请求体options透传给服务端createWorker默认bundle true、minify false版本快照snapshotFiles对文件集做 JSON 快照runWorker只在快照变化时递增workerVersion并一并提交供服务端或未来的缓存失效策略参考结果面板按Response (status)、Console (N logs)、Timing (cold/warm)、Bundle Info四块展示。prettyBody对 JSON 响应体做美化getContentType显示Content-TypeConsole 按error/warn/其他级别用不同前缀✕/!/›与配色渲染Timing 面板展示 Build / Load / Run / Total 四个毫秒数Bundle Info 展示Main入口、模块清单彩色标签与打包Warnings。八、部署与配置要点生产部署执行npm run deploy # 即 vite build wrangler deploywrangler.jsonc 中有几处值得留意的配置{ assets: { not_found_handling: single-page-application, run_worker_first: [/api/*] }, observability: { enabled: true } }assets把 Vite 构建出的前端静态资源托管在 Workers 上not_found_handling设为 SPA 回退run_worker_first声明/api/*的请求先由 Worker 处理避免静态资源路由抢先命中 APIobservability.enabled: true开启 Workers 可观测性与DynamicWorkerTail里的console.log(structuredEvent)呼应保证整条日志管道在控制台可追踪compatibility_flags: [nodejs_compat]启用 Node.js 兼容层满足worker-bundler运行时打包对 Node 生态 API 的依赖。整个示例依赖关系见 package.json包括cloudflare/worker-bundler运行时打包引擎、cloudflare/kumoUI 组件库提供 Button/Dropdown/Surface/Textarea 等、phosphor-icons/react图标、agents本仓库的 Agents SDK、React 19 与 Tailwind CSS 4。九、延伸这套链路能复用到哪里Dynamic Workers Playground 的本质是把代码即输入的产品化范式在 Cloudflare Workers 上做了一次完整闭环演示。从源码结构可以推断以下模式都可直接借鉴插件 / 扩展运行时用createWorker把用户脚本打包成隔离的 Worker用worker_loaders做按内容寻址的缓存天然获得版本化与冷热启动管理日志回传统一通道Tail Worker Durable ObjectgetByName RPC 等待/唤醒不依赖流式协议即可实现执行结束一次性回收日志若换成 SSE/WebSocket 也可平滑演进为真正的实时流Env 注入与配置继承compatibilityDate/compatibilityFlags/env在加载器工厂里统一注入让动态 Worker 与宿主保持一致的运行时语义GitHub 内容导入Contents API 递归拉取 入口推断 自动补package.json稍作扩展即可做成URL 即 Worker的分享体验。如需深入了解底层机制可继续阅读仓库内 packages/worker-bundler 的类型定义与实现以及示例目录下的 server.ts、logging.ts、github.ts 与 client.tsx。本地起服务后在浏览器里改几行代码、导入一个真实仓库再点 Run Worker即可直观感受源码 → 打包 → 加载 → 执行 → 日志与耗时回流的完整链路。【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。