@moostjs/vite 0.6.30 → 0.6.31
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/dist/index.cjs +48 -3
- package/dist/index.d.ts +55 -0
- package/dist/index.mjs +48 -3
- package/dist/prod-server.cjs +43 -2
- package/dist/prod-server.d.ts +24 -1
- package/dist/prod-server.mjs +43 -2
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -40,6 +40,32 @@ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
|
40
40
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
41
41
|
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
42
42
|
/**
|
|
43
|
+
* Invokes the SSR `render()` for a page request. Shared by all three render call
|
|
44
|
+
* sites so the render contract stays identical across them:
|
|
45
|
+
* - always passes the page request context ({@link TSSRRenderContext}) as the
|
|
46
|
+
* second `render()` argument;
|
|
47
|
+
* - when a Moost HTTP adapter is provided (and supports it), runs the render
|
|
48
|
+
* inside an HTTP event context seeded from the real `(req, res)` pair, so
|
|
49
|
+
* SSR-time `fetch('/api/...')` self-calls inherit the viewer's identity
|
|
50
|
+
* headers (`forwardHeaders`) instead of arriving anonymous, and `Set-Cookie`
|
|
51
|
+
* headers from those self-calls are drained onto the page response;
|
|
52
|
+
* - degrades to a plain `render(url, ctx)` call when the adapter is absent or
|
|
53
|
+
* predates `withHttpContext`.
|
|
54
|
+
*/ async function renderSSRPage(opts) {
|
|
55
|
+
const { render, url, req, res, http } = opts;
|
|
56
|
+
const ctx = {
|
|
57
|
+
headers: req.headers,
|
|
58
|
+
method: req.method || "GET",
|
|
59
|
+
req
|
|
60
|
+
};
|
|
61
|
+
if (http && typeof http.withHttpContext === "function") {
|
|
62
|
+
const { result, response } = await http.withHttpContext(req, res, () => render(url, ctx));
|
|
63
|
+
for (const cookie of response.getSetCookieStrings()) res.appendHeader("set-cookie", cookie);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
return render(url, ctx);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
43
69
|
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
44
70
|
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
45
71
|
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
@@ -390,6 +416,14 @@ function moostVite(options) {
|
|
|
390
416
|
let moostMiddleware = null;
|
|
391
417
|
let localFetchTeardown = null;
|
|
392
418
|
/**
|
|
419
|
+
* Live ref to the booted MoostHttp instance (from Vite's SSR module graph).
|
|
420
|
+
* Used to run SSR renders inside an HTTP event context (`withHttpContext`)
|
|
421
|
+
* so SSR self-fetches inherit the page request's identity. Exposed on the
|
|
422
|
+
* resolved config for the `createSSRServer()` dev branch; re-pointed on every
|
|
423
|
+
* boot, cleared on eject.
|
|
424
|
+
*/ const moostHttpRef = { current: null };
|
|
425
|
+
const ssrForwarding = options.ssrFetchForwarding !== false;
|
|
426
|
+
/**
|
|
393
427
|
* Boot-identity stamps (dev-only "mongrel state" diagnostic — a stale pipeline
|
|
394
428
|
* presents as security middleware silently switched off). `bootGeneration` is
|
|
395
429
|
* bumped on every eject; `bootingGeneration` records which generation the
|
|
@@ -407,6 +441,7 @@ function moostVite(options) {
|
|
|
407
441
|
logger.log(`🔌 [2mOvertaking HTTP.listen`);
|
|
408
442
|
if (bootingGeneration !== bootGeneration) logger.error(`⚠️ A stale Moost boot captured the HTTP middleware (boot generation ${bootingGeneration}, latest ${bootGeneration}) — an HMR reload race; a follow-up reload will replace it.`);
|
|
409
443
|
httpCaptured = true;
|
|
444
|
+
moostHttpRef.current = this;
|
|
410
445
|
if (options.middleware) moostMiddleware = this.getServerCb((req) => {
|
|
411
446
|
pendingNextMap.get(req)?.();
|
|
412
447
|
});
|
|
@@ -457,6 +492,7 @@ function moostVite(options) {
|
|
|
457
492
|
*/ const ejectApp = (cleanupInstances) => {
|
|
458
493
|
bootGeneration++;
|
|
459
494
|
moostMiddleware = null;
|
|
495
|
+
moostHttpRef.current = null;
|
|
460
496
|
if (localFetchTeardown) {
|
|
461
497
|
localFetchTeardown();
|
|
462
498
|
localFetchTeardown = null;
|
|
@@ -476,7 +512,8 @@ function moostVite(options) {
|
|
|
476
512
|
const serverDefines = {
|
|
477
513
|
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
478
514
|
__MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
|
|
479
|
-
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null)
|
|
515
|
+
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null),
|
|
516
|
+
__MOOST_SSR_FORWARDING__: JSON.stringify(ssrForwarding)
|
|
480
517
|
};
|
|
481
518
|
const ssrInput = {
|
|
482
519
|
server: options.serverEntry || generatedServerEntry(cfg.root),
|
|
@@ -578,8 +615,10 @@ function moostVite(options) {
|
|
|
578
615
|
port: options.port,
|
|
579
616
|
ssrOutlet: options.ssrOutlet,
|
|
580
617
|
ssrState: options.ssrState,
|
|
581
|
-
ssrHead: options.ssrHead
|
|
618
|
+
ssrHead: options.ssrHead,
|
|
619
|
+
ssrFetchForwarding: options.ssrFetchForwarding
|
|
582
620
|
};
|
|
621
|
+
config.__moostViteHttpRef = moostHttpRef;
|
|
583
622
|
},
|
|
584
623
|
async transform(code, id) {
|
|
585
624
|
if (!id.endsWith(".ts")) return null;
|
|
@@ -712,7 +751,13 @@ function moostVite(options) {
|
|
|
712
751
|
let template = await fs.readFile((0, node_path.resolve)(server.config.root, "index.html"), "utf8");
|
|
713
752
|
template = await server.transformIndexHtml(url, template);
|
|
714
753
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
715
|
-
const result = await
|
|
754
|
+
const result = await renderSSRPage({
|
|
755
|
+
render,
|
|
756
|
+
url,
|
|
757
|
+
req,
|
|
758
|
+
res,
|
|
759
|
+
http: ssrForwarding ? moostHttpRef.current : null
|
|
760
|
+
});
|
|
716
761
|
sendSSRResponse(res, template, {
|
|
717
762
|
ssrOutlet,
|
|
718
763
|
ssrState,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PluginOption } from 'vite';
|
|
2
|
+
import { IncomingMessage } from 'node:http';
|
|
2
3
|
|
|
3
4
|
interface TMoostViteDevOptions {
|
|
4
5
|
/**
|
|
@@ -76,6 +77,20 @@ interface TMoostViteDevOptions {
|
|
|
76
77
|
* Default: `true`.
|
|
77
78
|
*/
|
|
78
79
|
ssrFetch?: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Run each SSR render inside an HTTP event context seeded from the incoming
|
|
82
|
+
* page request. SSR-time `fetch('/api/...')` self-calls then inherit the
|
|
83
|
+
* viewer's identity headers (authorization, cookie, accept-language,
|
|
84
|
+
* x-forwarded-for, x-request-id — see the HTTP adapter's `forwardHeaders`
|
|
85
|
+
* option) instead of arriving anonymous, and `Set-Cookie` headers they
|
|
86
|
+
* produce are drained onto the page response. Applies in dev and is baked
|
|
87
|
+
* into the generated production server.
|
|
88
|
+
*
|
|
89
|
+
* Set to `false` to keep SSR self-calls anonymous.
|
|
90
|
+
*
|
|
91
|
+
* Default: `true`.
|
|
92
|
+
*/
|
|
93
|
+
ssrFetchForwarding?: boolean;
|
|
79
94
|
/**
|
|
80
95
|
* Run Moost as Connect middleware instead of taking over the server.
|
|
81
96
|
* When enabled, Moost runs first and unmatched requests fall through to
|
|
@@ -161,4 +176,44 @@ interface TMoostViteDevOptions {
|
|
|
161
176
|
}
|
|
162
177
|
declare function moostVite(options: TMoostViteDevOptions): PluginOption;
|
|
163
178
|
|
|
179
|
+
/**
|
|
180
|
+
* The value an SSR `render(url)` function may return. Only `html` is required;
|
|
181
|
+
* every other field is optional, so a render returning `{ html }` (or
|
|
182
|
+
* `{ html, state }`) keeps working unchanged.
|
|
183
|
+
*/
|
|
184
|
+
interface TSSRRenderResult {
|
|
185
|
+
/** Server-rendered app markup, substituted into the `ssrOutlet` marker. */
|
|
186
|
+
html: string;
|
|
187
|
+
/**
|
|
188
|
+
* Serialized app state. Wrapped in `<script>window.__SSR_STATE__=…</script>`
|
|
189
|
+
* and substituted into the `ssrState` marker (nothing is emitted when absent).
|
|
190
|
+
*/
|
|
191
|
+
state?: string;
|
|
192
|
+
/**
|
|
193
|
+
* Ready-to-insert `<head>` tags (`<title>`, `<meta>`, canonical, Open Graph,
|
|
194
|
+
* JSON-LD, …), substituted verbatim into the `ssrHead` marker. This is exactly
|
|
195
|
+
* the shape head managers emit (e.g. unhead's `renderSSRHead(head).headTags`).
|
|
196
|
+
*/
|
|
197
|
+
head?: string;
|
|
198
|
+
/** HTTP status code for the response (default `200`) — e.g. `404` for a soft-404, `301` for a redirect. */
|
|
199
|
+
status?: number;
|
|
200
|
+
/** Extra response headers — e.g. `cache-control`, or `location` alongside `status: 301`. */
|
|
201
|
+
headers?: Record<string, string>;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Context of the incoming page request, passed as the second argument to the
|
|
205
|
+
* SSR `render()` entry. Additive: entries typed `render(url)` keep working.
|
|
206
|
+
*/
|
|
207
|
+
interface TSSRRenderContext {
|
|
208
|
+
/** Raw request headers of the page request (node `IncomingMessage.headers`). */
|
|
209
|
+
headers: IncomingMessage['headers'];
|
|
210
|
+
/** HTTP method of the page request (the SSR fallbacks only render `GET` today). */
|
|
211
|
+
method: string;
|
|
212
|
+
/** Escape hatch for everything else (socket address, etc.). */
|
|
213
|
+
req: IncomingMessage;
|
|
214
|
+
}
|
|
215
|
+
/** The SSR entry contract: `render(url, ctx?)`. `ctx` is new and optional. */
|
|
216
|
+
type TSSRRender = (url: string, ctx?: TSSRRenderContext) => Promise<TSSRRenderResult> | TSSRRenderResult;
|
|
217
|
+
|
|
164
218
|
export { moostVite };
|
|
219
|
+
export type { TMoostViteDevOptions, TSSRRender, TSSRRenderContext, TSSRRenderResult };
|
package/dist/index.mjs
CHANGED
|
@@ -11,6 +11,32 @@ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
|
11
11
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
12
12
|
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
13
13
|
/**
|
|
14
|
+
* Invokes the SSR `render()` for a page request. Shared by all three render call
|
|
15
|
+
* sites so the render contract stays identical across them:
|
|
16
|
+
* - always passes the page request context ({@link TSSRRenderContext}) as the
|
|
17
|
+
* second `render()` argument;
|
|
18
|
+
* - when a Moost HTTP adapter is provided (and supports it), runs the render
|
|
19
|
+
* inside an HTTP event context seeded from the real `(req, res)` pair, so
|
|
20
|
+
* SSR-time `fetch('/api/...')` self-calls inherit the viewer's identity
|
|
21
|
+
* headers (`forwardHeaders`) instead of arriving anonymous, and `Set-Cookie`
|
|
22
|
+
* headers from those self-calls are drained onto the page response;
|
|
23
|
+
* - degrades to a plain `render(url, ctx)` call when the adapter is absent or
|
|
24
|
+
* predates `withHttpContext`.
|
|
25
|
+
*/ async function renderSSRPage(opts) {
|
|
26
|
+
const { render, url, req, res, http } = opts;
|
|
27
|
+
const ctx = {
|
|
28
|
+
headers: req.headers,
|
|
29
|
+
method: req.method || "GET",
|
|
30
|
+
req
|
|
31
|
+
};
|
|
32
|
+
if (http && typeof http.withHttpContext === "function") {
|
|
33
|
+
const { result, response } = await http.withHttpContext(req, res, () => render(url, ctx));
|
|
34
|
+
for (const cookie of response.getSetCookieStrings()) res.appendHeader("set-cookie", cookie);
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
return render(url, ctx);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
14
40
|
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
15
41
|
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
16
42
|
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
@@ -359,6 +385,14 @@ function moostVite(options) {
|
|
|
359
385
|
let moostMiddleware = null;
|
|
360
386
|
let localFetchTeardown = null;
|
|
361
387
|
/**
|
|
388
|
+
* Live ref to the booted MoostHttp instance (from Vite's SSR module graph).
|
|
389
|
+
* Used to run SSR renders inside an HTTP event context (`withHttpContext`)
|
|
390
|
+
* so SSR self-fetches inherit the page request's identity. Exposed on the
|
|
391
|
+
* resolved config for the `createSSRServer()` dev branch; re-pointed on every
|
|
392
|
+
* boot, cleared on eject.
|
|
393
|
+
*/ const moostHttpRef = { current: null };
|
|
394
|
+
const ssrForwarding = options.ssrFetchForwarding !== false;
|
|
395
|
+
/**
|
|
362
396
|
* Boot-identity stamps (dev-only "mongrel state" diagnostic — a stale pipeline
|
|
363
397
|
* presents as security middleware silently switched off). `bootGeneration` is
|
|
364
398
|
* bumped on every eject; `bootingGeneration` records which generation the
|
|
@@ -376,6 +410,7 @@ function moostVite(options) {
|
|
|
376
410
|
logger.log(`🔌 [2mOvertaking HTTP.listen`);
|
|
377
411
|
if (bootingGeneration !== bootGeneration) logger.error(`⚠️ A stale Moost boot captured the HTTP middleware (boot generation ${bootingGeneration}, latest ${bootGeneration}) — an HMR reload race; a follow-up reload will replace it.`);
|
|
378
412
|
httpCaptured = true;
|
|
413
|
+
moostHttpRef.current = this;
|
|
379
414
|
if (options.middleware) moostMiddleware = this.getServerCb((req) => {
|
|
380
415
|
pendingNextMap.get(req)?.();
|
|
381
416
|
});
|
|
@@ -426,6 +461,7 @@ function moostVite(options) {
|
|
|
426
461
|
*/ const ejectApp = (cleanupInstances) => {
|
|
427
462
|
bootGeneration++;
|
|
428
463
|
moostMiddleware = null;
|
|
464
|
+
moostHttpRef.current = null;
|
|
429
465
|
if (localFetchTeardown) {
|
|
430
466
|
localFetchTeardown();
|
|
431
467
|
localFetchTeardown = null;
|
|
@@ -445,7 +481,8 @@ function moostVite(options) {
|
|
|
445
481
|
const serverDefines = {
|
|
446
482
|
"process.env.MOOST_DEFERRED_ENV": "\"production\"",
|
|
447
483
|
__MOOST_ENTRY__: JSON.stringify(`./${entryKey}.js`),
|
|
448
|
-
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null)
|
|
484
|
+
__MOOST_PREFIX__: JSON.stringify(prefixes ?? null),
|
|
485
|
+
__MOOST_SSR_FORWARDING__: JSON.stringify(ssrForwarding)
|
|
449
486
|
};
|
|
450
487
|
const ssrInput = {
|
|
451
488
|
server: options.serverEntry || generatedServerEntry(cfg.root),
|
|
@@ -547,8 +584,10 @@ function moostVite(options) {
|
|
|
547
584
|
port: options.port,
|
|
548
585
|
ssrOutlet: options.ssrOutlet,
|
|
549
586
|
ssrState: options.ssrState,
|
|
550
|
-
ssrHead: options.ssrHead
|
|
587
|
+
ssrHead: options.ssrHead,
|
|
588
|
+
ssrFetchForwarding: options.ssrFetchForwarding
|
|
551
589
|
};
|
|
590
|
+
config.__moostViteHttpRef = moostHttpRef;
|
|
552
591
|
},
|
|
553
592
|
async transform(code, id) {
|
|
554
593
|
if (!id.endsWith(".ts")) return null;
|
|
@@ -673,7 +712,13 @@ function moostVite(options) {
|
|
|
673
712
|
let template = await fs.readFile(resolve(server.config.root, "index.html"), "utf8");
|
|
674
713
|
template = await server.transformIndexHtml(url, template);
|
|
675
714
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
676
|
-
const result = await
|
|
715
|
+
const result = await renderSSRPage({
|
|
716
|
+
render,
|
|
717
|
+
url,
|
|
718
|
+
req,
|
|
719
|
+
res,
|
|
720
|
+
http: ssrForwarding ? moostHttpRef.current : null
|
|
721
|
+
});
|
|
677
722
|
sendSSRResponse(res, template, {
|
|
678
723
|
ssrOutlet,
|
|
679
724
|
ssrState,
|
package/dist/prod-server.cjs
CHANGED
|
@@ -7,6 +7,32 @@ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
|
7
7
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
8
8
|
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
9
9
|
/**
|
|
10
|
+
* Invokes the SSR `render()` for a page request. Shared by all three render call
|
|
11
|
+
* sites so the render contract stays identical across them:
|
|
12
|
+
* - always passes the page request context ({@link TSSRRenderContext}) as the
|
|
13
|
+
* second `render()` argument;
|
|
14
|
+
* - when a Moost HTTP adapter is provided (and supports it), runs the render
|
|
15
|
+
* inside an HTTP event context seeded from the real `(req, res)` pair, so
|
|
16
|
+
* SSR-time `fetch('/api/...')` self-calls inherit the viewer's identity
|
|
17
|
+
* headers (`forwardHeaders`) instead of arriving anonymous, and `Set-Cookie`
|
|
18
|
+
* headers from those self-calls are drained onto the page response;
|
|
19
|
+
* - degrades to a plain `render(url, ctx)` call when the adapter is absent or
|
|
20
|
+
* predates `withHttpContext`.
|
|
21
|
+
*/ async function renderSSRPage(opts) {
|
|
22
|
+
const { render, url, req, res, http } = opts;
|
|
23
|
+
const ctx = {
|
|
24
|
+
headers: req.headers,
|
|
25
|
+
method: req.method || "GET",
|
|
26
|
+
req
|
|
27
|
+
};
|
|
28
|
+
if (http && typeof http.withHttpContext === "function") {
|
|
29
|
+
const { result, response } = await http.withHttpContext(req, res, () => render(url, ctx));
|
|
30
|
+
for (const cookie of response.getSetCookieStrings()) res.appendHeader("set-cookie", cookie);
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
return render(url, ctx);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
10
36
|
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
11
37
|
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
12
38
|
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
@@ -72,6 +98,8 @@ async function createSSRServer(options) {
|
|
|
72
98
|
const ssrOutlet = config.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
73
99
|
const ssrState = config.ssrState || DEFAULT_SSR_STATE;
|
|
74
100
|
const ssrHead = config.ssrHead || DEFAULT_SSR_HEAD;
|
|
101
|
+
const ssrForwarding = config.ssrFetchForwarding !== false;
|
|
102
|
+
const httpRef = vite.config.__moostViteHttpRef;
|
|
75
103
|
let ssrFallback = null;
|
|
76
104
|
if (ssrEntry) {
|
|
77
105
|
const fs = await import("node:fs/promises");
|
|
@@ -83,7 +111,13 @@ async function createSSRServer(options) {
|
|
|
83
111
|
let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
|
|
84
112
|
template = await vite.transformIndexHtml(url, template);
|
|
85
113
|
const { render } = await vite.ssrLoadModule(ssrEntry);
|
|
86
|
-
const result = await
|
|
114
|
+
const result = await renderSSRPage({
|
|
115
|
+
render,
|
|
116
|
+
url,
|
|
117
|
+
req,
|
|
118
|
+
res,
|
|
119
|
+
http: ssrForwarding ? httpRef?.current : null
|
|
120
|
+
});
|
|
87
121
|
sendSSRResponse(res, template, {
|
|
88
122
|
ssrOutlet,
|
|
89
123
|
ssrState,
|
|
@@ -117,6 +151,7 @@ async function createSSRServer(options) {
|
|
|
117
151
|
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : DEFAULT_SSR_STATE);
|
|
118
152
|
const ssrHead = opts.ssrHead || (__MOOST_SSR_HEAD__ !== void 0 ? __MOOST_SSR_HEAD__ : DEFAULT_SSR_HEAD);
|
|
119
153
|
const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
|
|
154
|
+
const ssrForwarding = opts.ssrFetchForwarding ?? __MOOST_SSR_FORWARDING__ ?? true;
|
|
120
155
|
const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
|
|
121
156
|
const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf8");
|
|
122
157
|
let render = null;
|
|
@@ -165,7 +200,13 @@ async function createSSRServer(options) {
|
|
|
165
200
|
}
|
|
166
201
|
try {
|
|
167
202
|
if (render) {
|
|
168
|
-
const result = await
|
|
203
|
+
const result = await renderSSRPage({
|
|
204
|
+
render,
|
|
205
|
+
url,
|
|
206
|
+
req,
|
|
207
|
+
res,
|
|
208
|
+
http: ssrForwarding ? moostHttpRef.instance : null
|
|
209
|
+
});
|
|
169
210
|
sendSSRResponse(res, template, {
|
|
170
211
|
ssrOutlet,
|
|
171
212
|
ssrState,
|
package/dist/prod-server.d.ts
CHANGED
|
@@ -24,6 +24,20 @@ interface TSSRRenderResult {
|
|
|
24
24
|
/** Extra response headers — e.g. `cache-control`, or `location` alongside `status: 301`. */
|
|
25
25
|
headers?: Record<string, string>;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Context of the incoming page request, passed as the second argument to the
|
|
29
|
+
* SSR `render()` entry. Additive: entries typed `render(url)` keep working.
|
|
30
|
+
*/
|
|
31
|
+
interface TSSRRenderContext {
|
|
32
|
+
/** Raw request headers of the page request (node `IncomingMessage.headers`). */
|
|
33
|
+
headers: IncomingMessage['headers'];
|
|
34
|
+
/** HTTP method of the page request (the SSR fallbacks only render `GET` today). */
|
|
35
|
+
method: string;
|
|
36
|
+
/** Escape hatch for everything else (socket address, etc.). */
|
|
37
|
+
req: IncomingMessage;
|
|
38
|
+
}
|
|
39
|
+
/** The SSR entry contract: `render(url, ctx?)`. `ctx` is new and optional. */
|
|
40
|
+
type TSSRRender = (url: string, ctx?: TSSRRenderContext) => Promise<TSSRRenderResult> | TSSRRenderResult;
|
|
27
41
|
|
|
28
42
|
type TMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
|
29
43
|
interface TSSRServerOptions {
|
|
@@ -48,6 +62,15 @@ interface TSSRServerOptions {
|
|
|
48
62
|
ssrState?: string;
|
|
49
63
|
/** Override: HTML placeholder for SSR-rendered `<head>` tags */
|
|
50
64
|
ssrHead?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Override: run each SSR render inside an HTTP event context seeded from the
|
|
67
|
+
* incoming page request, so SSR-time `fetch('/api/...')` self-calls inherit
|
|
68
|
+
* the viewer's identity headers (authorization, cookie, accept-language,
|
|
69
|
+
* x-forwarded-for, x-request-id — see the adapter's `forwardHeaders` option)
|
|
70
|
+
* and their `Set-Cookie` headers land on the page response.
|
|
71
|
+
* Set to `false` to keep SSR self-calls anonymous. Default: `true`.
|
|
72
|
+
*/
|
|
73
|
+
ssrFetchForwarding?: boolean;
|
|
51
74
|
}
|
|
52
75
|
interface TSSRServer {
|
|
53
76
|
/** Add Connect-compatible middleware (runs in both dev and prod) */
|
|
@@ -58,4 +81,4 @@ interface TSSRServer {
|
|
|
58
81
|
declare function createSSRServer(options?: TSSRServerOptions): Promise<TSSRServer>;
|
|
59
82
|
|
|
60
83
|
export { createSSRServer };
|
|
61
|
-
export type { TSSRRenderResult, TSSRServer, TSSRServerOptions };
|
|
84
|
+
export type { TSSRRender, TSSRRenderContext, TSSRRenderResult, TSSRServer, TSSRServerOptions };
|
package/dist/prod-server.mjs
CHANGED
|
@@ -8,6 +8,32 @@ const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
|
8
8
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
9
9
|
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
10
10
|
/**
|
|
11
|
+
* Invokes the SSR `render()` for a page request. Shared by all three render call
|
|
12
|
+
* sites so the render contract stays identical across them:
|
|
13
|
+
* - always passes the page request context ({@link TSSRRenderContext}) as the
|
|
14
|
+
* second `render()` argument;
|
|
15
|
+
* - when a Moost HTTP adapter is provided (and supports it), runs the render
|
|
16
|
+
* inside an HTTP event context seeded from the real `(req, res)` pair, so
|
|
17
|
+
* SSR-time `fetch('/api/...')` self-calls inherit the viewer's identity
|
|
18
|
+
* headers (`forwardHeaders`) instead of arriving anonymous, and `Set-Cookie`
|
|
19
|
+
* headers from those self-calls are drained onto the page response;
|
|
20
|
+
* - degrades to a plain `render(url, ctx)` call when the adapter is absent or
|
|
21
|
+
* predates `withHttpContext`.
|
|
22
|
+
*/ async function renderSSRPage(opts) {
|
|
23
|
+
const { render, url, req, res, http } = opts;
|
|
24
|
+
const ctx = {
|
|
25
|
+
headers: req.headers,
|
|
26
|
+
method: req.method || "GET",
|
|
27
|
+
req
|
|
28
|
+
};
|
|
29
|
+
if (http && typeof http.withHttpContext === "function") {
|
|
30
|
+
const { result, response } = await http.withHttpContext(req, res, () => render(url, ctx));
|
|
31
|
+
for (const cookie of response.getSetCookieStrings()) res.appendHeader("set-cookie", cookie);
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
return render(url, ctx);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
11
37
|
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
12
38
|
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
13
39
|
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
@@ -73,6 +99,8 @@ async function createSSRServer(options) {
|
|
|
73
99
|
const ssrOutlet = config.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
74
100
|
const ssrState = config.ssrState || DEFAULT_SSR_STATE;
|
|
75
101
|
const ssrHead = config.ssrHead || DEFAULT_SSR_HEAD;
|
|
102
|
+
const ssrForwarding = config.ssrFetchForwarding !== false;
|
|
103
|
+
const httpRef = vite.config.__moostViteHttpRef;
|
|
76
104
|
let ssrFallback = null;
|
|
77
105
|
if (ssrEntry) {
|
|
78
106
|
const fs = await import("node:fs/promises");
|
|
@@ -84,7 +112,13 @@ async function createSSRServer(options) {
|
|
|
84
112
|
let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
|
|
85
113
|
template = await vite.transformIndexHtml(url, template);
|
|
86
114
|
const { render } = await vite.ssrLoadModule(ssrEntry);
|
|
87
|
-
const result = await
|
|
115
|
+
const result = await renderSSRPage({
|
|
116
|
+
render,
|
|
117
|
+
url,
|
|
118
|
+
req,
|
|
119
|
+
res,
|
|
120
|
+
http: ssrForwarding ? httpRef?.current : null
|
|
121
|
+
});
|
|
88
122
|
sendSSRResponse(res, template, {
|
|
89
123
|
ssrOutlet,
|
|
90
124
|
ssrState,
|
|
@@ -118,6 +152,7 @@ async function createSSRServer(options) {
|
|
|
118
152
|
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : DEFAULT_SSR_STATE);
|
|
119
153
|
const ssrHead = opts.ssrHead || (__MOOST_SSR_HEAD__ !== void 0 ? __MOOST_SSR_HEAD__ : DEFAULT_SSR_HEAD);
|
|
120
154
|
const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
|
|
155
|
+
const ssrForwarding = opts.ssrFetchForwarding ?? __MOOST_SSR_FORWARDING__ ?? true;
|
|
121
156
|
const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
|
|
122
157
|
const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf8");
|
|
123
158
|
let render = null;
|
|
@@ -166,7 +201,13 @@ async function createSSRServer(options) {
|
|
|
166
201
|
}
|
|
167
202
|
try {
|
|
168
203
|
if (render) {
|
|
169
|
-
const result = await
|
|
204
|
+
const result = await renderSSRPage({
|
|
205
|
+
render,
|
|
206
|
+
url,
|
|
207
|
+
req,
|
|
208
|
+
res,
|
|
209
|
+
http: ssrForwarding ? moostHttpRef.instance : null
|
|
210
|
+
});
|
|
170
211
|
sendSSRResponse(res, template, {
|
|
171
212
|
ssrOutlet,
|
|
172
213
|
ssrState,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moostjs/vite",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.31",
|
|
4
4
|
"description": "Vite Dev plugin for moostjs",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"composables",
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"peerDependencies": {
|
|
59
59
|
"sirv": "^3.0.0",
|
|
60
60
|
"vite": "^8.0.0",
|
|
61
|
-
"@moostjs/event-http": "^0.6.
|
|
62
|
-
"moost": "^0.6.
|
|
61
|
+
"@moostjs/event-http": "^0.6.31",
|
|
62
|
+
"moost": "^0.6.31"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"sirv": {
|