Worker Loader 绑定允许您在运行时加载包含任意代码的其他 Worker。
isolate 类似于轻量级容器。Workers 平台使用 isolate 而非容器或 VM,因此每个 Worker 已经在 isolate 中运行。但是,Worker Loader 绑定允许 Worker 创建按需加载任意代码的其他 isolate。
isolate 比容器便宜得多。您可以在毫秒内启动 isolate,仅为了运行代码片段并立即丢弃也没问题。无需担心 isolate 池化或尝试复用已预热的 isolate,这与容器不同。
Worker Loaders 还支持代码的沙箱化(sandboxing),这意味着您可以严格限制代码被允许执行的操作。特别是:
- 您可以安排拦截或简单地阻止内部 Worker 使用
fetch()发出的所有网络请求。 - 您可以向沙箱 Worker 提供自定义绑定(binding),以表示它应该被允许访问的特定资源。
通过正确配置沙箱,您可以安全地运行您不信任的代码。
Dynamic Worker Loaders 的主要用例是 Agents SDK 中的 Code Mode。Code Mode 将您的工具转换为类型化的 TypeScript API,并为 LLM 提供单个"编写代码"工具。生成的代码在隔离的 Worker 沙箱中运行,这使 AI agent 可以在一次执行中链接多个工具调用,并减少通过模型的往返次数。
Code Mode 使 AI SDK 工具、来自 MCP 客户端连接 的工具以及 OpenAPI 操作 可用于模型编写的代码。
Worker Loader 是只有一个方法 get() 的绑定,用于加载 isolate。用法示例:
let id = "foo";
// Get the isolate with the given ID, creating it if no such isolate exists yet.
let worker = env.LOADER.get(id, async () => {
// If the isolate does not already exist, this callback is invoked to fetch
// the isolate's Worker code.
return {
compatibilityDate: "2025-06-01",
// Specify the worker's code (module files).
mainModule: "foo.js",
modules: {
"foo.js":
"export default {\n" +
" fetch(req, env, ctx) { return new Response('Hello'); }\n" +
"}\n",
},
// Specify the dynamic Worker's environment (`env`). This is specified
// as a JavaScript object, exactly as you want it to appear to the
// child Worker. It can contain basic serializable types as well as
// Service Bindings (see below).
env: {
SOME_ENV_VAR: 123,
},
// To block the worker from talking to the internet using `fetch()` or
// `connect()`, set `globalOutbound` to `null`. You can also set this
// to any service binding, to have calls be intercepted and redirected
// to that binding.
globalOutbound: null,
};
});
// Now you can get the Worker's entrypoint and send requests to it.
let defaultEntrypoint = worker.getEntrypoint();
await defaultEntrypoint.fetch("http://example.com");
// You can get non-default entrypoints as well, and specify the
// `ctx.props` value to be delivered to the entrypoint.
let someEntrypoint = worker.getEntrypoint("SomeEntrypointClass", {
props: { someProp: 123 },
});要向 Worker 添加动态 worker loader 绑定,请像下面这样在 Wrangler 配置中添加:
{
"worker_loaders": [
{
"binding": "LOADER",
},
],
}[[worker_loaders]]
binding = "LOADER"get(id string, getCodeCallback () => Promise<WorkerCode> ): WorkerStub
加载具有给定 ID 的 Worker,返回可用于调用 Worker 的 WorkerStub。
作为便利,loader 实现了 isolate 缓存。首次看到新 ID 时,会加载新 isolate。但是,isolate 可能会在内存中保持预热一段时间。如果 loader 的后续调用请求相同 ID,可能会再次返回现有 isolate,而不是创建新 isolate。但没有保证:后续使用相同 ID 的调用可能从头开始新 isolate。
每当系统确定需要启动新 isolate,且尚未缓存代码副本时,它将调用 codeCallback 获取 Worker 代码。这是异步回调,因此应用程序可以从远程存储加载代码(如果需要)。回调返回 WorkerCode 对象(如下所述)。
由于缓存,您应确保回调对于相同 ID 始终返回完全相同的内容。如果内容有任何变化,必须使用新 ID。但如果内容没有变化,最好复用相同 ID 以利用缓存。如果每次 WorkerCode 都不同,可以传递随机 ID。
例如,您可以使用 <worker-name>:<version-number> 形式的 ID,其中版本号在代码每次更改时递增。或者,您可以基于代码和配置的哈希计算 ID,以便任何更改都产生新 ID。
get() 返回 WorkerStub,可用于向加载的 Worker 发送请求。请注意 stub 是同步返回的——您不必 await 它。如果 Worker 尚未加载,对 stub 的请求将等待 Worker 加载后再传递。如果加载失败,请求将抛出异常。
从不保证两个请求会进入同一 isolate。即使您使用相同的 WorkerStub 发出多个请求,它们也可能在不同 isolate 中执行。传递给 loader.get() 的回调可能被调用任意次数(尽管被调用超过一次是不常见的)。
这是 getCodeCallback 返回的结构,用于表示 worker。
Worker 的兼容性日期(compatibility date)。这与 Wrangler 配置文件中的 compatibility_date 设置具有相同含义。
可选的兼容性标志(compatibility flags) 列表,用于增强兼容性日期。这与 Wrangler 配置文件中的 compatibility_flags 设置具有相同含义。
如果为 true,则 compatibilityFlags 中允许实验性兼容性标志。要设置此值,调用 loader 的 worker 本身必须设置了兼容性标志 "experimental"。实验性标志无法在生产环境中启用。
Worker 主模块的名称。必须是 modules 中列出的模块之一。
将模块名称映射到其字符串内容的字典对象。如果模块内容是纯字符串,则模块名称必须具有指示其类型的文件扩展名:.js 或 .py。
模块内容也可以指定为对象,以便独立于名称指定其类型。允许的对象有:
{js: string}:JavaScript 模块,使用 ES modules 语法进行 import 和 export。{cjs: string}:CommonJS 模块,使用require()语法进行 import。{py: string}:Python 模块,但请参阅下面的警告。{text: string}:可 import 的字符串值。{data: ArrayBuffer}:可 import 的ArrayBuffer值。{json: object}:可 import 的对象。值必须是 JSON 可序列化的。但是,请注意值作为解析后的对象提供,并作为解析后的对象传递;两端实际上都看不到 JSON 序列化。
控制动态 Worker 是否可以访问网络。全局 fetch() 和 connect() 函数(分别用于发出 HTTP 请求和 TCP 连接)可以被阻止或重定向以隔离 Worker。
如果未指定 globalOutbound,默认是继承父级的网络访问,这通常意味着动态 Worker 将完全访问公共 Internet。
如果 globalOutbound 为 null,则动态 Worker 将完全与网络断开。fetch() 和 connect() 都将抛出异常。
globalOutbound 也可以设置为任何 service binding,包括父 worker 的 env 中的 service bindings 以及来自 ctx.exports 的 loopback bindings。
使用 ctx.exports 特别有用,因为它允许您通过设置应传递回它的 ctx.props 值,进一步为特定沙箱自定义 binding。props 可以包含用于标识发出请求的特定动态 Worker 的信息。
例如:
import { WorkerEntrypoint } from "cloudflare:workers";
export class Greeter extends WorkerEntrypoint {
fetch(request) {
return new Response(`Hello, ${this.ctx.props.name}!`);
}
}
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
// Redirect the worker's global outbound to send all requests
// to the `Greeter` class, filling in `ctx.props.name` with
// the name "Alice", so that it always responds "Hello, Alice!".
globalOutbound: ctx.exports.Greeter({ props: { name: "Alice" } }),
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};提供给动态 Worker 的环境对象。
使用此功能,您可以向 Worker 提供自定义绑定(binding)。
env 被序列化并传输到动态 Worker 中,在那里直接用作 env 的值。它可以包含:
第二点是创建自定义绑定的关键:您可以通过定义实现 RPC API 的 WorkerEntrypoint 类 来定义具有任意 API 的 binding,然后将其作为 Service Binding 提供给动态 Worker。
此外,通过使用 ctx.exports loopback bindings,您可以通过设置 ctx.props 进一步为特定动态 Worker 自定义 bindings,如上所述的 globalOutbound。
import { WorkerEntrypoint } from "cloudflare:workers";
// Implement a binding which can be called by the dynamic Worker.
export class Greeter extends WorkerEntrypoint {
greet() {
return `Hello, ${this.ctx.props.name}!`;
}
}
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
env: {
// Provide a binding which has a method greet() which can be called
// to receive a greeting. The binding knows the Worker's name.
GREETER: ctx.exports.Greeter({ props: { name: "Alice" } }),
},
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};您可以指定一个或多个 Tail Workers,它们将观察动态加载 worker 执行的控制台日志、错误和其他详细信息。请求完成后,tail 事件将传递给 Tail Worker。与往常一样,您可以将 Tail Worker 实现为父 Worker 中的替代 entrypoint,使用 ctx.exports 引用它:
import { WorkerEntrypoint } from "cloudflare:workers";
export default {
async fetch(request, env, ctx) {
let worker = env.LOADER.get("alice", () => {
return {
// Send logs, errors, etc. to `LogTailer`. We pass `name` in the
// `ctx.props` so that `LogTailer` knows what generated the logs.
// (You can pass anything you want in `props`.)
tails: [ctx.exports.LogTailer({ props: { name: "alice" } })],
// ... code ...
};
});
return worker.getEntrypoint().fetch(request);
},
};
export class LogTailer extends WorkerEntrypoint {
async tail(events) {
let name = this.ctx.props.name;
// Send the logs off to our log endpoint, specifying the worker name in
// the URL.
//
// Note that `events` will always be an array of size 1 in this scenario,
// describing the event delivered to the dynamically-loaded Worker.
await fetch(`https://example.com/submit-logs/${name}`, {
method: "POST",
body: JSON.stringify(events),
});
}
}