@oselvar/sveltekit-add-worker-exports 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/index.d.ts +28 -0
- package/dist/index.js +144 -54
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
A Vite plugin that makes any class-based Cloudflare Worker export (Durable Objects, Workflows, `WorkerEntrypoint` RPC, voice agents, …) and any non-fetch handler (`scheduled`, `queue`, `email`, `tail`, `trace`) work with SvelteKit on Cloudflare, in both dev and production.
|
|
4
4
|
|
|
5
|
+
Works with **SvelteKit 2 and SvelteKit 3** (see [`example/`](example) and [`example-v3/`](example-v3)).
|
|
6
|
+
|
|
5
7
|
**Build mode:** SvelteKit's `adapter-cloudflare` generates `_worker.js` with only a default export (the fetch handler). Cloudflare Workers requires class-based bindings (Durable Objects, Workflows, `WorkerEntrypoint`, etc.) to be **named exports**, and non-fetch handlers (`scheduled`, `queue`, `email`, …) to be **methods on the default export**. This plugin post-processes the build output to merge both kinds onto SvelteKit's worker.
|
|
6
8
|
|
|
7
9
|
**Dev mode:** `getPlatformProxy` (used by `adapter-cloudflare` in dev) can't run internal Durable Objects, Workflows, or other class-based bindings. This plugin starts a separate wrangler dev server that runs the real worker with hot-reload. SvelteKit `+server.ts` handlers call bindings through `platform.env.MY_BINDING.<rpc>()` as usual — the plugin rewrites those bindings to point at the sidecar via wrangler's dev registry, so cross-worker calls Just Work. WebSocket clients in dev connect to the sidecar directly on a separate port — see [WebSockets](#websockets) below. The sidecar also exposes `/__scheduled` so `scheduled` handlers can be fired manually in dev — see [Scheduled, queue, email, tail handlers](#scheduled-queue-email-tail-handlers).
|
|
@@ -228,7 +230,7 @@ declare global {
|
|
|
228
230
|
|
|
229
231
|
### Build mode
|
|
230
232
|
|
|
231
|
-
The plugin runs
|
|
233
|
+
The plugin runs after SvelteKit's adapter has generated `_worker.js`. SvelteKit v2 runs the adapter in Vite's `closeBundle` hook, while SvelteKit v3 moved it into the newer `buildApp` hook (which runs *after* every `closeBundle`). The plugin registers its patch step on **both** hooks (idempotent), so whichever one runs after the adapter does the work:
|
|
232
234
|
|
|
233
235
|
1. Bundles your `entryPoint` with esbuild into `_extra_exports.js`
|
|
234
236
|
2. Renames the original `_worker.js` to `_sveltekit_worker.js`
|
|
@@ -242,7 +244,9 @@ const { fetch: _ignored, ...extraHandlers } = extra.default ?? {};
|
|
|
242
244
|
export default { ...sveltekitWorker, ...extraHandlers };
|
|
243
245
|
```
|
|
244
246
|
|
|
245
|
-
|
|
247
|
+
4. Appends its own outputs (`_sveltekit_worker.js`, `_extra_exports.js`, and their `.map` sidecars) to the adapter's `.assetsignore`. These are server-side / Durable Object bundles and source maps; the adapter's generated `.assetsignore` only excludes its own outputs, so without this Cloudflare would serve them as public static assets (the maps' `sourcesContent` would expose your original TypeScript). `.assetsignore` only controls public asset serving — the files stay on disk, so wrangler still bundles the worker and uploads its source maps.
|
|
248
|
+
|
|
249
|
+
The operation is idempotent -- if `_sveltekit_worker.js` already exists, the second hook skips. Tested against SvelteKit v2 (see [`example/`](example)) and v3 (see [`example-v3/`](example-v3)).
|
|
246
250
|
|
|
247
251
|
### Dev mode
|
|
248
252
|
|
package/dist/index.d.ts
CHANGED
|
@@ -29,6 +29,23 @@ import type { Plugin } from 'vite';
|
|
|
29
29
|
* Exported for tests; the dev plugin uses it via the configureServer hook.
|
|
30
30
|
*/
|
|
31
31
|
export declare function parseWranglerConfig(path: string, contents: string): unknown;
|
|
32
|
+
/**
|
|
33
|
+
* The build outputs this plugin writes into the adapter's output dir. They are
|
|
34
|
+
* server-side / Durable Object bundles and their source maps — never meant to
|
|
35
|
+
* be downloadable. adapter-cloudflare's generated `.assetsignore` only excludes
|
|
36
|
+
* its own outputs (`_worker.js`, `_routes.json`, `_headers`, `_redirects`), so
|
|
37
|
+
* without this Cloudflare would serve these as public static assets, leaking
|
|
38
|
+
* the bundled code and (via the maps' `sourcesContent`) the original
|
|
39
|
+
* TypeScript. See https://github.com/oselvar/sveltekit-add-worker-exports/issues/4
|
|
40
|
+
*/
|
|
41
|
+
export declare const PLUGIN_ASSETS_IGNORE_ENTRIES: string[];
|
|
42
|
+
/**
|
|
43
|
+
* Returns `.assetsignore` content with `entries` appended, skipping any that
|
|
44
|
+
* are already present (trim-compared). Preserves existing lines and order,
|
|
45
|
+
* normalises to a trailing newline. Idempotent — `.assetsignore` controls only
|
|
46
|
+
* public asset serving, so the listed files stay on disk for wrangler to bundle.
|
|
47
|
+
*/
|
|
48
|
+
export declare function mergeAssetsIgnore(existing: string, entries: string[]): string;
|
|
32
49
|
export interface AddWorkerExportsOptions {
|
|
33
50
|
/** File that exports Durable Object and/or Workflow classes */
|
|
34
51
|
entryPoint: string;
|
|
@@ -38,6 +55,17 @@ export interface AddWorkerExportsOptions {
|
|
|
38
55
|
wranglerConfig?: string;
|
|
39
56
|
/** Port for the dev worker server (default: 8787) */
|
|
40
57
|
devPort?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Handles each structured log line emitted by the sidecar worker (one entry
|
|
60
|
+
* per `console.log` / `console.error` / etc. call). When omitted, a default
|
|
61
|
+
* handler prints each line to the host's stdout (or stderr for `error`
|
|
62
|
+
* level) prefixed with the sidecar name. Pass `() => {}` to silence.
|
|
63
|
+
*/
|
|
64
|
+
structuredLogsHandler?: (log: {
|
|
65
|
+
level: string;
|
|
66
|
+
message: string;
|
|
67
|
+
timestamp: number;
|
|
68
|
+
}) => void;
|
|
41
69
|
}
|
|
42
70
|
/**
|
|
43
71
|
* Returns two Vite plugins: one for build (patches _worker.js) and one for
|
package/dist/index.js
CHANGED
|
@@ -40,6 +40,37 @@ export function parseWranglerConfig(path, contents) {
|
|
|
40
40
|
return path.endsWith('.toml') ? parseToml(contents) : parseJsonc(contents);
|
|
41
41
|
}
|
|
42
42
|
const DEFAULT_DEV_PORT = 8787;
|
|
43
|
+
/**
|
|
44
|
+
* The build outputs this plugin writes into the adapter's output dir. They are
|
|
45
|
+
* server-side / Durable Object bundles and their source maps — never meant to
|
|
46
|
+
* be downloadable. adapter-cloudflare's generated `.assetsignore` only excludes
|
|
47
|
+
* its own outputs (`_worker.js`, `_routes.json`, `_headers`, `_redirects`), so
|
|
48
|
+
* without this Cloudflare would serve these as public static assets, leaking
|
|
49
|
+
* the bundled code and (via the maps' `sourcesContent`) the original
|
|
50
|
+
* TypeScript. See https://github.com/oselvar/sveltekit-add-worker-exports/issues/4
|
|
51
|
+
*/
|
|
52
|
+
export const PLUGIN_ASSETS_IGNORE_ENTRIES = [
|
|
53
|
+
'_sveltekit_worker.js',
|
|
54
|
+
'_sveltekit_worker.js.map',
|
|
55
|
+
'_extra_exports.js',
|
|
56
|
+
'_extra_exports.js.map'
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Returns `.assetsignore` content with `entries` appended, skipping any that
|
|
60
|
+
* are already present (trim-compared). Preserves existing lines and order,
|
|
61
|
+
* normalises to a trailing newline. Idempotent — `.assetsignore` controls only
|
|
62
|
+
* public asset serving, so the listed files stay on disk for wrangler to bundle.
|
|
63
|
+
*/
|
|
64
|
+
export function mergeAssetsIgnore(existing, entries) {
|
|
65
|
+
const lines = existing.split('\n');
|
|
66
|
+
const present = new Set(lines.map((l) => l.trim()));
|
|
67
|
+
const additions = entries.filter((e) => !present.has(e.trim()));
|
|
68
|
+
// Drop trailing blank lines so we append cleanly, then re-add one newline.
|
|
69
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
|
|
70
|
+
lines.pop();
|
|
71
|
+
}
|
|
72
|
+
return [...lines, ...additions].join('\n') + '\n';
|
|
73
|
+
}
|
|
43
74
|
/**
|
|
44
75
|
* Mirrors wrangler's internal getRegistryPath() — wrangler doesn't export it.
|
|
45
76
|
* Workers register themselves under this path so cross-worker `script_name`
|
|
@@ -76,66 +107,100 @@ function getWranglerRegistryPath() {
|
|
|
76
107
|
export function addWorkerExports(options) {
|
|
77
108
|
return [buildPlugin(options), devPlugin(options)];
|
|
78
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Patches the adapter-generated `_worker.js`: bundles the named exports into
|
|
112
|
+
* `_extra_exports.js`, renames the original worker to `_sveltekit_worker.js`,
|
|
113
|
+
* and writes a merged `_worker.js`. Idempotent — safe to call from more than
|
|
114
|
+
* one Vite hook; the first call that finds `_worker.js` does the work, later
|
|
115
|
+
* calls see `_sveltekit_worker.js` and return.
|
|
116
|
+
*
|
|
117
|
+
* Returns `true` if it patched (or had already patched), `false` if the
|
|
118
|
+
* adapter hasn't written `_worker.js` yet.
|
|
119
|
+
*/
|
|
120
|
+
async function patchWorker(options) {
|
|
121
|
+
const outputDir = resolve(options.outputDir ?? '.svelte-kit/cloudflare');
|
|
122
|
+
const workerPath = resolve(outputDir, '_worker.js');
|
|
123
|
+
const sveltekitPath = resolve(outputDir, '_sveltekit_worker.js');
|
|
124
|
+
const exportsPath = resolve(outputDir, '_extra_exports.js');
|
|
125
|
+
// Skip if already patched (idempotent)
|
|
126
|
+
if (await exists(sveltekitPath)) {
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
// Skip if the adapter hasn't run yet (e.g. during the SSR build phase, or
|
|
130
|
+
// before SvelteKit v3's `buildApp` hook runs the adapter)
|
|
131
|
+
if (!(await exists(workerPath))) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
// Bundle the named exports.
|
|
135
|
+
//
|
|
136
|
+
// `conditions: ['workerd', 'worker', 'browser']` is the Cloudflare-
|
|
137
|
+
// recommended set for bundlers targeting Workers — it picks the
|
|
138
|
+
// workerd-specific or browser-shimmed variants of packages over
|
|
139
|
+
// their Node variants (e.g. nanoid's webcrypto-via-`globalThis.crypto`
|
|
140
|
+
// browser build instead of the `node:crypto`-importing main build).
|
|
141
|
+
//
|
|
142
|
+
// Node built-ins are externalised so transitive deps that do
|
|
143
|
+
// `require('path')` or `import 'node:async_hooks'` survive bundling;
|
|
144
|
+
// the Workers runtime resolves them when `nodejs_compat` is enabled
|
|
145
|
+
// in wrangler.jsonc.
|
|
146
|
+
await build({
|
|
147
|
+
entryPoints: [options.entryPoint],
|
|
148
|
+
bundle: true,
|
|
149
|
+
format: 'esm',
|
|
150
|
+
sourcemap: true,
|
|
151
|
+
target: 'esnext',
|
|
152
|
+
conditions: ['workerd', 'worker', 'browser'],
|
|
153
|
+
external: ['cloudflare:*', ...NODE_BUILTINS, ...NODE_BUILTINS.map((m) => `node:${m}`)],
|
|
154
|
+
outfile: exportsPath
|
|
155
|
+
});
|
|
156
|
+
// Rename original worker and create merged entry point.
|
|
157
|
+
//
|
|
158
|
+
// `export *` re-exports named exports only, so class-based bindings
|
|
159
|
+
// (DOs, Workflows, WorkerEntrypoint) from `_extra_exports.js` flow
|
|
160
|
+
// through. Non-fetch handlers (scheduled, queue, email, tail, trace)
|
|
161
|
+
// live on the user entry's `default` export — Cloudflare invokes
|
|
162
|
+
// them as methods on the worker's default object, not as named
|
|
163
|
+
// exports — so we spread them onto the SvelteKit default. `fetch`
|
|
164
|
+
// is dropped from the user default because SvelteKit owns request
|
|
165
|
+
// handling in production.
|
|
166
|
+
await rename(workerPath, sveltekitPath);
|
|
167
|
+
await writeFile(workerPath, `import sveltekitWorker from './_sveltekit_worker.js';\n` +
|
|
168
|
+
`import * as extra from './_extra_exports.js';\n` +
|
|
169
|
+
`export * from './_extra_exports.js';\n` +
|
|
170
|
+
`const { fetch: _ignored, ...extraHandlers } = extra.default ?? {};\n` +
|
|
171
|
+
`export default { ...sveltekitWorker, ...extraHandlers };\n`);
|
|
172
|
+
// Keep this plugin's server bundles and source maps out of the
|
|
173
|
+
// publicly-served assets. The adapter regenerates `.assetsignore` every
|
|
174
|
+
// build with only its own outputs; we append ours here, right after
|
|
175
|
+
// patching, so it survives each deploy regardless of which hook ran the
|
|
176
|
+
// patch (v2: `closeBundle`, v3: `buildApp`).
|
|
177
|
+
const assetsIgnorePath = resolve(outputDir, '.assetsignore');
|
|
178
|
+
const currentIgnore = (await exists(assetsIgnorePath))
|
|
179
|
+
? await readFile(assetsIgnorePath, 'utf-8')
|
|
180
|
+
: '';
|
|
181
|
+
await writeFile(assetsIgnorePath, mergeAssetsIgnore(currentIgnore, PLUGIN_ASSETS_IGNORE_ENTRIES));
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
79
184
|
function buildPlugin(options) {
|
|
80
185
|
return {
|
|
81
186
|
name: 'add-worker-exports',
|
|
82
187
|
apply: 'build',
|
|
83
188
|
enforce: 'post',
|
|
189
|
+
// SvelteKit v2 runs the Cloudflare adapter inside `closeBundle`; our
|
|
190
|
+
// `enforce: 'post'` hook then runs after it and sees `_worker.js`.
|
|
84
191
|
async closeBundle() {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
192
|
+
await patchWorker(options);
|
|
193
|
+
},
|
|
194
|
+
// SvelteKit v3 moved the adapter into Vite's `buildApp` hook, which runs
|
|
195
|
+
// *after* every `closeBundle`. By that point our `closeBundle` has
|
|
196
|
+
// already run and skipped (no `_worker.js` yet), so we patch here
|
|
197
|
+
// instead. `order: 'post'` ensures we run after SvelteKit's own
|
|
198
|
+
// `buildApp` that generates `_worker.js`. Harmless on v2 (idempotent).
|
|
199
|
+
buildApp: {
|
|
200
|
+
order: 'post',
|
|
201
|
+
async handler() {
|
|
202
|
+
await patchWorker(options);
|
|
96
203
|
}
|
|
97
|
-
// Bundle the named exports.
|
|
98
|
-
//
|
|
99
|
-
// `conditions: ['workerd', 'worker', 'browser']` is the Cloudflare-
|
|
100
|
-
// recommended set for bundlers targeting Workers — it picks the
|
|
101
|
-
// workerd-specific or browser-shimmed variants of packages over
|
|
102
|
-
// their Node variants (e.g. nanoid's webcrypto-via-`globalThis.crypto`
|
|
103
|
-
// browser build instead of the `node:crypto`-importing main build).
|
|
104
|
-
//
|
|
105
|
-
// Node built-ins are externalised so transitive deps that do
|
|
106
|
-
// `require('path')` or `import 'node:async_hooks'` survive bundling;
|
|
107
|
-
// the Workers runtime resolves them when `nodejs_compat` is enabled
|
|
108
|
-
// in wrangler.jsonc.
|
|
109
|
-
await build({
|
|
110
|
-
entryPoints: [options.entryPoint],
|
|
111
|
-
bundle: true,
|
|
112
|
-
format: 'esm',
|
|
113
|
-
sourcemap: true,
|
|
114
|
-
target: 'esnext',
|
|
115
|
-
conditions: ['workerd', 'worker', 'browser'],
|
|
116
|
-
external: [
|
|
117
|
-
'cloudflare:*',
|
|
118
|
-
...NODE_BUILTINS,
|
|
119
|
-
...NODE_BUILTINS.map((m) => `node:${m}`)
|
|
120
|
-
],
|
|
121
|
-
outfile: exportsPath
|
|
122
|
-
});
|
|
123
|
-
// Rename original worker and create merged entry point.
|
|
124
|
-
//
|
|
125
|
-
// `export *` re-exports named exports only, so class-based bindings
|
|
126
|
-
// (DOs, Workflows, WorkerEntrypoint) from `_extra_exports.js` flow
|
|
127
|
-
// through. Non-fetch handlers (scheduled, queue, email, tail, trace)
|
|
128
|
-
// live on the user entry's `default` export — Cloudflare invokes
|
|
129
|
-
// them as methods on the worker's default object, not as named
|
|
130
|
-
// exports — so we spread them onto the SvelteKit default. `fetch`
|
|
131
|
-
// is dropped from the user default because SvelteKit owns request
|
|
132
|
-
// handling in production.
|
|
133
|
-
await rename(workerPath, sveltekitPath);
|
|
134
|
-
await writeFile(workerPath, `import sveltekitWorker from './_sveltekit_worker.js';\n` +
|
|
135
|
-
`import * as extra from './_extra_exports.js';\n` +
|
|
136
|
-
`export * from './_extra_exports.js';\n` +
|
|
137
|
-
`const { fetch: _ignored, ...extraHandlers } = extra.default ?? {};\n` +
|
|
138
|
-
`export default { ...sveltekitWorker, ...extraHandlers };\n`);
|
|
139
204
|
}
|
|
140
205
|
};
|
|
141
206
|
}
|
|
@@ -210,6 +275,7 @@ function devPlugin(options) {
|
|
|
210
275
|
delete activeScope.migrations;
|
|
211
276
|
proxyConfigPath = resolve('.platform-proxy-wrangler.jsonc');
|
|
212
277
|
await writeFile(proxyConfigPath, JSON.stringify(proxyConfig, null, '\t'));
|
|
278
|
+
const structuredLogsHandler = options.structuredLogsHandler ?? makeDefaultWorkerLogHandler(sidecarName);
|
|
213
279
|
worker = await unstable_startWorker({
|
|
214
280
|
config: tempConfigPath,
|
|
215
281
|
// `testScheduled` mounts a `/__scheduled` endpoint on the sidecar
|
|
@@ -217,7 +283,19 @@ function devPlugin(options) {
|
|
|
217
283
|
// never auto-fires crons. Curl
|
|
218
284
|
// `http://localhost:<devPort>/__scheduled?cron=*+*+*+*+*` to
|
|
219
285
|
// trigger your `scheduled` handler.
|
|
220
|
-
|
|
286
|
+
//
|
|
287
|
+
// `structuredLogsHandler` (added in wrangler 4.99) routes each
|
|
288
|
+
// worker log line through `onWorkerLog`. Without it, wrangler
|
|
289
|
+
// pipes worker logs through its own Logger which can get
|
|
290
|
+
// swallowed when `unstable_startWorker` is driven
|
|
291
|
+
// programmatically — `console.log` inside the worker
|
|
292
|
+
// disappears. Cast is needed because peer wrangler types may
|
|
293
|
+
// be older.
|
|
294
|
+
dev: {
|
|
295
|
+
registry: getWranglerRegistryPath(),
|
|
296
|
+
testScheduled: true,
|
|
297
|
+
structuredLogsHandler
|
|
298
|
+
},
|
|
221
299
|
build: {
|
|
222
300
|
nodejsCompatMode: (parsedConfig) => getNodejsCompatMode(parsedConfig.compatibility_date, parsedConfig.compatibility_flags)
|
|
223
301
|
}
|
|
@@ -237,6 +315,18 @@ function devPlugin(options) {
|
|
|
237
315
|
}
|
|
238
316
|
};
|
|
239
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Default handler for sidecar worker logs. Writes each line to stdout (or
|
|
320
|
+
* stderr for `error` level) tagged with the sidecar name so it's obvious
|
|
321
|
+
* which process produced it.
|
|
322
|
+
*/
|
|
323
|
+
function makeDefaultWorkerLogHandler(sidecarName) {
|
|
324
|
+
const prefix = `[${sidecarName}]`;
|
|
325
|
+
return ({ level, message }) => {
|
|
326
|
+
const stream = level === 'error' ? process.stderr : process.stdout;
|
|
327
|
+
stream.write(`${prefix} ${message}\n`);
|
|
328
|
+
};
|
|
329
|
+
}
|
|
240
330
|
async function exists(path) {
|
|
241
331
|
try {
|
|
242
332
|
await access(path);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oselvar/sveltekit-add-worker-exports",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Vite plugin that adds Durable Object and Workflow exports to SvelteKit Cloudflare workers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -37,15 +37,15 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@release-it/conventional-changelog": "^11.0.1",
|
|
40
|
-
"@sveltejs/kit": "^2.
|
|
41
|
-
"@types/node": "^
|
|
42
|
-
"esbuild": "^0.28.
|
|
43
|
-
"miniflare": "^4.
|
|
40
|
+
"@sveltejs/kit": "^2.66.0",
|
|
41
|
+
"@types/node": "^26.0.0",
|
|
42
|
+
"esbuild": "^0.28.1",
|
|
43
|
+
"miniflare": "^4.20260617.1",
|
|
44
44
|
"release-it": "^20.2.0",
|
|
45
45
|
"typescript": "^6.0.3",
|
|
46
46
|
"vite": "^8.0.16",
|
|
47
|
-
"vitest": "^4.1.
|
|
48
|
-
"wrangler": "^4.
|
|
47
|
+
"vitest": "^4.1.9",
|
|
48
|
+
"wrangler": "^4.103.0"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"build": "tsc",
|