@octanejs/vite-plugin 0.1.4 → 0.1.6
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/package.json +16 -4
- package/src/config-entry.js +15 -0
- package/src/constants.js +1 -3
- package/src/index.js +74 -24
- package/src/load-config.js +61 -81
- package/src/project-codegen.js +10 -218
- package/src/resolve-config.js +1 -171
- package/src/routes.js +1 -133
- package/src/server/component-wrappers.js +5 -56
- package/src/server/html-stream.js +1 -0
- package/src/server/html-template.js +9 -0
- package/src/server/middleware.js +1 -127
- package/src/server/node-http.js +1 -188
- package/src/server/production.js +1 -286
- package/src/server/render-route.js +66 -41
- package/src/server/router.js +1 -123
- package/src/server/server-route.js +1 -47
- package/src/server/virtual-entry.js +1 -184
- package/types/index.d.ts +30 -260
- package/types/node.d.ts +1 -31
- package/types/production.d.ts +1 -71
- package/CHANGELOG.md +0 -244
- package/tests/_fixtures/app/index.html +0 -11
- package/tests/_fixtures/app/octane.config.ts +0 -14
- package/tests/_fixtures/app/package.json +0 -7
- package/tests/_fixtures/app/src/Layout.tsrx +0 -6
- package/tests/_fixtures/app/src/Page.tsrx +0 -17
- package/tests/_fixtures/app/vite.config.ts +0 -12
- package/tests/handler.test.ts +0 -134
- package/tests/plugin.test.ts +0 -155
- package/tests/production.test.ts +0 -157
- package/tsconfig.typecheck.json +0 -18
|
@@ -1,7 +1,18 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
import { readFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
4
|
+
import { composeHtmlStream } from './html-stream.js';
|
|
5
|
+
import {
|
|
6
|
+
getContextNonce,
|
|
7
|
+
injectHydrationEntry,
|
|
8
|
+
nonceAttribute,
|
|
9
|
+
splitSsrTemplate,
|
|
10
|
+
} from './html-template.js';
|
|
11
|
+
import {
|
|
12
|
+
createLayoutWrapper,
|
|
13
|
+
createPropsWrapper,
|
|
14
|
+
createRootBoundaryWrapper,
|
|
15
|
+
} from './component-wrappers.js';
|
|
5
16
|
import {
|
|
6
17
|
get_component_export,
|
|
7
18
|
get_route_entry_export_name,
|
|
@@ -55,7 +66,8 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
55
66
|
// Load the octane streaming renderer. The wrappers call components
|
|
56
67
|
// directly (no ssrComponent injection — the root must NOT be
|
|
57
68
|
// marker-wrapped).
|
|
58
|
-
const
|
|
69
|
+
const serverRuntime = await vite.ssrLoadModule('octane/server');
|
|
70
|
+
const { renderToReadableStream } = serverRuntime;
|
|
59
71
|
|
|
60
72
|
// Load the page component (compiled in server mode by octane()).
|
|
61
73
|
const entryPath = get_route_entry_path(route.entry);
|
|
@@ -76,7 +88,8 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
76
88
|
// entry exports.
|
|
77
89
|
let RootComponent;
|
|
78
90
|
const requestUrl = context.url.pathname + context.url.search;
|
|
79
|
-
const pageProps = { params: context.params, url: requestUrl };
|
|
91
|
+
const pageProps = { params: context.params, url: requestUrl, state: context.state };
|
|
92
|
+
const nonce = getContextNonce(context);
|
|
80
93
|
|
|
81
94
|
if (route.layout) {
|
|
82
95
|
const layoutModule = await vite.ssrLoadModule(route.layout);
|
|
@@ -95,6 +108,16 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
95
108
|
RootComponent = createPropsWrapper(/** @type {any} */ (PageComponent), pageProps);
|
|
96
109
|
}
|
|
97
110
|
|
|
111
|
+
const pendingEntry = octaneConfig?.rootBoundary.pending;
|
|
112
|
+
const catchEntry = octaneConfig?.rootBoundary.catch;
|
|
113
|
+
const PendingComponent = await loadBoundaryComponent(vite, pendingEntry, 'pending');
|
|
114
|
+
const CatchComponent = await loadBoundaryComponent(vite, catchEntry, 'catch');
|
|
115
|
+
RootComponent = createRootBoundaryWrapper(
|
|
116
|
+
RootComponent,
|
|
117
|
+
{ pending: PendingComponent, catch: CatchComponent },
|
|
118
|
+
/** @type {any} */ (serverRuntime),
|
|
119
|
+
);
|
|
120
|
+
|
|
98
121
|
// Build head content with hydration data. The client entry is CONFIG-FREE
|
|
99
122
|
// (importing octane.config.ts into the browser would drag the plugin + the
|
|
100
123
|
// server adapter — and their `node:fs` imports — into the client graph and
|
|
@@ -111,8 +134,12 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
111
134
|
params: context.params,
|
|
112
135
|
url: requestUrl,
|
|
113
136
|
preHydrate: octaneConfig?.router.preHydrate ?? null,
|
|
137
|
+
rootBoundary: {
|
|
138
|
+
pending: serializeComponentEntry(pendingEntry),
|
|
139
|
+
catch: serializeComponentEntry(catchEntry),
|
|
140
|
+
},
|
|
114
141
|
});
|
|
115
|
-
const headContent = `<script id="__octane_data" type="application/json">${escapeScript(routeData)}</script>`;
|
|
142
|
+
const headContent = `<script id="__octane_data" type="application/json"${nonceAttribute(nonce)}>${escapeScript(routeData)}</script>`;
|
|
116
143
|
|
|
117
144
|
// Load and process index.html template.
|
|
118
145
|
const templatePath = join(vite.config.root, 'index.html');
|
|
@@ -121,17 +148,18 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
121
148
|
// Apply Vite's HTML transforms (HMR client, module resolution, etc.).
|
|
122
149
|
template = await vite.transformIndexHtml(context.url.pathname, template);
|
|
123
150
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
html = html.replace('</body>', `${hydrationScript}\n</body>`);
|
|
151
|
+
// Validate the raw SSR template and inject the request-nonced hydrate entry
|
|
152
|
+
// before consuming the one required head marker with request data.
|
|
153
|
+
let html = injectHydrationEntry(template, '/@id/virtual:octane-hydrate', nonce);
|
|
154
|
+
html = html.replace('<!--ssr-head-->', headContent);
|
|
129
155
|
|
|
130
156
|
// Start the render. This await resolves at SHELL-ready (so a synchronous
|
|
131
157
|
// render error still falls into the catch below and produces the dev 500
|
|
132
158
|
// page); segments keep flushing through the returned stream afterwards.
|
|
133
159
|
/** @type {ReadableStream<Uint8Array>} */
|
|
134
160
|
const renderStream = await renderToReadableStream(RootComponent, undefined, {
|
|
161
|
+
nonce: nonce ?? undefined,
|
|
162
|
+
signal: context.request.signal,
|
|
135
163
|
onError(/** @type {unknown} */ error) {
|
|
136
164
|
if (error instanceof Error) vite.ssrFixStacktrace(error);
|
|
137
165
|
console.error('[octane] SSR render error:', error);
|
|
@@ -141,42 +169,12 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
141
169
|
const status = route.status ?? 200;
|
|
142
170
|
const headers = { 'Content-Type': 'text/html; charset=utf-8' };
|
|
143
171
|
|
|
144
|
-
|
|
145
|
-
// serve the transformed template (matches the old buffered behavior).
|
|
146
|
-
const splitAt = html.indexOf('<!--ssr-body-->');
|
|
147
|
-
if (splitAt === -1) {
|
|
148
|
-
await renderStream.cancel();
|
|
149
|
-
return new Response(html, { status, headers });
|
|
150
|
-
}
|
|
151
|
-
const prefix = html.slice(0, splitAt);
|
|
152
|
-
const suffix = html.slice(splitAt + '<!--ssr-body-->'.length);
|
|
172
|
+
const [prefix, suffix] = splitSsrTemplate(html);
|
|
153
173
|
|
|
154
174
|
// Template prefix → render stream (shell, then out-of-order segments) →
|
|
155
175
|
// template suffix. The hydration <script> is in the SUFFIX, so by the time
|
|
156
176
|
// the browser requests the entry every segment is already in the DOM.
|
|
157
|
-
const
|
|
158
|
-
const body = new ReadableStream({
|
|
159
|
-
async start(controller) {
|
|
160
|
-
controller.enqueue(encoder.encode(prefix));
|
|
161
|
-
const reader = renderStream.getReader();
|
|
162
|
-
try {
|
|
163
|
-
while (true) {
|
|
164
|
-
const { done, value } = await reader.read();
|
|
165
|
-
if (done) break;
|
|
166
|
-
controller.enqueue(value);
|
|
167
|
-
}
|
|
168
|
-
controller.enqueue(encoder.encode(suffix));
|
|
169
|
-
controller.close();
|
|
170
|
-
} catch (error) {
|
|
171
|
-
controller.error(error);
|
|
172
|
-
} finally {
|
|
173
|
-
reader.releaseLock();
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
cancel(reason) {
|
|
177
|
-
return renderStream.cancel(reason);
|
|
178
|
-
},
|
|
179
|
-
});
|
|
177
|
+
const body = composeHtmlStream(prefix, renderStream, suffix);
|
|
180
178
|
|
|
181
179
|
return new Response(body, { status, headers });
|
|
182
180
|
} catch (error) {
|
|
@@ -192,6 +190,33 @@ export async function handleRenderRoute(route, context, vite, octaneConfig) {
|
|
|
192
190
|
}
|
|
193
191
|
}
|
|
194
192
|
|
|
193
|
+
/**
|
|
194
|
+
* @param {ViteDevServer} vite
|
|
195
|
+
* @param {import('@octanejs/vite-plugin').RenderRouteEntry | undefined} entry
|
|
196
|
+
* @param {'pending' | 'catch'} kind
|
|
197
|
+
* @returns {Promise<((props?: any, scope?: any, extra?: any) => string | void) | null>}
|
|
198
|
+
*/
|
|
199
|
+
async function loadBoundaryComponent(vite, entry, kind) {
|
|
200
|
+
if (!entry) return null;
|
|
201
|
+
const modulePath = get_route_entry_path(entry);
|
|
202
|
+
const module = await vite.ssrLoadModule(/** @type {string} */ (modulePath));
|
|
203
|
+
const component = get_component_export(module, get_route_entry_export_name(entry));
|
|
204
|
+
if (!component) {
|
|
205
|
+
throw new Error(`No ${kind} rootBoundary component found in ${modulePath}`);
|
|
206
|
+
}
|
|
207
|
+
return /** @type {(props?: any, scope?: any, extra?: any) => string | void} */ (component);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @param {import('@octanejs/vite-plugin').RenderRouteEntry | undefined} entry
|
|
212
|
+
* @returns {{ path: string, exportName: string | null } | null}
|
|
213
|
+
*/
|
|
214
|
+
function serializeComponentEntry(entry) {
|
|
215
|
+
const path = get_route_entry_path(entry);
|
|
216
|
+
if (!path) return null;
|
|
217
|
+
return { path, exportName: get_route_entry_export_name(entry) ?? null };
|
|
218
|
+
}
|
|
219
|
+
|
|
195
220
|
/**
|
|
196
221
|
* @param {ResolvedOctaneConfig | undefined} config
|
|
197
222
|
* @param {RenderRoute} route
|
package/src/server/router.js
CHANGED
|
@@ -1,123 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @typedef {import('@octanejs/vite-plugin').Route} Route
|
|
4
|
-
* @typedef {import('@octanejs/vite-plugin').RenderRoute} RenderRoute
|
|
5
|
-
* @typedef {import('@octanejs/vite-plugin').ServerRoute} ServerRoute
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {Object} RouteMatch
|
|
10
|
-
* @property {Route} route
|
|
11
|
-
* @property {Record<string, string>} params
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @typedef {Object} CompiledRoute
|
|
16
|
-
* @property {Route} route
|
|
17
|
-
* @property {RegExp} pattern
|
|
18
|
-
* @property {string[]} paramNames
|
|
19
|
-
* @property {number} specificity - Higher = more specific (static > param > catch-all)
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Convert a route path pattern to a RegExp
|
|
24
|
-
* Supports:
|
|
25
|
-
* - Static segments: /about, /api/hello
|
|
26
|
-
* - Named params: /posts/:id, /users/:userId/posts/:postId
|
|
27
|
-
* - Catch-all: /docs/*slug
|
|
28
|
-
*
|
|
29
|
-
* @param {string} path
|
|
30
|
-
* @returns {{ pattern: RegExp, paramNames: string[], specificity: number }}
|
|
31
|
-
*/
|
|
32
|
-
function compilePath(path) {
|
|
33
|
-
/** @type {string[]} */
|
|
34
|
-
const paramNames = [];
|
|
35
|
-
let specificity = 0;
|
|
36
|
-
|
|
37
|
-
// Escape special regex characters except our param syntax
|
|
38
|
-
const regexString = path
|
|
39
|
-
.split('/')
|
|
40
|
-
.map((segment) => {
|
|
41
|
-
if (!segment) return '';
|
|
42
|
-
|
|
43
|
-
// Catch-all param: *slug
|
|
44
|
-
if (segment.startsWith('*')) {
|
|
45
|
-
const paramName = segment.slice(1);
|
|
46
|
-
paramNames.push(paramName);
|
|
47
|
-
specificity += 1; // Lowest specificity
|
|
48
|
-
return '(.+)';
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Named param: :id
|
|
52
|
-
if (segment.startsWith(':')) {
|
|
53
|
-
const paramName = segment.slice(1);
|
|
54
|
-
paramNames.push(paramName);
|
|
55
|
-
specificity += 10; // Medium specificity
|
|
56
|
-
return '([^/]+)';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Static segment
|
|
60
|
-
specificity += 100; // Highest specificity
|
|
61
|
-
return escapeRegex(segment);
|
|
62
|
-
})
|
|
63
|
-
.join('/');
|
|
64
|
-
|
|
65
|
-
const pattern = new RegExp(`^${regexString || '/'}$`);
|
|
66
|
-
return { pattern, paramNames, specificity };
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Escape special regex characters
|
|
71
|
-
* @param {string} str
|
|
72
|
-
* @returns {string}
|
|
73
|
-
*/
|
|
74
|
-
function escapeRegex(str) {
|
|
75
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Create a router from a list of routes
|
|
80
|
-
* @param {Route[]} routes
|
|
81
|
-
* @returns {{ match: (method: string, pathname: string) => RouteMatch | null }}
|
|
82
|
-
*/
|
|
83
|
-
export function createRouter(routes) {
|
|
84
|
-
/** @type {CompiledRoute[]} */
|
|
85
|
-
const compiledRoutes = routes.map((route) => {
|
|
86
|
-
const { pattern, paramNames, specificity } = compilePath(route.path);
|
|
87
|
-
return { route, pattern, paramNames, specificity };
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Sort by specificity (higher first) for correct matching order
|
|
91
|
-
compiledRoutes.sort((a, b) => b.specificity - a.specificity);
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
/**
|
|
95
|
-
* Match a request to a route
|
|
96
|
-
* @param {string} method
|
|
97
|
-
* @param {string} pathname
|
|
98
|
-
* @returns {RouteMatch | null}
|
|
99
|
-
*/
|
|
100
|
-
match(method, pathname) {
|
|
101
|
-
for (const { route, pattern, paramNames } of compiledRoutes) {
|
|
102
|
-
// Check method for ServerRoute
|
|
103
|
-
if (route.type === 'server') {
|
|
104
|
-
const methods = /** @type {ServerRoute} */ (route).methods;
|
|
105
|
-
if (!methods.includes(method.toUpperCase())) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const match = pathname.match(pattern);
|
|
111
|
-
if (match) {
|
|
112
|
-
/** @type {Record<string, string>} */
|
|
113
|
-
const params = {};
|
|
114
|
-
for (let i = 0; i < paramNames.length; i++) {
|
|
115
|
-
params[paramNames[i]] = decodeURIComponent(match[i + 1]);
|
|
116
|
-
}
|
|
117
|
-
return { route, params };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return null;
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
}
|
|
1
|
+
export { createRouter } from '@octanejs/app-core/routes';
|
|
@@ -1,47 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* @typedef {import('@octanejs/vite-plugin').Context} Context
|
|
4
|
-
* @typedef {import('@octanejs/vite-plugin').ServerRoute} ServerRoute
|
|
5
|
-
* @typedef {import('@octanejs/vite-plugin').Middleware} Middleware
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { runMiddlewareChain } from './middleware.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Handle a ServerRoute (API endpoint)
|
|
12
|
-
*
|
|
13
|
-
* @param {ServerRoute} route
|
|
14
|
-
* @param {Context} context
|
|
15
|
-
* @param {Middleware[]} globalMiddlewares
|
|
16
|
-
* @returns {Promise<Response>}
|
|
17
|
-
*/
|
|
18
|
-
export async function handleServerRoute(route, context, globalMiddlewares) {
|
|
19
|
-
try {
|
|
20
|
-
// The handler wrapped as a function returning Promise<Response>
|
|
21
|
-
const handler = async () => {
|
|
22
|
-
return route.handler(context);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// Run the middleware chain: global → before → handler → after
|
|
26
|
-
const response = await runMiddlewareChain(
|
|
27
|
-
context,
|
|
28
|
-
globalMiddlewares,
|
|
29
|
-
route.before,
|
|
30
|
-
handler,
|
|
31
|
-
route.after,
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
return response;
|
|
35
|
-
} catch (error) {
|
|
36
|
-
console.error('[octane] API route error:', error);
|
|
37
|
-
|
|
38
|
-
// Return error response
|
|
39
|
-
const message = error instanceof Error ? error.message : 'Internal Server Error';
|
|
40
|
-
return new Response(JSON.stringify({ error: message }), {
|
|
41
|
-
status: 500,
|
|
42
|
-
headers: {
|
|
43
|
-
'Content-Type': 'application/json',
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
export { handleServerRoute } from '@octanejs/app-core/middleware';
|
|
@@ -1,184 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Production server-entry generator.
|
|
4
|
-
*
|
|
5
|
-
* `generateServerEntry` emits the module the SSR sub-build (closeBundle in
|
|
6
|
-
* src/index.js) uses as its Rollup input. The generated module statically
|
|
7
|
-
* imports every RenderRoute entry/layout module (compiled in server mode by
|
|
8
|
-
* the octane plugin the sub-build inherits from the app's vite.config) plus
|
|
9
|
-
* octane.config.ts itself, wires them into `createHandler`, and:
|
|
10
|
-
*
|
|
11
|
-
* - exports `handler` — the Web fetch handler `(Request) => Promise<Response>`
|
|
12
|
-
* - exports `nodeHandler` — a Node `(req, res)` wrapper for serverless
|
|
13
|
-
* platforms (e.g. a Vercel Node function does
|
|
14
|
-
* `export { nodeHandler as default } from '../dist/server/entry.js'`)
|
|
15
|
-
* - auto-boots when run directly (`node dist/server/entry.js`): the
|
|
16
|
-
* adapter's `serve()` when configured, else the built-in Node server
|
|
17
|
-
* (static dist/client assets + the handler).
|
|
18
|
-
*
|
|
19
|
-
* It is unused in dev (dev SSR loads modules through `vite.ssrLoadModule`).
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/** @import { Route } from '@octanejs/vite-plugin' */
|
|
23
|
-
/** @import { ClientAssetEntry } from '../../types/production.d.ts' */
|
|
24
|
-
|
|
25
|
-
import { get_route_entry_path } from '../routes.js';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @typedef {Object} ServerEntryOptions
|
|
29
|
-
* @property {Route[]} routes - Route definitions from octane.config.ts
|
|
30
|
-
* @property {string} octaneConfigPath - Absolute path to octane.config.ts
|
|
31
|
-
* @property {Record<string, ClientAssetEntry>} [clientAssetMap] - Route entry path → built client asset paths
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* @param {ServerEntryOptions} options
|
|
36
|
-
* @returns {string} The generated JavaScript module source
|
|
37
|
-
*/
|
|
38
|
-
export function generateServerEntry(options) {
|
|
39
|
-
const { routes, octaneConfigPath, clientAssetMap = {} } = options;
|
|
40
|
-
|
|
41
|
-
// Unique page-entry and layout module paths (multiple routes may share both).
|
|
42
|
-
/** @type {Map<string, string>} module path → import variable name */
|
|
43
|
-
const page_imports = new Map();
|
|
44
|
-
/** @type {Map<string, string>} */
|
|
45
|
-
const layout_imports = new Map();
|
|
46
|
-
|
|
47
|
-
for (const route of routes) {
|
|
48
|
-
if (route.type !== 'render') continue;
|
|
49
|
-
const entryPath = get_route_entry_path(route.entry);
|
|
50
|
-
if (entryPath && !page_imports.has(entryPath)) {
|
|
51
|
-
page_imports.set(entryPath, `_page_${page_imports.size}`);
|
|
52
|
-
}
|
|
53
|
-
if (typeof route.layout === 'string' && !layout_imports.has(route.layout)) {
|
|
54
|
-
layout_imports.set(route.layout, `_layout_${layout_imports.size}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const import_lines = [];
|
|
59
|
-
for (const [modulePath, varName] of page_imports) {
|
|
60
|
-
import_lines.push(`import * as ${varName} from ${JSON.stringify(modulePath)};`);
|
|
61
|
-
}
|
|
62
|
-
for (const [modulePath, varName] of layout_imports) {
|
|
63
|
-
import_lines.push(`import * as ${varName} from ${JSON.stringify(modulePath)};`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// The manifest maps MODULE PATHS to module namespaces; createHandler picks
|
|
67
|
-
// the export per-route with the same `get_component_export` dev uses.
|
|
68
|
-
const component_entries = [...page_imports]
|
|
69
|
-
.map(([modulePath, varName]) => `\t${JSON.stringify(modulePath)}: ${varName},`)
|
|
70
|
-
.join('\n');
|
|
71
|
-
const layout_entries = [...layout_imports]
|
|
72
|
-
.map(([modulePath, varName]) => `\t${JSON.stringify(modulePath)}: ${varName},`)
|
|
73
|
-
.join('\n');
|
|
74
|
-
|
|
75
|
-
return `\
|
|
76
|
-
// Auto-generated by @octanejs/vite-plugin — the production server entry.
|
|
77
|
-
// Do not edit; regenerated on every build.
|
|
78
|
-
|
|
79
|
-
import { readFileSync } from 'node:fs';
|
|
80
|
-
import { createHash } from 'node:crypto';
|
|
81
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
82
|
-
import { fileURLToPath } from 'node:url';
|
|
83
|
-
import { dirname, join, resolve } from 'node:path';
|
|
84
|
-
|
|
85
|
-
import { renderToReadableStream, executeServerFunction } from 'octane/server';
|
|
86
|
-
import { prerender } from 'octane/static';
|
|
87
|
-
import { createHandler, resolveOctaneConfig } from '@octanejs/vite-plugin/production';
|
|
88
|
-
import { createNodeServer, nodeRequestToWebRequest, sendWebResponse } from '@octanejs/vite-plugin/node';
|
|
89
|
-
|
|
90
|
-
// The app config — bundled (the sub-build aliases '@octanejs/vite-plugin' to
|
|
91
|
-
// its config-surface facade, so this does not drag the compiler in).
|
|
92
|
-
import _rawOctaneConfig from ${JSON.stringify(octaneConfigPath)};
|
|
93
|
-
|
|
94
|
-
${import_lines.join('\n')}
|
|
95
|
-
|
|
96
|
-
const octaneConfig = resolveOctaneConfig(_rawOctaneConfig);
|
|
97
|
-
|
|
98
|
-
// Platform primitives: the adapter's when configured, else Node defaults.
|
|
99
|
-
// (hash mirrors the compiler's module-server hashing: sha-256 hex, 8 chars.)
|
|
100
|
-
const runtime = octaneConfig.adapter?.runtime ?? {
|
|
101
|
-
hash: (str) => createHash('sha256').update(str).digest('hex').slice(0, 8),
|
|
102
|
-
createAsyncContext: () => {
|
|
103
|
-
const als = new AsyncLocalStorage();
|
|
104
|
-
return { run: (store, fn) => als.run(store, fn), getStore: () => als.getStore() };
|
|
105
|
-
},
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
109
|
-
// The HTML template is the BUILT client index.html (hashed hydrate script and
|
|
110
|
-
// asset links already in place), moved next to this entry by the build.
|
|
111
|
-
const htmlTemplate = readFileSync(join(__dirname, './index.html'), 'utf-8');
|
|
112
|
-
|
|
113
|
-
const components = {
|
|
114
|
-
${component_entries}
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const layouts = {
|
|
118
|
-
${layout_entries}
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
const clientAssets = ${JSON.stringify(clientAssetMap, null, '\t')};
|
|
122
|
-
|
|
123
|
-
export const handler = createHandler(
|
|
124
|
-
{
|
|
125
|
-
routes: octaneConfig.router.routes,
|
|
126
|
-
components,
|
|
127
|
-
layouts,
|
|
128
|
-
middlewares: octaneConfig.middlewares,
|
|
129
|
-
trustProxy: octaneConfig.server.trustProxy,
|
|
130
|
-
render: octaneConfig.server.render,
|
|
131
|
-
rootBoundary: octaneConfig.rootBoundary,
|
|
132
|
-
preHydrate: octaneConfig.router.preHydrate ?? null,
|
|
133
|
-
rpcModules: {},
|
|
134
|
-
runtime,
|
|
135
|
-
clientAssets,
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
renderToReadableStream,
|
|
139
|
-
prerender,
|
|
140
|
-
htmlTemplate,
|
|
141
|
-
executeServerFunction,
|
|
142
|
-
},
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Node-style (req, res) wrapper — for serverless platforms whose functions
|
|
147
|
-
* speak Node HTTP (e.g. Vercel's Node runtime).
|
|
148
|
-
*/
|
|
149
|
-
export async function nodeHandler(req, res) {
|
|
150
|
-
try {
|
|
151
|
-
const response = await handler(nodeRequestToWebRequest(req));
|
|
152
|
-
await sendWebResponse(res, response);
|
|
153
|
-
} catch (error) {
|
|
154
|
-
console.error('[@octanejs/vite-plugin] Request error:', error);
|
|
155
|
-
if (!res.headersSent) {
|
|
156
|
-
res.statusCode = 500;
|
|
157
|
-
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
158
|
-
}
|
|
159
|
-
res.end('Internal Server Error');
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Auto-boot when run directly (node dist/server/entry.js); stay quiet when
|
|
164
|
-
// imported by a serverless wrapper.
|
|
165
|
-
const isMainModule =
|
|
166
|
-
typeof process !== 'undefined' &&
|
|
167
|
-
process.argv[1] &&
|
|
168
|
-
fileURLToPath(import.meta.url) === resolve(process.argv[1]);
|
|
169
|
-
|
|
170
|
-
if (isMainModule) {
|
|
171
|
-
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
|
|
172
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
173
|
-
console.error('[@octanejs/vite-plugin] Invalid PORT value:', process.env.PORT);
|
|
174
|
-
process.exit(1);
|
|
175
|
-
}
|
|
176
|
-
const staticDir = join(__dirname, '../client');
|
|
177
|
-
const server = octaneConfig.adapter?.serve
|
|
178
|
-
? octaneConfig.adapter.serve(handler, { static: { dir: staticDir } })
|
|
179
|
-
: createNodeServer(handler, { staticDir });
|
|
180
|
-
server.listen(port);
|
|
181
|
-
console.log('[@octanejs/vite-plugin] Production server listening on port ' + port);
|
|
182
|
-
}
|
|
183
|
-
`;
|
|
184
|
-
}
|
|
1
|
+
export { generateServerEntry, generateServerManifestEntry } from '@octanejs/app-core/codegen';
|