@oselvar/sveltekit-add-worker-exports 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Oselvar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # @oselvar/sveltekit-add-worker-exports
2
+
3
+ A Vite plugin that makes Durable Objects and Workflows work with SvelteKit on Cloudflare, in both dev and production.
4
+
5
+ **Build mode:** SvelteKit's `adapter-cloudflare` generates `_worker.js` with only a default export (the fetch handler). Cloudflare Workers requires Durable Object and Workflow classes to be **named exports**. This plugin post-processes the build output to merge your named exports with SvelteKit's default export.
6
+
7
+ **Dev mode:** `getPlatformProxy` (used by `adapter-cloudflare` in dev) can't run internal Durable Objects. This plugin starts a separate wrangler dev server that runs the real DO worker with hot-reload. Clients connect directly to it via WebSocket on a separate port.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pnpm add -D @oselvar/sveltekit-add-worker-exports
13
+ ```
14
+
15
+ `esbuild`, `vite`, and `wrangler` are peer dependencies -- your SvelteKit project already has them.
16
+
17
+ ## Usage
18
+
19
+ Create a file that exports your Durable Object and/or Workflow classes:
20
+
21
+ ```typescript
22
+ // src/lib/server/index.ts
23
+ export { MyDurableObject } from './MyDurableObject';
24
+
25
+ export default {
26
+ async fetch(request: Request, env: Env): Promise<Response> {
27
+ // Route WebSocket upgrades to Durable Objects (used during dev)
28
+ const url = new URL(request.url);
29
+ const match = url.pathname.match(/^\/ws\/(.+)$/);
30
+ if (match && request.headers.get('Upgrade') === 'websocket') {
31
+ const id = env.MY_DO.idFromName(match[1]);
32
+ return env.MY_DO.get(id).fetch(request);
33
+ }
34
+ return new Response('Not found', { status: 404 });
35
+ }
36
+ };
37
+ ```
38
+
39
+ Add the plugin to your `vite.config.ts` **after** `sveltekit()`:
40
+
41
+ ```typescript
42
+ import { sveltekit } from '@sveltejs/kit/vite';
43
+ import { addWorkerExports } from '@oselvar/sveltekit-add-worker-exports';
44
+ import { defineConfig } from 'vite';
45
+
46
+ export default defineConfig({
47
+ plugins: [
48
+ sveltekit(),
49
+ addWorkerExports({ entryPoint: 'src/lib/server/index.ts' })
50
+ ]
51
+ });
52
+ ```
53
+
54
+ ### Dev mode: connecting to Durable Objects
55
+
56
+ In dev mode, the plugin starts a wrangler dev server on a separate port and injects `__DEV_WORKER_PORT__` as a compile-time constant. Use it to connect your client:
57
+
58
+ ```typescript
59
+ import { dev } from '$app/environment';
60
+
61
+ let wsUrl: string;
62
+ if (dev) {
63
+ wsUrl = `ws://${window.location.hostname}:${__DEV_WORKER_PORT__}/ws/${id}`;
64
+ } else {
65
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
66
+ wsUrl = `${protocol}//${window.location.host}/ws/${id}`;
67
+ }
68
+ const ws = new WebSocket(wsUrl);
69
+ ```
70
+
71
+ Add the type declaration to your `src/app.d.ts`:
72
+
73
+ ```typescript
74
+ declare global {
75
+ const __DEV_WORKER_PORT__: number;
76
+ }
77
+ ```
78
+
79
+ The plugin auto-discovers your `wrangler.jsonc` (or `wrangler.toml`) and reads DO bindings, migrations, and compatibility settings from it. It overrides only the `main` entry point to use your source file instead of the SvelteKit build output.
80
+
81
+ ## Options
82
+
83
+ | Option | Type | Required | Default | Description |
84
+ |--------|------|----------|---------|-------------|
85
+ | `entryPoint` | `string` | Yes | -- | Path to the file that exports your DO/Workflow classes |
86
+ | `outputDir` | `string` | No | `.svelte-kit/cloudflare` | Directory containing the SvelteKit-generated `_worker.js` |
87
+ | `wranglerConfig` | `string` | No | auto-discovered | Path to wrangler config file |
88
+ | `devPort` | `number` | No | `8787` | Port for the dev worker server |
89
+
90
+ ## How it works
91
+
92
+ ### Build mode
93
+
94
+ The plugin runs in the `closeBundle` hook (after SvelteKit's adapter has generated `_worker.js`):
95
+
96
+ 1. Bundles your `entryPoint` with esbuild into `_extra_exports.js`
97
+ 2. Renames the original `_worker.js` to `_sveltekit_worker.js`
98
+ 3. Creates a new `_worker.js` that re-exports both:
99
+
100
+ ```javascript
101
+ export { default } from './_sveltekit_worker.js';
102
+ export * from './_extra_exports.js';
103
+ ```
104
+
105
+ The operation is idempotent -- if `_sveltekit_worker.js` already exists, the plugin skips.
106
+
107
+ ### Dev mode
108
+
109
+ The plugin reads your wrangler config, creates a temporary config with `main` pointing to your `entryPoint`, and starts a wrangler dev server via `unstable_startWorker`. This gives you:
110
+
111
+ - Real workerd runtime (not emulated)
112
+ - Hot-reload when you change DO code
113
+ - Same WebSocket protocol as production
114
+
115
+ ## Why this exists
116
+
117
+ SvelteKit's adapter-cloudflare does not support named exports from the worker entry point ([sveltejs/kit#1712](https://github.com/sveltejs/kit/issues/1712)). Additionally, `getPlatformProxy` (used for local dev) cannot run internal Durable Objects because it uses an empty worker script.
118
+
119
+ ## License
120
+
121
+ MIT
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Vite plugin that makes Durable Objects and Workflows work with SvelteKit
3
+ * on Cloudflare, in both dev and production.
4
+ *
5
+ * **Build mode:** SvelteKit's adapter-cloudflare generates _worker.js with
6
+ * only a default export. Cloudflare requires DO/Workflow classes as named
7
+ * exports. This plugin post-processes the build output to merge them.
8
+ *
9
+ * **Dev mode:** `getPlatformProxy` (used by adapter-cloudflare in dev) can't
10
+ * run internal Durable Objects. This plugin starts a separate wrangler dev
11
+ * server via `unstable_startWorker` that runs the real DO worker. Clients
12
+ * connect directly to it via WebSocket on a separate port.
13
+ *
14
+ * Usage in vite.config.ts:
15
+ * addWorkerExports({ entryPoint: 'src/lib/server/index.ts' })
16
+ *
17
+ * @see https://github.com/sveltejs/kit/issues/1712
18
+ */
19
+ import type { Plugin } from 'vite';
20
+ export interface AddWorkerExportsOptions {
21
+ /** File that exports Durable Object and/or Workflow classes */
22
+ entryPoint: string;
23
+ /** Directory containing the SvelteKit-generated _worker.js (default: .svelte-kit/cloudflare) */
24
+ outputDir?: string;
25
+ /** Override wrangler config path (default: auto-discovered by wrangler) */
26
+ wranglerConfig?: string;
27
+ /** Port for the dev worker server (default: 8787) */
28
+ devPort?: number;
29
+ }
30
+ /**
31
+ * Returns two Vite plugins: one for build (patches _worker.js) and one for
32
+ * dev (starts a wrangler dev server for Durable Objects).
33
+ *
34
+ * The dev plugin injects `__DEV_WORKER_PORT__` as a compile-time constant
35
+ * via Vite's `define`. Declare it in your app's type definitions:
36
+ *
37
+ * ```typescript
38
+ * // src/app.d.ts
39
+ * declare global {
40
+ * const __DEV_WORKER_PORT__: number;
41
+ * }
42
+ * ```
43
+ */
44
+ export declare function addWorkerExports(options: AddWorkerExportsOptions): Plugin[];
package/dist/index.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Vite plugin that makes Durable Objects and Workflows work with SvelteKit
3
+ * on Cloudflare, in both dev and production.
4
+ *
5
+ * **Build mode:** SvelteKit's adapter-cloudflare generates _worker.js with
6
+ * only a default export. Cloudflare requires DO/Workflow classes as named
7
+ * exports. This plugin post-processes the build output to merge them.
8
+ *
9
+ * **Dev mode:** `getPlatformProxy` (used by adapter-cloudflare in dev) can't
10
+ * run internal Durable Objects. This plugin starts a separate wrangler dev
11
+ * server via `unstable_startWorker` that runs the real DO worker. Clients
12
+ * connect directly to it via WebSocket on a separate port.
13
+ *
14
+ * Usage in vite.config.ts:
15
+ * addWorkerExports({ entryPoint: 'src/lib/server/index.ts' })
16
+ *
17
+ * @see https://github.com/sveltejs/kit/issues/1712
18
+ */
19
+ import { build } from 'esbuild';
20
+ import { access, readFile, rename, writeFile, unlink } from 'node:fs/promises';
21
+ import { resolve } from 'node:path';
22
+ import { parse as parseJsonc } from 'jsonc-parser';
23
+ const DEFAULT_DEV_PORT = 8787;
24
+ /**
25
+ * Returns two Vite plugins: one for build (patches _worker.js) and one for
26
+ * dev (starts a wrangler dev server for Durable Objects).
27
+ *
28
+ * The dev plugin injects `__DEV_WORKER_PORT__` as a compile-time constant
29
+ * via Vite's `define`. Declare it in your app's type definitions:
30
+ *
31
+ * ```typescript
32
+ * // src/app.d.ts
33
+ * declare global {
34
+ * const __DEV_WORKER_PORT__: number;
35
+ * }
36
+ * ```
37
+ */
38
+ export function addWorkerExports(options) {
39
+ return [buildPlugin(options), devPlugin(options)];
40
+ }
41
+ function buildPlugin(options) {
42
+ return {
43
+ name: 'add-worker-exports',
44
+ apply: 'build',
45
+ enforce: 'post',
46
+ async closeBundle() {
47
+ const outputDir = resolve(options.outputDir ?? '.svelte-kit/cloudflare');
48
+ const workerPath = resolve(outputDir, '_worker.js');
49
+ const sveltekitPath = resolve(outputDir, '_sveltekit_worker.js');
50
+ const exportsPath = resolve(outputDir, '_extra_exports.js');
51
+ // Skip if already patched (idempotent)
52
+ if (await exists(sveltekitPath)) {
53
+ return;
54
+ }
55
+ // Skip if the adapter hasn't run yet (e.g. during SSR build phase)
56
+ if (!(await exists(workerPath))) {
57
+ return;
58
+ }
59
+ // Bundle the named exports
60
+ await build({
61
+ entryPoints: [options.entryPoint],
62
+ bundle: true,
63
+ format: 'esm',
64
+ sourcemap: true,
65
+ target: 'esnext',
66
+ external: ['cloudflare:*'],
67
+ outfile: exportsPath
68
+ });
69
+ // Rename original worker and create merged entry point
70
+ await rename(workerPath, sveltekitPath);
71
+ await writeFile(workerPath, `export { default } from './_sveltekit_worker.js';\nexport * from './_extra_exports.js';\n`);
72
+ }
73
+ };
74
+ }
75
+ function devPlugin(options) {
76
+ const devPort = options.devPort ?? DEFAULT_DEV_PORT;
77
+ let worker = null;
78
+ let tempConfigPath = null;
79
+ return {
80
+ name: 'add-worker-exports-dev',
81
+ apply: 'serve',
82
+ config() {
83
+ return {
84
+ define: {
85
+ __DEV_WORKER_PORT__: JSON.stringify(devPort)
86
+ }
87
+ };
88
+ },
89
+ async configureServer(server) {
90
+ const { unstable_readConfig, unstable_startWorker } = await import('wrangler');
91
+ // Read the project's wrangler config (auto-discovers wrangler.jsonc/toml)
92
+ const wranglerConfig = unstable_readConfig(options.wranglerConfig ? { config: options.wranglerConfig } : {});
93
+ // Copy the raw config and override main, name, and dev port
94
+ const rawJson = await readFile(wranglerConfig.configPath, 'utf-8');
95
+ const devConfig = parseJsonc(rawJson);
96
+ devConfig.name = `${devConfig.name ?? 'sveltekit'}-dev-worker`;
97
+ devConfig.main = options.entryPoint;
98
+ devConfig.dev = { ...devConfig.dev, port: devPort };
99
+ delete devConfig.assets;
100
+ tempConfigPath = resolve('.dev-worker-wrangler.jsonc');
101
+ await writeFile(tempConfigPath, JSON.stringify(devConfig, null, '\t'));
102
+ worker = await unstable_startWorker({ config: tempConfigPath });
103
+ server.httpServer?.on('close', async () => {
104
+ if (worker) {
105
+ await worker.dispose();
106
+ worker = null;
107
+ }
108
+ if (tempConfigPath) {
109
+ await unlink(tempConfigPath).catch(() => { });
110
+ }
111
+ });
112
+ }
113
+ };
114
+ }
115
+ async function exists(path) {
116
+ try {
117
+ await access(path);
118
+ return true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@oselvar/sveltekit-add-worker-exports",
3
+ "version": "0.1.2",
4
+ "description": "Vite plugin that adds Durable Object and Workflow exports to SvelteKit Cloudflare workers",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/oselvar/sveltekit-add-worker-exports"
22
+ },
23
+ "keywords": [
24
+ "sveltekit",
25
+ "cloudflare",
26
+ "workers",
27
+ "durable-objects",
28
+ "workflows",
29
+ "vite-plugin"
30
+ ],
31
+ "peerDependencies": {
32
+ "esbuild": ">=0.17",
33
+ "vite": ">=5",
34
+ "wrangler": ">=4"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "esbuild": "^0.25.0",
39
+ "np": "^11.2.0",
40
+ "typescript": "^5.0.0",
41
+ "vite": "^6.0.0",
42
+ "wrangler": "^4.84.0"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc",
46
+ "test": "tsc --noEmit",
47
+ "prepublishOnly": "tsc"
48
+ },
49
+ "dependencies": {
50
+ "jsonc-parser": "^3.3.1"
51
+ }
52
+ }