@moostjs/vite 0.6.27 → 0.6.29
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 +25 -0
- package/dist/index.cjs +31 -5
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +31 -5
- package/dist/prod-server.cjs +37 -10
- package/dist/prod-server.d.ts +28 -1
- package/dist/prod-server.mjs +37 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -122,6 +122,30 @@ In dev, the plugin adds an SSR fallback middleware that renders pages on the ser
|
|
|
122
122
|
- **ssr** — server-side render function (`dist/server/ssr/`)
|
|
123
123
|
- **server** — production Node.js server (`dist/server/server.js`)
|
|
124
124
|
|
|
125
|
+
### Render Contract
|
|
126
|
+
|
|
127
|
+
Your SSR entry exports a `render(url)` that returns HTML plus optional metadata. Only `html` is required — every other field is optional, so `{ html }` (or `{ html, state }`) keeps working unchanged:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// entry-server.ts
|
|
131
|
+
import type { TSSRRenderResult } from '@moostjs/vite/server'
|
|
132
|
+
|
|
133
|
+
export async function render(url: string): Promise<TSSRRenderResult> {
|
|
134
|
+
// ...render your app for `url`...
|
|
135
|
+
return {
|
|
136
|
+
html, // → substituted into `<!--ssr-outlet-->`
|
|
137
|
+
state, // → wrapped as <script>window.__SSR_STATE__=…</script> at `<!--ssr-state-->`
|
|
138
|
+
head, // → per-page <head> tags injected at `<!--ssr-head-->`
|
|
139
|
+
status, // → HTTP status code (default 200) — e.g. 404 for an unknown slug
|
|
140
|
+
headers, // → extra response headers — e.g. cache-control, or location with status: 301
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The three markers (`ssrOutlet`, `ssrState`, `ssrHead`) are plain string replacements — a marker missing from `index.html` is simply skipped. Put `<!--ssr-head-->` inside `<head>` to get crawler-visible per-page `<title>` / `<meta>` / canonical / Open Graph / JSON-LD tags. `head` is exactly the string a head manager emits (e.g. unhead's `renderSSRHead(head).headTags`).
|
|
146
|
+
|
|
147
|
+
`status` and `headers` give the render control over the response: return `status: 404` for a real not-found (crawlers treat soft-404s worse), set `cache-control`, or redirect with `status: 301` + `headers: { location: '/new-url' }`. Headers are applied after the default `Content-Type: text/html`, so a render may override it.
|
|
148
|
+
|
|
125
149
|
### SPA Mode
|
|
126
150
|
|
|
127
151
|
Omit `ssrEntry` and Vite serves the app as a standard SPA. The production build still generates a server that serves static files and API routes — it just skips server-side rendering.
|
|
@@ -236,6 +260,7 @@ The plugin injects a `__VITE_ID` decorator on `@Injectable` and `@Controller` cl
|
|
|
236
260
|
| `ssrEntry` | `string` | — | Vue/React SSR entry module (e.g. `'/src/entry-server.ts'`) |
|
|
237
261
|
| `ssrOutlet` | `string` | `'<!--ssr-outlet-->'` | HTML placeholder for SSR-rendered content |
|
|
238
262
|
| `ssrState` | `string` | `'<!--ssr-state-->'` | HTML placeholder for SSR state transfer script |
|
|
263
|
+
| `ssrHead` | `string` | `'<!--ssr-head-->'` | HTML placeholder for SSR-rendered `<head>` tags (place inside `<head>`) |
|
|
239
264
|
| `serverEntry` | `string` | — | Custom production server entry file (e.g. `'./server.ts'`) |
|
|
240
265
|
| `ssrExternal` | `string[]` | — | Packages to keep external in the SSR build (middleware mode, `vite build` only). Concatenated with `cfg.ssr.external`. See [SSR Bundle Size](#ssr-bundle-size). |
|
|
241
266
|
|
package/dist/index.cjs
CHANGED
|
@@ -38,6 +38,27 @@ let moost = require("moost");
|
|
|
38
38
|
const PLUGIN_NAME = "moost-vite";
|
|
39
39
|
const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
40
40
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
41
|
+
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
42
|
+
/**
|
|
43
|
+
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
44
|
+
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
45
|
+
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
46
|
+
* `createSSRServer` branch and the prod `createSSRServer` branch) so the render
|
|
47
|
+
* contract stays identical across them.
|
|
48
|
+
*
|
|
49
|
+
* Fully backwards compatible: every field beyond `html` is optional, a missing
|
|
50
|
+
* marker in the template is a no-op `String.replace`, and headers from the render
|
|
51
|
+
* result are applied after the default `Content-Type: text/html` so a render can
|
|
52
|
+
* override it. Replacements use a function argument so `$`-sequences in the
|
|
53
|
+
* rendered payloads (JSON state, JSON-LD `<head>` tags) are inserted literally
|
|
54
|
+
* rather than interpreted as `String.replace` special patterns.
|
|
55
|
+
*/ function sendSSRResponse(res, template, markers, result) {
|
|
56
|
+
res.statusCode = result.status ?? 200;
|
|
57
|
+
res.setHeader("Content-Type", "text/html");
|
|
58
|
+
if (result.headers) for (const [key, value] of Object.entries(result.headers)) res.setHeader(key, value);
|
|
59
|
+
const stateScript = result.state ? `<script>window.__SSR_STATE__=${result.state}<\/script>` : "";
|
|
60
|
+
res.end(template.replace(markers.ssrOutlet, () => result.html).replace(markers.ssrState, () => stateScript).replace(markers.ssrHead, () => result.head ?? ""));
|
|
61
|
+
}
|
|
41
62
|
function entryBasename(entry) {
|
|
42
63
|
return entry.split("/").pop().replace(/\.ts$/, ".js");
|
|
43
64
|
}
|
|
@@ -446,6 +467,7 @@ function moostVite(options) {
|
|
|
446
467
|
serverDefines.__MOOST_SSR_ENTRY__ = JSON.stringify(`./ssr/${ssrBasename}`);
|
|
447
468
|
serverDefines.__MOOST_SSR_OUTLET__ = JSON.stringify(options.ssrOutlet || DEFAULT_SSR_OUTLET);
|
|
448
469
|
serverDefines.__MOOST_SSR_STATE__ = JSON.stringify(options.ssrState || DEFAULT_SSR_STATE);
|
|
470
|
+
serverDefines.__MOOST_SSR_HEAD__ = JSON.stringify(options.ssrHead || DEFAULT_SSR_HEAD);
|
|
449
471
|
}
|
|
450
472
|
const outDir = cfg.build?.outDir || "dist";
|
|
451
473
|
const isBuild = env.command === "build";
|
|
@@ -534,7 +556,8 @@ function moostVite(options) {
|
|
|
534
556
|
prefix: prefixes,
|
|
535
557
|
port: options.port,
|
|
536
558
|
ssrOutlet: options.ssrOutlet,
|
|
537
|
-
ssrState: options.ssrState
|
|
559
|
+
ssrState: options.ssrState,
|
|
560
|
+
ssrHead: options.ssrHead
|
|
538
561
|
};
|
|
539
562
|
},
|
|
540
563
|
async transform(code, id) {
|
|
@@ -650,6 +673,7 @@ function moostVite(options) {
|
|
|
650
673
|
async configureServer(server) {
|
|
651
674
|
const ssrOutlet = options.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
652
675
|
const ssrState = options.ssrState || DEFAULT_SSR_STATE;
|
|
676
|
+
const ssrHead = options.ssrHead || DEFAULT_SSR_HEAD;
|
|
653
677
|
const fs = await import("node:fs/promises");
|
|
654
678
|
return () => {
|
|
655
679
|
server.middlewares.use(async (req, res, next) => {
|
|
@@ -660,10 +684,12 @@ function moostVite(options) {
|
|
|
660
684
|
let template = await fs.readFile((0, node_path.resolve)(server.config.root, "index.html"), "utf8");
|
|
661
685
|
template = await server.transformIndexHtml(url, template);
|
|
662
686
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
663
|
-
const
|
|
664
|
-
res
|
|
665
|
-
|
|
666
|
-
|
|
687
|
+
const result = await render(url);
|
|
688
|
+
sendSSRResponse(res, template, {
|
|
689
|
+
ssrOutlet,
|
|
690
|
+
ssrState,
|
|
691
|
+
ssrHead
|
|
692
|
+
}, result);
|
|
667
693
|
} catch (error) {
|
|
668
694
|
server.ssrFixStacktrace(error);
|
|
669
695
|
console.error(error);
|
package/dist/index.d.ts
CHANGED
|
@@ -117,6 +117,12 @@ interface TMoostViteDevOptions {
|
|
|
117
117
|
* Default: `'<!--ssr-state-->'`
|
|
118
118
|
*/
|
|
119
119
|
ssrState?: string;
|
|
120
|
+
/**
|
|
121
|
+
* HTML placeholder for SSR-rendered `<head>` tags (`<title>`, `<meta>`,
|
|
122
|
+
* canonical, Open Graph, JSON-LD). Place the marker inside `<head>` and return
|
|
123
|
+
* `head` from `render()` to inject per-page tags. Default: `'<!--ssr-head-->'`
|
|
124
|
+
*/
|
|
125
|
+
ssrHead?: string;
|
|
120
126
|
/**
|
|
121
127
|
* Path to a custom server entry file (e.g., `'./server.ts'`).
|
|
122
128
|
* When provided, this file is used as the production server build entry.
|
package/dist/index.mjs
CHANGED
|
@@ -9,6 +9,27 @@ import { Moost, clearGlobalWooks, createLogger, getMoostInfact, getMoostMate } f
|
|
|
9
9
|
const PLUGIN_NAME = "moost-vite";
|
|
10
10
|
const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
11
11
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
12
|
+
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
13
|
+
/**
|
|
14
|
+
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
15
|
+
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
16
|
+
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
17
|
+
* `createSSRServer` branch and the prod `createSSRServer` branch) so the render
|
|
18
|
+
* contract stays identical across them.
|
|
19
|
+
*
|
|
20
|
+
* Fully backwards compatible: every field beyond `html` is optional, a missing
|
|
21
|
+
* marker in the template is a no-op `String.replace`, and headers from the render
|
|
22
|
+
* result are applied after the default `Content-Type: text/html` so a render can
|
|
23
|
+
* override it. Replacements use a function argument so `$`-sequences in the
|
|
24
|
+
* rendered payloads (JSON state, JSON-LD `<head>` tags) are inserted literally
|
|
25
|
+
* rather than interpreted as `String.replace` special patterns.
|
|
26
|
+
*/ function sendSSRResponse(res, template, markers, result) {
|
|
27
|
+
res.statusCode = result.status ?? 200;
|
|
28
|
+
res.setHeader("Content-Type", "text/html");
|
|
29
|
+
if (result.headers) for (const [key, value] of Object.entries(result.headers)) res.setHeader(key, value);
|
|
30
|
+
const stateScript = result.state ? `<script>window.__SSR_STATE__=${result.state}<\/script>` : "";
|
|
31
|
+
res.end(template.replace(markers.ssrOutlet, () => result.html).replace(markers.ssrState, () => stateScript).replace(markers.ssrHead, () => result.head ?? ""));
|
|
32
|
+
}
|
|
12
33
|
function entryBasename(entry) {
|
|
13
34
|
return entry.split("/").pop().replace(/\.ts$/, ".js");
|
|
14
35
|
}
|
|
@@ -415,6 +436,7 @@ function moostVite(options) {
|
|
|
415
436
|
serverDefines.__MOOST_SSR_ENTRY__ = JSON.stringify(`./ssr/${ssrBasename}`);
|
|
416
437
|
serverDefines.__MOOST_SSR_OUTLET__ = JSON.stringify(options.ssrOutlet || DEFAULT_SSR_OUTLET);
|
|
417
438
|
serverDefines.__MOOST_SSR_STATE__ = JSON.stringify(options.ssrState || DEFAULT_SSR_STATE);
|
|
439
|
+
serverDefines.__MOOST_SSR_HEAD__ = JSON.stringify(options.ssrHead || DEFAULT_SSR_HEAD);
|
|
418
440
|
}
|
|
419
441
|
const outDir = cfg.build?.outDir || "dist";
|
|
420
442
|
const isBuild = env.command === "build";
|
|
@@ -503,7 +525,8 @@ function moostVite(options) {
|
|
|
503
525
|
prefix: prefixes,
|
|
504
526
|
port: options.port,
|
|
505
527
|
ssrOutlet: options.ssrOutlet,
|
|
506
|
-
ssrState: options.ssrState
|
|
528
|
+
ssrState: options.ssrState,
|
|
529
|
+
ssrHead: options.ssrHead
|
|
507
530
|
};
|
|
508
531
|
},
|
|
509
532
|
async transform(code, id) {
|
|
@@ -611,6 +634,7 @@ function moostVite(options) {
|
|
|
611
634
|
async configureServer(server) {
|
|
612
635
|
const ssrOutlet = options.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
613
636
|
const ssrState = options.ssrState || DEFAULT_SSR_STATE;
|
|
637
|
+
const ssrHead = options.ssrHead || DEFAULT_SSR_HEAD;
|
|
614
638
|
const fs = await import("node:fs/promises");
|
|
615
639
|
return () => {
|
|
616
640
|
server.middlewares.use(async (req, res, next) => {
|
|
@@ -621,10 +645,12 @@ function moostVite(options) {
|
|
|
621
645
|
let template = await fs.readFile(resolve(server.config.root, "index.html"), "utf8");
|
|
622
646
|
template = await server.transformIndexHtml(url, template);
|
|
623
647
|
const { render } = await server.ssrLoadModule(options.ssrEntry);
|
|
624
|
-
const
|
|
625
|
-
res
|
|
626
|
-
|
|
627
|
-
|
|
648
|
+
const result = await render(url);
|
|
649
|
+
sendSSRResponse(res, template, {
|
|
650
|
+
ssrOutlet,
|
|
651
|
+
ssrState,
|
|
652
|
+
ssrHead
|
|
653
|
+
}, result);
|
|
628
654
|
} catch (error) {
|
|
629
655
|
server.ssrFixStacktrace(error);
|
|
630
656
|
console.error(error);
|
package/dist/prod-server.cjs
CHANGED
|
@@ -5,6 +5,27 @@ let moost = require("moost");
|
|
|
5
5
|
const PLUGIN_NAME = "moost-vite";
|
|
6
6
|
const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
7
7
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
8
|
+
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
9
|
+
/**
|
|
10
|
+
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
11
|
+
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
12
|
+
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
13
|
+
* `createSSRServer` branch and the prod `createSSRServer` branch) so the render
|
|
14
|
+
* contract stays identical across them.
|
|
15
|
+
*
|
|
16
|
+
* Fully backwards compatible: every field beyond `html` is optional, a missing
|
|
17
|
+
* marker in the template is a no-op `String.replace`, and headers from the render
|
|
18
|
+
* result are applied after the default `Content-Type: text/html` so a render can
|
|
19
|
+
* override it. Replacements use a function argument so `$`-sequences in the
|
|
20
|
+
* rendered payloads (JSON state, JSON-LD `<head>` tags) are inserted literally
|
|
21
|
+
* rather than interpreted as `String.replace` special patterns.
|
|
22
|
+
*/ function sendSSRResponse(res, template, markers, result) {
|
|
23
|
+
res.statusCode = result.status ?? 200;
|
|
24
|
+
res.setHeader("Content-Type", "text/html");
|
|
25
|
+
if (result.headers) for (const [key, value] of Object.entries(result.headers)) res.setHeader(key, value);
|
|
26
|
+
const stateScript = result.state ? `<script>window.__SSR_STATE__=${result.state}<\/script>` : "";
|
|
27
|
+
res.end(template.replace(markers.ssrOutlet, () => result.html).replace(markers.ssrState, () => stateScript).replace(markers.ssrHead, () => result.head ?? ""));
|
|
28
|
+
}
|
|
8
29
|
/**
|
|
9
30
|
* Normalizes the `prefix` option (single mount or list of mounts) into a list of
|
|
10
31
|
* normalized entries: leading slash ensured, trailing slash stripped.
|
|
@@ -50,6 +71,7 @@ async function createSSRServer(options) {
|
|
|
50
71
|
const ssrEntry = config.ssrEntry;
|
|
51
72
|
const ssrOutlet = config.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
52
73
|
const ssrState = config.ssrState || DEFAULT_SSR_STATE;
|
|
74
|
+
const ssrHead = config.ssrHead || DEFAULT_SSR_HEAD;
|
|
53
75
|
let ssrFallback = null;
|
|
54
76
|
if (ssrEntry) {
|
|
55
77
|
const fs = await import("node:fs/promises");
|
|
@@ -61,10 +83,12 @@ async function createSSRServer(options) {
|
|
|
61
83
|
let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
|
|
62
84
|
template = await vite.transformIndexHtml(url, template);
|
|
63
85
|
const { render } = await vite.ssrLoadModule(ssrEntry);
|
|
64
|
-
const
|
|
65
|
-
res
|
|
66
|
-
|
|
67
|
-
|
|
86
|
+
const result = await render(url);
|
|
87
|
+
sendSSRResponse(res, template, {
|
|
88
|
+
ssrOutlet,
|
|
89
|
+
ssrState,
|
|
90
|
+
ssrHead
|
|
91
|
+
}, result);
|
|
68
92
|
} catch (error) {
|
|
69
93
|
vite.ssrFixStacktrace(error);
|
|
70
94
|
console.error(error);
|
|
@@ -89,8 +113,9 @@ async function createSSRServer(options) {
|
|
|
89
113
|
const path = await import("node:path");
|
|
90
114
|
const { createServer: createHttpServer } = await import("node:http");
|
|
91
115
|
const clientDir = opts.clientDir || path.resolve(__dirname, "../client");
|
|
92
|
-
const ssrOutlet = opts.ssrOutlet || (__MOOST_SSR_OUTLET__ !== void 0 ? __MOOST_SSR_OUTLET__ :
|
|
93
|
-
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ :
|
|
116
|
+
const ssrOutlet = opts.ssrOutlet || (__MOOST_SSR_OUTLET__ !== void 0 ? __MOOST_SSR_OUTLET__ : DEFAULT_SSR_OUTLET);
|
|
117
|
+
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : DEFAULT_SSR_STATE);
|
|
118
|
+
const ssrHead = opts.ssrHead || (__MOOST_SSR_HEAD__ !== void 0 ? __MOOST_SSR_HEAD__ : DEFAULT_SSR_HEAD);
|
|
94
119
|
const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
|
|
95
120
|
const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
|
|
96
121
|
const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf8");
|
|
@@ -140,10 +165,12 @@ async function createSSRServer(options) {
|
|
|
140
165
|
}
|
|
141
166
|
try {
|
|
142
167
|
if (render) {
|
|
143
|
-
const
|
|
144
|
-
res
|
|
145
|
-
|
|
146
|
-
|
|
168
|
+
const result = await render(url);
|
|
169
|
+
sendSSRResponse(res, template, {
|
|
170
|
+
ssrOutlet,
|
|
171
|
+
ssrState,
|
|
172
|
+
ssrHead
|
|
173
|
+
}, result);
|
|
147
174
|
} else {
|
|
148
175
|
res.statusCode = 200;
|
|
149
176
|
res.setHeader("Content-Type", "text/html");
|
package/dist/prod-server.d.ts
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* The value an SSR `render(url)` function may return. Only `html` is required;
|
|
5
|
+
* every other field is optional, so a render returning `{ html }` (or
|
|
6
|
+
* `{ html, state }`) keeps working unchanged.
|
|
7
|
+
*/
|
|
8
|
+
interface TSSRRenderResult {
|
|
9
|
+
/** Server-rendered app markup, substituted into the `ssrOutlet` marker. */
|
|
10
|
+
html: string;
|
|
11
|
+
/**
|
|
12
|
+
* Serialized app state. Wrapped in `<script>window.__SSR_STATE__=…</script>`
|
|
13
|
+
* and substituted into the `ssrState` marker (nothing is emitted when absent).
|
|
14
|
+
*/
|
|
15
|
+
state?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Ready-to-insert `<head>` tags (`<title>`, `<meta>`, canonical, Open Graph,
|
|
18
|
+
* JSON-LD, …), substituted verbatim into the `ssrHead` marker. This is exactly
|
|
19
|
+
* the shape head managers emit (e.g. unhead's `renderSSRHead(head).headTags`).
|
|
20
|
+
*/
|
|
21
|
+
head?: string;
|
|
22
|
+
/** HTTP status code for the response (default `200`) — e.g. `404` for a soft-404, `301` for a redirect. */
|
|
23
|
+
status?: number;
|
|
24
|
+
/** Extra response headers — e.g. `cache-control`, or `location` alongside `status: 301`. */
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
|
|
3
28
|
type TMiddleware = (req: IncomingMessage, res: ServerResponse, next: () => void) => void;
|
|
4
29
|
interface TSSRServerOptions {
|
|
5
30
|
/** Override: lazy import for the Moost app entry. */
|
|
@@ -21,6 +46,8 @@ interface TSSRServerOptions {
|
|
|
21
46
|
ssrOutlet?: string;
|
|
22
47
|
/** Override: HTML placeholder for SSR state */
|
|
23
48
|
ssrState?: string;
|
|
49
|
+
/** Override: HTML placeholder for SSR-rendered `<head>` tags */
|
|
50
|
+
ssrHead?: string;
|
|
24
51
|
}
|
|
25
52
|
interface TSSRServer {
|
|
26
53
|
/** Add Connect-compatible middleware (runs in both dev and prod) */
|
|
@@ -31,4 +58,4 @@ interface TSSRServer {
|
|
|
31
58
|
declare function createSSRServer(options?: TSSRServerOptions): Promise<TSSRServer>;
|
|
32
59
|
|
|
33
60
|
export { createSSRServer };
|
|
34
|
-
export type { TSSRServer, TSSRServerOptions };
|
|
61
|
+
export type { TSSRRenderResult, TSSRServer, TSSRServerOptions };
|
package/dist/prod-server.mjs
CHANGED
|
@@ -6,6 +6,27 @@ import { createLogger } from "moost";
|
|
|
6
6
|
const PLUGIN_NAME = "moost-vite";
|
|
7
7
|
const DEFAULT_SSR_OUTLET = "<!--ssr-outlet-->";
|
|
8
8
|
const DEFAULT_SSR_STATE = "<!--ssr-state-->";
|
|
9
|
+
const DEFAULT_SSR_HEAD = "<!--ssr-head-->";
|
|
10
|
+
/**
|
|
11
|
+
* Applies an SSR render result to the HTTP response: sets the status code and
|
|
12
|
+
* headers, then substitutes the outlet, state and head markers into the HTML
|
|
13
|
+
* template. Shared by all three render call sites (the dev middleware, the dev
|
|
14
|
+
* `createSSRServer` branch and the prod `createSSRServer` branch) so the render
|
|
15
|
+
* contract stays identical across them.
|
|
16
|
+
*
|
|
17
|
+
* Fully backwards compatible: every field beyond `html` is optional, a missing
|
|
18
|
+
* marker in the template is a no-op `String.replace`, and headers from the render
|
|
19
|
+
* result are applied after the default `Content-Type: text/html` so a render can
|
|
20
|
+
* override it. Replacements use a function argument so `$`-sequences in the
|
|
21
|
+
* rendered payloads (JSON state, JSON-LD `<head>` tags) are inserted literally
|
|
22
|
+
* rather than interpreted as `String.replace` special patterns.
|
|
23
|
+
*/ function sendSSRResponse(res, template, markers, result) {
|
|
24
|
+
res.statusCode = result.status ?? 200;
|
|
25
|
+
res.setHeader("Content-Type", "text/html");
|
|
26
|
+
if (result.headers) for (const [key, value] of Object.entries(result.headers)) res.setHeader(key, value);
|
|
27
|
+
const stateScript = result.state ? `<script>window.__SSR_STATE__=${result.state}<\/script>` : "";
|
|
28
|
+
res.end(template.replace(markers.ssrOutlet, () => result.html).replace(markers.ssrState, () => stateScript).replace(markers.ssrHead, () => result.head ?? ""));
|
|
29
|
+
}
|
|
9
30
|
/**
|
|
10
31
|
* Normalizes the `prefix` option (single mount or list of mounts) into a list of
|
|
11
32
|
* normalized entries: leading slash ensured, trailing slash stripped.
|
|
@@ -51,6 +72,7 @@ async function createSSRServer(options) {
|
|
|
51
72
|
const ssrEntry = config.ssrEntry;
|
|
52
73
|
const ssrOutlet = config.ssrOutlet || DEFAULT_SSR_OUTLET;
|
|
53
74
|
const ssrState = config.ssrState || DEFAULT_SSR_STATE;
|
|
75
|
+
const ssrHead = config.ssrHead || DEFAULT_SSR_HEAD;
|
|
54
76
|
let ssrFallback = null;
|
|
55
77
|
if (ssrEntry) {
|
|
56
78
|
const fs = await import("node:fs/promises");
|
|
@@ -62,10 +84,12 @@ async function createSSRServer(options) {
|
|
|
62
84
|
let template = await fs.readFile(path.resolve(vite.config.root, "index.html"), "utf8");
|
|
63
85
|
template = await vite.transformIndexHtml(url, template);
|
|
64
86
|
const { render } = await vite.ssrLoadModule(ssrEntry);
|
|
65
|
-
const
|
|
66
|
-
res
|
|
67
|
-
|
|
68
|
-
|
|
87
|
+
const result = await render(url);
|
|
88
|
+
sendSSRResponse(res, template, {
|
|
89
|
+
ssrOutlet,
|
|
90
|
+
ssrState,
|
|
91
|
+
ssrHead
|
|
92
|
+
}, result);
|
|
69
93
|
} catch (error) {
|
|
70
94
|
vite.ssrFixStacktrace(error);
|
|
71
95
|
console.error(error);
|
|
@@ -90,8 +114,9 @@ async function createSSRServer(options) {
|
|
|
90
114
|
const path = await import("node:path");
|
|
91
115
|
const { createServer: createHttpServer } = await import("node:http");
|
|
92
116
|
const clientDir = opts.clientDir || path.resolve(import.meta.dirname, "../client");
|
|
93
|
-
const ssrOutlet = opts.ssrOutlet || (__MOOST_SSR_OUTLET__ !== void 0 ? __MOOST_SSR_OUTLET__ :
|
|
94
|
-
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ :
|
|
117
|
+
const ssrOutlet = opts.ssrOutlet || (__MOOST_SSR_OUTLET__ !== void 0 ? __MOOST_SSR_OUTLET__ : DEFAULT_SSR_OUTLET);
|
|
118
|
+
const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : DEFAULT_SSR_STATE);
|
|
119
|
+
const ssrHead = opts.ssrHead || (__MOOST_SSR_HEAD__ !== void 0 ? __MOOST_SSR_HEAD__ : DEFAULT_SSR_HEAD);
|
|
95
120
|
const prefixes = normalizePrefixes(opts.prefix ?? __MOOST_PREFIX__);
|
|
96
121
|
const defaultPort = opts.port || Number(process.env.PORT) || 3e3;
|
|
97
122
|
const template = await fs.readFile(path.resolve(clientDir, "index.html"), "utf8");
|
|
@@ -141,10 +166,12 @@ async function createSSRServer(options) {
|
|
|
141
166
|
}
|
|
142
167
|
try {
|
|
143
168
|
if (render) {
|
|
144
|
-
const
|
|
145
|
-
res
|
|
146
|
-
|
|
147
|
-
|
|
169
|
+
const result = await render(url);
|
|
170
|
+
sendSSRResponse(res, template, {
|
|
171
|
+
ssrOutlet,
|
|
172
|
+
ssrState,
|
|
173
|
+
ssrHead
|
|
174
|
+
}, result);
|
|
148
175
|
} else {
|
|
149
176
|
res.statusCode = 200;
|
|
150
177
|
res.setHeader("Content-Type", "text/html");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moostjs/vite",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.29",
|
|
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.29",
|
|
62
|
+
"moost": "^0.6.29"
|
|
63
63
|
},
|
|
64
64
|
"peerDependenciesMeta": {
|
|
65
65
|
"sirv": {
|