@st-h/vite-ember-ssr 0.2.0-alpha.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/LICENSE.md +7 -0
- package/README.md +733 -0
- package/dist/client.d.ts +96 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +158 -0
- package/dist/client.js.map +1 -0
- package/dist/server.d.ts +236 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +349 -0
- package/dist/server.js.map +1 -0
- package/dist/vite-plugin-CQou_tr5.d.ts +145 -0
- package/dist/vite-plugin-CQou_tr5.d.ts.map +1 -0
- package/dist/vite-plugin-D-W5WQWe.js +398 -0
- package/dist/vite-plugin-D-W5WQWe.js.map +1 -0
- package/dist/vite-plugin.d.ts +2 -0
- package/dist/vite-plugin.js +2 -0
- package/dist/worker.d.ts +22 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +186 -0
- package/dist/worker.js.map +1 -0
- package/package.json +73 -0
- package/src/client.ts +242 -0
- package/src/dev.ts +318 -0
- package/src/server.ts +399 -0
- package/src/vite-plugin.ts +775 -0
- package/src/worker.ts +334 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
//#region src/client.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Client-side utilities for vite-ember-ssr.
|
|
4
|
+
*
|
|
5
|
+
* Currently the client Ember app boots normally and replaces the
|
|
6
|
+
* SSR-rendered content. True DOM hydration is planned for a future
|
|
7
|
+
* phase.
|
|
8
|
+
*
|
|
9
|
+
* For now, the SSR content provides the initial visual while client
|
|
10
|
+
* JavaScript loads, parses, and Ember boots.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Installs the shoebox fetch interceptor on the client.
|
|
14
|
+
*
|
|
15
|
+
* Reads the shoebox data from the server-injected <script> tag,
|
|
16
|
+
* removes the tag from the DOM, and monkey-patches globalThis.fetch
|
|
17
|
+
* to serve cached responses for URLs that match shoebox entries.
|
|
18
|
+
*
|
|
19
|
+
* Each entry is reference-counted: concurrent fetch calls to the same
|
|
20
|
+
* URL all receive the shoebox response. The entry is removed only when
|
|
21
|
+
* the last concurrent consumer has been served.
|
|
22
|
+
*
|
|
23
|
+
* Call this BEFORE creating the Ember application, typically as the
|
|
24
|
+
* first thing in your client entry point.
|
|
25
|
+
*
|
|
26
|
+
* @returns true if shoebox data was found and installed, false otherwise
|
|
27
|
+
*/
|
|
28
|
+
declare function installShoebox(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Restores the original fetch function and cleans up shoebox state.
|
|
31
|
+
*
|
|
32
|
+
* Called automatically when all shoebox entries have been consumed,
|
|
33
|
+
* or can be called manually to force cleanup.
|
|
34
|
+
*/
|
|
35
|
+
declare function cleanupShoebox(): void;
|
|
36
|
+
/**
|
|
37
|
+
* Removes the SSR-rendered content from the DOM so the client Ember
|
|
38
|
+
* app can render into a clean `<body>`. This prevents the "double
|
|
39
|
+
* render" where both server-rendered HTML and client-rendered HTML
|
|
40
|
+
* are visible simultaneously.
|
|
41
|
+
*
|
|
42
|
+
* Removes everything between (and including) the SSR boundary markers:
|
|
43
|
+
* <script type="x/boundary" id="ssr-body-start">
|
|
44
|
+
* ...server rendered content...
|
|
45
|
+
* <script type="x/boundary" id="ssr-body-end">
|
|
46
|
+
*
|
|
47
|
+
* **Call this from your application template** rather than from
|
|
48
|
+
* `entry.ts` — this ensures removal happens at the moment Ember
|
|
49
|
+
* renders, avoiding a flash of no content:
|
|
50
|
+
*
|
|
51
|
+
* ```gts
|
|
52
|
+
* import { cleanupSSRContent } from '@st-h/vite-ember-ssr/client';
|
|
53
|
+
*
|
|
54
|
+
* <template>
|
|
55
|
+
* {{cleanupSSRContent}}
|
|
56
|
+
* {{outlet}}
|
|
57
|
+
* </template>
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* Only used in cleanup mode (default). Not needed when using
|
|
61
|
+
* `rehydrate: true` — in that mode Glimmer reuses the existing DOM.
|
|
62
|
+
*/
|
|
63
|
+
declare function cleanupSSRContent(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Checks if the current page was server-side rendered by looking
|
|
66
|
+
* for SSR boundary markers in the DOM.
|
|
67
|
+
*/
|
|
68
|
+
declare function isSSRRendered(): boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Checks whether the current page was rendered with rehydration mode.
|
|
71
|
+
*
|
|
72
|
+
* Returns `true` when the server (or SSG build) injected the
|
|
73
|
+
* `window.__vite_ember_ssr_rehydrate__` flag. Use this in your client
|
|
74
|
+
* entry point to decide whether to boot Ember in rehydrate mode or
|
|
75
|
+
* with a normal boot:
|
|
76
|
+
*
|
|
77
|
+
* ```ts
|
|
78
|
+
* import { shouldRehydrate, installShoebox } from '@st-h/vite-ember-ssr/client';
|
|
79
|
+
*
|
|
80
|
+
* installShoebox();
|
|
81
|
+
*
|
|
82
|
+
* const app = Application.create({ ...config.APP, autoboot: false });
|
|
83
|
+
*
|
|
84
|
+
* app.visit(window.location.pathname + window.location.search, {
|
|
85
|
+
* ...(shouldRehydrate() ? { _renderMode: 'rehydrate' } : {}),
|
|
86
|
+
* });
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* This is especially important for SSG apps where only prerendered
|
|
90
|
+
* routes carry the flag — non-SSG routes will boot normally without
|
|
91
|
+
* attempting rehydration (which would fail with no serialized DOM).
|
|
92
|
+
*/
|
|
93
|
+
declare function shouldRehydrate(): boolean;
|
|
94
|
+
//#endregion
|
|
95
|
+
export { cleanupSSRContent, cleanupShoebox, installShoebox, isSSRRendered, shouldRehydrate };
|
|
96
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;AAoDA;;;;;AA8FA;;;;;AAqCA;;;;;AAyBA;;;;;AA4BA;;;;;iBAxLgB,cAAA,CAAA;;;;;;;iBA8FA,cAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqCA,iBAAA,CAAA;;;;;iBAyBA,aAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;iBA4BA,eAAA,CAAA"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
//#region src/client.ts
|
|
2
|
+
const SHOEBOX_SCRIPT_ID = "vite-ember-ssr-shoebox";
|
|
3
|
+
/** Original fetch function, saved before monkey-patching */
|
|
4
|
+
let _originalFetch = null;
|
|
5
|
+
/** Map of URL → { entry, refCount } for reference-counted consumption */
|
|
6
|
+
let _shoeboxMap = null;
|
|
7
|
+
/**
|
|
8
|
+
* Installs the shoebox fetch interceptor on the client.
|
|
9
|
+
*
|
|
10
|
+
* Reads the shoebox data from the server-injected <script> tag,
|
|
11
|
+
* removes the tag from the DOM, and monkey-patches globalThis.fetch
|
|
12
|
+
* to serve cached responses for URLs that match shoebox entries.
|
|
13
|
+
*
|
|
14
|
+
* Each entry is reference-counted: concurrent fetch calls to the same
|
|
15
|
+
* URL all receive the shoebox response. The entry is removed only when
|
|
16
|
+
* the last concurrent consumer has been served.
|
|
17
|
+
*
|
|
18
|
+
* Call this BEFORE creating the Ember application, typically as the
|
|
19
|
+
* first thing in your client entry point.
|
|
20
|
+
*
|
|
21
|
+
* @returns true if shoebox data was found and installed, false otherwise
|
|
22
|
+
*/
|
|
23
|
+
function installShoebox() {
|
|
24
|
+
const scriptEl = document.getElementById(SHOEBOX_SCRIPT_ID);
|
|
25
|
+
if (!scriptEl) return false;
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = JSON.parse(scriptEl.textContent ?? "[]");
|
|
29
|
+
} catch {
|
|
30
|
+
scriptEl.remove();
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
scriptEl.remove();
|
|
34
|
+
if (entries.length === 0) return false;
|
|
35
|
+
_shoeboxMap = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const entry of entries) _shoeboxMap.set(entry.url, {
|
|
37
|
+
entry,
|
|
38
|
+
refCount: 1
|
|
39
|
+
});
|
|
40
|
+
_originalFetch = globalThis.fetch;
|
|
41
|
+
globalThis.fetch = function shoeboxFetch(input, init) {
|
|
42
|
+
if ((init?.method?.toUpperCase() ?? "GET") !== "GET" || !_shoeboxMap || _shoeboxMap.size === 0) return _originalFetch(input, init);
|
|
43
|
+
let url;
|
|
44
|
+
try {
|
|
45
|
+
if (typeof input === "string") url = new URL(input, globalThis.location?.href).href;
|
|
46
|
+
else if (input instanceof URL) url = input.href;
|
|
47
|
+
else if (input instanceof Request) url = input.url;
|
|
48
|
+
else return _originalFetch(input, init);
|
|
49
|
+
} catch {
|
|
50
|
+
return _originalFetch(input, init);
|
|
51
|
+
}
|
|
52
|
+
const cached = _shoeboxMap.get(url);
|
|
53
|
+
if (!cached) return _originalFetch(input, init);
|
|
54
|
+
cached.refCount--;
|
|
55
|
+
if (cached.refCount <= 0) _shoeboxMap.delete(url);
|
|
56
|
+
const { entry } = cached;
|
|
57
|
+
const response = new Response(entry.body, {
|
|
58
|
+
status: entry.status,
|
|
59
|
+
statusText: entry.statusText,
|
|
60
|
+
headers: new Headers(entry.headers)
|
|
61
|
+
});
|
|
62
|
+
if (_shoeboxMap.size === 0) cleanupShoebox();
|
|
63
|
+
return Promise.resolve(response);
|
|
64
|
+
};
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Restores the original fetch function and cleans up shoebox state.
|
|
69
|
+
*
|
|
70
|
+
* Called automatically when all shoebox entries have been consumed,
|
|
71
|
+
* or can be called manually to force cleanup.
|
|
72
|
+
*/
|
|
73
|
+
function cleanupShoebox() {
|
|
74
|
+
if (_originalFetch) {
|
|
75
|
+
globalThis.fetch = _originalFetch;
|
|
76
|
+
_originalFetch = null;
|
|
77
|
+
}
|
|
78
|
+
_shoeboxMap = null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Removes the SSR-rendered content from the DOM so the client Ember
|
|
82
|
+
* app can render into a clean `<body>`. This prevents the "double
|
|
83
|
+
* render" where both server-rendered HTML and client-rendered HTML
|
|
84
|
+
* are visible simultaneously.
|
|
85
|
+
*
|
|
86
|
+
* Removes everything between (and including) the SSR boundary markers:
|
|
87
|
+
* <script type="x/boundary" id="ssr-body-start">
|
|
88
|
+
* ...server rendered content...
|
|
89
|
+
* <script type="x/boundary" id="ssr-body-end">
|
|
90
|
+
*
|
|
91
|
+
* **Call this from your application template** rather than from
|
|
92
|
+
* `entry.ts` — this ensures removal happens at the moment Ember
|
|
93
|
+
* renders, avoiding a flash of no content:
|
|
94
|
+
*
|
|
95
|
+
* ```gts
|
|
96
|
+
* import { cleanupSSRContent } from '@st-h/vite-ember-ssr/client';
|
|
97
|
+
*
|
|
98
|
+
* <template>
|
|
99
|
+
* {{cleanupSSRContent}}
|
|
100
|
+
* {{outlet}}
|
|
101
|
+
* </template>
|
|
102
|
+
* ```
|
|
103
|
+
*
|
|
104
|
+
* Only used in cleanup mode (default). Not needed when using
|
|
105
|
+
* `rehydrate: true` — in that mode Glimmer reuses the existing DOM.
|
|
106
|
+
*/
|
|
107
|
+
function cleanupSSRContent() {
|
|
108
|
+
const start = document.getElementById("ssr-body-start");
|
|
109
|
+
const end = document.getElementById("ssr-body-end");
|
|
110
|
+
if (!start || !end) return;
|
|
111
|
+
const parent = start.parentNode;
|
|
112
|
+
if (!parent) return;
|
|
113
|
+
let node = start;
|
|
114
|
+
while (node) {
|
|
115
|
+
const next = node.nextSibling;
|
|
116
|
+
parent.removeChild(node);
|
|
117
|
+
if (node === end) break;
|
|
118
|
+
node = next;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Checks if the current page was server-side rendered by looking
|
|
123
|
+
* for SSR boundary markers in the DOM.
|
|
124
|
+
*/
|
|
125
|
+
function isSSRRendered() {
|
|
126
|
+
return document.getElementById("ssr-body-start") !== null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Checks whether the current page was rendered with rehydration mode.
|
|
130
|
+
*
|
|
131
|
+
* Returns `true` when the server (or SSG build) injected the
|
|
132
|
+
* `window.__vite_ember_ssr_rehydrate__` flag. Use this in your client
|
|
133
|
+
* entry point to decide whether to boot Ember in rehydrate mode or
|
|
134
|
+
* with a normal boot:
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* import { shouldRehydrate, installShoebox } from '@st-h/vite-ember-ssr/client';
|
|
138
|
+
*
|
|
139
|
+
* installShoebox();
|
|
140
|
+
*
|
|
141
|
+
* const app = Application.create({ ...config.APP, autoboot: false });
|
|
142
|
+
*
|
|
143
|
+
* app.visit(window.location.pathname + window.location.search, {
|
|
144
|
+
* ...(shouldRehydrate() ? { _renderMode: 'rehydrate' } : {}),
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
147
|
+
*
|
|
148
|
+
* This is especially important for SSG apps where only prerendered
|
|
149
|
+
* routes carry the flag — non-SSG routes will boot normally without
|
|
150
|
+
* attempting rehydration (which would fail with no serialized DOM).
|
|
151
|
+
*/
|
|
152
|
+
function shouldRehydrate() {
|
|
153
|
+
return window.__vite_ember_ssr_rehydrate__ === true;
|
|
154
|
+
}
|
|
155
|
+
//#endregion
|
|
156
|
+
export { cleanupSSRContent, cleanupShoebox, installShoebox, isSSRRendered, shouldRehydrate };
|
|
157
|
+
|
|
158
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["/**\n * Client-side utilities for vite-ember-ssr.\n *\n * Currently the client Ember app boots normally and replaces the\n * SSR-rendered content. True DOM hydration is planned for a future\n * phase.\n *\n * For now, the SSR content provides the initial visual while client\n * JavaScript loads, parses, and Ember boots.\n */\n\n// ─── Shoebox Types ───────────────────────────────────────────────────\n\n/**\n * A captured fetch response transferred from the server.\n * Must match the ShoeboxEntry interface in server.ts.\n */\ninterface ShoeboxEntry {\n url: string;\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: string;\n}\n\nconst SHOEBOX_SCRIPT_ID = 'vite-ember-ssr-shoebox';\n\n// ─── Shoebox: Client-Side Fetch Replay ───────────────────────────────\n\n/** Original fetch function, saved before monkey-patching */\nlet _originalFetch: typeof fetch | null = null;\n\n/** Map of URL → { entry, refCount } for reference-counted consumption */\nlet _shoeboxMap: Map<string, { entry: ShoeboxEntry; refCount: number }> | null =\n null;\n\n/**\n * Installs the shoebox fetch interceptor on the client.\n *\n * Reads the shoebox data from the server-injected <script> tag,\n * removes the tag from the DOM, and monkey-patches globalThis.fetch\n * to serve cached responses for URLs that match shoebox entries.\n *\n * Each entry is reference-counted: concurrent fetch calls to the same\n * URL all receive the shoebox response. The entry is removed only when\n * the last concurrent consumer has been served.\n *\n * Call this BEFORE creating the Ember application, typically as the\n * first thing in your client entry point.\n *\n * @returns true if shoebox data was found and installed, false otherwise\n */\nexport function installShoebox(): boolean {\n const scriptEl = document.getElementById(SHOEBOX_SCRIPT_ID);\n if (!scriptEl) {\n return false;\n }\n\n // Parse the shoebox data\n let entries: ShoeboxEntry[];\n try {\n entries = JSON.parse(scriptEl.textContent ?? '[]');\n } catch {\n // Malformed shoebox data — skip\n scriptEl.remove();\n return false;\n }\n\n // Remove the script tag from the DOM\n scriptEl.remove();\n\n if (entries.length === 0) {\n return false;\n }\n\n // Build the lookup map with ref counts\n _shoeboxMap = new Map();\n for (const entry of entries) {\n _shoeboxMap.set(entry.url, { entry, refCount: 1 });\n }\n\n // Save the original fetch and install our interceptor\n _originalFetch = globalThis.fetch;\n\n globalThis.fetch = function shoeboxFetch(\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> {\n // Only intercept GET requests (or requests with no method, which default to GET)\n const method = init?.method?.toUpperCase() ?? 'GET';\n if (method !== 'GET' || !_shoeboxMap || _shoeboxMap.size === 0) {\n return _originalFetch!(input, init);\n }\n\n // Resolve the URL string for matching\n let url: string;\n try {\n if (typeof input === 'string') {\n url = new URL(input, globalThis.location?.href).href;\n } else if (input instanceof URL) {\n url = input.href;\n } else if (input instanceof Request) {\n url = input.url;\n } else {\n return _originalFetch!(input, init);\n }\n } catch {\n return _originalFetch!(input, init);\n }\n\n const cached = _shoeboxMap.get(url);\n if (!cached) {\n return _originalFetch!(input, init);\n }\n\n // Decrement ref count and remove if exhausted\n cached.refCount--;\n if (cached.refCount <= 0) {\n _shoeboxMap.delete(url);\n }\n\n // Construct a Response from the cached data\n const { entry } = cached;\n const response = new Response(entry.body, {\n status: entry.status,\n statusText: entry.statusText,\n headers: new Headers(entry.headers),\n });\n\n // Auto-cleanup when the map is empty\n if (_shoeboxMap.size === 0) {\n cleanupShoebox();\n }\n\n return Promise.resolve(response);\n };\n\n return true;\n}\n\n/**\n * Restores the original fetch function and cleans up shoebox state.\n *\n * Called automatically when all shoebox entries have been consumed,\n * or can be called manually to force cleanup.\n */\nexport function cleanupShoebox(): void {\n if (_originalFetch) {\n globalThis.fetch = _originalFetch;\n _originalFetch = null;\n }\n _shoeboxMap = null;\n}\n\n// ─── SSR Content Cleanup ─────────────────────────────────────────────\n\n/**\n * Removes the SSR-rendered content from the DOM so the client Ember\n * app can render into a clean `<body>`. This prevents the \"double\n * render\" where both server-rendered HTML and client-rendered HTML\n * are visible simultaneously.\n *\n * Removes everything between (and including) the SSR boundary markers:\n * <script type=\"x/boundary\" id=\"ssr-body-start\">\n * ...server rendered content...\n * <script type=\"x/boundary\" id=\"ssr-body-end\">\n *\n * **Call this from your application template** rather than from\n * `entry.ts` — this ensures removal happens at the moment Ember\n * renders, avoiding a flash of no content:\n *\n * ```gts\n * import { cleanupSSRContent } from '@st-h/vite-ember-ssr/client';\n *\n * <template>\n * {{cleanupSSRContent}}\n * {{outlet}}\n * </template>\n * ```\n *\n * Only used in cleanup mode (default). Not needed when using\n * `rehydrate: true` — in that mode Glimmer reuses the existing DOM.\n */\nexport function cleanupSSRContent(): void {\n const start = document.getElementById('ssr-body-start');\n const end = document.getElementById('ssr-body-end');\n\n if (!start || !end) {\n return; // Not an SSR-rendered page\n }\n\n // Remove all nodes between start and end markers (inclusive)\n const parent = start.parentNode;\n if (!parent) return;\n\n let node: ChildNode | null = start;\n while (node) {\n const next: ChildNode | null = node.nextSibling;\n parent.removeChild(node);\n if (node === end) break;\n node = next;\n }\n}\n\n/**\n * Checks if the current page was server-side rendered by looking\n * for SSR boundary markers in the DOM.\n */\nexport function isSSRRendered(): boolean {\n return document.getElementById('ssr-body-start') !== null;\n}\n\n/**\n * Checks whether the current page was rendered with rehydration mode.\n *\n * Returns `true` when the server (or SSG build) injected the\n * `window.__vite_ember_ssr_rehydrate__` flag. Use this in your client\n * entry point to decide whether to boot Ember in rehydrate mode or\n * with a normal boot:\n *\n * ```ts\n * import { shouldRehydrate, installShoebox } from '@st-h/vite-ember-ssr/client';\n *\n * installShoebox();\n *\n * const app = Application.create({ ...config.APP, autoboot: false });\n *\n * app.visit(window.location.pathname + window.location.search, {\n * ...(shouldRehydrate() ? { _renderMode: 'rehydrate' } : {}),\n * });\n * ```\n *\n * This is especially important for SSG apps where only prerendered\n * routes carry the flag — non-SSG routes will boot normally without\n * attempting rehydration (which would fail with no serialized DOM).\n */\nexport function shouldRehydrate(): boolean {\n return (\n (window as unknown as Record<string, unknown>)\n .__vite_ember_ssr_rehydrate__ === true\n );\n}\n"],"mappings":";AAyBA,MAAM,oBAAoB;;AAK1B,IAAI,iBAAsC;;AAG1C,IAAI,cACF;;;;;;;;;;;;;;;;;AAkBF,SAAgB,iBAA0B;CACxC,MAAM,WAAW,SAAS,eAAe,kBAAkB;AAC3D,KAAI,CAAC,SACH,QAAO;CAIT,IAAI;AACJ,KAAI;AACF,YAAU,KAAK,MAAM,SAAS,eAAe,KAAK;SAC5C;AAEN,WAAS,QAAQ;AACjB,SAAO;;AAIT,UAAS,QAAQ;AAEjB,KAAI,QAAQ,WAAW,EACrB,QAAO;AAIT,+BAAc,IAAI,KAAK;AACvB,MAAK,MAAM,SAAS,QAClB,aAAY,IAAI,MAAM,KAAK;EAAE;EAAO,UAAU;EAAG,CAAC;AAIpD,kBAAiB,WAAW;AAE5B,YAAW,QAAQ,SAAS,aAC1B,OACA,MACmB;AAGnB,OADe,MAAM,QAAQ,aAAa,IAAI,WAC/B,SAAS,CAAC,eAAe,YAAY,SAAS,EAC3D,QAAO,eAAgB,OAAO,KAAK;EAIrC,IAAI;AACJ,MAAI;AACF,OAAI,OAAO,UAAU,SACnB,OAAM,IAAI,IAAI,OAAO,WAAW,UAAU,KAAK,CAAC;YACvC,iBAAiB,IAC1B,OAAM,MAAM;YACH,iBAAiB,QAC1B,OAAM,MAAM;OAEZ,QAAO,eAAgB,OAAO,KAAK;UAE/B;AACN,UAAO,eAAgB,OAAO,KAAK;;EAGrC,MAAM,SAAS,YAAY,IAAI,IAAI;AACnC,MAAI,CAAC,OACH,QAAO,eAAgB,OAAO,KAAK;AAIrC,SAAO;AACP,MAAI,OAAO,YAAY,EACrB,aAAY,OAAO,IAAI;EAIzB,MAAM,EAAE,UAAU;EAClB,MAAM,WAAW,IAAI,SAAS,MAAM,MAAM;GACxC,QAAQ,MAAM;GACd,YAAY,MAAM;GAClB,SAAS,IAAI,QAAQ,MAAM,QAAQ;GACpC,CAAC;AAGF,MAAI,YAAY,SAAS,EACvB,iBAAgB;AAGlB,SAAO,QAAQ,QAAQ,SAAS;;AAGlC,QAAO;;;;;;;;AAST,SAAgB,iBAAuB;AACrC,KAAI,gBAAgB;AAClB,aAAW,QAAQ;AACnB,mBAAiB;;AAEnB,eAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgChB,SAAgB,oBAA0B;CACxC,MAAM,QAAQ,SAAS,eAAe,iBAAiB;CACvD,MAAM,MAAM,SAAS,eAAe,eAAe;AAEnD,KAAI,CAAC,SAAS,CAAC,IACb;CAIF,MAAM,SAAS,MAAM;AACrB,KAAI,CAAC,OAAQ;CAEb,IAAI,OAAyB;AAC7B,QAAO,MAAM;EACX,MAAM,OAAyB,KAAK;AACpC,SAAO,YAAY,KAAK;AACxB,MAAI,SAAS,IAAK;AAClB,SAAO;;;;;;;AAQX,SAAgB,gBAAyB;AACvC,QAAO,SAAS,eAAe,iBAAiB,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BvD,SAAgB,kBAA2B;AACzC,QACG,OACE,iCAAiC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.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;AAAA,UAGe,kBAAA;EAbf;;;;EAkBA,OAAA;EAf0B;;;;;;;;EAyB1B,SAAA;EApBA;;;;;AAKF;EAuBE,WAAA,GAAc,WAAA;;;;;;;;;;;AAoBhB;;;;;EAHE,OAAA,GAAU,MAAA;AAAA;AAAA,UAGK,YAAA;EAQf;EANA,IAAA;EAQQ;EANR,IAAA;EAMa;EAJb,SAAA,EAAW,MAAA;EAYgB;EAV3B,UAAA;EAce;EAZf,KAAA,GAAQ,KAAA;AAAA;;;;UAQO,YAAA;EACf,GAAA;EACA,MAAA;EACA,UAAA;EACA,OAAA,EAAS,MAAA;EACT,IAAA;AAAA;AAAA,UAKe,kBAAA;EAgBC;;;;;AAGlB;;;;;;;;;;EAHE,aAAA,GAAgB,IAAA,aAAiB,OAAA,CAAQ,MAAA;AAAA;AAAA,UAG1B,eAAA;;;;;;;;;;;;EAYf,OAAA;EAqDgE;;;;;AAgDlE;;;;;;;;;;;EAnFE,qBAAA;EAsFS;;;AAyFX;;;;;;;;;;AAkCA;;;;;EA7LE,cAAA;EA6L4D;;;AAe9D;EAtME,GAAA,GAAM,kBAAA;AAAA;AAAA,UAGS,QAAA;EAoMf;;;;;EA9LA,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"}
|