← Dynamic Workers / dynamic-workers / usage
Static Assets
Dynamic Workers могут отдавать статические ресурсы, такие как HTML-страницы, JavaScript-бандлы, изображения и другие файлы, наряду с кодом вашего Worker. Это полезно, когда Dynamic Worker должен обслуживать full-stack-приложение.
Статические ресурсы для Dynamic Workers работают иначе, чем статические ресурсы в обычных Workers. Вместо загрузки ресурсов во время развёртывания вы предоставляете их во время выполнения через Worker Loader get() колбэк, получая их из R2, KV или другого хранилища.
Как это работает
Настройка статических ресурсов для Dynamic Workers состоит из трёх частей:
- Хранение ресурсов : Загрузите статические файлы в пространство имён KV с ключами по ID проекта и пути к файлу.
- Определение привязки asset в Worker-загрузчике : Создайте класс, который обрабатывает запросы статических файлов, читая их из KV и возвращая с правильными заголовками.
- Передайте привязку в Dynamic Worker : Dynamic Worker использует его для отдачи статических файлов, вызывая
env.ASSETS.fetch(request).
Хранение статических ресурсов
Статические ресурсы хранятся в пространстве имен KV, разделенном по ID проекта, поэтому файлы каждого проекта изолированы друг от друга:
project/{projectId}/assets/index.html → file content
project/{projectId}/assets/app.js → file content
project/{projectId}/manifest → asset manifestКогда пользователь развёртывает свой проект через API загрузки вашей платформы, сохраняйте каждый файл в KV, используя его путь как ключ:
await env.KV_ASSETS.put(`project/${projectId}/assets${pathname}`, fileContent);Также нужно сохранить манифест: соответствие, которое сообщает обработчику ресурсов, какие файлы существуют и какой у них тип содержимого. Используйте buildAssetManifest() от @cloudflare/worker-bundler чтобы сгенерировать его из ваших ресурсов:
import { buildAssetManifest } from "@cloudflare/worker-bundler";
const assets = {
"/index.html": htmlContent,
"/app.js": jsContent,
"/style.css": cssContent,
};
const manifest = await buildAssetManifest(assets);
await env.KV_ASSETS.put(
`project/${projectId}/manifest`,
JSON.stringify(manifest),
);import { buildAssetManifest } from "@cloudflare/worker-bundler";
const assets = {
"/index.html": htmlContent,
"/app.js": jsContent,
"/style.css": cssContent,
};
const manifest = await buildAssetManifest(assets);
await env.KV_ASSETS.put(
`project/${projectId}/manifest`,
JSON.stringify(manifest),
);Добавление привязок в Worker-загрузчик
Предоставьте Worker-загрузчику доступ к пространству имён KV, в котором хранятся ресурсы:
{
"worker_loaders": [{ "binding": "LOADER" }],
"kv_namespaces": [
{
"binding": "KV_ASSETS",
"id": "<your-kv-namespace-id>",
},
],
}[[worker_loaders]]
binding = "LOADER"
[[kv_namespaces]]
binding = "KV_ASSETS"
id = "<your-kv-namespace-id>"Определение привязки asset
Создайте в Worker-загрузчике класс, который расширяет WorkerEntrypoint и определите fetch() метод. WorkerEntrypoint делает этот метод доступным для вызова из Dynamic Worker через RPC. Когда Dynamic Worker вызывает env.ASSETS.fetch(request), он выполняет этот метод в Worker-загрузчике, где находятся привязка KV и логика отдачи ресурсов.
Класс принимает projectId prop, чтобы он знал, ресурсы какого проекта искать. Когда fetch() вызывается, он:
- Загружает манифест ресурсов проекта из KV.
- Сопоставляет путь запроса с файлом.
- Получает содержимое файла из KV.
- Возвращает
Responseс правильнымContent-Typeзаголовок.
Используйте @cloudflare/worker-bundler для раздачи статических ресурсов
Вместо того чтобы писать собственную логику сопоставления путей запросов с файлами, определения типов содержимого и установки заголовков кеширования, используйте @cloudflare/worker-bundler пакет для раздачи статических ресурсов. В вашем fetch() методе передайте handleAssetRequest() две вещи:
- A манифест, сопоставление пути и типа содержимого, которое вы сохранили в KV при загрузке, построенное с помощью
buildAssetManifest(). Это сообщаетhandleAssetRequest()какие файлы существуют и каковы их типы содержимого. - A объект хранилища, сообщает
handleAssetRequest()как читать файлы из вашего пространства имён KV. У него один метод,get(pathname), который читает и возвращает содержимое для указанного пути к файлу.
handleAssetRequest() отдаёт файл, если находит совпадение в манифесте, с корректными заголовками для типа содержимого и кеширования.
import { WorkerEntrypoint } from "cloudflare:workers";
import { handleAssetRequest } from "@cloudflare/worker-bundler";
export class AssetBinding extends WorkerEntrypoint {
async fetch(request) {
const { projectId } = this.ctx.props;
// Load the project's asset manifest from KV
const manifest = await this.env.KV_ASSETS.get(
`project/${projectId}/manifest`,
{ type: "json", cacheTtl: 300 },
);
if (!manifest) {
return new Response("No assets found", { status: 404 });
}
// Storage object — handleAssetRequest calls get() to
// read file content when it needs to serve an asset
const storage = {
async get(pathname) {
return this.env.KV_ASSETS.get(
`project/${projectId}/assets${pathname}`,
{ type: "arrayBuffer", cacheTtl: 86_400 },
);
},
};
const response = await handleAssetRequest(request, manifest, storage);
return response ?? new Response("Not Found", { status: 404 });
}
}import { WorkerEntrypoint } from "cloudflare:workers";
import { handleAssetRequest } from "@cloudflare/worker-bundler";
export class AssetBinding extends WorkerEntrypoint {
async fetch(request: Request) {
const { projectId } = this.ctx.props;
// Load the project's asset manifest from KV
const manifest = await this.env.KV_ASSETS.get(
`project/${projectId}/manifest`,
{ type: "json", cacheTtl: 300 },
);
if (!manifest) {
return new Response("No assets found", { status: 404 });
}
// Storage object — handleAssetRequest calls get() to
// read file content when it needs to serve an asset
const storage = {
async get(pathname: string) {
return this.env.KV_ASSETS.get(
`project/${projectId}/assets${pathname}`,
{ type: "arrayBuffer", cacheTtl: 86_400 },
);
},
};
const response = await handleAssetRequest(request, manifest, storage);
return response ?? new Response("Not Found", { status: 404 });
}
}Как только AssetBinding экспортируется, он становится доступен в ctx.exports у Worker-загрузчика в fetch() обработчик. ctx является третьим параметром обработчика, после request и env. Именно так вы передаёте его Dynamic Worker на следующем шаге.
Передайте привязку ресурсов в Dynamic Worker
Когда вы вызываете get() для создания Dynamic Worker включите AssetBinding в env объект, чтобы Dynamic Worker мог использовать его для раздачи статических файлов. Чтобы сослаться на AssetBinding класс, который вы определили на предыдущем шаге, используйте ctx.exports.AssetBinding() и передайте projectId как prop, чтобы он знал, ресурсы какого проекта обслуживать. Это работает так же, как и с пользовательскими привязками: props используется для передачи информации классу, а класс считывает её в this.ctx.props при выполнении.
export default {
async fetch(request, env, ctx) {
const projectId = getProjectIdFromRequest(request);
const worker = env.LOADER.get(projectId, async () => {
const serverCode = await loadServerCode(projectId);
return {
mainModule: "index.js",
modules: {
"index.js": { js: serverCode },
},
compatibilityDate: "2026-08-28",
env: {
ASSETS: ctx.exports.AssetBinding({
props: { projectId },
}),
},
};
});
return await worker.getEntrypoint().fetch(request);
},
};export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext) {
const projectId = getProjectIdFromRequest(request);
const worker = env.LOADER.get(projectId, async () => {
const serverCode = await loadServerCode(projectId);
return {
mainModule: "index.js",
modules: {
"index.js": { js: serverCode },
},
compatibilityDate: "$today",
env: {
ASSETS: ctx.exports.AssetBinding({
props: { projectId },
}),
},
};
});
return await worker.getEntrypoint().fetch(request);
},
};Dynamic Worker видит ASSETS как привязку и может вызывать env.ASSETS.fetch(request) потому что это метод, который вы определили в AssetBinding. Когда Dynamic Worker вызывает этот метод, он выполняется в Worker-загрузчике, где находится ваш AssetBinding класс считывает манифест и содержимое файлов из KV.
Использование привязки ресурсов в Dynamic Worker
С точки зрения Dynamic Worker, env.ASSETS работает как любая другая привязка. Пользователь пишет код своего сервера и вызывает env.ASSETS.fetch() для раздачи статических файлов:
// Inside the Dynamic Worker
export default {
async fetch(request, env) {
const url = new URL(request.url);
// Handle API routes directly
if (url.pathname.startsWith("/api/")) {
return Response.json({ hello: "world" });
}
// Everything else — serve static assets
return env.ASSETS.fetch(request);
},
};// Inside the Dynamic Worker
export default {
async fetch(request: Request, env: Env) {
const url = new URL(request.url);
// Handle API routes directly
if (url.pathname.startsWith("/api/")) {
return Response.json({ hello: "world" });
}
// Everything else — serve static assets
return env.ASSETS.fetch(request);
},
};Когда Dynamic Worker вызывает env.ASSETS.fetch(request), вызов передаётся по RPC в Worker-загрузчик, к его AssetBinding, который ищет файл в манифесте и читает его из KV. Dynamic Worker не нужно ничего этого обрабатывать: он просто вызывает env.ASSETS.fetch(request) и получает файл с корректными заголовками, готовый для возврата клиенту.