@alxia/react-router 0.0.0-stage → 0.1.0
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 +21 -0
- package/README.md +289 -2
- package/dist/assets.d.ts +12 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/chunks/index-kfekhvgr.js +31 -0
- package/dist/chunks/index-kfekhvgr.js.map +10 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +160 -0
- package/dist/cli/index.js.map +13 -0
- package/dist/cli/main.d.ts +9 -0
- package/dist/cli/main.d.ts.map +1 -0
- package/dist/cli/reveal.d.ts +18 -0
- package/dist/cli/reveal.d.ts.map +1 -0
- package/dist/cli/template.d.ts +8 -0
- package/dist/cli/template.d.ts.map +1 -0
- package/dist/context.d.ts +70 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +166 -0
- package/dist/index.js.map +13 -0
- package/dist/react-router.d.ts +61 -0
- package/dist/react-router.d.ts.map +1 -0
- package/dist/server.d.ts +99 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/vite/config.d.ts +33 -0
- package/dist/vite/config.d.ts.map +1 -0
- package/dist/vite/dev.d.ts +23 -0
- package/dist/vite/dev.d.ts.map +1 -0
- package/dist/vite/entry.d.ts +20 -0
- package/dist/vite/entry.d.ts.map +1 -0
- package/dist/vite/index.d.ts +33 -0
- package/dist/vite/index.d.ts.map +1 -0
- package/dist/vite/index.js +391 -0
- package/dist/vite/index.js.map +16 -0
- package/dist/vite/node.d.ts +16 -0
- package/dist/vite/node.d.ts.map +1 -0
- package/dist/vite/preview.d.ts +8 -0
- package/dist/vite/preview.d.ts.map +1 -0
- package/dist/vite/runtime.d.ts +8 -0
- package/dist/vite/runtime.d.ts.map +1 -0
- package/dist/vite/socket.d.ts +68 -0
- package/dist/vite/socket.d.ts.map +1 -0
- package/docs/README.md +12 -0
- package/docs/guide.md +843 -0
- package/docs/roadmap.md +96 -0
- package/docs/troubleshooting.md +832 -0
- package/package.json +77 -5
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import {
|
|
2
|
+
NAME,
|
|
3
|
+
PASS_THROUGH,
|
|
4
|
+
serverEntry
|
|
5
|
+
} from "../chunks/index-kfekhvgr.js";
|
|
6
|
+
|
|
7
|
+
// src/vite/index.ts
|
|
8
|
+
import { relative as relative3 } from "node:path";
|
|
9
|
+
|
|
10
|
+
// src/vite/config.ts
|
|
11
|
+
import { existsSync } from "node:fs";
|
|
12
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
13
|
+
var SERVER = "virtual:alxia-react-router/server";
|
|
14
|
+
var DEFAULT = `${SERVER}-default`;
|
|
15
|
+
function contextOf(config) {
|
|
16
|
+
const context = config.__reactRouterPluginContext;
|
|
17
|
+
if (context === undefined) {
|
|
18
|
+
throw new Error(`${NAME}: React Router's Vite plugin is not in this config. Add reactRouter() from @react-router/dev/vite beside alxia() in vite.config.ts.`);
|
|
19
|
+
}
|
|
20
|
+
return context.reactRouterConfig;
|
|
21
|
+
}
|
|
22
|
+
function isChildCompiler(config) {
|
|
23
|
+
return config.__reactRouterPluginContext === undefined && config.cacheDir.endsWith(".vite-child-compiler");
|
|
24
|
+
}
|
|
25
|
+
function serverFile(entry, config) {
|
|
26
|
+
if (entry !== undefined) {
|
|
27
|
+
const file = isAbsolute(entry) ? entry : resolve(config.root, entry);
|
|
28
|
+
if (!existsSync(file)) {
|
|
29
|
+
throw new Error(`${NAME}: the entry ${entry} does not exist. Create it, or leave entry out to use app/server.ts, or the default server without it.`);
|
|
30
|
+
}
|
|
31
|
+
return file;
|
|
32
|
+
}
|
|
33
|
+
const file = join(contextOf(config).appDirectory, "server.ts");
|
|
34
|
+
return existsSync(file) ? file : undefined;
|
|
35
|
+
}
|
|
36
|
+
function serverBuildPath(config) {
|
|
37
|
+
const rr = contextOf(config);
|
|
38
|
+
const outDir = resolve(config.root, config.environments["ssr"]?.build.outDir ?? join(rr.buildDirectory, "server"));
|
|
39
|
+
return join(outDir, rr.serverBuildFile);
|
|
40
|
+
}
|
|
41
|
+
function clientPath(config) {
|
|
42
|
+
const built = dirname(serverBuildPath(config));
|
|
43
|
+
const path = relative(built, join(contextOf(config).buildDirectory, "client"));
|
|
44
|
+
return path.startsWith(".") ? path : `./${path}`;
|
|
45
|
+
}
|
|
46
|
+
function serverBuildOptions(rolldown) {
|
|
47
|
+
return rolldown ? {
|
|
48
|
+
rolldownOptions: {
|
|
49
|
+
input: SERVER,
|
|
50
|
+
output: { codeSplitting: false }
|
|
51
|
+
}
|
|
52
|
+
} : {
|
|
53
|
+
rollupOptions: {
|
|
54
|
+
input: SERVER,
|
|
55
|
+
output: { inlineDynamicImports: true }
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/vite/node.ts
|
|
61
|
+
import { Readable } from "node:stream";
|
|
62
|
+
function toRequest(req, res) {
|
|
63
|
+
const secure = req.socket.encrypted === true;
|
|
64
|
+
const url = new URL(req.originalUrl ?? req.url ?? "/", `${secure ? "https" : "http"}://${req.headers.host ?? "localhost"}`);
|
|
65
|
+
const headers = new Headers;
|
|
66
|
+
const raw = req.rawHeaders;
|
|
67
|
+
for (let i = 0;i + 1 < raw.length; i += 2) {
|
|
68
|
+
const name = raw[i];
|
|
69
|
+
if (!name.startsWith(":"))
|
|
70
|
+
headers.append(name, raw[i + 1]);
|
|
71
|
+
}
|
|
72
|
+
const controller = new AbortController;
|
|
73
|
+
res.once("close", () => {
|
|
74
|
+
if (!res.writableFinished)
|
|
75
|
+
controller.abort();
|
|
76
|
+
});
|
|
77
|
+
const method = req.method ?? "GET";
|
|
78
|
+
const init = {
|
|
79
|
+
method,
|
|
80
|
+
headers,
|
|
81
|
+
signal: controller.signal
|
|
82
|
+
};
|
|
83
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
84
|
+
init.body = Readable.toWeb(req);
|
|
85
|
+
init.duplex = "half";
|
|
86
|
+
}
|
|
87
|
+
return new Request(url, init);
|
|
88
|
+
}
|
|
89
|
+
async function send(res, response) {
|
|
90
|
+
res.statusCode = response.status;
|
|
91
|
+
if (response.statusText !== "")
|
|
92
|
+
res.statusMessage = response.statusText;
|
|
93
|
+
for (const [name, value] of response.headers) {
|
|
94
|
+
if (name !== "set-cookie")
|
|
95
|
+
res.setHeader(name, value);
|
|
96
|
+
}
|
|
97
|
+
const cookies = response.headers.getSetCookie();
|
|
98
|
+
if (cookies.length > 0)
|
|
99
|
+
res.setHeader("set-cookie", cookies);
|
|
100
|
+
if (response.body === null) {
|
|
101
|
+
res.end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
res.flushHeaders();
|
|
105
|
+
const reader = response.body.getReader();
|
|
106
|
+
const cancel = () => void reader.cancel().catch(() => {});
|
|
107
|
+
res.once("close", cancel);
|
|
108
|
+
try {
|
|
109
|
+
for (;; ) {
|
|
110
|
+
const { done, value } = await reader.read();
|
|
111
|
+
if (done)
|
|
112
|
+
break;
|
|
113
|
+
if (res.destroyed)
|
|
114
|
+
return;
|
|
115
|
+
if (!res.write(value)) {
|
|
116
|
+
await new Promise((resolve) => {
|
|
117
|
+
const go = () => {
|
|
118
|
+
res.off("drain", go);
|
|
119
|
+
res.off("close", go);
|
|
120
|
+
resolve();
|
|
121
|
+
};
|
|
122
|
+
res.on("drain", go);
|
|
123
|
+
res.on("close", go);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
res.end();
|
|
128
|
+
} finally {
|
|
129
|
+
res.off("close", cancel);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/vite/socket.ts
|
|
134
|
+
import { connect } from "node:net";
|
|
135
|
+
var VITE = new Set(["vite-hmr", "vite-ping"]);
|
|
136
|
+
function isVites(req) {
|
|
137
|
+
const protocols = req.headers["sec-websocket-protocol"];
|
|
138
|
+
if (protocols === undefined)
|
|
139
|
+
return false;
|
|
140
|
+
return protocols.split(",").some((protocol) => VITE.has(protocol.trim()));
|
|
141
|
+
}
|
|
142
|
+
function isProxied(url, proxy) {
|
|
143
|
+
for (const [context, entry] of Object.entries(proxy ?? {})) {
|
|
144
|
+
const matches = context.startsWith("^") && new RegExp(context).test(url) || url.startsWith(context);
|
|
145
|
+
if (!matches)
|
|
146
|
+
continue;
|
|
147
|
+
const options = typeof entry === "string" ? { target: entry } : entry;
|
|
148
|
+
const target = String(options.target ?? "");
|
|
149
|
+
if (options.ws === true || target.startsWith("ws:") || target.startsWith("wss:")) {
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
function head(req) {
|
|
156
|
+
const lines = [`${req.method ?? "GET"} ${req.url ?? "/"} HTTP/1.1`];
|
|
157
|
+
const raw = req.rawHeaders;
|
|
158
|
+
for (let i = 0;i + 1 < raw.length; i += 2) {
|
|
159
|
+
lines.push(`${raw[i]}: ${raw[i + 1]}`);
|
|
160
|
+
}
|
|
161
|
+
return `${lines.join(`\r
|
|
162
|
+
`)}\r
|
|
163
|
+
\r
|
|
164
|
+
`;
|
|
165
|
+
}
|
|
166
|
+
function sideFor(sides, app) {
|
|
167
|
+
if (sides.current?.app === app)
|
|
168
|
+
return sides.current.side;
|
|
169
|
+
const side = Bun.serve({
|
|
170
|
+
port: 0,
|
|
171
|
+
hostname: "127.0.0.1",
|
|
172
|
+
fetch: (request, bun) => app.fetch(request, bun),
|
|
173
|
+
websocket: app.websocket
|
|
174
|
+
});
|
|
175
|
+
const old = sides.current?.side;
|
|
176
|
+
if (old !== undefined) {
|
|
177
|
+
sides.retired.add(old);
|
|
178
|
+
old.stop().then(() => sides.retired.delete(old));
|
|
179
|
+
}
|
|
180
|
+
sides.current = { app, side };
|
|
181
|
+
return side;
|
|
182
|
+
}
|
|
183
|
+
function closeAll(sides) {
|
|
184
|
+
for (const side of [...sides.retired, sides.current?.side]) {
|
|
185
|
+
side?.stop(true);
|
|
186
|
+
}
|
|
187
|
+
sides.current = undefined;
|
|
188
|
+
sides.retired.clear();
|
|
189
|
+
}
|
|
190
|
+
function relay(req, socket, bytes, port) {
|
|
191
|
+
const upstream = connect(port, "127.0.0.1", () => {
|
|
192
|
+
upstream.write(head(req));
|
|
193
|
+
if (bytes.length > 0)
|
|
194
|
+
upstream.write(bytes);
|
|
195
|
+
upstream.pipe(socket);
|
|
196
|
+
socket.pipe(upstream);
|
|
197
|
+
});
|
|
198
|
+
upstream.on("error", () => socket.destroy());
|
|
199
|
+
upstream.on("close", () => socket.destroy());
|
|
200
|
+
socket.on("close", () => upstream.destroy());
|
|
201
|
+
}
|
|
202
|
+
function refuse(bridge, socket, error) {
|
|
203
|
+
if (error instanceof Error)
|
|
204
|
+
bridge.fix?.(error);
|
|
205
|
+
bridge.logger.error(`${NAME}: a WebSocket upgrade failed: ${error instanceof Error ? error.stack ?? error.message : String(error)}`);
|
|
206
|
+
if (socket.writable) {
|
|
207
|
+
socket.end(`HTTP/1.1 500 Internal Server Error\r
|
|
208
|
+
Connection: close\r
|
|
209
|
+
Content-Length: 0\r
|
|
210
|
+
\r
|
|
211
|
+
`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
function bridgeSockets(http, bridge) {
|
|
215
|
+
if (http === null)
|
|
216
|
+
return;
|
|
217
|
+
const sides = { current: undefined, retired: new Set };
|
|
218
|
+
http.on("upgrade", (req, socket, bytes) => {
|
|
219
|
+
if (isVites(req) || isProxied(req.url ?? "/", bridge.proxy))
|
|
220
|
+
return;
|
|
221
|
+
socket.on("error", () => socket.destroy());
|
|
222
|
+
bridge.load().then((app) => {
|
|
223
|
+
if (socket.destroyed)
|
|
224
|
+
return;
|
|
225
|
+
relay(req, socket, bytes, sideFor(sides, app).port);
|
|
226
|
+
}).catch((error) => refuse(bridge, socket, error));
|
|
227
|
+
});
|
|
228
|
+
http.once("close", () => closeAll(sides));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/vite/dev.ts
|
|
232
|
+
async function loadApp(server, entry) {
|
|
233
|
+
const runner = server.environments.ssr?.runner;
|
|
234
|
+
if (typeof runner?.import !== "function") {
|
|
235
|
+
throw new Error(`${NAME}: Vite's ssr environment does not run modules in this process, so the server cannot be loaded.`);
|
|
236
|
+
}
|
|
237
|
+
const id = serverFile(entry, server.config) === undefined ? DEFAULT : SERVER;
|
|
238
|
+
const module = await runner.import(id);
|
|
239
|
+
return module.default;
|
|
240
|
+
}
|
|
241
|
+
async function serveFromEntry(server, entry, req, res) {
|
|
242
|
+
const app = await loadApp(server, entry);
|
|
243
|
+
await send(res, await app.fetch(toRequest(req, res)));
|
|
244
|
+
}
|
|
245
|
+
function serveDev(server, entry) {
|
|
246
|
+
bridgeSockets(server.httpServer, {
|
|
247
|
+
proxy: server.config.server.proxy,
|
|
248
|
+
load: () => loadApp(server, entry),
|
|
249
|
+
logger: server.config.logger,
|
|
250
|
+
fix: (error) => server.ssrFixStacktrace(error)
|
|
251
|
+
});
|
|
252
|
+
return () => {
|
|
253
|
+
server.middlewares.use((req, res, next) => {
|
|
254
|
+
serveFromEntry(server, entry, req, res).catch((error) => {
|
|
255
|
+
if (error instanceof Error)
|
|
256
|
+
server.ssrFixStacktrace(error);
|
|
257
|
+
next(error);
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/vite/preview.ts
|
|
264
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
265
|
+
import { relative as relative2 } from "node:path";
|
|
266
|
+
import { pathToFileURL } from "node:url";
|
|
267
|
+
async function load(file, label) {
|
|
268
|
+
if (!existsSync2(file)) {
|
|
269
|
+
throw new Error(`${NAME}: ${label} does not exist. Run react-router build before vite preview.`);
|
|
270
|
+
}
|
|
271
|
+
const module = await import(pathToFileURL(file).href);
|
|
272
|
+
if (typeof module.default?.fetch !== "function") {
|
|
273
|
+
throw new Error(`${NAME}: ${label} is not alxia's server: its default export has no fetch. Build it with alxia() in vite.config.ts's plugins, then run vite preview again.`);
|
|
274
|
+
}
|
|
275
|
+
return module.default;
|
|
276
|
+
}
|
|
277
|
+
function servePreview(server) {
|
|
278
|
+
const file = serverBuildPath(server.config);
|
|
279
|
+
const label = relative2(server.config.root, file);
|
|
280
|
+
let app;
|
|
281
|
+
const current = () => {
|
|
282
|
+
app ??= load(file, label);
|
|
283
|
+
return app;
|
|
284
|
+
};
|
|
285
|
+
bridgeSockets(server.httpServer, {
|
|
286
|
+
proxy: server.config.preview.proxy,
|
|
287
|
+
load: () => current().catch((error) => {
|
|
288
|
+
app = undefined;
|
|
289
|
+
throw error;
|
|
290
|
+
}),
|
|
291
|
+
logger: server.config.logger
|
|
292
|
+
});
|
|
293
|
+
server.middlewares.use((req, res, next) => {
|
|
294
|
+
current().then(async (loaded) => {
|
|
295
|
+
try {
|
|
296
|
+
await send(res, await loaded.fetch(toRequest(req, res)));
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (res.headersSent)
|
|
299
|
+
res.destroy(error);
|
|
300
|
+
else
|
|
301
|
+
next(error);
|
|
302
|
+
}
|
|
303
|
+
}, (error) => {
|
|
304
|
+
app = undefined;
|
|
305
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
306
|
+
server.config.logger.error(message);
|
|
307
|
+
res.statusCode = 500;
|
|
308
|
+
res.setHeader("content-type", "text/plain; charset=utf-8");
|
|
309
|
+
res.end(message);
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// src/vite/runtime.ts
|
|
315
|
+
function requireBun(command, hint = `bun --bun ${command}`, onBun = globalThis.Bun !== undefined) {
|
|
316
|
+
if (onBun)
|
|
317
|
+
return;
|
|
318
|
+
throw new Error(`${NAME}: ${command} is running on Node, and alxia's server runs on Bun. Add a bunfig.toml beside package.json with "[run]" and "bun = true", so bun run starts it on Bun, or run it as ${hint}.`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/vite/index.ts
|
|
322
|
+
var RESOLVED_SERVER = `\x00${SERVER}`;
|
|
323
|
+
var RESOLVED_DEFAULT = `\x00${DEFAULT}`;
|
|
324
|
+
function alxia(options = {}) {
|
|
325
|
+
let config;
|
|
326
|
+
let enabled = true;
|
|
327
|
+
return {
|
|
328
|
+
name: NAME,
|
|
329
|
+
enforce: "pre",
|
|
330
|
+
config(_config, env) {
|
|
331
|
+
if (env.command !== "build")
|
|
332
|
+
return;
|
|
333
|
+
const meta = this.meta;
|
|
334
|
+
const bundler = serverBuildOptions(meta.rolldownVersion !== undefined);
|
|
335
|
+
return { environments: { ssr: { build: bundler } } };
|
|
336
|
+
},
|
|
337
|
+
configResolved(resolved) {
|
|
338
|
+
config = resolved;
|
|
339
|
+
if (isChildCompiler(resolved)) {
|
|
340
|
+
enabled = false;
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const rr = contextOf(resolved);
|
|
344
|
+
enabled = rr.ssr;
|
|
345
|
+
if (enabled && rr.serverBundles !== undefined) {
|
|
346
|
+
throw new Error(`${NAME}: serverBundles splits React Router's server build in several, and alxia serves one. Remove serverBundles from react-router.config.ts.`);
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
resolveId(id) {
|
|
350
|
+
if (id === SERVER)
|
|
351
|
+
return RESOLVED_SERVER;
|
|
352
|
+
if (id === DEFAULT)
|
|
353
|
+
return RESOLVED_DEFAULT;
|
|
354
|
+
return;
|
|
355
|
+
},
|
|
356
|
+
load(id) {
|
|
357
|
+
if (id !== RESOLVED_SERVER && id !== RESOLVED_DEFAULT)
|
|
358
|
+
return;
|
|
359
|
+
if (config === undefined)
|
|
360
|
+
return;
|
|
361
|
+
if (!enabled)
|
|
362
|
+
return PASS_THROUGH;
|
|
363
|
+
const dev = config.command === "serve";
|
|
364
|
+
const file = id === RESOLVED_DEFAULT ? undefined : serverFile(options.entry, config);
|
|
365
|
+
return serverEntry({
|
|
366
|
+
file,
|
|
367
|
+
label: file === undefined ? "" : relative3(config.root, file),
|
|
368
|
+
dev,
|
|
369
|
+
client: dev ? undefined : clientPath(config)
|
|
370
|
+
});
|
|
371
|
+
},
|
|
372
|
+
configureServer(server) {
|
|
373
|
+
if (!enabled)
|
|
374
|
+
return;
|
|
375
|
+
requireBun("react-router dev");
|
|
376
|
+
return serveDev(server, options.entry);
|
|
377
|
+
},
|
|
378
|
+
configurePreviewServer(server) {
|
|
379
|
+
if (!enabled)
|
|
380
|
+
return;
|
|
381
|
+
requireBun("vite preview, or a react-router build that prerenders,", "bun --bun vite preview or bun --bun react-router build");
|
|
382
|
+
servePreview(server);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
export {
|
|
387
|
+
alxia
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
//# debugId=C3255AE453E7A65B64756E2164756E21
|
|
391
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/vite/index.ts", "../src/vite/config.ts", "../src/vite/node.ts", "../src/vite/socket.ts", "../src/vite/dev.ts", "../src/vite/preview.ts", "../src/vite/runtime.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * `@alxia/react-router/vite`: `alxia()`, the Vite plugin that makes alxia\n * the server of a React Router app, under `react-router dev` and in\n * `react-router build`, with or without an `app/server.ts`.\n */\nimport { relative } from 'node:path';\n// Types only: the plugin runs on whichever Vite the app has, never on a\n// copy of this package's own.\nimport type { Plugin, ResolvedConfig } from 'vite';\nimport {\n\tclientPath,\n\tcontextOf,\n\tDEFAULT,\n\tisChildCompiler,\n\tSERVER,\n\tserverBuildOptions,\n\tserverFile,\n} from './config';\nimport { serveDev } from './dev';\nimport { NAME, PASS_THROUGH, serverEntry } from './entry';\nimport { servePreview } from './preview';\nimport { requireBun } from './runtime';\n\nexport interface AlxiaOptions {\n\t/**\n\t * The server file, relative to Vite's root: a module whose default export\n\t * is `createServer()` from `@alxia/react-router`. By default `server.ts`\n\t * in React Router's app directory, `app/server.ts`, when it exists, and\n\t * otherwise `createServer()` with no options.\n\t */\n\treadonly entry?: string;\n}\n\nconst RESOLVED_SERVER = `\\0${SERVER}`;\nconst RESOLVED_DEFAULT = `\\0${DEFAULT}`;\n\n/**\n * The Vite plugin that makes alxia the server of a React Router app,\n * anywhere in `plugins`:\n *\n * ```ts\n * export default defineConfig({ plugins: [alxia(), reactRouter()] });\n * ```\n *\n * - **`react-router dev`**: every request Vite does not answer itself —\n * pages, data, `/api` — goes to the server, loaded through Vite's SSR\n * runner. HMR, an edit to the server, the app's own context keys and\n * its WebSocket routes work, with no restart.\n * - **`react-router build`**: `build/server/index.js` is the server, with\n * React Router's build inside it. `bun build/server/index.js` listens on\n * `PORT` (3000) and `HOST` (`0.0.0.0`); importing it starts nothing.\n * - **`vite preview`**: every request goes to that built server, as it\n * would answer it; so does React Router's prerendering.\n *\n * The server is `app/server.ts`'s default export, `createServer()` from\n * `@alxia/react-router`, or without that file `createServer()` as it is.\n */\nexport function alxia(options: AlxiaOptions = {}): Plugin {\n\tlet config: ResolvedConfig | undefined;\n\tlet enabled = true;\n\n\treturn {\n\t\tname: NAME,\n\t\t// Before React Router's plugin, wherever it is listed: its config reads\n\t\t// the server build's input from this one's, and its dev middleware,\n\t\t// which would render the pages without alxia, comes after this one's.\n\t\tenforce: 'pre',\n\t\tconfig(_config, env) {\n\t\t\tif (env.command !== 'build') return;\n\t\t\t// Vite 7's types know neither `rolldownVersion` nor `rolldownOptions`.\n\t\t\tconst meta = this.meta as { readonly rolldownVersion?: string };\n\t\t\tconst bundler = serverBuildOptions(meta.rolldownVersion !== undefined);\n\t\t\treturn { environments: { ssr: { build: bundler as never } } };\n\t\t},\n\t\tconfigResolved(resolved) {\n\t\t\tconfig = resolved;\n\t\t\t// React Router compiles route modules in a Vite server of its own,\n\t\t\t// with every plugin but its own: there is nothing to serve there.\n\t\t\tif (isChildCompiler(resolved)) {\n\t\t\t\tenabled = false;\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst rr = contextOf(resolved);\n\t\t\t// A single-page app has no server to be.\n\t\t\tenabled = rr.ssr;\n\t\t\tif (enabled && rr.serverBundles !== undefined) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${NAME}: serverBundles splits React Router's server build in several, and alxia serves one. Remove serverBundles from react-router.config.ts.`,\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\t\tresolveId(id) {\n\t\t\tif (id === SERVER) return RESOLVED_SERVER;\n\t\t\tif (id === DEFAULT) return RESOLVED_DEFAULT;\n\t\t\treturn undefined;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== RESOLVED_SERVER && id !== RESOLVED_DEFAULT) return;\n\t\t\tif (config === undefined) return;\n\t\t\t// Disabled, a single-page app: React Router still builds its server\n\t\t\t// build, to render index.html, from this input.\n\t\t\tif (!enabled) return PASS_THROUGH;\n\t\t\tconst dev = config.command === 'serve';\n\t\t\tconst file =\n\t\t\t\tid === RESOLVED_DEFAULT ? undefined : serverFile(options.entry, config);\n\t\t\treturn serverEntry({\n\t\t\t\tfile,\n\t\t\t\tlabel: file === undefined ? '' : relative(config.root, file),\n\t\t\t\tdev,\n\t\t\t\tclient: dev ? undefined : clientPath(config),\n\t\t\t});\n\t\t},\n\t\tconfigureServer(server) {\n\t\t\tif (!enabled) return;\n\t\t\trequireBun('react-router dev');\n\t\t\treturn serveDev(server, options.entry);\n\t\t},\n\t\tconfigurePreviewServer(server) {\n\t\t\tif (!enabled) return;\n\t\t\t// React Router prerenders through a preview server of its own,\n\t\t\t// resolved as `serve` too: the message cannot tell the two apart.\n\t\t\trequireBun(\n\t\t\t\t'vite preview, or a react-router build that prerenders,',\n\t\t\t\t'bun --bun vite preview or bun --bun react-router build',\n\t\t\t);\n\t\t\t// Before Vite's own files: the built server answers every request.\n\t\t\tservePreview(server);\n\t\t},\n\t};\n}\n",
|
|
6
|
+
"/**\n * What the plugin reads of Vite's and React Router's config: the server\n * file, the client folder, and the server build's bundler options.\n */\nimport { existsSync } from 'node:fs';\nimport { dirname, isAbsolute, join, relative, resolve } from 'node:path';\nimport type { ResolvedConfig } from 'vite';\nimport { NAME } from './entry';\n\n/** The server build's input, and in dev the module the requests go to. */\nexport const SERVER = 'virtual:alxia-react-router/server';\n/** The same, when there is no server file: `createServer()` as it is. */\nexport const DEFAULT = `${SERVER}-default`;\n\n/** What the plugin reads of React Router's own config. */\nexport interface ReactRouterConfig {\n\treadonly appDirectory: string;\n\treadonly buildDirectory: string;\n\treadonly serverBuildFile: string;\n\treadonly ssr: boolean;\n\treadonly serverBundles?: unknown;\n}\n\n/** React Router's config, which its plugin puts on Vite's. */\nexport function contextOf(config: object): ReactRouterConfig {\n\tconst context = (\n\t\tconfig as {\n\t\t\treadonly __reactRouterPluginContext?: {\n\t\t\t\treadonly reactRouterConfig: ReactRouterConfig;\n\t\t\t};\n\t\t}\n\t).__reactRouterPluginContext;\n\tif (context === undefined) {\n\t\tthrow new Error(\n\t\t\t`${NAME}: React Router's Vite plugin is not in this config. Add reactRouter() from @react-router/dev/vite beside alxia() in vite.config.ts.`,\n\t\t);\n\t}\n\treturn context.reactRouterConfig;\n}\n\n/** React Router's child compiler: a Vite server without its plugin, cached apart. */\nexport function isChildCompiler(config: ResolvedConfig): boolean {\n\treturn (\n\t\t(config as { readonly __reactRouterPluginContext?: unknown })\n\t\t\t.__reactRouterPluginContext === undefined &&\n\t\tconfig.cacheDir.endsWith('.vite-child-compiler')\n\t);\n}\n\n/**\n * The server file: `entry`, which must exist, or `app/server.ts` when it\n * does, or `undefined` for the default server.\n */\nexport function serverFile(\n\tentry: string | undefined,\n\tconfig: ResolvedConfig,\n): string | undefined {\n\tif (entry !== undefined) {\n\t\tconst file = isAbsolute(entry) ? entry : resolve(config.root, entry);\n\t\tif (!existsSync(file)) {\n\t\t\tthrow new Error(\n\t\t\t\t`${NAME}: the entry ${entry} does not exist. Create it, or leave entry out to use app/server.ts, or the default server without it.`,\n\t\t\t);\n\t\t}\n\t\treturn file;\n\t}\n\tconst file = join(contextOf(config).appDirectory, 'server.ts');\n\treturn existsSync(file) ? file : undefined;\n}\n\n/** The built server, absolute: `build/server/index.js`. */\nexport function serverBuildPath(config: ResolvedConfig): string {\n\tconst rr = contextOf(config);\n\tconst outDir = resolve(\n\t\tconfig.root,\n\t\tconfig.environments['ssr']?.build.outDir ??\n\t\t\tjoin(rr.buildDirectory, 'server'),\n\t);\n\treturn join(outDir, rr.serverBuildFile);\n}\n\n/** `build/client`, relative to the built `build/server/index.js`. */\nexport function clientPath(config: ResolvedConfig): string {\n\tconst built = dirname(serverBuildPath(config));\n\tconst path = relative(\n\t\tbuilt,\n\t\tjoin(contextOf(config).buildDirectory, 'client'),\n\t);\n\treturn path.startsWith('.') ? path : `./${path}`;\n}\n\n/**\n * The ssr build's options: one file, `build/server/index.js`, the server\n * and React Router's build together. Rolldown, under Vite 8, names the\n * option otherwise.\n */\nexport function serverBuildOptions(rolldown: boolean): object {\n\treturn rolldown\n\t\t? {\n\t\t\t\trolldownOptions: {\n\t\t\t\t\tinput: SERVER,\n\t\t\t\t\toutput: { codeSplitting: false },\n\t\t\t\t},\n\t\t\t}\n\t\t: {\n\t\t\t\trollupOptions: {\n\t\t\t\t\tinput: SERVER,\n\t\t\t\t\toutput: { inlineDynamicImports: true },\n\t\t\t\t},\n\t\t\t};\n}\n",
|
|
7
|
+
"/**\n * The few lines between Vite's dev and preview servers, which speak\n * `node:http`, and an alxia app, which speaks `Request` and `Response`: no\n * dependency, Bun's own `node:http` and `node:stream` underneath.\n */\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\n\n/** What Connect, Vite's middleware stack, adds to a request: the URL before any rewrite. */\ntype ConnectRequest = IncomingMessage & {\n\treadonly originalUrl?: string | undefined;\n};\n\n/** The `Request` a Node request stands for. Aborted when the client goes away. */\nexport function toRequest(req: ConnectRequest, res: ServerResponse): Request {\n\tconst secure = (req.socket as { encrypted?: boolean }).encrypted === true;\n\tconst url = new URL(\n\t\treq.originalUrl ?? req.url ?? '/',\n\t\t`${secure ? 'https' : 'http'}://${req.headers.host ?? 'localhost'}`,\n\t);\n\tconst headers = new Headers();\n\tconst raw = req.rawHeaders;\n\tfor (let i = 0; i + 1 < raw.length; i += 2) {\n\t\tconst name = raw[i] as string;\n\t\t// HTTP/2's pseudo-headers (`:path`, `:method`) are not headers.\n\t\tif (!name.startsWith(':')) headers.append(name, raw[i + 1] as string);\n\t}\n\tconst controller = new AbortController();\n\tres.once('close', () => {\n\t\tif (!res.writableFinished) controller.abort();\n\t});\n\tconst method = req.method ?? 'GET';\n\tconst init: RequestInit & { duplex?: 'half' } = {\n\t\tmethod,\n\t\theaders,\n\t\tsignal: controller.signal,\n\t};\n\tif (method !== 'GET' && method !== 'HEAD') {\n\t\tinit.body = Readable.toWeb(req) as unknown as ReadableStream<Uint8Array>;\n\t\tinit.duplex = 'half';\n\t}\n\treturn new Request(url, init);\n}\n\n/** Writes `response` to `res`, streaming its body chunk by chunk as it comes. */\nexport async function send(\n\tres: ServerResponse,\n\tresponse: Response,\n): Promise<void> {\n\tres.statusCode = response.status;\n\tif (response.statusText !== '') res.statusMessage = response.statusText;\n\tfor (const [name, value] of response.headers) {\n\t\tif (name !== 'set-cookie') res.setHeader(name, value);\n\t}\n\tconst cookies = response.headers.getSetCookie();\n\tif (cookies.length > 0) res.setHeader('set-cookie', cookies);\n\tif (response.body === null) {\n\t\tres.end();\n\t\treturn;\n\t}\n\tres.flushHeaders();\n\tconst reader = response.body.getReader();\n\tconst cancel = () => void reader.cancel().catch(() => {});\n\tres.once('close', cancel);\n\ttry {\n\t\tfor (;;) {\n\t\t\tconst { done, value } = await reader.read();\n\t\t\tif (done) break;\n\t\t\tif (res.destroyed) return;\n\t\t\tif (!res.write(value)) {\n\t\t\t\tawait new Promise<void>((resolve) => {\n\t\t\t\t\tconst go = () => {\n\t\t\t\t\t\tres.off('drain', go);\n\t\t\t\t\t\tres.off('close', go);\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t};\n\t\t\t\t\tres.on('drain', go);\n\t\t\t\t\tres.on('close', go);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tres.end();\n\t} finally {\n\t\tres.off('close', cancel);\n\t}\n}\n",
|
|
8
|
+
"/**\n * Under `react-router dev` and `vite preview`: the app's WebSocket routes. Vite's server is a\n * `node:http` one, and an alxia socket is `Bun.serve`'s upgrade, so each\n * upgrade Vite leaves is relayed, bytes as they come, to a `Bun.serve` of\n * the app's own on a loopback port: its hooks, its validation, its socket\n * handlers, exactly as `listen` runs them.\n */\nimport type { IncomingMessage } from 'node:http';\nimport { connect, type Socket } from 'node:net';\nimport type { DevApp } from './dev';\nimport { NAME } from './entry';\n\n/** The subprotocols of Vite's own socket, the HMR one and its ping: Vite answers them. */\nconst VITE = new Set(['vite-hmr', 'vite-ping']);\n\n/** Whether the upgrade asks for Vite's own socket. */\nexport function isVites(req: Pick<IncomingMessage, 'headers'>): boolean {\n\tconst protocols = req.headers['sec-websocket-protocol'];\n\tif (protocols === undefined) return false;\n\treturn protocols.split(',').some((protocol) => VITE.has(protocol.trim()));\n}\n\n/** One entry of Vite's `server.proxy`, as far as its sockets go. */\ntype ProxyEntry = string | { readonly ws?: boolean; readonly target?: unknown };\n\n/**\n * Whether Vite's `server.proxy` relays this upgrade itself: an entry whose\n * context matches the URL, as Vite matches it (`^…` a RegExp, otherwise a\n * prefix), and that proxies sockets (`ws: true`, or a `ws:`/`wss:` target).\n */\nexport function isProxied(\n\turl: string,\n\tproxy: Readonly<Record<string, ProxyEntry>> | undefined,\n): boolean {\n\tfor (const [context, entry] of Object.entries(proxy ?? {})) {\n\t\tconst matches =\n\t\t\t(context.startsWith('^') && new RegExp(context).test(url)) ||\n\t\t\turl.startsWith(context);\n\t\tif (!matches) continue;\n\t\tconst options = typeof entry === 'string' ? { target: entry } : entry;\n\t\tconst target = String(options.target ?? '');\n\t\tif (\n\t\t\toptions.ws === true ||\n\t\t\ttarget.startsWith('ws:') ||\n\t\t\ttarget.startsWith('wss:')\n\t\t) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\n/** The request line and headers of `req`, as they came: what the side server reads. */\nexport function head(\n\treq: Pick<IncomingMessage, 'method' | 'url' | 'rawHeaders'>,\n): string {\n\tconst lines = [`${req.method ?? 'GET'} ${req.url ?? '/'} HTTP/1.1`];\n\tconst raw = req.rawHeaders;\n\tfor (let i = 0; i + 1 < raw.length; i += 2) {\n\t\tlines.push(`${raw[i]}: ${raw[i + 1]}`);\n\t}\n\treturn `${lines.join('\\r\\n')}\\r\\n\\r\\n`;\n}\n\n/** The side servers: the current app's, and those an edit retired, until their sockets close. */\nexport interface Sides {\n\tcurrent:\n\t\t| { readonly app: DevApp; readonly side: Bun.Server<unknown> }\n\t\t| undefined;\n\treadonly retired: Set<Bun.Server<unknown>>;\n}\n\n/**\n * The side server of `app`, started on its first upgrade. A new app, an\n * edit's, retires the previous one: no new connection, and the sockets open\n * on it stay, with the handlers they opened with, until they close.\n */\nexport function sideFor(sides: Sides, app: DevApp): Bun.Server<unknown> {\n\tif (sides.current?.app === app) return sides.current.side;\n\tconst side = Bun.serve({\n\t\tport: 0,\n\t\thostname: '127.0.0.1',\n\t\tfetch: (request, bun) => app.fetch(request, bun),\n\t\twebsocket: app.websocket,\n\t} as Bun.Serve.Options<never>) as Bun.Server<unknown>;\n\tconst old = sides.current?.side;\n\tif (old !== undefined) {\n\t\tsides.retired.add(old);\n\t\tvoid old.stop().then(() => sides.retired.delete(old));\n\t}\n\tsides.current = { app, side };\n\treturn side;\n}\n\n/** Stops every side server, open sockets and all: Vite's server closed. */\nexport function closeAll(sides: Sides): void {\n\tfor (const side of [...sides.retired, sides.current?.side]) {\n\t\tvoid side?.stop(true);\n\t}\n\tsides.current = undefined;\n\tsides.retired.clear();\n}\n\n/** Relays the upgrade on `socket` to the side server on `port`, both ways. */\nfunction relay(\n\treq: IncomingMessage,\n\tsocket: Socket,\n\tbytes: Buffer,\n\tport: number,\n): void {\n\tconst upstream = connect(port, '127.0.0.1', () => {\n\t\tupstream.write(head(req));\n\t\tif (bytes.length > 0) upstream.write(bytes);\n\t\tupstream.pipe(socket);\n\t\tsocket.pipe(upstream);\n\t});\n\tupstream.on('error', () => socket.destroy());\n\tupstream.on('close', () => socket.destroy());\n\tsocket.on('close', () => upstream.destroy());\n}\n\n/** What the relay listens to on Vite's HTTP server, HTTP/1 or HTTP/2 alike. */\ninterface Upgrades {\n\ton(\n\t\tevent: 'upgrade',\n\t\tlistener: (req: IncomingMessage, socket: Socket, bytes: Buffer) => void,\n\t): unknown;\n\tonce(event: 'close', listener: () => void): unknown;\n}\n\n/** What the relay needs of the server it listens on: Vite's dev server or its preview. */\nexport interface Bridge {\n\t/** Vite's `server.proxy` (or `preview.proxy`): the upgrades it relays are its own. */\n\treadonly proxy: Readonly<Record<string, ProxyEntry>> | undefined;\n\t/** The app, as it is now. */\n\treadonly load: () => Promise<DevApp>;\n\t/** Where an upgrade that failed is printed: Vite's logger. */\n\treadonly logger: { error(message: string): void };\n\t/** Maps an error's stack to the source, as Vite's `ssrFixStacktrace` does. */\n\treadonly fix?: (error: Error) => void;\n}\n\n/** An upgrade the app could not take: reported, and a 500 to the client. */\nfunction refuse(bridge: Bridge, socket: Socket, error: unknown): void {\n\tif (error instanceof Error) bridge.fix?.(error);\n\tbridge.logger.error(\n\t\t`${NAME}: a WebSocket upgrade failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`,\n\t);\n\tif (socket.writable) {\n\t\tsocket.end(\n\t\t\t'HTTP/1.1 500 Internal Server Error\\r\\nConnection: close\\r\\nContent-Length: 0\\r\\n\\r\\n',\n\t\t);\n\t}\n}\n\n/**\n * Relays the app's upgrades on Vite's HTTP server to a side server of the\n * app. Vite's own socket and the sockets its proxy relays are left to\n * Vite. Without an HTTP server (Vite's middleware mode), there is nothing\n * to listen on.\n */\nexport function bridgeSockets(http: Upgrades | null, bridge: Bridge): void {\n\tif (http === null) return;\n\tconst sides: Sides = { current: undefined, retired: new Set() };\n\thttp.on('upgrade', (req: IncomingMessage, socket: Socket, bytes: Buffer) => {\n\t\tif (isVites(req) || isProxied(req.url ?? '/', bridge.proxy)) return;\n\t\tsocket.on('error', () => socket.destroy());\n\t\tbridge\n\t\t\t.load()\n\t\t\t.then((app) => {\n\t\t\t\tif (socket.destroyed) return;\n\t\t\t\trelay(req, socket, bytes, sideFor(sides, app).port as number);\n\t\t\t})\n\t\t\t.catch((error: unknown) => refuse(bridge, socket, error));\n\t});\n\thttp.once('close', () => closeAll(sides));\n}\n",
|
|
9
|
+
"/**\n * Under `react-router dev`: a request Vite left, answered by the server,\n * loaded through the ssr environment's module runner.\n */\nimport type { ViteDevServer } from 'vite';\nimport { DEFAULT, SERVER, serverFile } from './config';\nimport { NAME } from './entry';\nimport { send, toRequest } from './node';\nimport { bridgeSockets } from './socket';\n\n/** What the dev server module exports: the alxia app, as `server.create` made it. */\nexport interface DevApp {\n\tfetch(request: Request, server?: Bun.Server<unknown>): Promise<Response>;\n\treadonly websocket: Bun.WebSocketHandler<never>;\n}\n\n/**\n * The app, as the server file is now: the runner keeps the module until an\n * edit invalidates it, so an edit is a new app.\n */\nexport async function loadApp(\n\tserver: ViteDevServer,\n\tentry: string | undefined,\n): Promise<DevApp> {\n\t// Duck-typed rather than Vite's `isRunnableDevEnvironment`: the app's\n\t// Vite may be another copy than the one this package would import.\n\tconst runner = (\n\t\tserver.environments.ssr as\n\t\t\t| { readonly runner?: { readonly import?: unknown } }\n\t\t\t| undefined\n\t)?.runner;\n\tif (typeof runner?.import !== 'function') {\n\t\tthrow new Error(\n\t\t\t`${NAME}: Vite's ssr environment does not run modules in this process, so the server cannot be loaded.`,\n\t\t);\n\t}\n\t// Chosen on each request: a server file created or deleted while the\n\t// dev server runs is used from the next one.\n\tconst id = serverFile(entry, server.config) === undefined ? DEFAULT : SERVER;\n\tconst module = (await (runner.import as (id: string) => Promise<unknown>)(\n\t\tid,\n\t)) as { readonly default: DevApp };\n\treturn module.default;\n}\n\nexport async function serveFromEntry(\n\tserver: ViteDevServer,\n\tentry: string | undefined,\n\treq: Parameters<typeof toRequest>[0],\n\tres: Parameters<typeof toRequest>[1],\n): Promise<void> {\n\tconst app = await loadApp(server, entry);\n\tawait send(res, await app.fetch(toRequest(req, res)));\n}\n\n/**\n * `react-router dev`: the app's sockets on Vite's server now, and, returned\n * for after Vite's own middlewares, every request Vite leaves to the app.\n */\nexport function serveDev(\n\tserver: ViteDevServer,\n\tentry: string | undefined,\n): () => void {\n\tbridgeSockets(server.httpServer, {\n\t\tproxy: server.config.server.proxy,\n\t\tload: () => loadApp(server, entry),\n\t\tlogger: server.config.logger,\n\t\tfix: (error) => server.ssrFixStacktrace(error),\n\t});\n\treturn () => {\n\t\tserver.middlewares.use((req, res, next) => {\n\t\t\tserveFromEntry(server, entry, req, res).catch((error: unknown) => {\n\t\t\t\tif (error instanceof Error) server.ssrFixStacktrace(error);\n\t\t\t\tnext(error);\n\t\t\t});\n\t\t});\n\t};\n}\n",
|
|
10
|
+
"/**\n * Under `vite preview`, and React Router's prerendering, which runs on it:\n * every request goes to the built server, `build/server/index.js`'s\n * default export, as `bun build/server/index.js` would answer it — its\n * hooks, its `/api`, the client's files and the pages.\n */\nimport { existsSync } from 'node:fs';\nimport { relative } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { PreviewServer } from 'vite';\nimport { serverBuildPath } from './config';\nimport type { DevApp } from './dev';\nimport { NAME } from './entry';\nimport { send, toRequest } from './node';\nimport { bridgeSockets } from './socket';\n\n/** What the built server's default export is: the alxia app. */\ninterface Fetcher {\n\tfetch(request: Request): Response | Promise<Response>;\n}\n\n/** The built server's app, or an error saying why there is none. */\nasync function load(file: string, label: string): Promise<Fetcher> {\n\tif (!existsSync(file)) {\n\t\tthrow new Error(\n\t\t\t`${NAME}: ${label} does not exist. Run react-router build before vite preview.`,\n\t\t);\n\t}\n\tconst module = (await import(pathToFileURL(file).href)) as {\n\t\treadonly default?: Partial<Fetcher>;\n\t};\n\tif (typeof module.default?.fetch !== 'function') {\n\t\tthrow new Error(\n\t\t\t`${NAME}: ${label} is not alxia's server: its default export has no fetch. Build it with alxia() in vite.config.ts's plugins, then run vite preview again.`,\n\t\t);\n\t}\n\treturn module.default as Fetcher;\n}\n\n/**\n * Hands every request to the built server, before Vite's own files: the\n * app serves `build/client` itself, with its hooks and cache headers. Its\n * WebSocket routes connect too, relayed as under `react-router dev`.\n */\nexport function servePreview(server: PreviewServer): void {\n\tconst file = serverBuildPath(server.config);\n\tconst label = relative(server.config.root, file);\n\t// Loaded on the first request, as `bun build/server/index.js` would be\n\t// at startup; a failed load is tried again on the next, after a build.\n\tlet app: Promise<Fetcher> | undefined;\n\tconst current = (): Promise<Fetcher> => {\n\t\tapp ??= load(file, label);\n\t\treturn app;\n\t};\n\tbridgeSockets(server.httpServer, {\n\t\tproxy: server.config.preview.proxy,\n\t\t// The built server's default export is the alxia app, `websocket` and all.\n\t\tload: () =>\n\t\t\tcurrent().catch((error: unknown) => {\n\t\t\t\tapp = undefined;\n\t\t\t\tthrow error;\n\t\t\t}) as Promise<DevApp>,\n\t\tlogger: server.config.logger,\n\t});\n\tserver.middlewares.use((req, res, next) => {\n\t\tcurrent().then(\n\t\t\tasync (loaded) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait send(res, await loaded.fetch(toRequest(req, res)));\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Mid-body, the response has started: end it rather than answer twice.\n\t\t\t\t\tif (res.headersSent) res.destroy(error as Error);\n\t\t\t\t\telse next(error);\n\t\t\t\t}\n\t\t\t},\n\t\t\t(error: unknown) => {\n\t\t\t\tapp = undefined;\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tserver.config.logger.error(message);\n\t\t\t\tres.statusCode = 500;\n\t\t\t\tres.setHeader('content-type', 'text/plain; charset=utf-8');\n\t\t\t\tres.end(message);\n\t\t\t},\n\t\t);\n\t});\n}\n",
|
|
11
|
+
"import { NAME } from './entry';\n\n/**\n * Refuses, with what to do, a dev or preview server running on Node. The\n * React Router CLI's `#!/usr/bin/env node` runs it on Node whenever a node\n * is installed, even from `bun run dev`, and alxia's server then fails on\n * its first request with `ReferenceError: Bun is not defined`.\n */\nexport function requireBun(\n\tcommand: string,\n\thint = `bun --bun ${command}`,\n\tonBun = (globalThis as { Bun?: unknown }).Bun !== undefined,\n): void {\n\tif (onBun) return;\n\tthrow new Error(\n\t\t`${NAME}: ${command} is running on Node, and alxia's server runs on Bun. Add a bunfig.toml beside package.json with \"[run]\" and \"bun = true\", so bun run starts it on Bun, or run it as ${hint}.`,\n\t);\n}\n"
|
|
12
|
+
],
|
|
13
|
+
"mappings": "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",
|
|
14
|
+
"debugId": "C3255AE453E7A65B64756E2164756E21",
|
|
15
|
+
"names": []
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The few lines between Vite's dev and preview servers, which speak
|
|
3
|
+
* `node:http`, and an alxia app, which speaks `Request` and `Response`: no
|
|
4
|
+
* dependency, Bun's own `node:http` and `node:stream` underneath.
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
7
|
+
/** What Connect, Vite's middleware stack, adds to a request: the URL before any rewrite. */
|
|
8
|
+
type ConnectRequest = IncomingMessage & {
|
|
9
|
+
readonly originalUrl?: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
/** The `Request` a Node request stands for. Aborted when the client goes away. */
|
|
12
|
+
export declare function toRequest(req: ConnectRequest, res: ServerResponse): Request;
|
|
13
|
+
/** Writes `response` to `res`, streaming its body chunk by chunk as it comes. */
|
|
14
|
+
export declare function send(res: ServerResponse, response: Response): Promise<void>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=node.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/vite/node.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGjE,4FAA4F;AAC5F,KAAK,cAAc,GAAG,eAAe,GAAG;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,CAAC;AAEF,kFAAkF;AAClF,wBAAgB,SAAS,CAAC,GAAG,EAAE,cAAc,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CA4B3E;AAED,iFAAiF;AACjF,wBAAsB,IAAI,CACzB,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,QAAQ,GAChB,OAAO,CAAC,IAAI,CAAC,CAqCf"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { PreviewServer } from 'vite';
|
|
2
|
+
/**
|
|
3
|
+
* Hands every request to the built server, before Vite's own files: the
|
|
4
|
+
* app serves `build/client` itself, with its hooks and cache headers. Its
|
|
5
|
+
* WebSocket routes connect too, relayed as under `react-router dev`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function servePreview(server: PreviewServer): void;
|
|
8
|
+
//# sourceMappingURL=preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../../src/vite/preview.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,MAAM,CAAC;AA8B1C;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAyCxD"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuses, with what to do, a dev or preview server running on Node. The
|
|
3
|
+
* React Router CLI's `#!/usr/bin/env node` runs it on Node whenever a node
|
|
4
|
+
* is installed, even from `bun run dev`, and alxia's server then fails on
|
|
5
|
+
* its first request with `ReferenceError: Bun is not defined`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function requireBun(command: string, hint?: string, onBun?: boolean): void;
|
|
8
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/vite/runtime.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAgB,UAAU,CACzB,OAAO,EAAE,MAAM,EACf,IAAI,SAAyB,EAC7B,KAAK,UAAsD,GACzD,IAAI,CAKN"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Under `react-router dev` and `vite preview`: the app's WebSocket routes. Vite's server is a
|
|
3
|
+
* `node:http` one, and an alxia socket is `Bun.serve`'s upgrade, so each
|
|
4
|
+
* upgrade Vite leaves is relayed, bytes as they come, to a `Bun.serve` of
|
|
5
|
+
* the app's own on a loopback port: its hooks, its validation, its socket
|
|
6
|
+
* handlers, exactly as `listen` runs them.
|
|
7
|
+
*/
|
|
8
|
+
import type { IncomingMessage } from 'node:http';
|
|
9
|
+
import { type Socket } from 'node:net';
|
|
10
|
+
import type { DevApp } from './dev';
|
|
11
|
+
/** Whether the upgrade asks for Vite's own socket. */
|
|
12
|
+
export declare function isVites(req: Pick<IncomingMessage, 'headers'>): boolean;
|
|
13
|
+
/** One entry of Vite's `server.proxy`, as far as its sockets go. */
|
|
14
|
+
type ProxyEntry = string | {
|
|
15
|
+
readonly ws?: boolean;
|
|
16
|
+
readonly target?: unknown;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Whether Vite's `server.proxy` relays this upgrade itself: an entry whose
|
|
20
|
+
* context matches the URL, as Vite matches it (`^…` a RegExp, otherwise a
|
|
21
|
+
* prefix), and that proxies sockets (`ws: true`, or a `ws:`/`wss:` target).
|
|
22
|
+
*/
|
|
23
|
+
export declare function isProxied(url: string, proxy: Readonly<Record<string, ProxyEntry>> | undefined): boolean;
|
|
24
|
+
/** The request line and headers of `req`, as they came: what the side server reads. */
|
|
25
|
+
export declare function head(req: Pick<IncomingMessage, 'method' | 'url' | 'rawHeaders'>): string;
|
|
26
|
+
/** The side servers: the current app's, and those an edit retired, until their sockets close. */
|
|
27
|
+
export interface Sides {
|
|
28
|
+
current: {
|
|
29
|
+
readonly app: DevApp;
|
|
30
|
+
readonly side: Bun.Server<unknown>;
|
|
31
|
+
} | undefined;
|
|
32
|
+
readonly retired: Set<Bun.Server<unknown>>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The side server of `app`, started on its first upgrade. A new app, an
|
|
36
|
+
* edit's, retires the previous one: no new connection, and the sockets open
|
|
37
|
+
* on it stay, with the handlers they opened with, until they close.
|
|
38
|
+
*/
|
|
39
|
+
export declare function sideFor(sides: Sides, app: DevApp): Bun.Server<unknown>;
|
|
40
|
+
/** Stops every side server, open sockets and all: Vite's server closed. */
|
|
41
|
+
export declare function closeAll(sides: Sides): void;
|
|
42
|
+
/** What the relay listens to on Vite's HTTP server, HTTP/1 or HTTP/2 alike. */
|
|
43
|
+
interface Upgrades {
|
|
44
|
+
on(event: 'upgrade', listener: (req: IncomingMessage, socket: Socket, bytes: Buffer) => void): unknown;
|
|
45
|
+
once(event: 'close', listener: () => void): unknown;
|
|
46
|
+
}
|
|
47
|
+
/** What the relay needs of the server it listens on: Vite's dev server or its preview. */
|
|
48
|
+
export interface Bridge {
|
|
49
|
+
/** Vite's `server.proxy` (or `preview.proxy`): the upgrades it relays are its own. */
|
|
50
|
+
readonly proxy: Readonly<Record<string, ProxyEntry>> | undefined;
|
|
51
|
+
/** The app, as it is now. */
|
|
52
|
+
readonly load: () => Promise<DevApp>;
|
|
53
|
+
/** Where an upgrade that failed is printed: Vite's logger. */
|
|
54
|
+
readonly logger: {
|
|
55
|
+
error(message: string): void;
|
|
56
|
+
};
|
|
57
|
+
/** Maps an error's stack to the source, as Vite's `ssrFixStacktrace` does. */
|
|
58
|
+
readonly fix?: (error: Error) => void;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Relays the app's upgrades on Vite's HTTP server to a side server of the
|
|
62
|
+
* app. Vite's own socket and the sockets its proxy relays are left to
|
|
63
|
+
* Vite. Without an HTTP server (Vite's middleware mode), there is nothing
|
|
64
|
+
* to listen on.
|
|
65
|
+
*/
|
|
66
|
+
export declare function bridgeSockets(http: Upgrades | null, bridge: Bridge): void;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=socket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"socket.d.ts","sourceRoot":"","sources":["../../src/vite/socket.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAMpC,sDAAsD;AACtD,wBAAgB,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,CAAC,GAAG,OAAO,CAItE;AAED,oEAAoE;AACpE,KAAK,UAAU,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,SAAS,CACxB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,SAAS,GACrD,OAAO,CAiBT;AAED,uFAAuF;AACvF,wBAAgB,IAAI,CACnB,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,QAAQ,GAAG,KAAK,GAAG,YAAY,CAAC,GACzD,MAAM,CAOR;AAED,iGAAiG;AACjG,MAAM,WAAW,KAAK;IACrB,OAAO,EACJ;QAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;KAAE,GAC5D,SAAS,CAAC;IACb,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;CAC3C;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAetE;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAM3C;AAoBD,+EAA+E;AAC/E,UAAU,QAAQ;IACjB,EAAE,CACD,KAAK,EAAE,SAAS,EAChB,QAAQ,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,GACrE,OAAO,CAAC;IACX,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC;CACpD;AAED,0FAA0F;AAC1F,MAAM,WAAW,MAAM;IACtB,sFAAsF;IACtF,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,GAAG,SAAS,CAAC;IACjE,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,8DAA8D;IAC9D,QAAQ,CAAC,MAAM,EAAE;QAAE,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAClD,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACtC;AAeD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,QAAQ,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAezE"}
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @alxia/react-router documentation
|
|
2
|
+
|
|
3
|
+
The [package README](../README.md) is the short version. This folder is
|
|
4
|
+
the long one: the setup, the Vite plugin and its default server,
|
|
5
|
+
customising the server, the typed context, the client's files, and what to
|
|
6
|
+
do with each message.
|
|
7
|
+
|
|
8
|
+
| Page | Read it when |
|
|
9
|
+
| --- | --- |
|
|
10
|
+
| [Guide](guide.md) | adding alxia to the official template, revealing the default server with `bunx alxia-react-router reveal`, customising the server with `createServer()`, typing the loaders with `Register` or a type argument, the app's own context keys, what `vite preview` and prerendering serve, the escape hatches (another entry, overriding the wiring, a server of your own), what runs around the pages, WebSockets in dev, in preview and in the build, the client's files, OpenAPI, testing, or deploying |
|
|
11
|
+
| [Troubleshooting](troubleshooting.md) | something went wrong and you have the message, or a page streams, hydrates or reads its context unlike you expected |
|
|
12
|
+
| [Roadmap](roadmap.md) | wondering what is coming, and what is not planned |
|