@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 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 { html: appHtml, state } = await render(url);
664
- res.statusCode = 200;
665
- res.setHeader("Content-Type", "text/html");
666
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
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 { html: appHtml, state } = await render(url);
625
- res.statusCode = 200;
626
- res.setHeader("Content-Type", "text/html");
627
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
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);
@@ -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 { html: appHtml, state } = await render(url);
65
- res.statusCode = 200;
66
- res.setHeader("Content-Type", "text/html");
67
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
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__ : "<!--ssr-outlet-->");
93
- const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : "<!--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 { html: appHtml, state } = await render(url);
144
- res.statusCode = 200;
145
- res.setHeader("Content-Type", "text/html");
146
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
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");
@@ -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 };
@@ -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 { html: appHtml, state } = await render(url);
66
- res.statusCode = 200;
67
- res.setHeader("Content-Type", "text/html");
68
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
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__ : "<!--ssr-outlet-->");
94
- const ssrState = opts.ssrState || (__MOOST_SSR_STATE__ !== void 0 ? __MOOST_SSR_STATE__ : "<!--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 { html: appHtml, state } = await render(url);
145
- res.statusCode = 200;
146
- res.setHeader("Content-Type", "text/html");
147
- res.end(template.replace(ssrOutlet, appHtml).replace(ssrState, state ? `<script>window.__SSR_STATE__=${state}<\/script>` : ""));
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.27",
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.27",
62
- "moost": "^0.6.27"
61
+ "@moostjs/event-http": "^0.6.29",
62
+ "moost": "^0.6.29"
63
63
  },
64
64
  "peerDependenciesMeta": {
65
65
  "sirv": {