Skip to content

Commit 30d1f46

Browse files
feat: use the new Workers Static Assets feature from Cloudflare
This changes the adapter to stop using the old Workers Sites (kv-asset-handler) approach. Instead, making use of the new Workers Static Assets feature, which is embedded into Cloudflare natively. Also this change removes the extra esbuild step that was being run inside the adapter, relying upon Wrangler to do the bundling. The extra esbuild step required a hardcoded list of Node.js compatible modules. This is no longer needed since Wrangler now manages all of that. - This version of the adapter requires Wrangler version 3.87.0 or later. Run `npm add -D wrangler@latest` (or similar) in your project to update Wrangler. - The user's Wrangler configuration (`wrangler.toml`) must be migrated from using Workers Sites to using Workers Assets. Previously a user's `wrangler.toml` might look like: ```toml name = "<your-site-name>" account_id = "<your-account-id>" compatibility_date = "2021-11-12" main = "./.cloudflare/worker.js" # Workers Sites configuration site.bucket = "./.cloudflare/public" ``` Change it to to look like: ```toml name = "<your-site-name>" account_id = "<your-account-id>" compatibility_date = "2021-11-12"` main = ".svelte-kit/cloudflare/server/index.js" # Workers Assets configuration assets = { directory = ".svelte-kit/cloudflare/client" } ``` - Workers Assets defaults to serving assets directly for a matching request, rather than routing it through the Worker code. The previous adapter would add custom headers to assets responses (such as `cache-control`, `content-type`, and `x-robots-tag`. Such direct asset responses no longer contain these headers - but the will include eTag headers that have proven (in Pages) to be an effective caching strategy for assets. If you wish to always run the Worker before every request then add `serve_directly = false` to the assets configuration section. For example: ```toml assets = { directory = ".svelte-kit/cloudflare/client", serve_directly = false } ```
1 parent 7246a54 commit 30d1f46

File tree

8 files changed

+260
-182
lines changed

8 files changed

+260
-182
lines changed

.changeset/smart-owls-trade.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
---
2+
'@sveltejs/adapter-cloudflare-workers': major
3+
---
4+
5+
feat: use the new Workers Static Assets feature from Cloudflare
6+
7+
This changes the adapter to stop using the old Workers Sites (kv-asset-handler) approach.
8+
Instead, making use of the new Workers Static Assets feature, which is embedded into Cloudflare natively.
9+
10+
Also this change removes the extra esbuild step that was being run inside the adapter, relying upon Wrangler to do the bundling.
11+
The extra esbuild step required a hardcoded list of Node.js compatible modules.
12+
This is no longer needed since Wrangler now manages all of that.
13+
14+
## Breaking changes and migration
15+
16+
- This version of the adapter requires Wrangler version 3.87.0 or later.
17+
18+
Run `npm add -D wrangler@latest` (or similar) in your project to update Wrangler.
19+
- The user's Wrangler configuration (`wrangler.toml`) must be migrated from using Workers Sites to using Workers Assets.
20+
21+
Previously a user's `wrangler.toml` might look like:
22+
23+
```toml
24+
name = "<your-site-name>"
25+
account_id = "<your-account-id>"
26+
compatibility_date = "2021-11-12"
27+
main = "./.cloudflare/worker.js"
28+
29+
# Workers Sites configuration
30+
site.bucket = "./.cloudflare/public"
31+
```
32+
33+
Change it to to look like:
34+
35+
```toml
36+
name = "<your-site-name>"
37+
account_id = "<your-account-id>"
38+
compatibility_date = "2021-11-12"`
39+
main = ".svelte-kit/cloudflare/server/index.js"
40+
41+
# Workers Assets configuration
42+
assets = { directory = ".svelte-kit/cloudflare/client" }
43+
```
44+
45+
- Workers Assets defaults to serving assets directly for a matching request, rather than routing it through the Worker code.
46+
47+
The previous adapter would add custom headers to assets responses (such as `cache-control`, `content-type`, and `x-robots-tag`. Such direct asset responses no longer contain these headers - but the will include eTag headers that have proven (in Pages) to be an effective caching strategy for assets.
48+
49+
If you wish to always run the Worker before every request then add `serve_directly = false` to the assets configuration section. For example:
50+
51+
```toml
52+
assets = { directory = ".svelte-kit/cloudflare/client", serve_directly = false }
53+
```

packages/adapter-cloudflare-workers/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
SvelteKit adapter that creates a Cloudflare Workers site using a function for dynamic server rendering.
44

5-
**Requires [Wrangler v2](https://developers.cloudflare.com/workers/wrangler/get-started/).** Wrangler v1 is no longer supported.
5+
**Requires [Wrangler v3 or later](https://developers.cloudflare.com/workers/wrangler/get-started/).**.
66

77
## Docs
88

packages/adapter-cloudflare-workers/files/_package.json

-9
This file was deleted.

packages/adapter-cloudflare-workers/files/entry.js

+6-39
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { Server } from 'SERVER';
22
import { manifest, prerendered, base_path } from 'MANIFEST';
3-
import { getAssetFromKV, mapRequestToAsset } from '@cloudflare/kv-asset-handler';
4-
import static_asset_manifest_json from '__STATIC_CONTENT_MANIFEST';
5-
const static_asset_manifest = JSON.parse(static_asset_manifest_json);
63

74
const server = new Server(manifest);
85

@@ -25,7 +22,7 @@ export default {
2522
// static assets
2623
if (url.pathname.startsWith(app_path)) {
2724
/** @type {Response} */
28-
const res = await get_asset_from_kv(req, env, context);
25+
const res = await env.ASSETS.fetch(req);
2926
if (is_error(res.status)) return res;
3027

3128
const cache_control = url.pathname.startsWith(immutable)
@@ -65,20 +62,11 @@ export default {
6562

6663
let location = pathname.at(-1) === '/' ? stripped_pathname : pathname + '/';
6764

68-
if (
69-
is_static_asset ||
70-
prerendered.has(pathname) ||
71-
pathname === version_file ||
72-
pathname.startsWith(immutable)
73-
) {
74-
return get_asset_from_kv(req, env, context, (request, options) => {
75-
if (prerendered.has(pathname)) {
76-
url.pathname = '/' + prerendered.get(pathname).file;
77-
return new Request(url.toString(), request);
78-
}
79-
80-
return mapRequestToAsset(request, options);
81-
});
65+
if (prerendered.has(pathname)) {
66+
url.pathname = '/' + prerendered.get(pathname).file;
67+
return env.ASSETS.fetch(new Request(url.toString(), req));
68+
} else if (is_static_asset || pathname === version_file || pathname.startsWith(immutable)) {
69+
return env.ASSETS.fetch(req);
8270
} else if (location && prerendered.has(location)) {
8371
if (search) location += search;
8472
return new Response('', {
@@ -106,27 +94,6 @@ export default {
10694
}
10795
};
10896

109-
/**
110-
* @param {Request} req
111-
* @param {any} env
112-
* @param {any} context
113-
*/
114-
async function get_asset_from_kv(req, env, context, map = mapRequestToAsset) {
115-
return await getAssetFromKV(
116-
{
117-
request: req,
118-
waitUntil(promise) {
119-
return context.waitUntil(promise);
120-
}
121-
},
122-
{
123-
ASSET_NAMESPACE: env.__STATIC_CONTENT,
124-
ASSET_MANIFEST: static_asset_manifest,
125-
mapRequestToAsset: map
126-
}
127-
);
128-
}
129-
13097
/**
13198
* @param {number} status
13299
* @returns {boolean}

packages/adapter-cloudflare-workers/index.js

+25-117
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,64 @@
11
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
22
import { posix, dirname } from 'node:path';
3-
import { execSync } from 'node:child_process';
4-
import esbuild from 'esbuild';
53
import toml from '@iarna/toml';
64
import { fileURLToPath } from 'node:url';
75
import { getPlatformProxy } from 'wrangler';
86

97
/**
108
* @typedef {{
119
* main: string;
12-
* site: {
13-
* bucket: string;
10+
* assets: {
11+
* directory: string;
12+
* binding: string;
1413
* }
15-
* compatibility_flags?: string[];
1614
* }} WranglerConfig
1715
*/
1816

19-
// list from https://developers.cloudflare.com/workers/runtime-apis/nodejs/
20-
const compatible_node_modules = [
21-
'assert',
22-
'async_hooks',
23-
'buffer',
24-
'crypto',
25-
'diagnostics_channel',
26-
'events',
27-
'path',
28-
'process',
29-
'stream',
30-
'string_decoder',
31-
'util'
32-
];
33-
3417
/** @type {import('./index.js').default} */
3518
export default function ({ config = 'wrangler.toml', platformProxy = {} } = {}) {
3619
return {
3720
name: '@sveltejs/adapter-cloudflare-workers',
3821

39-
async adapt(builder) {
40-
const { main, site, compatibility_flags } = validate_config(builder, config);
41-
22+
adapt(builder) {
23+
const { main, assets } = validate_config(builder, config);
4224
const files = fileURLToPath(new URL('./files', import.meta.url).href);
43-
const tmp = builder.getBuildDirectory('cloudflare-workers-tmp');
44-
45-
builder.rimraf(site.bucket);
46-
builder.rimraf(dirname(main));
47-
48-
builder.log.info('Installing worker dependencies...');
49-
builder.copy(`${files}/_package.json`, `${tmp}/package.json`);
50-
51-
// TODO would be cool if we could make this step unnecessary somehow
52-
const stdout = execSync('npm install', { cwd: tmp });
53-
builder.log.info(stdout.toString());
25+
const outDir = dirname(main);
26+
const relativePath = posix.relative(outDir, builder.getServerDirectory());
5427

5528
builder.log.minor('Generating worker...');
56-
const relativePath = posix.relative(tmp, builder.getServerDirectory());
5729

58-
builder.copy(`${files}/entry.js`, `${tmp}/entry.js`, {
30+
// Clear out old files
31+
builder.rimraf(assets.directory);
32+
builder.rimraf(outDir);
33+
34+
// Create the entry-point for the Worker
35+
builder.copy(`${files}/entry.js`, main, {
5936
replace: {
6037
SERVER: `${relativePath}/index.js`,
61-
MANIFEST: './manifest.js'
38+
MANIFEST: './manifest.js',
39+
ASSETS: assets.binding || 'ASSETS'
6240
}
6341
});
6442

43+
// Create the manifest for the Worker
6544
let prerendered_entries = Array.from(builder.prerendered.pages.entries());
66-
6745
if (builder.config.kit.paths.base) {
6846
prerendered_entries = prerendered_entries.map(([path, { file }]) => [
6947
path,
7048
{ file: `${builder.config.kit.paths.base}/${file}` }
7149
]);
7250
}
73-
7451
writeFileSync(
75-
`${tmp}/manifest.js`,
52+
`${outDir}/manifest.js`,
7653
`export const manifest = ${builder.generateManifest({ relativePath })};\n\n` +
7754
`export const prerendered = new Map(${JSON.stringify(prerendered_entries)});\n\n` +
7855
`export const base_path = ${JSON.stringify(builder.config.kit.paths.base)};\n`
7956
);
8057

81-
const external = ['__STATIC_CONTENT_MANIFEST', 'cloudflare:*'];
82-
if (compatibility_flags && compatibility_flags.includes('nodejs_compat')) {
83-
external.push(...compatible_node_modules.map((id) => `node:${id}`));
84-
}
85-
86-
try {
87-
const result = await esbuild.build({
88-
platform: 'browser',
89-
// https://github.com/cloudflare/workers-sdk/blob/a12b2786ce745f24475174bcec994ad691e65b0f/packages/wrangler/src/deployment-bundle/bundle.ts#L35-L36
90-
conditions: ['workerd', 'worker', 'browser'],
91-
sourcemap: 'linked',
92-
target: 'es2022',
93-
entryPoints: [`${tmp}/entry.js`],
94-
outfile: main,
95-
bundle: true,
96-
external,
97-
alias: Object.fromEntries(compatible_node_modules.map((id) => [id, `node:${id}`])),
98-
format: 'esm',
99-
loader: {
100-
'.wasm': 'copy',
101-
'.woff': 'copy',
102-
'.woff2': 'copy',
103-
'.ttf': 'copy',
104-
'.eot': 'copy',
105-
'.otf': 'copy'
106-
},
107-
logLevel: 'silent'
108-
});
109-
110-
if (result.warnings.length > 0) {
111-
const formatted = await esbuild.formatMessages(result.warnings, {
112-
kind: 'warning',
113-
color: true
114-
});
115-
116-
console.error(formatted.join('\n'));
117-
}
118-
} catch (error) {
119-
for (const e of error.errors) {
120-
for (const node of e.notes) {
121-
const match =
122-
/The package "(.+)" wasn't found on the file system but is built into node/.exec(
123-
node.text
124-
);
125-
126-
if (match) {
127-
node.text = `Cannot use "${match[1]}" when deploying to Cloudflare.`;
128-
}
129-
}
130-
}
131-
132-
const formatted = await esbuild.formatMessages(error.errors, {
133-
kind: 'error',
134-
color: true
135-
});
136-
137-
console.error(formatted.join('\n'));
138-
139-
throw new Error(
140-
`Bundling with esbuild failed with ${error.errors.length} ${
141-
error.errors.length === 1 ? 'error' : 'errors'
142-
}`
143-
);
144-
}
145-
14658
builder.log.minor('Copying assets...');
147-
const bucket_dir = `${site.bucket}${builder.config.kit.paths.base}`;
148-
builder.writeClient(bucket_dir);
149-
builder.writePrerendered(bucket_dir);
59+
const assets_dir = `${assets.directory}${builder.config.kit.paths.base}`;
60+
builder.writeClient(assets_dir);
61+
builder.writePrerendered(assets_dir);
15062
},
15163

15264
async emulate() {
@@ -198,9 +110,9 @@ function validate_config(builder, config_file) {
198110
throw err;
199111
}
200112

201-
if (!wrangler_config.site?.bucket) {
113+
if (!wrangler_config.assets?.directory) {
202114
throw new Error(
203-
`You must specify site.bucket in ${config_file}. Consult https://developers.cloudflare.com/workers/platform/sites/configuration`
115+
`You must specify assets.directory in ${config_file}. Consult https://developers.cloudflare.com/workers/platform/sites/configuration`
204116
);
205117
}
206118

@@ -223,14 +135,10 @@ function validate_config(builder, config_file) {
223135
224136
name = "<your-site-name>"
225137
account_id = "<your-account-id>"
226-
227-
main = "./.cloudflare/worker.js"
228-
site.bucket = "./.cloudflare/public"
229-
138+
main = ".svelte-kit/cloudflare/server/index.js"
139+
assets = { directory = ".svelte-kit/cloudflare/client" }
230140
build.command = "npm run build"
231-
232-
compatibility_date = "2021-11-12"
233-
workers_dev = true`
141+
compatibility_date = "2021-11-12"`
234142
.replace(/^\t+/gm, '')
235143
.trim()
236144
);

packages/adapter-cloudflare-workers/package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,12 @@
4343
"esbuild": "^0.21.5"
4444
},
4545
"devDependencies": {
46-
"@cloudflare/kv-asset-handler": "^0.3.0",
4746
"@sveltejs/kit": "workspace:^",
4847
"@types/node": "^18.19.48",
4948
"typescript": "^5.3.3"
5049
},
5150
"peerDependencies": {
5251
"@sveltejs/kit": "^2.0.0",
53-
"wrangler": "^3.28.4"
52+
"wrangler": "^3.87.0"
5453
}
5554
}

packages/adapter-cloudflare-workers/placeholders.d.ts

-5
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,3 @@ declare module 'MANIFEST' {
99
export const prerendered: Map<string, { file: string }>;
1010
export const base_path: string;
1111
}
12-
13-
declare module '__STATIC_CONTENT_MANIFEST' {
14-
const json: string;
15-
export default json;
16-
}

0 commit comments

Comments
 (0)