@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.
- package/README.md +257 -416
- package/dist/client.d.ts +42 -51
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +39 -62
- package/dist/client.js.map +1 -1
- package/dist/fetch-middleware-DPLxOLL6.js +98 -0
- package/dist/fetch-middleware-DPLxOLL6.js.map +1 -0
- package/dist/server-DJRlVUcm.d.ts +260 -0
- package/dist/server-DJRlVUcm.d.ts.map +1 -0
- package/dist/server.d.ts +3 -236
- package/dist/server.js +46 -42
- package/dist/server.js.map +1 -1
- package/dist/{vite-plugin-D-W5WQWe.js → vite-plugin-9BSJgEL9.js} +3 -4
- package/dist/vite-plugin-9BSJgEL9.js.map +1 -0
- package/dist/{vite-plugin-CQou_tr5.d.ts → vite-plugin-Dl5DbheW.d.ts} +2 -17
- package/dist/vite-plugin-Dl5DbheW.d.ts.map +1 -0
- package/dist/vite-plugin.d.ts +1 -1
- package/dist/vite-plugin.js +1 -1
- package/dist/worker.d.ts +4 -3
- package/dist/worker.d.ts.map +1 -1
- package/dist/worker.js +69 -54
- package/dist/worker.js.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +64 -73
- package/src/dev.ts +91 -61
- package/src/fetch-middleware.ts +166 -0
- package/src/server.ts +48 -23
- package/src/vite-plugin.ts +2 -24
- package/src/worker.ts +153 -105
- package/dist/server.d.ts.map +0 -1
- package/dist/vite-plugin-CQou_tr5.d.ts.map +0 -1
- package/dist/vite-plugin-D-W5WQWe.js.map +0 -1
|
@@ -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-
|
|
2
|
-
|
|
3
|
-
|
|
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-
|
|
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 '
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
166
|
+
_renderMode: "serialize"
|
|
180
167
|
};
|
|
181
168
|
const instance = await app.visit(url, bootOptions);
|
|
182
|
-
|
|
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 (
|
|
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 +
|
|
198
|
-
body
|
|
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 '
|
|
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 '
|
|
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
|
-
|
|
278
|
+
settledTimeout: renderOptions.settledTimeout ?? 1e4,
|
|
279
|
+
forwardCookie: renderOptions.forwardCookie ?? null
|
|
276
280
|
});
|
|
277
281
|
return {
|
|
278
282
|
head: result.head,
|