@st-h/vite-ember-ssr 0.2.0-alpha.1 → 0.3.1

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.
@@ -0,0 +1,260 @@
1
+ import { n as CssManifest } from "./vite-plugin-Dl5DbheW.js";
2
+
3
+ //#region src/server.d.ts
4
+ /**
5
+ * Minimal interface for an Ember Application that supports SSR.
6
+ *
7
+ * The app must be created with `autoboot: false` so the server can
8
+ * control boot timing via `app.visit(url, options)`.
9
+ */
10
+ interface EmberApplication {
11
+ visit(url: string, options?: BootOptions): Promise<EmberApplicationInstance>;
12
+ destroy(): void;
13
+ }
14
+ interface EmberApplicationInstance {
15
+ destroy(): void;
16
+ getURL?(): string;
17
+ _booted?: boolean;
18
+ lookup?(fullName: string): unknown;
19
+ }
20
+ interface BootOptions {
21
+ isBrowser: boolean;
22
+ isInteractive?: boolean;
23
+ document: Document;
24
+ rootElement: Element;
25
+ shouldRender: boolean;
26
+ location?: string;
27
+ _renderMode?: 'serialize' | 'rehydrate' | undefined;
28
+ }
29
+ /**
30
+ * Configures forwarding of the incoming request's `Cookie` header to
31
+ * fetch() calls made during SSR rendering.
32
+ *
33
+ * `allowedHosts` is required: forwarding the session cookie to every
34
+ * outbound fetch would leak credentials to third-party APIs the route
35
+ * happens to call. Each entry is matched against the request URL's
36
+ * `host` (hostname plus port) using exact equality — suffix wildcards
37
+ * are not supported.
38
+ */
39
+ interface ForwardedCookie {
40
+ /** Cookie header value from the incoming request. */
41
+ value: string;
42
+ /**
43
+ * Hosts (`URL.host`) the cookie may be sent to. Exact match, no wildcards.
44
+ *
45
+ * @example ['api.example.com', 'auth.example.com:8080']
46
+ */
47
+ allowedHosts: string[];
48
+ }
49
+ interface RenderRouteOptions {
50
+ /**
51
+ * When true, intercepts all fetch() calls during SSR rendering and
52
+ * serializes the responses into a <script> tag in the HTML output.
53
+ */
54
+ shoebox?: boolean;
55
+ /**
56
+ * CSS manifest mapping route names to their associated CSS asset paths.
57
+ *
58
+ * Generated automatically by the `emberSsr()` Vite plugin during the
59
+ * client build (written as `css-manifest.json`).
60
+ */
61
+ cssManifest?: CssManifest;
62
+ /**
63
+ * Maximum time (in milliseconds) to wait for `settled()` to resolve after
64
+ * `app.visit()`. Only applies when the SSR bundle exports a `settled`
65
+ * function (typically re-exported from `@ember/test-helpers`).
66
+ *
67
+ * If the timeout is exceeded, a warning is logged and the DOM is captured
68
+ * regardless. Use this to bound render time when a route registers a
69
+ * waiter that never resolves.
70
+ *
71
+ * @default 10000
72
+ */
73
+ settledTimeout?: number;
74
+ /**
75
+ * Forward the incoming request's `Cookie` header to fetch() calls made
76
+ * during SSR rendering. The cookie is only sent to hosts listed in
77
+ * `allowedHosts`, so credentials never leak to third-party APIs the
78
+ * route may also call.
79
+ *
80
+ * @example
81
+ * ```js
82
+ * await app.renderRoute(req.url, {
83
+ * forwardCookie: {
84
+ * value: req.headers.cookie ?? '',
85
+ * allowedHosts: ['api.example.com'],
86
+ * },
87
+ * });
88
+ * ```
89
+ */
90
+ forwardCookie?: ForwardedCookie;
91
+ }
92
+ interface RenderResult {
93
+ /** Rendered HTML from the document's <head> */
94
+ head: string;
95
+ /** Rendered HTML from the document's <body> */
96
+ body: string;
97
+ /** Attributes set on the <body> element during rendering (e.g., data-theme, class) */
98
+ bodyAttrs: Record<string, string>;
99
+ /** HTTP status code (200 by default) */
100
+ statusCode: number;
101
+ /** Any error that occurred during rendering */
102
+ error?: Error;
103
+ }
104
+ /**
105
+ * A captured fetch response for transfer from server to client.
106
+ */
107
+ interface ShoeboxEntry {
108
+ url: string;
109
+ status: number;
110
+ statusText: string;
111
+ headers: Record<string, string>;
112
+ body: string;
113
+ }
114
+ interface EmberAppDevOptions {
115
+ /**
116
+ * Vite's `ssrLoadModule` function from the dev server.
117
+ *
118
+ * When provided, `createEmberApp` skips tinypool entirely and renders
119
+ * in-process using Vite's module resolution pipeline. The SSR entry is
120
+ * re-loaded on every render so HMR changes are reflected immediately.
121
+ *
122
+ * Obtain this from your Vite dev server instance:
123
+ * ```js
124
+ * const vite = await createServer({ ... });
125
+ * await createEmberApp('app/app-ssr.ts', {
126
+ * dev: { ssrLoadModule: vite.ssrLoadModule.bind(vite) },
127
+ * });
128
+ * ```
129
+ */
130
+ ssrLoadModule: (path: string) => Promise<Record<string, unknown>>;
131
+ }
132
+ interface EmberAppOptions {
133
+ /**
134
+ * Number of long-lived worker threads in the pool.
135
+ *
136
+ * Each worker imports the SSR bundle once and handles all subsequent
137
+ * render requests without re-importing — making per-render cost ~4ms
138
+ * instead of ~200ms for a fresh-worker approach.
139
+ *
140
+ * Ignored when `dev` is provided.
141
+ *
142
+ * @default os.cpus().length
143
+ */
144
+ workers?: number;
145
+ /**
146
+ * How often (in milliseconds) to recycle all workers in the pool.
147
+ *
148
+ * When set, `pool.recycleWorkers()` is called on this interval —
149
+ * tinypool waits for all in-flight tasks to complete, then replaces
150
+ * every worker with a fresh one. This bounds memory growth in
151
+ * long-running processes where workers accumulate state over time.
152
+ *
153
+ * Set to `0` or omit to disable periodic recycling.
154
+ *
155
+ * Ignored when `dev` is provided.
156
+ *
157
+ * @example
158
+ * // Recycle workers every hour
159
+ * await createEmberApp(bundlePath, { recycleWorkerInterval: 60 * 60 * 1000 });
160
+ */
161
+ recycleWorkerInterval?: number;
162
+ /**
163
+ * When `true`, each render task is handled by a freshly-started worker.
164
+ *
165
+ * This maps directly to tinypool's `isolateWorkers` option. The worker is
166
+ * replaced after every task, so module-level state (caches, singletons,
167
+ * open handles) never bleeds between requests. The trade-off is that every
168
+ * render pays the full worker-startup and bundle-import cost instead of
169
+ * reusing a warm worker.
170
+ *
171
+ * For most apps the default (long-lived, warm workers) is preferred.
172
+ * Enable isolation when you need strict request-level process boundaries,
173
+ * e.g. when the SSR bundle keeps global state that cannot be reset between
174
+ * renders.
175
+ *
176
+ * Ignored when `dev` is provided.
177
+ *
178
+ * @default false
179
+ */
180
+ isolateWorkers?: boolean;
181
+ /**
182
+ * Dev mode options. When provided, skips tinypool and renders in-process
183
+ * via Vite's `ssrLoadModule` so HMR changes are picked up on every render.
184
+ */
185
+ dev?: EmberAppDevOptions;
186
+ }
187
+ interface EmberApp {
188
+ /**
189
+ * Renders a route and returns the raw head/body HTML fragments.
190
+ *
191
+ * @param url The URL path to render, e.g. `'/'` or `'/about'`
192
+ */
193
+ renderRoute(url: string, options?: RenderRouteOptions): Promise<RenderResult>;
194
+ /**
195
+ * Shuts down the worker pool. Call this when the app server is
196
+ * stopping or after SSG prerendering is complete.
197
+ */
198
+ destroy(): Promise<void>;
199
+ }
200
+ /**
201
+ * Creates a long-lived worker thread pool for SSR/SSG rendering.
202
+ *
203
+ * Each worker imports the SSR bundle once at startup and reuses it for all
204
+ * subsequent renders — no bundle re-import, no Worker respawn.
205
+ *
206
+ * Pass `dev: { ssrLoadModule }` to run in dev mode instead: renders happen
207
+ * in-process via Vite's module resolution pipeline with no tinypool workers.
208
+ * The SSR entry is re-loaded on every render so HMR changes are reflected
209
+ * immediately.
210
+ *
211
+ * @example Production
212
+ * ```js
213
+ * import { createEmberApp, assembleHTML } from 'vite-ember-ssr/server';
214
+ * import { resolve } from 'node:path';
215
+ *
216
+ * const app = await createEmberApp(resolve('dist/server/app-ssr.mjs'));
217
+ *
218
+ * // In a request handler:
219
+ * const result = await app.renderRoute(req.url);
220
+ * const html = assembleHTML(template, result);
221
+ *
222
+ * // On server shutdown:
223
+ * await app.destroy();
224
+ * ```
225
+ *
226
+ * @example Development
227
+ * ```js
228
+ * import { createServer } from 'vite';
229
+ * import { createEmberApp, assembleHTML } from 'vite-ember-ssr/server';
230
+ *
231
+ * const vite = await createServer({ server: { middlewareMode: true }, appType: 'custom' });
232
+ * const app = await createEmberApp('app/app-ssr.ts', {
233
+ * dev: { ssrLoadModule: vite.ssrLoadModule.bind(vite) },
234
+ * });
235
+ * ```
236
+ */
237
+ declare function createEmberApp(ssrBundlePath: string, options?: EmberAppOptions): Promise<EmberApp>;
238
+ /**
239
+ * Assembles the final HTML response by inserting rendered content
240
+ * into the index.html template.
241
+ *
242
+ * When `rendered.bodyAttrs` is provided, attributes set on the `<body>`
243
+ * element during SSR (e.g., `data-theme`, `class`) are applied to the
244
+ * `<body>` tag in the template HTML.
245
+ */
246
+ declare function assembleHTML(template: string, rendered: Pick<RenderResult, 'head' | 'body' | 'bodyAttrs'>): string;
247
+ /**
248
+ * Checks whether an HTML template contains the required SSR markers.
249
+ */
250
+ declare function hasSSRMarkers(html: string): {
251
+ head: boolean;
252
+ body: boolean;
253
+ };
254
+ /**
255
+ * Loads the CSS manifest from the client build output directory.
256
+ */
257
+ declare function loadCssManifest(clientDir: string): Promise<CssManifest | undefined>;
258
+ //#endregion
259
+ export { EmberApplication as a, RenderResult as c, assembleHTML as d, createEmberApp as f, EmberAppOptions as i, RenderRouteOptions as l, loadCssManifest as m, EmberApp as n, EmberApplicationInstance as o, hasSSRMarkers as p, EmberAppDevOptions as r, ForwardedCookie as s, BootOptions as t, ShoeboxEntry as u };
260
+ //# sourceMappingURL=server-DJRlVUcm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-DJRlVUcm.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;;;;AAmBA;;;;UAAiB,gBAAA;EACf,KAAA,CAAM,GAAA,UAAa,OAAA,GAAU,WAAA,GAAc,OAAA,CAAQ,wBAAA;EACnD,OAAA;AAAA;AAAA,UAGe,wBAAA;EACf,OAAA;EACA,MAAA;EACA,OAAA;EACA,MAAA,EAAQ,QAAA;AAAA;AAAA,UAGO,WAAA;EACf,SAAA;EACA,aAAA;EACA,QAAA,EAAU,QAAA;EACV,WAAA,EAAa,OAAA;EACb,YAAA;EACA,QAAA;EACA,WAAA;AAAA;;;;;;AAPF;;;;;UAoBiB,eAAA;EAjBf;EAmBA,KAAA;EAlBA;;;;;EAwBA,YAAA;AAAA;AAAA,UAGe,kBAAA;EAXe;;;;EAgB9B,OAAA;EALiC;;;;;;EAajC,WAAA,GAAc,WAAA;EA+Bd;;;;AAGF;;;;;;;EArBE,cAAA;EA6BA;;;;;AAUF;;;;;;;;;;;EArBE,aAAA,GAAgB,eAAA;AAAA;AAAA,UAGD,YAAA;;EAEf,IAAA;EA0CA;EAxCA,IAAA;EAwCiC;EAtCjC,SAAA,EAAW,MAAA;EAsCoC;EApC/C,UAAA;EAuCe;EArCf,KAAA,GAAQ,KAAA;AAAA;;;;UAQO,YAAA;EACf,GAAA;EACA,MAAA;EACA,UAAA;EACA,OAAA,EAAS,MAAA;EACT,IAAA;AAAA;AAAA,UAKe,kBAAA;EAoFoB;;;;;;;;;;;;;;;EApEnC,aAAA,GAAgB,IAAA,aAAiB,OAAA,CAAQ,MAAA;AAAA;AAAA,UAG1B,eAAA;;;;;;;;;;;;EAYf,OAAA;EAwGiB;AAyFnB;;;;;;;;;;AAkCA;;;;;EAjNE,qBAAA;EAiN4D;;;AAe9D;;;;;;;;;;;;;;;EA5ME,cAAA;;;;;EAMA,GAAA,GAAM,kBAAA;AAAA;AAAA,UAGS,QAAA;;;;;;EAMf,WAAA,CAAY,GAAA,UAAa,OAAA,GAAU,kBAAA,GAAqB,OAAA,CAAQ,YAAA;;;;;EAMhE,OAAA,IAAW,OAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA0CS,cAAA,CACpB,aAAA,UACA,OAAA,GAAS,eAAA,GACR,OAAA,CAAQ,QAAA;;;;;;;;;iBAyFK,YAAA,CACd,QAAA,UACA,QAAA,EAAU,IAAA,CAAK,YAAA;;;;iBAgCD,aAAA,CAAc,IAAA;EAAiB,IAAA;EAAe,IAAA;AAAA;;;;iBAexC,eAAA,CACpB,SAAA,WACC,OAAA,CAAQ,WAAA"}
package/dist/server.d.ts CHANGED
@@ -1,236 +1,3 @@
1
- import { n as CssManifest, t as CSS_MANIFEST_FILENAME } from "./vite-plugin-CQou_tr5.js";
2
-
3
- //#region src/server.d.ts
4
- /**
5
- * Minimal interface for an Ember Application that supports SSR.
6
- *
7
- * The app must be created with `autoboot: false` so the server can
8
- * control boot timing via `app.visit(url, options)`.
9
- */
10
- interface EmberApplication {
11
- visit(url: string, options?: BootOptions): Promise<EmberApplicationInstance>;
12
- destroy(): void;
13
- }
14
- interface EmberApplicationInstance {
15
- destroy(): void;
16
- getURL?(): string;
17
- _booted?: boolean;
18
- lookup?(fullName: string): unknown;
19
- }
20
- interface BootOptions {
21
- isBrowser: boolean;
22
- isInteractive?: boolean;
23
- document: Document;
24
- rootElement: Element;
25
- shouldRender: boolean;
26
- location?: string;
27
- _renderMode?: 'serialize' | 'rehydrate' | undefined;
28
- }
29
- interface RenderRouteOptions {
30
- /**
31
- * When true, intercepts all fetch() calls during SSR rendering and
32
- * serializes the responses into a <script> tag in the HTML output.
33
- */
34
- shoebox?: boolean;
35
- /**
36
- * Enable Glimmer VM rehydration mode.
37
- *
38
- * When true, the server renders with `_renderMode: 'serialize'`,
39
- * annotating the DOM with markers Glimmer can reuse on the client.
40
- *
41
- * @default false
42
- */
43
- rehydrate?: boolean;
44
- /**
45
- * CSS manifest mapping route names to their associated CSS asset paths.
46
- *
47
- * Generated automatically by the `emberSsr()` Vite plugin during the
48
- * client build (written as `css-manifest.json`).
49
- */
50
- cssManifest?: CssManifest;
51
- /**
52
- * HTTP headers from the incoming request to forward to fetch() calls
53
- * made during SSR rendering.
54
- *
55
- * Use this to forward authentication cookies, authorization tokens,
56
- * or other request-scoped headers so the SSR render can make
57
- * authenticated API calls on behalf of the user.
58
- *
59
- * Only the specified headers are forwarded. Common usage:
60
- * ```js
61
- * const rendered = await app.renderRoute(req.url, {
62
- * headers: { cookie: req.headers.cookie },
63
- * });
64
- * ```
65
- */
66
- headers?: Record<string, string>;
67
- }
68
- interface RenderResult {
69
- /** Rendered HTML from the document's <head> */
70
- head: string;
71
- /** Rendered HTML from the document's <body> */
72
- body: string;
73
- /** Attributes set on the <body> element during rendering (e.g., data-theme, class) */
74
- bodyAttrs: Record<string, string>;
75
- /** HTTP status code (200 by default) */
76
- statusCode: number;
77
- /** Any error that occurred during rendering */
78
- error?: Error;
79
- }
80
- /**
81
- * A captured fetch response for transfer from server to client.
82
- */
83
- interface ShoeboxEntry {
84
- url: string;
85
- status: number;
86
- statusText: string;
87
- headers: Record<string, string>;
88
- body: string;
89
- }
90
- interface EmberAppDevOptions {
91
- /**
92
- * Vite's `ssrLoadModule` function from the dev server.
93
- *
94
- * When provided, `createEmberApp` skips tinypool entirely and renders
95
- * in-process using Vite's module resolution pipeline. The SSR entry is
96
- * re-loaded on every render so HMR changes are reflected immediately.
97
- *
98
- * Obtain this from your Vite dev server instance:
99
- * ```js
100
- * const vite = await createServer({ ... });
101
- * await createEmberApp('app/app-ssr.ts', {
102
- * dev: { ssrLoadModule: vite.ssrLoadModule.bind(vite) },
103
- * });
104
- * ```
105
- */
106
- ssrLoadModule: (path: string) => Promise<Record<string, unknown>>;
107
- }
108
- interface EmberAppOptions {
109
- /**
110
- * Number of long-lived worker threads in the pool.
111
- *
112
- * Each worker imports the SSR bundle once and handles all subsequent
113
- * render requests without re-importing — making per-render cost ~4ms
114
- * instead of ~200ms for a fresh-worker approach.
115
- *
116
- * Ignored when `dev` is provided.
117
- *
118
- * @default os.cpus().length
119
- */
120
- workers?: number;
121
- /**
122
- * How often (in milliseconds) to recycle all workers in the pool.
123
- *
124
- * When set, `pool.recycleWorkers()` is called on this interval —
125
- * tinypool waits for all in-flight tasks to complete, then replaces
126
- * every worker with a fresh one. This bounds memory growth in
127
- * long-running processes where workers accumulate state over time.
128
- *
129
- * Set to `0` or omit to disable periodic recycling.
130
- *
131
- * Ignored when `dev` is provided.
132
- *
133
- * @example
134
- * // Recycle workers every hour
135
- * await createEmberApp(bundlePath, { recycleWorkerInterval: 60 * 60 * 1000 });
136
- */
137
- recycleWorkerInterval?: number;
138
- /**
139
- * When `true`, each render task is handled by a freshly-started worker.
140
- *
141
- * This maps directly to tinypool's `isolateWorkers` option. The worker is
142
- * replaced after every task, so module-level state (caches, singletons,
143
- * open handles) never bleeds between requests. The trade-off is that every
144
- * render pays the full worker-startup and bundle-import cost instead of
145
- * reusing a warm worker.
146
- *
147
- * For most apps the default (long-lived, warm workers) is preferred.
148
- * Enable isolation when you need strict request-level process boundaries,
149
- * e.g. when the SSR bundle keeps global state that cannot be reset between
150
- * renders.
151
- *
152
- * Ignored when `dev` is provided.
153
- *
154
- * @default false
155
- */
156
- isolateWorkers?: boolean;
157
- /**
158
- * Dev mode options. When provided, skips tinypool and renders in-process
159
- * via Vite's `ssrLoadModule` so HMR changes are picked up on every render.
160
- */
161
- dev?: EmberAppDevOptions;
162
- }
163
- interface EmberApp {
164
- /**
165
- * Renders a route and returns the raw head/body HTML fragments.
166
- *
167
- * @param url The URL path to render, e.g. `'/'` or `'/about'`
168
- */
169
- renderRoute(url: string, options?: RenderRouteOptions): Promise<RenderResult>;
170
- /**
171
- * Shuts down the worker pool. Call this when the app server is
172
- * stopping or after SSG prerendering is complete.
173
- */
174
- destroy(): Promise<void>;
175
- }
176
- /**
177
- * Creates a long-lived worker thread pool for SSR/SSG rendering.
178
- *
179
- * Each worker imports the SSR bundle once at startup and reuses it for all
180
- * subsequent renders — no bundle re-import, no Worker respawn.
181
- *
182
- * Pass `dev: { ssrLoadModule }` to run in dev mode instead: renders happen
183
- * in-process via Vite's module resolution pipeline with no tinypool workers.
184
- * The SSR entry is re-loaded on every render so HMR changes are reflected
185
- * immediately.
186
- *
187
- * @example Production
188
- * ```js
189
- * import { createEmberApp, assembleHTML } from '@st-h/vite-ember-ssr/server';
190
- * import { resolve } from 'node:path';
191
- *
192
- * const app = await createEmberApp(resolve('dist/server/app-ssr.mjs'));
193
- *
194
- * // In a request handler:
195
- * const result = await app.renderRoute(req.url);
196
- * const html = assembleHTML(template, result);
197
- *
198
- * // On server shutdown:
199
- * await app.destroy();
200
- * ```
201
- *
202
- * @example Development
203
- * ```js
204
- * import { createServer } from 'vite';
205
- * import { createEmberApp, assembleHTML } from '@st-h/vite-ember-ssr/server';
206
- *
207
- * const vite = await createServer({ server: { middlewareMode: true }, appType: 'custom' });
208
- * const app = await createEmberApp('app/app-ssr.ts', {
209
- * dev: { ssrLoadModule: vite.ssrLoadModule.bind(vite) },
210
- * });
211
- * ```
212
- */
213
- declare function createEmberApp(ssrBundlePath: string, options?: EmberAppOptions): Promise<EmberApp>;
214
- /**
215
- * Assembles the final HTML response by inserting rendered content
216
- * into the index.html template.
217
- *
218
- * When `rendered.bodyAttrs` is provided, attributes set on the `<body>`
219
- * element during SSR (e.g., `data-theme`, `class`) are applied to the
220
- * `<body>` tag in the template HTML.
221
- */
222
- declare function assembleHTML(template: string, rendered: Pick<RenderResult, 'head' | 'body' | 'bodyAttrs'>): string;
223
- /**
224
- * Checks whether an HTML template contains the required SSR markers.
225
- */
226
- declare function hasSSRMarkers(html: string): {
227
- head: boolean;
228
- body: boolean;
229
- };
230
- /**
231
- * Loads the CSS manifest from the client build output directory.
232
- */
233
- declare function loadCssManifest(clientDir: string): Promise<CssManifest | undefined>;
234
- //#endregion
235
- export { BootOptions, CSS_MANIFEST_FILENAME, type CssManifest, EmberApp, EmberAppDevOptions, EmberAppOptions, EmberApplication, EmberApplicationInstance, RenderResult, RenderRouteOptions, ShoeboxEntry, assembleHTML, createEmberApp, hasSSRMarkers, loadCssManifest };
236
- //# sourceMappingURL=server.d.ts.map
1
+ import { n as CssManifest, t as CSS_MANIFEST_FILENAME } from "./vite-plugin-Dl5DbheW.js";
2
+ import { a as EmberApplication, c as RenderResult, d as assembleHTML, f as createEmberApp, i as EmberAppOptions, l as RenderRouteOptions, m as loadCssManifest, n as EmberApp, o as EmberApplicationInstance, p as hasSSRMarkers, r as EmberAppDevOptions, s as ForwardedCookie, t as BootOptions, u as ShoeboxEntry } from "./server-DJRlVUcm.js";
3
+ export { BootOptions, CSS_MANIFEST_FILENAME, CssManifest, EmberApp, EmberAppDevOptions, EmberAppOptions, EmberApplication, EmberApplicationInstance, ForwardedCookie, RenderResult, RenderRouteOptions, ShoeboxEntry, assembleHTML, createEmberApp, hasSSRMarkers, loadCssManifest };
package/dist/server.js CHANGED
@@ -1,4 +1,5 @@
1
- import { t as CSS_MANIFEST_FILENAME } from "./vite-plugin-D-W5WQWe.js";
1
+ import { t as CSS_MANIFEST_FILENAME } from "./vite-plugin-9BSJgEL9.js";
2
+ import { i as shoeboxMiddleware, n as compose, r as forwardCookieMiddleware } from "./fetch-middleware-DPLxOLL6.js";
2
3
  import { fileURLToPath, pathToFileURL } from "node:url";
3
4
  import { cpus } from "node:os";
4
5
  import { Window } from "happy-dom";
@@ -16,7 +17,7 @@ import { Window } from "happy-dom";
16
17
  * Usage:
17
18
  * ```js
18
19
  * import { createServer } from 'vite';
19
- * import { createEmberApp, assembleHTML } from '@st-h/vite-ember-ssr/server';
20
+ * import { createEmberApp, assembleHTML } from 'vite-ember-ssr/server';
20
21
  *
21
22
  * const vite = await createServer({ server: { middlewareMode: true }, appType: 'custom' });
22
23
  * const app = await createEmberApp('app/app-ssr.ts', {
@@ -55,8 +56,7 @@ const BROWSER_GLOBALS = [
55
56
  "CSSStyleSheet"
56
57
  ];
57
58
  const SHOEBOX_SCRIPT_ID = "vite-ember-ssr-shoebox";
58
- const SSR_BODY_START = "<script type=\"x/boundary\" id=\"ssr-body-start\"><\/script>";
59
- const SSR_BODY_END = "<script type=\"x/boundary\" id=\"ssr-body-end\"><\/script>";
59
+ let warnedMissingSettled = false;
60
60
  function installGlobals(win) {
61
61
  const saved = {};
62
62
  for (const name of BROWSER_GLOBALS) {
@@ -116,9 +116,20 @@ function buildRouteCssLinks(manifest, instance) {
116
116
  */
117
117
  function createDevEmberApp(entryPath, devOptions) {
118
118
  const { ssrLoadModule } = devOptions;
119
+ installGlobals(new Window({
120
+ url: "http://localhost/",
121
+ width: 1024,
122
+ height: 768,
123
+ settings: {
124
+ disableJavaScriptFileLoading: true,
125
+ disableJavaScriptEvaluation: true,
126
+ disableCSSFileLoading: true,
127
+ navigator: { userAgent: "vite-ember-ssr" }
128
+ }
129
+ }));
119
130
  return {
120
131
  async renderRoute(url, renderOptions = {}) {
121
- const { shoebox = false, rehydrate = false, cssManifest, headers: forwardHeaders } = renderOptions;
132
+ const { shoebox = false, cssManifest, settledTimeout = 1e4, forwardCookie } = renderOptions;
122
133
  const win = new Window({
123
134
  url: "http://localhost/",
124
135
  width: 1024,
@@ -133,34 +144,9 @@ function createDevEmberApp(entryPath, devOptions) {
133
144
  const savedGlobals = installGlobals(win);
134
145
  const realFetch = globalThis.fetch;
135
146
  const shoeboxEntries = shoebox ? /* @__PURE__ */ new Map() : null;
136
- if (shoebox || forwardHeaders) globalThis.fetch = async (input, init) => {
137
- let effectiveInit = init;
138
- if (forwardHeaders) {
139
- effectiveInit = { ...init };
140
- const existingHeaders = new Headers(effectiveInit.headers);
141
- for (const [key, value] of Object.entries(forwardHeaders)) if (!existingHeaders.has(key)) existingHeaders.set(key, value);
142
- effectiveInit.headers = existingHeaders;
143
- }
144
- const request = new Request(input, effectiveInit);
145
- if (request.method.toUpperCase() !== "GET") return realFetch(request);
146
- const response = await realFetch(request);
147
- if (shoeboxEntries) try {
148
- const clone = response.clone();
149
- const body = await clone.text();
150
- const headers = {};
151
- clone.headers.forEach((v, k) => {
152
- headers[k] = v;
153
- });
154
- shoeboxEntries.set(request.url, {
155
- url: request.url,
156
- status: clone.status,
157
- statusText: clone.statusText,
158
- headers,
159
- body
160
- });
161
- } catch {}
162
- return response;
163
- };
147
+ const cookie = forwardCookie ?? null;
148
+ const middlewareActive = shoeboxEntries !== null || cookie !== null;
149
+ if (middlewareActive) globalThis.fetch = compose([forwardCookieMiddleware(() => cookie), shoeboxMiddleware(() => shoeboxEntries)], (request) => realFetch(request));
164
150
  let head = "";
165
151
  let body = "";
166
152
  let bodyAttrs = {};
@@ -171,15 +157,33 @@ function createDevEmberApp(entryPath, devOptions) {
171
157
  const mod = await ssrLoadModule(entryPath);
172
158
  if (typeof mod.createSsrApp !== "function") throw new Error(`SSR entry '${entryPath}' does not export a 'createSsrApp' function. Found exports: ${Object.keys(mod).join(", ")}`);
173
159
  const app = mod.createSsrApp();
160
+ const appSettled = typeof mod.settled === "function" ? mod.settled : null;
174
161
  const bootOptions = {
175
162
  isBrowser: false,
176
163
  document,
177
164
  rootElement: document.body,
178
165
  shouldRender: true,
179
- ...rehydrate ? { _renderMode: "serialize" } : {}
166
+ _renderMode: "serialize"
180
167
  };
181
168
  const instance = await app.visit(url, bootOptions);
182
- await new Promise((resolve) => setTimeout(resolve, 0));
169
+ if (appSettled) {
170
+ let timer;
171
+ try {
172
+ await Promise.race([appSettled(), new Promise((_, reject) => {
173
+ timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`settled() timed out after ${settledTimeout}ms`)), settledTimeout);
174
+ })]);
175
+ } catch (e) {
176
+ console.warn(`[vite-ember-ssr] settled() did not resolve within ${settledTimeout}ms, capturing DOM anyway:`, e instanceof Error ? e.message : e);
177
+ } finally {
178
+ if (timer) clearTimeout(timer);
179
+ }
180
+ } else {
181
+ if (settledTimeout > 0 && !warnedMissingSettled) {
182
+ warnedMissingSettled = true;
183
+ console.warn("[vite-ember-ssr] settledTimeout is set but the SSR entry does not export `settled` — renders will NOT wait for the app to settle and may capture incomplete HTML. Add `export { settled } from '@ember/test-helpers';` to your SSR entry.");
184
+ }
185
+ await new Promise((resolve) => setTimeout(resolve, 0));
186
+ }
183
187
  if (cssManifest) cssLinks = buildRouteCssLinks(cssManifest, instance);
184
188
  head = document.head?.innerHTML ?? "";
185
189
  body = document.body?.innerHTML ?? "";
@@ -188,14 +192,14 @@ function createDevEmberApp(entryPath, devOptions) {
188
192
  } catch (e) {
189
193
  error = e instanceof Error ? e : new Error(String(e));
190
194
  } finally {
191
- if (shoebox || forwardHeaders) globalThis.fetch = realFetch;
195
+ if (middlewareActive) globalThis.fetch = realFetch;
192
196
  restoreGlobals(savedGlobals);
193
197
  await win.happyDOM?.close?.();
194
198
  }
195
199
  const shoeboxHTML = shoeboxEntries && shoeboxEntries.size > 0 ? serializeShoebox(Array.from(shoeboxEntries.values())) : "";
196
200
  return {
197
- head: cssLinks + (rehydrate ? "<script>window.__vite_ember_ssr_rehydrate__=true<\/script>" : "") + shoeboxHTML + head,
198
- body: rehydrate ? body : `${SSR_BODY_START}${body}${SSR_BODY_END}`,
201
+ head: cssLinks + "<script>window.__vite_ember_ssr_rehydrate__=true<\/script>" + shoeboxHTML + head,
202
+ body,
199
203
  bodyAttrs,
200
204
  statusCode: error ? 500 : 200,
201
205
  ...error ? { error } : {}
@@ -220,7 +224,7 @@ const WORKER_PATH = fileURLToPath(new URL("./worker.js", import.meta.url));
220
224
  *
221
225
  * @example Production
222
226
  * ```js
223
- * import { createEmberApp, assembleHTML } from '@st-h/vite-ember-ssr/server';
227
+ * import { createEmberApp, assembleHTML } from 'vite-ember-ssr/server';
224
228
  * import { resolve } from 'node:path';
225
229
  *
226
230
  * const app = await createEmberApp(resolve('dist/server/app-ssr.mjs'));
@@ -236,7 +240,7 @@ const WORKER_PATH = fileURLToPath(new URL("./worker.js", import.meta.url));
236
240
  * @example Development
237
241
  * ```js
238
242
  * import { createServer } from 'vite';
239
- * import { createEmberApp, assembleHTML } from '@st-h/vite-ember-ssr/server';
243
+ * import { createEmberApp, assembleHTML } from 'vite-ember-ssr/server';
240
244
  *
241
245
  * const vite = await createServer({ server: { middlewareMode: true }, appType: 'custom' });
242
246
  * const app = await createEmberApp('app/app-ssr.ts', {
@@ -270,9 +274,9 @@ async function createEmberApp(ssrBundlePath, options = {}) {
270
274
  ssrBundlePath: bundleURL,
271
275
  url,
272
276
  shoebox: renderOptions.shoebox ?? false,
273
- rehydrate: renderOptions.rehydrate ?? false,
274
277
  cssManifest: renderOptions.cssManifest ?? null,
275
- headers: renderOptions.headers ?? null
278
+ settledTimeout: renderOptions.settledTimeout ?? 1e4,
279
+ forwardCookie: renderOptions.forwardCookie ?? null
276
280
  });
277
281
  return {
278
282
  head: result.head,