Skip to content

Commit 412866d

Browse files
committed
feat: able to fully setup cloudflare workers/pages
1 parent 88d5c9d commit 412866d

File tree

7 files changed

+182
-3
lines changed

7 files changed

+182
-3
lines changed

.changeset/fifty-cobras-wish.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'sv': minor
3+
---
4+
5+
feat: able to fully setup cloudflare workers/pages

packages/sv/lib/addons/sveltekit-adapter/index.ts

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { defineAddon, defineAddonOptions } from '../../core/index.ts';
2-
import { exports, functions, imports, object } from '../../core/tooling/js/index.ts';
3-
import { parseJson, parseScript } from '../../core/tooling/parsers.ts';
2+
import { exports, functions, imports, object, type AstTypes } from '../../core/tooling/js/index.ts';
3+
import { parseJson, parseScript, parseToml } from '../../core/tooling/parsers.ts';
4+
import { fileExists, readFile } from '../../cli/add/utils.ts';
5+
import { resolveCommand } from 'package-manager-detector';
6+
import * as js from '../../core/tooling/js/index.ts';
47

58
const adapters = [
69
{ id: 'auto', package: '@sveltejs/adapter-auto', version: '^7.0.0' },
@@ -18,6 +21,16 @@ const options = defineAddonOptions()
1821
default: 'auto',
1922
options: adapters.map((p) => ({ value: p.id, label: p.id, hint: p.package }))
2023
})
24+
.add('cfTarget', {
25+
condition: (options) => options.adapter === 'cloudflare',
26+
type: 'select',
27+
question: 'Are you deploying to Workers (assets) or Pages?',
28+
default: 'workers',
29+
options: [
30+
{ value: 'workers', label: 'Workers', hint: 'Recommended way to deploy to Cloudflare' },
31+
{ value: 'pages', label: 'Pages' }
32+
]
33+
})
2134
.build();
2235

2336
export default defineAddon({
@@ -29,7 +42,7 @@ export default defineAddon({
2942
setup: ({ kit, unsupported }) => {
3043
if (!kit) unsupported('Requires SvelteKit');
3144
},
32-
run: ({ sv, options, files }) => {
45+
run: ({ sv, options, files, cwd, packageManager, typescript }) => {
3346
const adapter = adapters.find((a) => a.id === options.adapter)!;
3447

3548
// removes previously installed adapters
@@ -99,5 +112,135 @@ export default defineAddon({
99112

100113
return generateCode();
101114
});
115+
116+
if (adapter.package === '@sveltejs/adapter-cloudflare') {
117+
sv.devDependency('wrangler', 'latest');
118+
119+
// default to jsonc
120+
const configFormat = fileExists(cwd, 'wrangler.toml') ? 'toml' : 'jsonc';
121+
122+
// Setup Cloudlfare workers/pages config
123+
sv.file(`wrangler.${configFormat}`, (content) => {
124+
const { data, generateCode } =
125+
configFormat === 'jsonc' ? parseJson(content) : parseToml(content);
126+
127+
if (configFormat === 'jsonc') {
128+
data.$schema ??= './node_modules/wrangler/config-schema.json';
129+
}
130+
131+
if (!data.name) {
132+
const pkg = parseJson(readFile(cwd, files.package));
133+
data.name = pkg.data.name;
134+
}
135+
136+
data.compatibility_date ??= new Date().toISOString().split('T')[0];
137+
data.compatibility_flags ??= [];
138+
139+
if (
140+
!data.compatibility_flags.includes('nodejs_compat') &&
141+
!data.compatibility_flags.includes('nodejs_als')
142+
) {
143+
data.compatibility_flags.push('nodejs_als');
144+
}
145+
146+
switch (options.cfTarget) {
147+
case 'workers':
148+
data.main = '.svelte-kit/cloudflare/_worker.js';
149+
data.assets ??= {};
150+
data.assets.binding = 'ASSETS';
151+
data.assets.directory = '.svelte-kit/cloudflare';
152+
data.workers_dev = true;
153+
data.preview_urls = true;
154+
break;
155+
156+
case 'pages':
157+
data.pages_build_output_dir = '.svelte-kit/cloudflare';
158+
break;
159+
}
160+
161+
return generateCode();
162+
});
163+
164+
const jsconfig = fileExists(cwd, 'jsconfig.json');
165+
const typeChecked = typescript || jsconfig;
166+
167+
if (typeChecked) {
168+
// Ignore generated Cloudflare Types
169+
sv.file(files.gitignore, (content) => {
170+
return content.includes('.wrangler') && content.includes('worker-configuration.d.ts')
171+
? content
172+
: `${content.trimEnd()}\n\n# Cloudflare Types\n/worker-configuration.d.ts`;
173+
});
174+
175+
// Setup wrangler types command
176+
sv.file(files.package, (content) => {
177+
const { data, generateCode } = parseJson(content);
178+
179+
data.scripts ??= {};
180+
data.scripts.types = 'wrangler types';
181+
const { command, args } = resolveCommand(packageManager, 'run', ['types'])!;
182+
data.scripts.prepare = data.scripts.prepare
183+
? `${command} ${args.join(' ')} && ${data.scripts.prepare}`
184+
: `${command} ${args.join(' ')}`;
185+
186+
return generateCode();
187+
});
188+
189+
// Add Cloudflare generated types to tsconfig
190+
sv.file(`${jsconfig ? 'jsconfig' : 'tsconfig'}.json`, (content) => {
191+
const { data, generateCode } = parseJson(content);
192+
193+
data.compilerOptions ??= {};
194+
data.compilerOptions.types ??= [];
195+
data.compilerOptions.types.push('worker-configuration.d.ts');
196+
197+
return generateCode();
198+
});
199+
200+
sv.file('src/app.d.ts', (content) => {
201+
const { ast, generateCode } = parseScript(content);
202+
203+
const platform = js.kit.addGlobalAppInterface(ast, { name: 'Platform' });
204+
if (!platform) {
205+
throw new Error('Failed detecting `platform` interface in `src/app.d.ts`');
206+
}
207+
208+
platform.body.body.push(
209+
createCloudflarePlatformType('env', 'Env'),
210+
createCloudflarePlatformType('ctx', 'ExecutionContext'),
211+
createCloudflarePlatformType('caches', 'CacheStorage'),
212+
createCloudflarePlatformType('cf', 'IncomingRequestCfProperties', true)
213+
);
214+
215+
return generateCode();
216+
});
217+
}
218+
}
102219
}
103220
});
221+
222+
function createCloudflarePlatformType(
223+
name: string,
224+
value: string,
225+
optional = false
226+
): AstTypes.TSInterfaceBody['body'][number] {
227+
return {
228+
type: 'TSPropertySignature',
229+
key: {
230+
type: 'Identifier',
231+
name
232+
},
233+
computed: false,
234+
optional,
235+
typeAnnotation: {
236+
type: 'TSTypeAnnotation',
237+
typeAnnotation: {
238+
type: 'TSTypeReference',
239+
typeName: {
240+
type: 'Identifier',
241+
name: value
242+
}
243+
}
244+
}
245+
};
246+
}

packages/sv/lib/core/tooling/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { tsPlugin } from '@sveltejs/acorn-typescript';
88
import { parse as svelteParse, type AST as SvelteAst, print as sveltePrint } from 'svelte/compiler';
99
import * as yaml from 'yaml';
1010
import type { BaseNode } from 'estree';
11+
import * as toml from 'smol-toml';
1112

1213
export {
1314
// ast walker
@@ -288,3 +289,11 @@ export function parseSvelte(content: string): SvelteAst.Root {
288289
export function serializeSvelte(ast: SvelteAst.SvelteNode): string {
289290
return sveltePrint(ast).code;
290291
}
292+
293+
export function parseToml(content: string): toml.TomlTable {
294+
return toml.parse(content);
295+
}
296+
297+
export function serializeToml(data: toml.TomlTable): string {
298+
return toml.stringify(data);
299+
}

packages/sv/lib/core/tooling/js/ts-estree.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ declare module 'estree' {
4646
type: 'TSPropertySignature';
4747
computed: boolean;
4848
key: Identifier;
49+
optional?: boolean;
4950
typeAnnotation: TSTypeAnnotation;
5051
}
5152
interface TSProgram extends Omit<Program, 'body'> {

packages/sv/lib/core/tooling/parsers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { TomlTable } from 'smol-toml';
12
import * as utils from './index.ts';
23

34
type ParseBase = {
@@ -57,3 +58,13 @@ export function parseSvelte(source: string): { ast: utils.SvelteAst.Root } & Par
5758
generateCode
5859
};
5960
}
61+
62+
export function parseToml(source: string): { data: TomlTable } & ParseBase {
63+
const data = utils.parseToml(source);
64+
65+
return {
66+
data,
67+
source,
68+
generateCode: () => utils.serializeToml(data)
69+
};
70+
}

packages/sv/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"picocolors": "^1.1.1",
5454
"ps-tree": "^1.2.0",
5555
"silver-fleece": "^1.2.1",
56+
"smol-toml": "^1.5.2",
5657
"sucrase": "^3.35.0",
5758
"svelte": "^5.45.9",
5859
"tiny-glob": "^0.2.9",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)