svelte-adapter-uws 0.2.6 → 0.2.7
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 +14 -8
- package/files/handler.js +1 -1
- package/files/index.js +2 -2
- package/index.js +44 -61
- package/package.json +1 -1
- package/vite.js +36 -1
package/README.md
CHANGED
|
@@ -170,7 +170,10 @@ That's it. This gives you a pub/sub WebSocket server at `/ws` with no authentica
|
|
|
170
170
|
|
|
171
171
|
### Step 2: Add the Vite plugin
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
The Vite plugin does two things:
|
|
174
|
+
|
|
175
|
+
1. **Dev mode** - spins up a WebSocket server so `event.platform` works during `npm run dev`
|
|
176
|
+
2. **Production builds** - runs your `hooks.ws` file through the same Vite pipeline as the rest of your server code, so `$lib`, `$env`, and `$app` imports work correctly
|
|
174
177
|
|
|
175
178
|
**vite.config.js**
|
|
176
179
|
```js
|
|
@@ -258,6 +261,8 @@ The client store automatically uses `wss://` when the page is served over HTTPS
|
|
|
258
261
|
|
|
259
262
|
Development works as expected. The Vite plugin (`svelte-adapter-uws/vite`) spins up a `ws` WebSocket server alongside Vite's dev server, so your client store and `event.platform` work identically to production.
|
|
260
263
|
|
|
264
|
+
The same plugin also handles production builds - it runs your `hooks.ws` file through the Vite pipeline so that `$lib`, `$env`, and `$app` imports are resolved correctly and shared with the rest of your server code.
|
|
265
|
+
|
|
261
266
|
**vite.config.js**
|
|
262
267
|
```js
|
|
263
268
|
import { sveltekit } from '@sveltejs/kit/vite';
|
|
@@ -270,8 +275,9 @@ export default {
|
|
|
270
275
|
|
|
271
276
|
Without the Vite plugin:
|
|
272
277
|
- HTTP routes work fine
|
|
273
|
-
- `event.platform` is `undefined` - any code calling `platform.publish()` will throw
|
|
274
|
-
- The client store will try to connect to `/ws` and fail silently (auto-reconnect will keep trying)
|
|
278
|
+
- `event.platform` is `undefined` in dev - any code calling `platform.publish()` will throw
|
|
279
|
+
- The client store will try to connect to `/ws` in dev and fail silently (auto-reconnect will keep trying)
|
|
280
|
+
- Production builds will fall back to a limited bundler that only resolves `$lib` (not `$env` or `$app`)
|
|
275
281
|
|
|
276
282
|
### `npm run preview` - WebSockets don't work
|
|
277
283
|
|
|
@@ -1193,12 +1199,12 @@ If a worker crashes, it is automatically restarted with exponential backoff. On
|
|
|
1193
1199
|
|
|
1194
1200
|
### WebSocket + clustering
|
|
1195
1201
|
|
|
1196
|
-
`platform.publish()` is automatically relayed across all workers via the primary thread, so subscribers on any worker receive the message. This is built in
|
|
1202
|
+
`platform.publish()` is automatically relayed across all workers via the primary thread, so subscribers on any worker receive the message. This is built in - no external pub/sub needed.
|
|
1197
1203
|
|
|
1198
1204
|
Per-worker limitations (acceptable for most apps):
|
|
1199
|
-
- `platform.connections`
|
|
1200
|
-
- `platform.subscribers(topic)`
|
|
1201
|
-
- `platform.sendTo(filter, ...)`
|
|
1205
|
+
- `platform.connections` - returns the count for the local worker only
|
|
1206
|
+
- `platform.subscribers(topic)` - returns the count for the local worker only
|
|
1207
|
+
- `platform.sendTo(filter, ...)` - only reaches connections on the local worker
|
|
1202
1208
|
|
|
1203
1209
|
---
|
|
1204
1210
|
|
|
@@ -1254,7 +1260,7 @@ The two largest WebSocket costs were `JSON.parse()` on every message for the sub
|
|
|
1254
1260
|
| Layer | Before | After | How |
|
|
1255
1261
|
|---|---|---|---|
|
|
1256
1262
|
| Subscribe/unsubscribe check | ~15% | ~0% | Byte-prefix discriminator: control messages start with `{"ty` (byte[3]=`y`), user envelopes start with `{"to` (byte[3]=`o`). A single byte comparison skips `JSON.parse` for all regular messages -- from 0.39us to 0.001us per message. |
|
|
1257
|
-
| Envelope wrapping | ~8% | ~4.5% | String template with `esc()` validation instead of `JSON.stringify` on a wrapper object. Topic and event names are validated with a fast char scan (~10ns) that throws on quotes, backslashes, or control characters
|
|
1263
|
+
| Envelope wrapping | ~8% | ~4.5% | String template with `esc()` validation instead of `JSON.stringify` on a wrapper object. Topic and event names are validated with a fast char scan (~10ns) that throws on quotes, backslashes, or control characters - only `data` is stringified. From 0.135us to ~0.085us per publish. |
|
|
1258
1264
|
| Connection tracking | ~2% | ~2% | Unchanged |
|
|
1259
1265
|
| Origin validation, upgrade headers | ~2% | ~2% | Unchanged |
|
|
1260
1266
|
|
package/files/handler.js
CHANGED
|
@@ -26,7 +26,7 @@ class PayloadTooLargeError extends Error {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Safely quote a string for JSON embedding. Topics and events are
|
|
29
|
-
* developer-defined identifiers
|
|
29
|
+
* developer-defined identifiers - a quote, backslash, or control character
|
|
30
30
|
* is always a bug, so we throw instead of silently escaping.
|
|
31
31
|
* @param {string} s
|
|
32
32
|
* @returns {string} JSON-quoted string, e.g. '"todos"'
|
package/files/index.js
CHANGED
|
@@ -56,7 +56,7 @@ if (is_primary) {
|
|
|
56
56
|
workers.set(worker, msg.descriptor);
|
|
57
57
|
acceptorApp.addChildAppDescriptor(msg.descriptor);
|
|
58
58
|
console.log(`Worker thread ${worker.threadId} registered`);
|
|
59
|
-
// Worker started successfully
|
|
59
|
+
// Worker started successfully - reset backoff
|
|
60
60
|
restart_delay = 0;
|
|
61
61
|
if (backoff_reset_timer) { clearTimeout(backoff_reset_timer); backoff_reset_timer = null; }
|
|
62
62
|
} else if (msg.type === 'publish') {
|
|
@@ -137,7 +137,7 @@ if (is_primary) {
|
|
|
137
137
|
// Single-process mode (no clustering)
|
|
138
138
|
start(host, parseInt(port, 10));
|
|
139
139
|
} else {
|
|
140
|
-
// Worker thread
|
|
140
|
+
// Worker thread - register with acceptor, don't listen
|
|
141
141
|
parentPort.postMessage({ type: 'descriptor', descriptor: getDescriptor() });
|
|
142
142
|
|
|
143
143
|
parentPort.on('message', (msg) => {
|
package/index.js
CHANGED
|
@@ -72,70 +72,53 @@ export default function (opts = {}) {
|
|
|
72
72
|
|
|
73
73
|
// Write the WebSocket handler module
|
|
74
74
|
if (websocket) {
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
75
|
+
// If the Vite plugin was used, ws-handler.js is already in the
|
|
76
|
+
// writeServer output - built through the same Vite pipeline as
|
|
77
|
+
// hooks.server.ts, with $lib/$env/$app resolved and shared modules.
|
|
78
|
+
if (existsSync(`${tmp}/ws-handler.js`)) {
|
|
79
|
+
builder.log.minor('WebSocket handler: built by Vite plugin');
|
|
80
|
+
} else {
|
|
81
|
+
// Vite plugin not installed - resolve handler ourselves
|
|
82
|
+
let handlerFile = websocket.handler;
|
|
83
|
+
|
|
84
|
+
if (!handlerFile) {
|
|
85
|
+
const candidates = ['src/hooks.ws.js', 'src/hooks.ws.ts', 'src/hooks.ws.mjs'];
|
|
86
|
+
for (const candidate of candidates) {
|
|
87
|
+
if (existsSync(candidate)) {
|
|
88
|
+
handlerFile = candidate;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
85
91
|
}
|
|
86
92
|
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (handlerFile) {
|
|
90
|
-
// Bundle through esbuild to resolve SvelteKit aliases ($lib, $env, $app)
|
|
91
|
-
// and handle TypeScript. Without this, these imports survive into
|
|
92
|
-
// the Rollup step which doesn't know about SvelteKit virtual modules.
|
|
93
|
-
const esbuild = await import('esbuild');
|
|
94
|
-
const { loadEnv } = await import('vite');
|
|
95
|
-
const libDir = path.resolve(builder.config.kit.files?.lib || 'src/lib');
|
|
96
|
-
const publicPrefix = builder.config.kit.env?.publicPrefix ?? 'PUBLIC_';
|
|
97
|
-
const allEnv = loadEnv('production', process.cwd(), '');
|
|
98
|
-
const version = builder.config.kit.version?.name ?? '';
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
// dynamic: re-export process.env directly
|
|
129
|
-
return { contents: 'export const env = process.env;' };
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
}]
|
|
133
|
-
});
|
|
134
|
-
builder.log.minor(`WebSocket handler: ${handlerFile}`);
|
|
135
|
-
} else {
|
|
136
|
-
// No handler found - use built-in default (subscribe/unsubscribe only)
|
|
137
|
-
writeFileSync(`${tmp}/ws-handler.js`, DEFAULT_WS_HANDLER);
|
|
138
|
-
builder.log.minor('WebSocket enabled (built-in handler)');
|
|
94
|
+
if (handlerFile) {
|
|
95
|
+
// Bundle through esbuild to resolve $lib and handle TypeScript.
|
|
96
|
+
// This is the fallback path - the Vite plugin approach is preferred
|
|
97
|
+
// because it shares modules with the server bundle and resolves
|
|
98
|
+
// all SvelteKit aliases ($env, $app) natively.
|
|
99
|
+
const esbuild = await import('esbuild');
|
|
100
|
+
const libDir = path.resolve(builder.config.kit.files?.lib || 'src/lib');
|
|
101
|
+
|
|
102
|
+
await esbuild.build({
|
|
103
|
+
entryPoints: [path.resolve(handlerFile)],
|
|
104
|
+
bundle: true,
|
|
105
|
+
format: 'esm',
|
|
106
|
+
platform: 'node',
|
|
107
|
+
outfile: `${tmp}/ws-handler.js`,
|
|
108
|
+
alias: { '$lib': libDir },
|
|
109
|
+
packages: 'external'
|
|
110
|
+
});
|
|
111
|
+
builder.log.minor(`WebSocket handler: ${handlerFile} (esbuild fallback)`);
|
|
112
|
+
builder.log.warn(
|
|
113
|
+
'Add the Vite plugin for full SvelteKit alias support in hooks.ws:\n' +
|
|
114
|
+
" import uwsDev from 'svelte-adapter-uws/vite';\n" +
|
|
115
|
+
' export default { plugins: [sveltekit(), uwsDev()] };'
|
|
116
|
+
);
|
|
117
|
+
} else {
|
|
118
|
+
// No handler found - use built-in default (subscribe/unsubscribe only)
|
|
119
|
+
writeFileSync(`${tmp}/ws-handler.js`, DEFAULT_WS_HANDLER);
|
|
120
|
+
builder.log.minor('WebSocket enabled (built-in handler)');
|
|
121
|
+
}
|
|
139
122
|
}
|
|
140
123
|
} else {
|
|
141
124
|
// No WebSocket - empty module
|
package/package.json
CHANGED
package/vite.js
CHANGED
|
@@ -5,7 +5,7 @@ import { parseCookies } from './files/cookies.js';
|
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Safely quote a string for JSON embedding. Throws on invalid characters
|
|
8
|
-
* (quotes, backslashes, control chars)
|
|
8
|
+
* (quotes, backslashes, control chars) - these are always bugs in topic/event names.
|
|
9
9
|
* @param {string} s
|
|
10
10
|
* @returns {string}
|
|
11
11
|
*/
|
|
@@ -187,8 +187,43 @@ export default function uwsDev(options = {}) {
|
|
|
187
187
|
};
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Discover the WS handler file path.
|
|
192
|
+
* @param {string} root
|
|
193
|
+
* @returns {string | null}
|
|
194
|
+
*/
|
|
195
|
+
function discoverHandler(root) {
|
|
196
|
+
if (options.handler) return path.resolve(root, options.handler);
|
|
197
|
+
const candidates = ['src/hooks.ws.js', 'src/hooks.ws.ts', 'src/hooks.ws.mjs'];
|
|
198
|
+
for (const candidate of candidates) {
|
|
199
|
+
const full = path.resolve(root, candidate);
|
|
200
|
+
if (existsSync(full)) return full;
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
|
|
190
205
|
return {
|
|
191
206
|
name: 'svelte-adapter-uws',
|
|
207
|
+
config(config, env) {
|
|
208
|
+
// During SSR build, inject ws-handler as an additional entry so it
|
|
209
|
+
// goes through the same Vite pipeline as hooks.server.ts - resolving
|
|
210
|
+
// $lib, $env, $app aliases and sharing modules with the server bundle.
|
|
211
|
+
if (env.isSsrBuild) {
|
|
212
|
+
const root = config.root || process.cwd();
|
|
213
|
+
const handlerPath = discoverHandler(root);
|
|
214
|
+
if (handlerPath) {
|
|
215
|
+
return {
|
|
216
|
+
build: {
|
|
217
|
+
rollupOptions: {
|
|
218
|
+
input: {
|
|
219
|
+
'ws-handler': handlerPath
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
},
|
|
192
227
|
configureServer(server) {
|
|
193
228
|
wss = new WebSocketServer({ noServer: true });
|
|
194
229
|
const root = server.config.root;
|