INTEGRITY Документация

Static Assets

Dynamic Workers могут отдавать статические ресурсы, такие как HTML-страницы, JavaScript-бандлы, изображения и другие файлы, наряду с кодом вашего Worker. Это полезно, когда Dynamic Worker должен обслуживать full-stack-приложение.

Статические ресурсы для Dynamic Workers работают иначе, чем статические ресурсы в обычных Workers. Вместо загрузки ресурсов во время развёртывания вы предоставляете их во время выполнения через Worker Loader get() колбэк, получая их из R2, KV или другого хранилища.

Как это работает

Настройка статических ресурсов для Dynamic Workers состоит из трёх частей:

  1. Хранение ресурсов : Загрузите статические файлы в пространство имён KV с ключами по ID проекта и пути к файлу.
  2. Определение привязки asset в Worker-загрузчике : Создайте класс, который обрабатывает запросы статических файлов, читая их из KV и возвращая с правильными заголовками.
  3. Передайте привязку в 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() вызывается, он:

  1. Загружает манифест ресурсов проекта из KV.
  2. Сопоставляет путь запроса с файлом.
  3. Получает содержимое файла из KV.
  4. Возвращает Response с правильным Content-Type заголовок.

Используйте @cloudflare/worker-bundler для раздачи статических ресурсов

Вместо того чтобы писать собственную логику сопоставления путей запросов с файлами, определения типов содержимого и установки заголовков кеширования, используйте @cloudflare/worker-bundler пакет для раздачи статических ресурсов. В вашем fetch() методе передайте handleAssetRequest() две вещи:

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) и получает файл с корректными заголовками, готовый для возврата клиенту.