@analogjs/vite-plugin-nitro 2.4.0-alpha.1 → 2.4.0-alpha.3
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 +2 -2
- package/package.json +4 -2
- package/src/index.d.ts +1 -1
- package/src/lib/build-server.d.ts +1 -1
- package/src/lib/build-server.js +30 -3
- package/src/lib/build-server.js.map +1 -1
- package/src/lib/hooks/post-rendering-hook.d.ts +1 -1
- package/src/lib/options.d.ts +1 -1
- package/src/lib/plugins/dev-server-plugin.js +4 -2
- package/src/lib/plugins/dev-server-plugin.js.map +1 -1
- package/src/lib/plugins/page-endpoints.js +33 -10
- package/src/lib/plugins/page-endpoints.js.map +1 -1
- package/src/lib/utils/get-content-files.js +1 -4
- package/src/lib/utils/get-content-files.js.map +1 -1
- package/src/lib/utils/get-page-handlers.d.ts +1 -1
- package/src/lib/utils/get-page-handlers.js +5 -3
- package/src/lib/utils/get-page-handlers.js.map +1 -1
- package/src/lib/utils/node-web-bridge.d.ts +3 -0
- package/src/lib/utils/node-web-bridge.js +50 -0
- package/src/lib/utils/node-web-bridge.js.map +1 -0
- package/src/lib/utils/register-dev-middleware.d.ts +4 -32
- package/src/lib/utils/register-dev-middleware.js +22 -43
- package/src/lib/utils/register-dev-middleware.js.map +1 -1
- package/src/lib/utils/renderers.d.ts +42 -3
- package/src/lib/utils/renderers.js +98 -26
- package/src/lib/utils/renderers.js.map +1 -1
- package/src/lib/vite-plugin-nitro.d.ts +1 -1
- package/src/lib/vite-plugin-nitro.js +207 -68
- package/src/lib/vite-plugin-nitro.js.map +1 -1
|
@@ -1,58 +1,130 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* SSR renderer virtual module content.
|
|
3
|
+
*
|
|
4
|
+
* This code runs inside Nitro's server runtime (Node.js context) where
|
|
5
|
+
* event.node is always populated. In h3 v2, event.node is typed as optional,
|
|
6
|
+
* so we use h3's first-class event properties (event.path, event.method) where
|
|
7
|
+
* possible and apply optional chaining when accessing the Node.js context for
|
|
8
|
+
* the Angular renderer which requires raw req/res objects.
|
|
9
|
+
*
|
|
10
|
+
* h3 v2 idiomatic APIs used:
|
|
11
|
+
* - defineHandler (replaces defineEventHandler / eventHandler)
|
|
12
|
+
* - event.path (replaces event.node.req.url)
|
|
13
|
+
* - getResponseHeader compat shim (still available in h3 v2)
|
|
14
|
+
*/
|
|
15
|
+
export function ssrRenderer(templatePath) {
|
|
16
|
+
return `
|
|
17
|
+
import { readFileSync } from 'node:fs';
|
|
18
|
+
import { createFetch } from 'ofetch';
|
|
19
|
+
import { defineHandler, fetchWithEvent } from 'h3';
|
|
3
20
|
// @ts-ignore
|
|
4
21
|
import renderer from '#analog/ssr';
|
|
5
|
-
// @ts-ignore
|
|
6
|
-
import template from '#analog/index';
|
|
7
22
|
|
|
8
|
-
|
|
9
|
-
|
|
23
|
+
const template = readFileSync(${JSON.stringify(templatePath)}, 'utf8');
|
|
24
|
+
const normalizeHtmlRequestUrl = (url) =>
|
|
25
|
+
url.replace(/\\/index\\.html(?=$|[?#])/, '/');
|
|
26
|
+
|
|
27
|
+
export default defineHandler(async (event) => {
|
|
28
|
+
event.res.headers.set('content-type', 'text/html; charset=utf-8');
|
|
29
|
+
const noSSR = event.res.headers.get('x-analog-no-ssr');
|
|
30
|
+
const requestPath = normalizeHtmlRequestUrl(event.path);
|
|
10
31
|
|
|
11
32
|
if (noSSR === 'true') {
|
|
12
33
|
return template;
|
|
13
34
|
}
|
|
14
35
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
36
|
+
// event.path is the canonical h3 v2 way to access the request URL.
|
|
37
|
+
// event.node?.req and event.node?.res are needed by the Angular SSR renderer
|
|
38
|
+
// which operates on raw Node.js request/response objects.
|
|
39
|
+
// During prerendering (Nitro v3 fetch-based pipeline), event.node is undefined.
|
|
40
|
+
// The Angular renderer requires a req object with at least { headers, url },
|
|
41
|
+
// so we provide a minimal stub to avoid runtime errors in prerender context.
|
|
42
|
+
const req = event.node?.req
|
|
43
|
+
? {
|
|
44
|
+
...event.node.req,
|
|
45
|
+
url: requestPath,
|
|
46
|
+
originalUrl: requestPath,
|
|
47
|
+
}
|
|
48
|
+
: {
|
|
49
|
+
headers: { host: 'localhost' },
|
|
50
|
+
url: requestPath,
|
|
51
|
+
originalUrl: requestPath,
|
|
52
|
+
connection: {},
|
|
53
|
+
};
|
|
54
|
+
const res = event.node?.res;
|
|
55
|
+
const fetch = createFetch({
|
|
56
|
+
fetch: (resource, init) => {
|
|
57
|
+
const url = resource instanceof Request ? resource.url : resource.toString();
|
|
58
|
+
return fetchWithEvent(event, url, init);
|
|
59
|
+
}
|
|
18
60
|
});
|
|
19
61
|
|
|
62
|
+
const html = await renderer(requestPath, template, { req, res, fetch });
|
|
63
|
+
|
|
20
64
|
return html;
|
|
21
65
|
});`;
|
|
22
|
-
|
|
23
|
-
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Client-only renderer virtual module content.
|
|
69
|
+
*
|
|
70
|
+
* Used when SSR is disabled — simply serves the static index.html template
|
|
71
|
+
* for every route, letting the client-side Angular router handle navigation.
|
|
72
|
+
*/
|
|
73
|
+
export function clientRenderer(templatePath) {
|
|
74
|
+
return `
|
|
75
|
+
import { readFileSync } from 'node:fs';
|
|
76
|
+
import { defineHandler } from 'h3';
|
|
24
77
|
|
|
25
|
-
|
|
26
|
-
import template from '#analog/index';
|
|
78
|
+
const template = readFileSync(${JSON.stringify(templatePath)}, 'utf8');
|
|
27
79
|
|
|
28
|
-
export default
|
|
80
|
+
export default defineHandler(async (event) => {
|
|
81
|
+
event.res.headers.set('content-type', 'text/html; charset=utf-8');
|
|
29
82
|
return template;
|
|
30
83
|
});
|
|
31
84
|
`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* API middleware virtual module content.
|
|
88
|
+
*
|
|
89
|
+
* Intercepts requests matching the configured API prefix and either:
|
|
90
|
+
* - Uses event-bound internal forwarding for GET requests (except .xml routes)
|
|
91
|
+
* - Uses request proxying for all other methods to forward the full request
|
|
92
|
+
*
|
|
93
|
+
* h3 v2 idiomatic APIs used:
|
|
94
|
+
* - defineHandler (replaces defineEventHandler / eventHandler)
|
|
95
|
+
* - event.path (replaces event.node.req.url)
|
|
96
|
+
* - event.method (replaces event.node.req.method)
|
|
97
|
+
* - proxyRequest is retained internally because it preserves Nitro route
|
|
98
|
+
* matching for event-bound server requests during SSR/prerender
|
|
99
|
+
* - Object.fromEntries(event.req.headers.entries()) replaces direct event.node.req.headers access
|
|
100
|
+
*
|
|
101
|
+
* `fetchWithEvent` keeps the active event context while forwarding to a
|
|
102
|
+
* rewritten path, which avoids falling through to the HTML renderer when
|
|
103
|
+
* SSR code makes relative API requests.
|
|
104
|
+
*/
|
|
32
105
|
export const apiMiddleware = `
|
|
33
|
-
import {
|
|
34
|
-
import { useRuntimeConfig } from '
|
|
106
|
+
import { defineHandler, fetchWithEvent, proxyRequest } from 'h3';
|
|
107
|
+
import { useRuntimeConfig } from 'nitro/runtime-config';
|
|
35
108
|
|
|
36
|
-
export default
|
|
109
|
+
export default defineHandler(async (event) => {
|
|
37
110
|
const prefix = useRuntimeConfig().prefix;
|
|
38
111
|
const apiPrefix = \`\${prefix}/\${useRuntimeConfig().apiPrefix}\`;
|
|
39
112
|
|
|
40
|
-
if (event.
|
|
41
|
-
const reqUrl = event.
|
|
113
|
+
if (event.path?.startsWith(apiPrefix)) {
|
|
114
|
+
const reqUrl = event.path?.replace(apiPrefix, '');
|
|
42
115
|
|
|
43
116
|
if (
|
|
44
|
-
event.
|
|
117
|
+
event.method === 'GET' &&
|
|
45
118
|
// in the case of XML routes, we want to proxy the request so that nitro gets the correct headers
|
|
46
119
|
// and can render the XML correctly as a static asset
|
|
47
|
-
!event.
|
|
120
|
+
!event.path?.endsWith('.xml')
|
|
48
121
|
) {
|
|
49
|
-
return
|
|
122
|
+
return fetchWithEvent(event, reqUrl, {
|
|
123
|
+
headers: Object.fromEntries(event.req.headers.entries()),
|
|
124
|
+
});
|
|
50
125
|
}
|
|
51
126
|
|
|
52
|
-
return proxyRequest(event, reqUrl
|
|
53
|
-
// @ts-ignore
|
|
54
|
-
fetch: $fetch.native,
|
|
55
|
-
});
|
|
127
|
+
return proxyRequest(event, reqUrl);
|
|
56
128
|
}
|
|
57
129
|
});`;
|
|
58
130
|
//# sourceMappingURL=renderers.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderers.js","sourceRoot":"","sources":["../../../../../../packages/vite-plugin-nitro/src/lib/utils/renderers.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"renderers.js","sourceRoot":"","sources":["../../../../../../packages/vite-plugin-nitro/src/lib/utils/renderers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,YAAoB;IAC9C,OAAO;;;;;;;gCAOuB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA0CxD,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,YAAoB;IACjD,OAAO;;;;gCAIuB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC;;;;;;CAM3D,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;;;;;;IAwBzB,CAAC"}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { build, createDevServer, createNitro } from '
|
|
2
|
-
import
|
|
1
|
+
import { build, createDevServer, createNitro } from 'nitro/builder';
|
|
2
|
+
import * as vite from 'vite';
|
|
3
3
|
import { mergeConfig, normalizePath } from 'vite';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
+
import { relative, resolve } from 'node:path';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
7
6
|
import { existsSync, readFileSync } from 'node:fs';
|
|
8
7
|
import { buildServer } from './build-server.js';
|
|
9
8
|
import { buildSSRApp } from './build-ssr.js';
|
|
@@ -11,13 +10,115 @@ import { pageEndpointsPlugin } from './plugins/page-endpoints.js';
|
|
|
11
10
|
import { getPageHandlers } from './utils/get-page-handlers.js';
|
|
12
11
|
import { buildSitemap } from './build-sitemap.js';
|
|
13
12
|
import { devServerPlugin } from './plugins/dev-server-plugin.js';
|
|
13
|
+
import { toWebRequest, writeWebResponseToNode, } from './utils/node-web-bridge.js';
|
|
14
14
|
import { getMatchingContentFilesWithFrontMatter } from './utils/get-content-files.js';
|
|
15
15
|
import { ssrRenderer, clientRenderer, apiMiddleware, } from './utils/renderers.js';
|
|
16
|
-
const isWindows = platform() === 'win32';
|
|
17
|
-
const filePrefix = isWindows ? 'file:///' : '';
|
|
18
16
|
let clientOutputPath = '';
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
let rendererIndexEntry = '';
|
|
18
|
+
function createNitroMiddlewareHandler(handler) {
|
|
19
|
+
return {
|
|
20
|
+
route: '/**',
|
|
21
|
+
handler,
|
|
22
|
+
middleware: true,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createRollupBeforeHook(externalEntries) {
|
|
26
|
+
return (_nitro, bundlerConfig) => {
|
|
27
|
+
removeInvalidRollupCodeSplitting(_nitro, bundlerConfig);
|
|
28
|
+
if (externalEntries.length === 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const existing = bundlerConfig.external;
|
|
32
|
+
if (!existing) {
|
|
33
|
+
bundlerConfig.external = externalEntries;
|
|
34
|
+
}
|
|
35
|
+
else if (typeof existing === 'function') {
|
|
36
|
+
bundlerConfig.external = (source, importer, isResolved) => existing(source, importer, isResolved) ||
|
|
37
|
+
externalEntries.includes(source);
|
|
38
|
+
}
|
|
39
|
+
else if (Array.isArray(existing)) {
|
|
40
|
+
bundlerConfig.external = [...existing, ...externalEntries];
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
bundlerConfig.external = [existing, ...externalEntries];
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function appendNoExternals(noExternals, ...entries) {
|
|
48
|
+
if (!noExternals) {
|
|
49
|
+
return entries;
|
|
50
|
+
}
|
|
51
|
+
return Array.isArray(noExternals)
|
|
52
|
+
? [...noExternals, ...entries]
|
|
53
|
+
: noExternals;
|
|
54
|
+
}
|
|
55
|
+
function removeInvalidRollupCodeSplitting(_nitro, bundlerConfig) {
|
|
56
|
+
// Workaround for a Nitro v3 alpha bundler bug:
|
|
57
|
+
//
|
|
58
|
+
// Analog does not add `output.codeSplitting` to Nitro's Rollup config, but
|
|
59
|
+
// Nitro 3.0.1-alpha.2 builds an internal server bundler config that can
|
|
60
|
+
// still contain that key while running under Vite 8 / Rolldown. At runtime
|
|
61
|
+
// this surfaces as:
|
|
62
|
+
//
|
|
63
|
+
// Warning: Invalid output options (1 issue found)
|
|
64
|
+
// - For the "codeSplitting". Invalid key: Expected never but received "codeSplitting".
|
|
65
|
+
//
|
|
66
|
+
// That warning comes from Nitro's own bundler handoff, not from user config
|
|
67
|
+
// in Analog apps. We remove only the invalid `output.codeSplitting` field
|
|
68
|
+
// right before Nitro starts prerender/server builds.
|
|
69
|
+
//
|
|
70
|
+
// Why this is safe:
|
|
71
|
+
// - Analog is not relying on Nitro-side `output.codeSplitting`.
|
|
72
|
+
// - The warning path only rejects the option; removing it restores the
|
|
73
|
+
// default Nitro/Rollup behavior instead of changing any Analog semantics.
|
|
74
|
+
// - The hook is narrowly scoped to the final Nitro bundler config, so it
|
|
75
|
+
// does not affect the normal Vite client/SSR environment build config.
|
|
76
|
+
const output = bundlerConfig['output'];
|
|
77
|
+
if (!output || Array.isArray(output) || typeof output !== 'object') {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if ('codeSplitting' in output) {
|
|
81
|
+
delete output['codeSplitting'];
|
|
82
|
+
}
|
|
83
|
+
// Nitro's default server bundler config currently enables manual chunking for
|
|
84
|
+
// node_modules. Under Nitro v3 alpha + Rollup 4.59 this can crash during the
|
|
85
|
+
// prerender rebundle with "Cannot read properties of undefined (reading
|
|
86
|
+
// 'included')" while generating chunks. A single server bundle is acceptable
|
|
87
|
+
// here, so strip manualChunks until the upstream bug is fixed.
|
|
88
|
+
if ('manualChunks' in output) {
|
|
89
|
+
delete output['manualChunks'];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function resolveClientOutputPath(workspaceRoot, rootDir, configuredOutDir, ssrBuild) {
|
|
93
|
+
if (clientOutputPath) {
|
|
94
|
+
return clientOutputPath;
|
|
95
|
+
}
|
|
96
|
+
if (!ssrBuild) {
|
|
97
|
+
return resolve(workspaceRoot, rootDir, configuredOutDir || 'dist/client');
|
|
98
|
+
}
|
|
99
|
+
// SSR builds write server assets to dist/<app>/ssr, but the renderer template
|
|
100
|
+
// still needs the client index.html emitted to dist/<app>/client.
|
|
101
|
+
return resolve(workspaceRoot, 'dist', rootDir, 'client');
|
|
102
|
+
}
|
|
103
|
+
function toNitroSsrEntrypointSpecifier(ssrEntryPath) {
|
|
104
|
+
// Nitro rebundles the generated SSR entry. On Windows, a file URL preserves
|
|
105
|
+
// the importer location so relative "./assets/*" imports resolve correctly.
|
|
106
|
+
return process.platform === 'win32'
|
|
107
|
+
? pathToFileURL(ssrEntryPath).href
|
|
108
|
+
: normalizePath(ssrEntryPath);
|
|
109
|
+
}
|
|
110
|
+
function resolveBuiltSsrEntryPath(ssrOutDir) {
|
|
111
|
+
const candidatePaths = [
|
|
112
|
+
resolve(ssrOutDir, 'main.server.mjs'),
|
|
113
|
+
resolve(ssrOutDir, 'main.server.js'),
|
|
114
|
+
resolve(ssrOutDir, 'main.server'),
|
|
115
|
+
];
|
|
116
|
+
const ssrEntryPath = candidatePaths.find((candidatePath) => existsSync(candidatePath));
|
|
117
|
+
if (!ssrEntryPath) {
|
|
118
|
+
throw new Error(`Unable to locate the built SSR entry in "${ssrOutDir}". Expected one of: ${candidatePaths.join(', ')}`);
|
|
119
|
+
}
|
|
120
|
+
return ssrEntryPath;
|
|
121
|
+
}
|
|
21
122
|
export function nitro(options, nitroOptions) {
|
|
22
123
|
const workspaceRoot = options?.workspaceRoot ?? process.cwd();
|
|
23
124
|
const sourceRoot = options?.sourceRoot ?? 'src';
|
|
@@ -35,6 +136,7 @@ export function nitro(options, nitroOptions) {
|
|
|
35
136
|
let nitroConfig;
|
|
36
137
|
let environmentBuild = false;
|
|
37
138
|
let hasAPIDir = false;
|
|
139
|
+
const rollupExternalEntries = [];
|
|
38
140
|
const routeSitemaps = {};
|
|
39
141
|
const routeSourceFiles = {};
|
|
40
142
|
let rootDir = workspaceRoot;
|
|
@@ -57,7 +159,8 @@ export function nitro(options, nitroOptions) {
|
|
|
57
159
|
rootDir = relative(workspaceRoot, config.root || '.') || '.';
|
|
58
160
|
hasAPIDir = existsSync(resolve(workspaceRoot, rootDir, `${sourceRoot}/server/routes/${options?.apiPrefix || 'api'}`));
|
|
59
161
|
const buildPreset = process.env['BUILD_PRESET'] ??
|
|
60
|
-
nitroOptions?.preset
|
|
162
|
+
nitroOptions?.preset ??
|
|
163
|
+
(process.env['VERCEL'] ? 'vercel' : undefined);
|
|
61
164
|
const pageHandlers = getPageHandlers({
|
|
62
165
|
workspaceRoot,
|
|
63
166
|
sourceRoot,
|
|
@@ -65,12 +168,14 @@ export function nitro(options, nitroOptions) {
|
|
|
65
168
|
additionalPagesDirs: options?.additionalPagesDirs,
|
|
66
169
|
hasAPIDir,
|
|
67
170
|
});
|
|
171
|
+
const resolvedClientOutputPath = resolveClientOutputPath(workspaceRoot, rootDir, config.build?.outDir, ssrBuild);
|
|
172
|
+
rendererIndexEntry = normalizePath(resolve(resolvedClientOutputPath, 'index.html'));
|
|
68
173
|
nitroConfig = {
|
|
69
|
-
rootDir,
|
|
174
|
+
rootDir: normalizePath(rootDir),
|
|
70
175
|
preset: buildPreset,
|
|
71
176
|
compatibilityDate: '2024-11-19',
|
|
72
177
|
logLevel: nitroOptions?.logLevel || 0,
|
|
73
|
-
|
|
178
|
+
serverDir: normalizePath(`${sourceRoot}/server`),
|
|
74
179
|
scanDirs: [
|
|
75
180
|
normalizePath(`${rootDir}/${sourceRoot}/server`),
|
|
76
181
|
...(options?.additionalAPIDirs || []).map((dir) => normalizePath(`${workspaceRoot}${dir}`)),
|
|
@@ -87,10 +192,15 @@ export function nitro(options, nitroOptions) {
|
|
|
87
192
|
apiPrefix: apiPrefix.substring(1),
|
|
88
193
|
prefix,
|
|
89
194
|
},
|
|
90
|
-
//
|
|
195
|
+
// Analog provides its own renderer handler; prevent Nitro v3 from
|
|
196
|
+
// auto-detecting index.html in rootDir and adding a conflicting one.
|
|
197
|
+
renderer: false,
|
|
91
198
|
imports: {
|
|
92
199
|
autoImport: false,
|
|
93
200
|
},
|
|
201
|
+
hooks: {
|
|
202
|
+
'rollup:before': createRollupBeforeHook(rollupExternalEntries),
|
|
203
|
+
},
|
|
94
204
|
rollupConfig: {
|
|
95
205
|
onwarn(warning) {
|
|
96
206
|
if (warning.message.includes('empty chunk') &&
|
|
@@ -104,12 +214,7 @@ export function nitro(options, nitroOptions) {
|
|
|
104
214
|
...(hasAPIDir
|
|
105
215
|
? []
|
|
106
216
|
: useAPIMiddleware
|
|
107
|
-
? [
|
|
108
|
-
{
|
|
109
|
-
handler: '#ANALOG_API_MIDDLEWARE',
|
|
110
|
-
middleware: true,
|
|
111
|
-
},
|
|
112
|
-
]
|
|
217
|
+
? [createNitroMiddlewareHandler('#ANALOG_API_MIDDLEWARE')]
|
|
113
218
|
: []),
|
|
114
219
|
...pageHandlers,
|
|
115
220
|
],
|
|
@@ -123,13 +228,13 @@ export function nitro(options, nitroOptions) {
|
|
|
123
228
|
},
|
|
124
229
|
},
|
|
125
230
|
virtual: {
|
|
126
|
-
'#ANALOG_SSR_RENDERER': ssrRenderer,
|
|
127
|
-
'#ANALOG_CLIENT_RENDERER': clientRenderer,
|
|
231
|
+
'#ANALOG_SSR_RENDERER': ssrRenderer(rendererIndexEntry),
|
|
232
|
+
'#ANALOG_CLIENT_RENDERER': clientRenderer(rendererIndexEntry),
|
|
128
233
|
...(hasAPIDir ? {} : { '#ANALOG_API_MIDDLEWARE': apiMiddleware }),
|
|
129
234
|
},
|
|
130
235
|
};
|
|
131
236
|
if (isVercelPreset(buildPreset)) {
|
|
132
|
-
nitroConfig = withVercelOutputAPI(nitroConfig, workspaceRoot);
|
|
237
|
+
nitroConfig = withVercelOutputAPI(nitroConfig, workspaceRoot, buildPreset);
|
|
133
238
|
}
|
|
134
239
|
if (isCloudflarePreset(buildPreset)) {
|
|
135
240
|
nitroConfig = withCloudflareOutput(nitroConfig);
|
|
@@ -144,17 +249,30 @@ export function nitro(options, nitroOptions) {
|
|
|
144
249
|
}
|
|
145
250
|
if (!ssrBuild && !isTest) {
|
|
146
251
|
// store the client output path for the SSR build config
|
|
147
|
-
clientOutputPath =
|
|
252
|
+
clientOutputPath = resolvedClientOutputPath;
|
|
148
253
|
}
|
|
149
|
-
const indexEntry = normalizePath(resolve(clientOutputPath, 'index.html'));
|
|
150
|
-
nitroConfig.alias = {
|
|
151
|
-
'#analog/index': indexEntry,
|
|
152
|
-
};
|
|
153
254
|
if (isBuild) {
|
|
154
|
-
nitroConfig.publicAssets = [
|
|
155
|
-
|
|
255
|
+
nitroConfig.publicAssets = [
|
|
256
|
+
{ dir: normalizePath(clientOutputPath), maxAge: 0 },
|
|
257
|
+
];
|
|
258
|
+
// In Nitro v3, renderer.entry is resolved via resolveModulePath()
|
|
259
|
+
// during options normalization, which requires a real filesystem path.
|
|
260
|
+
// Virtual modules (prefixed with #) can't survive this resolution.
|
|
261
|
+
// Instead, we add the renderer as a catch-all handler directly —
|
|
262
|
+
// this is functionally equivalent to what Nitro does internally
|
|
263
|
+
// (it converts renderer.entry into a { route: '/**', lazy: true }
|
|
264
|
+
// handler), but avoids the filesystem resolution step.
|
|
265
|
+
const rendererHandler = options?.ssr
|
|
156
266
|
? '#ANALOG_SSR_RENDERER'
|
|
157
267
|
: '#ANALOG_CLIENT_RENDERER';
|
|
268
|
+
nitroConfig.handlers = [
|
|
269
|
+
...(nitroConfig.handlers || []),
|
|
270
|
+
{
|
|
271
|
+
handler: rendererHandler,
|
|
272
|
+
route: '/**',
|
|
273
|
+
lazy: true,
|
|
274
|
+
},
|
|
275
|
+
];
|
|
158
276
|
if (isEmptyPrerenderRoutes(options)) {
|
|
159
277
|
nitroConfig.prerender = {};
|
|
160
278
|
nitroConfig.prerender.routes = ['/'];
|
|
@@ -220,30 +338,26 @@ export function nitro(options, nitroOptions) {
|
|
|
220
338
|
}, []);
|
|
221
339
|
}
|
|
222
340
|
if (ssrBuild) {
|
|
223
|
-
if (
|
|
224
|
-
nitroConfig.
|
|
225
|
-
inline: ['std-env'],
|
|
226
|
-
};
|
|
341
|
+
if (process.platform === 'win32') {
|
|
342
|
+
nitroConfig.noExternals = appendNoExternals(nitroConfig.noExternals, 'std-env');
|
|
227
343
|
}
|
|
344
|
+
rollupExternalEntries.push('rxjs', 'node-fetch-native/dist/polyfill');
|
|
228
345
|
nitroConfig = {
|
|
229
346
|
...nitroConfig,
|
|
230
|
-
externals: {
|
|
231
|
-
...nitroConfig.externals,
|
|
232
|
-
external: ['rxjs', 'node-fetch-native/dist/polyfill'],
|
|
233
|
-
},
|
|
234
347
|
moduleSideEffects: ['zone.js/node', 'zone.js/fesm2015/zone-node'],
|
|
235
348
|
handlers: [
|
|
236
349
|
...(hasAPIDir
|
|
237
350
|
? []
|
|
238
351
|
: useAPIMiddleware
|
|
239
|
-
? [
|
|
240
|
-
{
|
|
241
|
-
handler: '#ANALOG_API_MIDDLEWARE',
|
|
242
|
-
middleware: true,
|
|
243
|
-
},
|
|
244
|
-
]
|
|
352
|
+
? [createNitroMiddlewareHandler('#ANALOG_API_MIDDLEWARE')]
|
|
245
353
|
: []),
|
|
246
354
|
...pageHandlers,
|
|
355
|
+
// Preserve the renderer catch-all handler added above
|
|
356
|
+
{
|
|
357
|
+
handler: rendererHandler,
|
|
358
|
+
route: '/**',
|
|
359
|
+
lazy: true,
|
|
360
|
+
},
|
|
247
361
|
],
|
|
248
362
|
};
|
|
249
363
|
}
|
|
@@ -260,7 +374,7 @@ export function nitro(options, nitroOptions) {
|
|
|
260
374
|
ssr: {
|
|
261
375
|
build: {
|
|
262
376
|
ssr: true,
|
|
263
|
-
rollupOptions: {
|
|
377
|
+
[vite.rolldownVersion ? 'rolldownOptions' : 'rollupOptions']: {
|
|
264
378
|
input: options?.entryServer ||
|
|
265
379
|
resolve(workspaceRoot, rootDir, `${sourceRoot}/main.server.ts`),
|
|
266
380
|
},
|
|
@@ -278,17 +392,16 @@ export function nitro(options, nitroOptions) {
|
|
|
278
392
|
builds.push(builder.build(builder.environments['ssr']));
|
|
279
393
|
}
|
|
280
394
|
await Promise.all(builds);
|
|
281
|
-
|
|
282
|
-
resolve(workspaceRoot, 'dist', rootDir, `ssr`)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
395
|
+
const ssrOutDir = options?.ssrBuildDir ||
|
|
396
|
+
resolve(workspaceRoot, 'dist', rootDir, `ssr`);
|
|
397
|
+
if (options?.ssr || nitroConfig.prerender?.routes?.length) {
|
|
398
|
+
const ssrEntryPath = resolveBuiltSsrEntryPath(ssrOutDir);
|
|
399
|
+
const ssrEntry = toNitroSsrEntrypointSpecifier(ssrEntryPath);
|
|
400
|
+
nitroConfig.alias = {
|
|
401
|
+
...nitroConfig.alias,
|
|
402
|
+
'#analog/ssr': ssrEntry,
|
|
403
|
+
};
|
|
286
404
|
}
|
|
287
|
-
const ssrEntry = normalizePath(filePrefix + ssrEntryPath);
|
|
288
|
-
nitroConfig.alias = {
|
|
289
|
-
...nitroConfig.alias,
|
|
290
|
-
'#analog/ssr': ssrEntry,
|
|
291
|
-
};
|
|
292
405
|
await buildServer(options, nitroConfig, routeSourceFiles);
|
|
293
406
|
if (nitroConfig.prerender?.routes?.length &&
|
|
294
407
|
options?.prerender?.sitemap) {
|
|
@@ -309,22 +422,33 @@ export function nitro(options, nitroOptions) {
|
|
|
309
422
|
if (isServe && !isTest) {
|
|
310
423
|
const nitro = await createNitro({
|
|
311
424
|
dev: true,
|
|
425
|
+
// Nitro's Vite builder now rejects `build()` in dev mode, but Analog's
|
|
426
|
+
// dev integration still relies on the builder-driven reload hooks.
|
|
427
|
+
// Force the server worker onto Rollup for this dev-only path.
|
|
428
|
+
builder: 'rollup',
|
|
312
429
|
...nitroConfig,
|
|
313
430
|
});
|
|
314
431
|
const server = createDevServer(nitro);
|
|
315
432
|
await build(nitro);
|
|
316
|
-
const apiHandler =
|
|
433
|
+
const apiHandler = async (req, res) => {
|
|
434
|
+
// Nitro v3's dev server is fetch-first, so adapt Vite's Node
|
|
435
|
+
// request once and let Nitro respond with a standard Web Response.
|
|
436
|
+
const response = await server.fetch(toWebRequest(req));
|
|
437
|
+
await writeWebResponseToNode(res, response);
|
|
438
|
+
};
|
|
317
439
|
if (hasAPIDir) {
|
|
318
440
|
viteServer.middlewares.use((req, res, next) => {
|
|
319
441
|
if (req.url?.startsWith(`${prefix}${apiPrefix}`)) {
|
|
320
|
-
apiHandler(req, res);
|
|
442
|
+
void apiHandler(req, res).catch((error) => next(error));
|
|
321
443
|
return;
|
|
322
444
|
}
|
|
323
445
|
next();
|
|
324
446
|
});
|
|
325
447
|
}
|
|
326
448
|
else {
|
|
327
|
-
viteServer.middlewares.use(apiPrefix,
|
|
449
|
+
viteServer.middlewares.use(apiPrefix, (req, res, next) => {
|
|
450
|
+
void apiHandler(req, res).catch((error) => next(error));
|
|
451
|
+
});
|
|
328
452
|
}
|
|
329
453
|
viteServer.httpServer?.once('listening', () => {
|
|
330
454
|
process.env['ANALOG_HOST'] = !viteServer.config.server.host
|
|
@@ -358,17 +482,16 @@ export function nitro(options, nitroOptions) {
|
|
|
358
482
|
// sitemap needs to be built after all directories are built
|
|
359
483
|
await buildSitemap(config, options.prerender.sitemap, nitroConfig.prerender.routes, clientOutputPath, routeSitemaps);
|
|
360
484
|
}
|
|
361
|
-
|
|
362
|
-
resolve(workspaceRoot, 'dist', rootDir, `ssr`)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
485
|
+
const closeBundleSsrOutDir = options?.ssrBuildDir ||
|
|
486
|
+
resolve(workspaceRoot, 'dist', rootDir, `ssr`);
|
|
487
|
+
if (options?.ssr || nitroConfig.prerender?.routes?.length) {
|
|
488
|
+
const ssrEntryPath = resolveBuiltSsrEntryPath(closeBundleSsrOutDir);
|
|
489
|
+
const ssrEntry = toNitroSsrEntrypointSpecifier(ssrEntryPath);
|
|
490
|
+
nitroConfig.alias = {
|
|
491
|
+
...nitroConfig.alias,
|
|
492
|
+
'#analog/ssr': ssrEntry,
|
|
493
|
+
};
|
|
366
494
|
}
|
|
367
|
-
const ssrEntry = normalizePath(filePrefix + ssrEntryPath);
|
|
368
|
-
nitroConfig.alias = {
|
|
369
|
-
...nitroConfig.alias,
|
|
370
|
-
'#analog/ssr': ssrEntry,
|
|
371
|
-
};
|
|
372
495
|
await buildServer(options, nitroConfig, routeSourceFiles);
|
|
373
496
|
console.log(`\n\nThe '@analogjs/platform' server has been successfully built.`);
|
|
374
497
|
}
|
|
@@ -397,8 +520,24 @@ function isArrayWithElements(arr) {
|
|
|
397
520
|
}
|
|
398
521
|
const isVercelPreset = (buildPreset) => process.env['VERCEL'] ||
|
|
399
522
|
(buildPreset && buildPreset.toLowerCase().includes('vercel'));
|
|
400
|
-
const withVercelOutputAPI = (nitroConfig, workspaceRoot) => ({
|
|
523
|
+
const withVercelOutputAPI = (nitroConfig, workspaceRoot, buildPreset) => ({
|
|
401
524
|
...nitroConfig,
|
|
525
|
+
preset: nitroConfig?.preset ??
|
|
526
|
+
(buildPreset?.toLowerCase().includes('vercel-edge')
|
|
527
|
+
? 'vercel-edge'
|
|
528
|
+
: 'vercel'),
|
|
529
|
+
vercel: {
|
|
530
|
+
...nitroConfig?.vercel,
|
|
531
|
+
...(buildPreset?.toLowerCase().includes('vercel-edge')
|
|
532
|
+
? {}
|
|
533
|
+
: {
|
|
534
|
+
entryFormat: nitroConfig?.vercel?.entryFormat ?? 'node',
|
|
535
|
+
functions: {
|
|
536
|
+
runtime: nitroConfig?.vercel?.functions?.runtime ?? 'nodejs24.x',
|
|
537
|
+
...nitroConfig?.vercel?.functions,
|
|
538
|
+
},
|
|
539
|
+
}),
|
|
540
|
+
},
|
|
402
541
|
output: {
|
|
403
542
|
...nitroConfig?.output,
|
|
404
543
|
dir: normalizePath(resolve(workspaceRoot, '.vercel', 'output')),
|