@analogjs/vite-plugin-nitro 3.0.0-alpha.8 → 3.0.0-alpha.9

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.
Files changed (35) hide show
  1. package/package.json +3 -6
  2. package/src/index.d.ts +1 -1
  3. package/src/index.js +6 -2
  4. package/src/index.js.map +1 -1
  5. package/src/lib/build-server.js +97 -140
  6. package/src/lib/build-server.js.map +1 -1
  7. package/src/lib/build-sitemap.js +48 -60
  8. package/src/lib/build-sitemap.js.map +1 -1
  9. package/src/lib/build-ssr.js +16 -19
  10. package/src/lib/build-ssr.js.map +1 -1
  11. package/src/lib/hooks/post-rendering-hook.js +10 -6
  12. package/src/lib/hooks/post-rendering-hook.js.map +1 -1
  13. package/src/lib/plugins/dev-server-plugin.js +91 -101
  14. package/src/lib/plugins/dev-server-plugin.js.map +1 -1
  15. package/src/lib/plugins/page-endpoints.js +26 -46
  16. package/src/lib/plugins/page-endpoints.js.map +1 -1
  17. package/src/lib/utils/get-content-files.js +88 -97
  18. package/src/lib/utils/get-content-files.js.map +1 -1
  19. package/src/lib/utils/get-page-handlers.js +70 -84
  20. package/src/lib/utils/get-page-handlers.js.map +1 -1
  21. package/src/lib/utils/node-web-bridge.js +34 -45
  22. package/src/lib/utils/node-web-bridge.js.map +1 -1
  23. package/src/lib/utils/register-dev-middleware.js +41 -47
  24. package/src/lib/utils/register-dev-middleware.js.map +1 -1
  25. package/src/lib/utils/renderers.js +55 -51
  26. package/src/lib/utils/renderers.js.map +1 -1
  27. package/src/lib/utils/rolldown.js +9 -5
  28. package/src/lib/utils/rolldown.js.map +1 -1
  29. package/src/lib/vite-plugin-nitro.js +442 -701
  30. package/src/lib/vite-plugin-nitro.js.map +1 -1
  31. package/README.md +0 -125
  32. package/src/lib/options.js +0 -2
  33. package/src/lib/options.js.map +0 -1
  34. package/src/lib/utils/load-esm.js +0 -23
  35. package/src/lib/utils/load-esm.js.map +0 -1
@@ -1,730 +1,471 @@
1
- import { build, createDevServer, createNitro } from 'nitro/builder';
2
- import { mergeConfig, normalizePath } from 'vite';
3
- import { relative, resolve } from 'node:path';
4
- import { pathToFileURL } from 'node:url';
5
- import { existsSync, readFileSync } from 'node:fs';
6
- import { buildServer, isVercelPreset } from './build-server.js';
7
- import { buildSSRApp } from './build-ssr.js';
8
- import { pageEndpointsPlugin } from './plugins/page-endpoints.js';
9
- import { getPageHandlers } from './utils/get-page-handlers.js';
10
- import { buildSitemap } from './build-sitemap.js';
11
- import { devServerPlugin } from './plugins/dev-server-plugin.js';
12
- import { toWebRequest, writeWebResponseToNode, } from './utils/node-web-bridge.js';
13
- import { getMatchingContentFilesWithFrontMatter } from './utils/get-content-files.js';
14
- import { ssrRenderer, clientRenderer, apiMiddleware, } from './utils/renderers.js';
15
- import { getBundleOptionsKey, isRolldown } from './utils/rolldown.js';
1
+ import { buildServer, isVercelPreset } from "./build-server.js";
2
+ import { getBundleOptionsKey, isRolldown } from "./utils/rolldown.js";
3
+ import { buildSSRApp } from "./build-ssr.js";
4
+ import { apiMiddleware, clientRenderer, ssrRenderer } from "./utils/renderers.js";
5
+ import { pageEndpointsPlugin } from "./plugins/page-endpoints.js";
6
+ import { getPageHandlers } from "./utils/get-page-handlers.js";
7
+ import { buildSitemap } from "./build-sitemap.js";
8
+ import { toWebRequest, writeWebResponseToNode } from "./utils/node-web-bridge.js";
9
+ import { devServerPlugin } from "./plugins/dev-server-plugin.js";
10
+ import { getMatchingContentFilesWithFrontMatter } from "./utils/get-content-files.js";
11
+ import { build, createDevServer, createNitro } from "nitro/builder";
12
+ import { mergeConfig, normalizePath } from "vite";
13
+ import { relative, resolve } from "node:path";
14
+ import { pathToFileURL } from "node:url";
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ //#region packages/vite-plugin-nitro/src/lib/vite-plugin-nitro.ts
16
17
  function createNitroMiddlewareHandler(handler) {
17
- return {
18
- route: '/**',
19
- handler,
20
- middleware: true,
21
- };
18
+ return {
19
+ route: "/**",
20
+ handler,
21
+ middleware: true
22
+ };
22
23
  }
23
24
  /**
24
- * Creates a `rollup:before` hook that marks specified packages as external
25
- * in Nitro's bundler config (applied to both the server build and the
26
- * prerender build).
27
- *
28
- * ## Subpath matching (Rolldown compatibility)
29
- *
30
- * When `bundlerConfig.external` is an **array**, Rollup automatically
31
- * prefix-matches entries — `'rxjs'` in the array will also externalise
32
- * `'rxjs/operators'`, `'rxjs/internal/Observable'`, etc.
33
- *
34
- * Rolldown (the default bundler in Nitro v3) does **not** do this. It
35
- * treats array entries as exact strings. To keep behaviour consistent
36
- * across both bundlers, the **function** branch already needed explicit
37
- * subpath matching. We now use the same `isExternal` helper for all
38
- * branches so that `'rxjs'` reliably matches `'rxjs/operators'`
39
- * regardless of whether the existing `external` value is a function,
40
- * array, or absent.
41
- *
42
- * Without this, the Nitro prerender build fails on Windows CI with:
43
- *
44
- * [RESOLVE_ERROR] Could not resolve 'rxjs/operators'
45
- */
25
+ * Creates a `rollup:before` hook that marks specified packages as external
26
+ * in Nitro's bundler config (applied to both the server build and the
27
+ * prerender build).
28
+ *
29
+ * ## Subpath matching (Rolldown compatibility)
30
+ *
31
+ * When `bundlerConfig.external` is an **array**, Rollup automatically
32
+ * prefix-matches entries — `'rxjs'` in the array will also externalise
33
+ * `'rxjs/operators'`, `'rxjs/internal/Observable'`, etc.
34
+ *
35
+ * Rolldown (the default bundler in Nitro v3) does **not** do this. It
36
+ * treats array entries as exact strings. To keep behaviour consistent
37
+ * across both bundlers, the **function** branch already needed explicit
38
+ * subpath matching. We now use the same `isExternal` helper for all
39
+ * branches so that `'rxjs'` reliably matches `'rxjs/operators'`
40
+ * regardless of whether the existing `external` value is a function,
41
+ * array, or absent.
42
+ *
43
+ * Without this, the Nitro prerender build fails on Windows CI with:
44
+ *
45
+ * [RESOLVE_ERROR] Could not resolve 'rxjs/operators'
46
+ */
46
47
  function createRollupBeforeHook(externalEntries) {
47
- const isExternal = (source) => externalEntries.some((entry) => source === entry || source.startsWith(entry + '/'));
48
- return (_nitro, bundlerConfig) => {
49
- sanitizeNitroBundlerConfig(_nitro, bundlerConfig);
50
- if (externalEntries.length === 0) {
51
- return;
52
- }
53
- const existing = bundlerConfig.external;
54
- if (!existing) {
55
- bundlerConfig.external = externalEntries;
56
- }
57
- else if (typeof existing === 'function') {
58
- bundlerConfig.external = (source, importer, isResolved) => existing(source, importer, isResolved) || isExternal(source);
59
- }
60
- else if (Array.isArray(existing)) {
61
- bundlerConfig.external = [...existing, ...externalEntries];
62
- }
63
- else {
64
- bundlerConfig.external = [existing, ...externalEntries];
65
- }
66
- };
48
+ const isExternal = (source) => externalEntries.some((entry) => source === entry || source.startsWith(entry + "/"));
49
+ return (_nitro, bundlerConfig) => {
50
+ sanitizeNitroBundlerConfig(_nitro, bundlerConfig);
51
+ if (externalEntries.length === 0) return;
52
+ const existing = bundlerConfig.external;
53
+ if (!existing) bundlerConfig.external = externalEntries;
54
+ else if (typeof existing === "function") bundlerConfig.external = (source, importer, isResolved) => existing(source, importer, isResolved) || isExternal(source);
55
+ else if (Array.isArray(existing)) bundlerConfig.external = [...existing, ...externalEntries];
56
+ else bundlerConfig.external = [existing, ...externalEntries];
57
+ };
67
58
  }
68
59
  function appendNoExternals(noExternals, ...entries) {
69
- if (!noExternals) {
70
- return entries;
71
- }
72
- return Array.isArray(noExternals)
73
- ? [...noExternals, ...entries]
74
- : noExternals;
60
+ if (!noExternals) return entries;
61
+ return Array.isArray(noExternals) ? [...noExternals, ...entries] : noExternals;
75
62
  }
76
63
  /**
77
- * Patches Nitro's internal Rollup/Rolldown bundler config to work around
78
- * incompatibilities in the Nitro v3 alpha series.
79
- *
80
- * Called from the `rollup:before` hook, this function runs against the *final*
81
- * bundler config that Nitro assembles for its server/prerender builds — it
82
- * does NOT touch the normal Vite client or SSR environment configs.
83
- *
84
- * Each workaround is narrowly scoped and safe to remove once the corresponding
85
- * upstream Nitro issue is resolved.
86
- */
64
+ * Patches Nitro's internal Rollup/Rolldown bundler config to work around
65
+ * incompatibilities in the Nitro v3 alpha series.
66
+ *
67
+ * Called from the `rollup:before` hook, this function runs against the *final*
68
+ * bundler config that Nitro assembles for its server/prerender builds — it
69
+ * does NOT touch the normal Vite client or SSR environment configs.
70
+ *
71
+ * Each workaround is narrowly scoped and safe to remove once the corresponding
72
+ * upstream Nitro issue is resolved.
73
+ */
87
74
  function sanitizeNitroBundlerConfig(_nitro, bundlerConfig) {
88
- const output = bundlerConfig['output'];
89
- if (!output || Array.isArray(output) || typeof output !== 'object') {
90
- return;
91
- }
92
- // ── 1. Remove invalid `output.codeSplitting` ────────────────────────
93
- //
94
- // Nitro 3.0.1-alpha.2 adds `output.codeSplitting` to its internal bundler
95
- // config, but Rolldown rejects it as an unknown key:
96
- //
97
- // Warning: Invalid output options (1 issue found)
98
- // - For the "codeSplitting". Invalid key: Expected never but received "codeSplitting".
99
- //
100
- // Analog never sets this option. Removing it restores default bundler
101
- // behavior without changing any Analog semantics.
102
- if ('codeSplitting' in output) {
103
- delete output['codeSplitting'];
104
- }
105
- // ── 2. Remove invalid `output.manualChunks` ─────────────────────────
106
- //
107
- // Nitro's default config enables manual chunking for node_modules. Under
108
- // Nitro v3 alpha + Rollup 4.59 this crashes during the prerender rebundle:
109
- //
110
- // Cannot read properties of undefined (reading 'included')
111
- //
112
- // A single server bundle is acceptable for Analog's use case, so we strip
113
- // `manualChunks` until the upstream bug is fixed.
114
- if ('manualChunks' in output) {
115
- delete output['manualChunks'];
116
- }
117
- // ── 3. Escape route params in `output.chunkFileNames` ───────────────
118
- //
119
- // Nitro's `getChunkName()` derives chunk filenames from route patterns,
120
- // using its internal `routeToFsPath()` helper to convert route params
121
- // (`:productId` → `[productId]`) and catch-alls (`**` → `[...]`).
122
- //
123
- // Rollup/Rolldown interprets *any* `[token]` in the string returned by a
124
- // `chunkFileNames` function as a placeholder. Only a handful are valid —
125
- // `[name]`, `[hash]`, `[format]`, `[ext]` — so route-derived tokens like
126
- // `[productId]` or `[...]` trigger a build error:
127
- //
128
- // "[productId]" is not a valid placeholder in the "output.chunkFileNames" pattern.
129
- //
130
- // We wrap the original function to replace non-standard `[token]` patterns
131
- // with `_token_`, preserving the intended filename while avoiding the
132
- // placeholder validation error.
133
- //
134
- // Example: `_routes/products/[productId].mjs` → `_routes/products/_productId_.mjs`
135
- const VALID_ROLLUP_PLACEHOLDER = /^\[(?:name|hash|format|ext)\]$/;
136
- const chunkFileNames = output['chunkFileNames'];
137
- if (typeof chunkFileNames === 'function') {
138
- const originalFn = chunkFileNames;
139
- output['chunkFileNames'] = (...args) => {
140
- const result = originalFn(...args);
141
- if (typeof result !== 'string')
142
- return result;
143
- return result.replace(/\[[^\]]+\]/g, (match) => VALID_ROLLUP_PLACEHOLDER.test(match)
144
- ? match
145
- : `_${match.slice(1, -1)}_`);
146
- };
147
- }
75
+ const output = bundlerConfig["output"];
76
+ if (!output || Array.isArray(output) || typeof output !== "object") return;
77
+ if ("codeSplitting" in output) delete output["codeSplitting"];
78
+ if ("manualChunks" in output) delete output["manualChunks"];
79
+ const VALID_ROLLUP_PLACEHOLDER = /^\[(?:name|hash|format|ext)\]$/;
80
+ const chunkFileNames = output["chunkFileNames"];
81
+ if (typeof chunkFileNames === "function") {
82
+ const originalFn = chunkFileNames;
83
+ output["chunkFileNames"] = (...args) => {
84
+ const result = originalFn(...args);
85
+ if (typeof result !== "string") return result;
86
+ return result.replace(/\[[^\]]+\]/g, (match) => VALID_ROLLUP_PLACEHOLDER.test(match) ? match : `_${match.slice(1, -1)}_`);
87
+ };
88
+ }
148
89
  }
149
90
  function resolveClientOutputPath(cachedPath, workspaceRoot, rootDir, configuredOutDir, ssrBuild) {
150
- if (cachedPath) {
151
- return cachedPath;
152
- }
153
- if (!ssrBuild) {
154
- return resolve(workspaceRoot, rootDir, configuredOutDir || 'dist/client');
155
- }
156
- // SSR builds write server assets to dist/<app>/ssr, but the renderer template
157
- // still needs the client index.html emitted to dist/<app>/client.
158
- return resolve(workspaceRoot, 'dist', rootDir, 'client');
91
+ if (cachedPath) return cachedPath;
92
+ if (!ssrBuild) return resolve(workspaceRoot, rootDir, configuredOutDir || "dist/client");
93
+ return resolve(workspaceRoot, "dist", rootDir, "client");
159
94
  }
160
95
  /**
161
- * Converts the built SSR entry path into a specifier that Nitro's bundler
162
- * can resolve, including all relative `./assets/*` chunk imports inside
163
- * the entry.
164
- *
165
- * The returned path **must** be an absolute filesystem path with forward
166
- * slashes (e.g. `D:/a/analog/dist/apps/blog-app/ssr/main.server.js`).
167
- * This lets Rollup/Rolldown determine the entry's directory and resolve
168
- * sibling chunk imports like `./assets/core-DTazUigR.js` correctly.
169
- *
170
- * ## Why not pathToFileURL() on Windows?
171
- *
172
- * Earlier versions converted the path to a `file:///D:/a/...` URL on
173
- * Windows, which worked with Nitro v2 + Rollup. Nitro v3 switched its
174
- * default bundler to Rolldown, and Rolldown does **not** extract the
175
- * importer directory from `file://` URLs. This caused every relative
176
- * import inside the SSR entry to fail during the prerender build:
177
- *
178
- * [RESOLVE_ERROR] Could not resolve './assets/core-DTazUigR.js'
179
- * in ../../dist/apps/blog-app/ssr/main.server.js
180
- *
181
- * `normalizePath()` (from Vite) simply converts backslashes to forward
182
- * slashes, which both Rollup and Rolldown handle correctly on all
183
- * platforms.
184
- */
96
+ * Converts the built SSR entry path into a specifier that Nitro's bundler
97
+ * can resolve, including all relative `./assets/*` chunk imports inside
98
+ * the entry.
99
+ *
100
+ * The returned path **must** be an absolute filesystem path with forward
101
+ * slashes (e.g. `D:/a/analog/dist/apps/blog-app/ssr/main.server.js`).
102
+ * This lets Rollup/Rolldown determine the entry's directory and resolve
103
+ * sibling chunk imports like `./assets/core-DTazUigR.js` correctly.
104
+ *
105
+ * ## Why not pathToFileURL() on Windows?
106
+ *
107
+ * Earlier versions converted the path to a `file:///D:/a/...` URL on
108
+ * Windows, which worked with Nitro v2 + Rollup. Nitro v3 switched its
109
+ * default bundler to Rolldown, and Rolldown does **not** extract the
110
+ * importer directory from `file://` URLs. This caused every relative
111
+ * import inside the SSR entry to fail during the prerender build:
112
+ *
113
+ * [RESOLVE_ERROR] Could not resolve './assets/core-DTazUigR.js'
114
+ * in ../../dist/apps/blog-app/ssr/main.server.js
115
+ *
116
+ * `normalizePath()` (from Vite) simply converts backslashes to forward
117
+ * slashes, which both Rollup and Rolldown handle correctly on all
118
+ * platforms.
119
+ */
185
120
  function toNitroSsrEntrypointSpecifier(ssrEntryPath) {
186
- return normalizePath(ssrEntryPath);
121
+ return normalizePath(ssrEntryPath);
187
122
  }
188
123
  function applySsrEntryAlias(nitroConfig, options, workspaceRoot, rootDir) {
189
- const ssrOutDir = options?.ssrBuildDir || resolve(workspaceRoot, 'dist', rootDir, 'ssr');
190
- if (options?.ssr || nitroConfig.prerender?.routes?.length) {
191
- const ssrEntryPath = resolveBuiltSsrEntryPath(ssrOutDir);
192
- const ssrEntry = toNitroSsrEntrypointSpecifier(ssrEntryPath);
193
- nitroConfig.alias = {
194
- ...nitroConfig.alias,
195
- '#analog/ssr': ssrEntry,
196
- };
197
- }
124
+ const ssrOutDir = options?.ssrBuildDir || resolve(workspaceRoot, "dist", rootDir, "ssr");
125
+ if (options?.ssr || nitroConfig.prerender?.routes?.length) {
126
+ const ssrEntry = toNitroSsrEntrypointSpecifier(resolveBuiltSsrEntryPath(ssrOutDir));
127
+ nitroConfig.alias = {
128
+ ...nitroConfig.alias,
129
+ "#analog/ssr": ssrEntry
130
+ };
131
+ }
198
132
  }
199
133
  function resolveBuiltSsrEntryPath(ssrOutDir) {
200
- const candidatePaths = [
201
- resolve(ssrOutDir, 'main.server.mjs'),
202
- resolve(ssrOutDir, 'main.server.js'),
203
- resolve(ssrOutDir, 'main.server'),
204
- ];
205
- const ssrEntryPath = candidatePaths.find((candidatePath) => existsSync(candidatePath));
206
- if (!ssrEntryPath) {
207
- throw new Error(`Unable to locate the built SSR entry in "${ssrOutDir}". Expected one of: ${candidatePaths.join(', ')}`);
208
- }
209
- return ssrEntryPath;
134
+ const candidatePaths = [
135
+ resolve(ssrOutDir, "main.server.mjs"),
136
+ resolve(ssrOutDir, "main.server.js"),
137
+ resolve(ssrOutDir, "main.server")
138
+ ];
139
+ const ssrEntryPath = candidatePaths.find((candidatePath) => existsSync(candidatePath));
140
+ if (!ssrEntryPath) throw new Error(`Unable to locate the built SSR entry in "${ssrOutDir}". Expected one of: ${candidatePaths.join(", ")}`);
141
+ return ssrEntryPath;
210
142
  }
211
- export function nitro(options, nitroOptions) {
212
- const workspaceRoot = options?.workspaceRoot ?? process.cwd();
213
- const sourceRoot = options?.sourceRoot ?? 'src';
214
- let isTest = process.env['NODE_ENV'] === 'test' || !!process.env['VITEST'];
215
- const baseURL = process.env['NITRO_APP_BASE_URL'] || '';
216
- const prefix = baseURL ? baseURL.substring(0, baseURL.length - 1) : '';
217
- const apiPrefix = `/${options?.apiPrefix || 'api'}`;
218
- const useAPIMiddleware = typeof options?.useAPIMiddleware !== 'undefined'
219
- ? options?.useAPIMiddleware
220
- : true;
221
- const viteRolldownOutput = options?.vite?.build?.rolldownOptions?.output;
222
- // Vite's native build typing allows `output` to be either a single object or
223
- // an array. Analog only forwards `codeSplitting` into the client environment
224
- // when there is a single output object to merge into.
225
- const viteRolldownOutputConfig = viteRolldownOutput && !Array.isArray(viteRolldownOutput)
226
- ? viteRolldownOutput
227
- : undefined;
228
- const codeSplitting = viteRolldownOutputConfig?.codeSplitting;
229
- let isBuild = false;
230
- let isServe = false;
231
- let ssrBuild = false;
232
- let config;
233
- let nitroConfig;
234
- let environmentBuild = false;
235
- let hasAPIDir = false;
236
- let clientOutputPath = '';
237
- let rendererIndexEntry = '';
238
- const rollupExternalEntries = [];
239
- const routeSitemaps = {};
240
- const routeSourceFiles = {};
241
- let rootDir = workspaceRoot;
242
- return [
243
- (options?.ssr
244
- ? devServerPlugin({
245
- entryServer: options?.entryServer,
246
- index: options?.index,
247
- routeRules: nitroOptions?.routeRules,
248
- })
249
- : false),
250
- {
251
- name: '@analogjs/vite-plugin-nitro',
252
- async config(userConfig, { mode, command }) {
253
- isServe = command === 'serve';
254
- isBuild = command === 'build';
255
- ssrBuild = userConfig.build?.ssr === true;
256
- config = userConfig;
257
- isTest = isTest ? isTest : mode === 'test';
258
- rootDir = relative(workspaceRoot, config.root || '.') || '.';
259
- hasAPIDir = existsSync(resolve(workspaceRoot, rootDir, `${sourceRoot}/server/routes/${options?.apiPrefix || 'api'}`));
260
- const buildPreset = process.env['BUILD_PRESET'] ??
261
- nitroOptions?.preset ??
262
- (process.env['VERCEL'] ? 'vercel' : undefined);
263
- const pageHandlers = getPageHandlers({
264
- workspaceRoot,
265
- sourceRoot,
266
- rootDir,
267
- additionalPagesDirs: options?.additionalPagesDirs,
268
- hasAPIDir,
269
- });
270
- const resolvedClientOutputPath = resolveClientOutputPath(clientOutputPath, workspaceRoot, rootDir, config.build?.outDir, ssrBuild);
271
- rendererIndexEntry = normalizePath(resolve(resolvedClientOutputPath, 'index.html'));
272
- nitroConfig = {
273
- rootDir: normalizePath(rootDir),
274
- preset: buildPreset,
275
- compatibilityDate: '2025-11-19',
276
- logLevel: nitroOptions?.logLevel || 0,
277
- serverDir: normalizePath(`${sourceRoot}/server`),
278
- scanDirs: [
279
- normalizePath(`${rootDir}/${sourceRoot}/server`),
280
- ...(options?.additionalAPIDirs || []).map((dir) => normalizePath(`${workspaceRoot}${dir}`)),
281
- ],
282
- output: {
283
- dir: normalizePath(resolve(workspaceRoot, 'dist', rootDir, 'analog')),
284
- publicDir: normalizePath(resolve(workspaceRoot, 'dist', rootDir, 'analog/public')),
285
- },
286
- buildDir: normalizePath(resolve(workspaceRoot, 'dist', rootDir, '.nitro')),
287
- typescript: {
288
- generateTsConfig: false,
289
- },
290
- runtimeConfig: {
291
- apiPrefix: apiPrefix.substring(1),
292
- prefix,
293
- },
294
- // Analog provides its own renderer handler; prevent Nitro v3 from
295
- // auto-detecting index.html in rootDir and adding a conflicting one.
296
- renderer: false,
297
- imports: {
298
- autoImport: false,
299
- },
300
- hooks: {
301
- 'rollup:before': createRollupBeforeHook(rollupExternalEntries),
302
- },
303
- rollupConfig: {
304
- onwarn(warning) {
305
- if (warning.message.includes('empty chunk') &&
306
- warning.message.endsWith('.server')) {
307
- return;
308
- }
309
- },
310
- plugins: [pageEndpointsPlugin()],
311
- },
312
- handlers: [
313
- ...(hasAPIDir
314
- ? []
315
- : useAPIMiddleware
316
- ? [createNitroMiddlewareHandler('#ANALOG_API_MIDDLEWARE')]
317
- : []),
318
- ...pageHandlers,
319
- ],
320
- routeRules: hasAPIDir
321
- ? undefined
322
- : useAPIMiddleware
323
- ? undefined
324
- : {
325
- [`${prefix}${apiPrefix}/**`]: {
326
- proxy: { to: '/**' },
327
- },
328
- },
329
- virtual: {
330
- '#ANALOG_SSR_RENDERER': ssrRenderer(rendererIndexEntry),
331
- '#ANALOG_CLIENT_RENDERER': clientRenderer(rendererIndexEntry),
332
- ...(hasAPIDir ? {} : { '#ANALOG_API_MIDDLEWARE': apiMiddleware }),
333
- },
334
- };
335
- if (isVercelPreset(buildPreset)) {
336
- nitroConfig = withVercelOutputAPI(nitroConfig, workspaceRoot);
337
- }
338
- if (isCloudflarePreset(buildPreset)) {
339
- nitroConfig = withCloudflareOutput(nitroConfig);
340
- }
341
- if (isNetlifyPreset(buildPreset) &&
342
- rootDir === '.' &&
343
- !existsSync(resolve(workspaceRoot, 'netlify.toml'))) {
344
- nitroConfig = withNetlifyOutputAPI(nitroConfig, workspaceRoot);
345
- }
346
- if (isFirebaseAppHosting()) {
347
- nitroConfig = withAppHostingOutput(nitroConfig);
348
- }
349
- if (!ssrBuild && !isTest) {
350
- // store the client output path for the SSR build config
351
- clientOutputPath = resolvedClientOutputPath;
352
- }
353
- if (isBuild) {
354
- nitroConfig.publicAssets = [
355
- { dir: normalizePath(clientOutputPath), maxAge: 0 },
356
- ];
357
- // In Nitro v3, renderer.entry is resolved via resolveModulePath()
358
- // during options normalization, which requires a real filesystem path.
359
- // Virtual modules (prefixed with #) can't survive this resolution.
360
- // Instead, we add the renderer as a catch-all handler directly —
361
- // this is functionally equivalent to what Nitro does internally
362
- // (it converts renderer.entry into a { route: '/**', lazy: true }
363
- // handler), but avoids the filesystem resolution step.
364
- const rendererHandler = options?.ssr
365
- ? '#ANALOG_SSR_RENDERER'
366
- : '#ANALOG_CLIENT_RENDERER';
367
- nitroConfig.handlers = [
368
- ...(nitroConfig.handlers || []),
369
- {
370
- handler: rendererHandler,
371
- route: '/**',
372
- lazy: true,
373
- },
374
- ];
375
- if (isEmptyPrerenderRoutes(options)) {
376
- nitroConfig.prerender = {};
377
- nitroConfig.prerender.routes = ['/'];
378
- }
379
- if (options?.prerender) {
380
- nitroConfig.prerender = nitroConfig.prerender ?? {};
381
- nitroConfig.prerender.crawlLinks = options?.prerender?.discover;
382
- let routes = [];
383
- const prerenderRoutes = options?.prerender?.routes;
384
- if (isArrayWithElements(prerenderRoutes)) {
385
- routes = prerenderRoutes;
386
- }
387
- else if (typeof prerenderRoutes === 'function') {
388
- routes = await prerenderRoutes();
389
- }
390
- nitroConfig.prerender.routes = routes.reduce((prev, current) => {
391
- if (!current) {
392
- return prev;
393
- }
394
- if (typeof current === 'string') {
395
- prev.push(current);
396
- return prev;
397
- }
398
- if ('route' in current) {
399
- if (current.sitemap) {
400
- routeSitemaps[current.route] = current.sitemap;
401
- }
402
- if (current.outputSourceFile) {
403
- const sourcePath = resolve(workspaceRoot, rootDir, current.outputSourceFile);
404
- routeSourceFiles[current.route] = readFileSync(sourcePath, 'utf8');
405
- }
406
- prev.push(current.route);
407
- // Add the server-side data fetching endpoint URL
408
- if ('staticData' in current) {
409
- prev.push(`${apiPrefix}/_analog/pages/${current.route}`);
410
- }
411
- return prev;
412
- }
413
- const affectedFiles = getMatchingContentFilesWithFrontMatter(workspaceRoot, rootDir, current.contentDir);
414
- affectedFiles.forEach((f) => {
415
- const result = current.transform(f);
416
- if (result) {
417
- if (current.sitemap) {
418
- routeSitemaps[result] =
419
- current.sitemap && typeof current.sitemap === 'function'
420
- ? current.sitemap?.(f)
421
- : current.sitemap;
422
- }
423
- if (current.outputSourceFile) {
424
- const sourceContent = current.outputSourceFile(f);
425
- if (sourceContent) {
426
- routeSourceFiles[result] = sourceContent;
427
- }
428
- }
429
- prev.push(result);
430
- // Add the server-side data fetching endpoint URL
431
- if ('staticData' in current) {
432
- prev.push(`${apiPrefix}/_analog/pages/${result}`);
433
- }
434
- }
435
- });
436
- return prev;
437
- }, []);
438
- }
439
- // ── SSR / prerender Nitro config ─────────────────────────────
440
- //
441
- // This block configures Nitro for builds that rebundle the SSR
442
- // entry (main.server.{js,mjs}). That happens in two cases:
443
- //
444
- // 1. Full SSR apps — `options.ssr === true`
445
- // 2. Prerender-only — no runtime SSR, but the prerender build
446
- // still imports the SSR entry to render static pages.
447
- //
448
- // The original gate was `if (ssrBuild)`, which checks the Vite
449
- // top-level `build.ssr` flag. That worked with the legacy
450
- // single-pass build but breaks with two newer code paths:
451
- //
452
- // a. **Vite Environment API (Vite 6+)** — SSR config lives in
453
- // `environments.ssr.build.ssr`, not `build.ssr`, so
454
- // `ssrBuild` is always `false`.
455
- // b. **Prerender-only apps** (e.g. blog-app) — `options.ssr`
456
- // is `false`, but prerender routes exist and the prerender
457
- // build still processes the SSR entry.
458
- //
459
- // Without this block:
460
- // - `rxjs` is never externalised → RESOLVE_ERROR in the
461
- // Nitro prerender build (especially on Windows CI).
462
- // - `moduleSideEffects` for zone.js is never set → zone.js
463
- // side-effects may be tree-shaken.
464
- // - The handlers list is not reassembled with page endpoints
465
- // + the renderer catch-all.
466
- //
467
- // The widened condition covers all three code paths:
468
- // - `ssrBuild` → legacy closeBundle
469
- // - `options?.ssr` → Environment API SSR
470
- // - `nitroConfig.prerender?.routes?.length` → prerender-only
471
- if (ssrBuild ||
472
- options?.ssr ||
473
- nitroConfig.prerender?.routes?.length) {
474
- if (process.platform === 'win32') {
475
- nitroConfig.noExternals = appendNoExternals(nitroConfig.noExternals, 'std-env');
476
- }
477
- rollupExternalEntries.push('rxjs', 'node-fetch-native/dist/polyfill');
478
- nitroConfig = {
479
- ...nitroConfig,
480
- moduleSideEffects: ['zone.js/node', 'zone.js/fesm2015/zone-node'],
481
- handlers: [
482
- ...(hasAPIDir
483
- ? []
484
- : useAPIMiddleware
485
- ? [createNitroMiddlewareHandler('#ANALOG_API_MIDDLEWARE')]
486
- : []),
487
- ...pageHandlers,
488
- // Preserve the renderer catch-all handler added above
489
- {
490
- handler: rendererHandler,
491
- route: '/**',
492
- lazy: true,
493
- },
494
- ],
495
- };
496
- }
497
- }
498
- nitroConfig = mergeConfig(nitroConfig, nitroOptions);
499
- return {
500
- environments: {
501
- client: {
502
- build: {
503
- outDir: config?.build?.outDir ||
504
- resolve(workspaceRoot, 'dist', rootDir, 'client'),
505
- // Forward code-splitting config to Rolldown when running
506
- // under Vite 8+. `false` disables splitting (inlines all
507
- // dynamic imports); an object configures chunk groups.
508
- // The `!== undefined` check ensures `codeSplitting: false`
509
- // is forwarded correctly (a truthy check would swallow it).
510
- ...(isRolldown() && codeSplitting !== undefined
511
- ? {
512
- rolldownOptions: {
513
- output: {
514
- // Preserve any sibling Rolldown output options while
515
- // overriding just `codeSplitting` for the client build.
516
- ...viteRolldownOutputConfig,
517
- codeSplitting,
518
- },
519
- },
520
- }
521
- : {}),
522
- },
523
- },
524
- ssr: {
525
- build: {
526
- ssr: true,
527
- [getBundleOptionsKey()]: {
528
- input: options?.entryServer ||
529
- resolve(workspaceRoot, rootDir, `${sourceRoot}/main.server.ts`),
530
- },
531
- outDir: options?.ssrBuildDir ||
532
- resolve(workspaceRoot, 'dist', rootDir, 'ssr'),
533
- },
534
- },
535
- },
536
- builder: {
537
- sharedPlugins: true,
538
- buildApp: async (builder) => {
539
- environmentBuild = true;
540
- const builds = [builder.build(builder.environments['client'])];
541
- if (options?.ssr || nitroConfig.prerender?.routes?.length) {
542
- builds.push(builder.build(builder.environments['ssr']));
543
- }
544
- await Promise.all(builds);
545
- applySsrEntryAlias(nitroConfig, options, workspaceRoot, rootDir);
546
- await buildServer(options, nitroConfig, routeSourceFiles);
547
- if (nitroConfig.prerender?.routes?.length &&
548
- options?.prerender?.sitemap) {
549
- const publicDir = nitroConfig.output?.publicDir;
550
- if (!publicDir) {
551
- throw new Error('Nitro public output directory is required to build the sitemap.');
552
- }
553
- console.log('Building Sitemap...');
554
- // sitemap needs to be built after all directories are built
555
- await buildSitemap(config, options.prerender.sitemap, nitroConfig.prerender.routes, publicDir, routeSitemaps);
556
- }
557
- console.log(`\n\nThe '@analogjs/platform' server has been successfully built.`);
558
- },
559
- },
560
- };
561
- },
562
- async configureServer(viteServer) {
563
- if (isServe && !isTest) {
564
- const nitro = await createNitro({
565
- dev: true,
566
- // Nitro's Vite builder now rejects `build()` in dev mode, but Analog's
567
- // dev integration still relies on the builder-driven reload hooks.
568
- // Force the server worker onto Rollup for this dev-only path.
569
- builder: 'rollup',
570
- ...nitroConfig,
571
- });
572
- const server = createDevServer(nitro);
573
- await build(nitro);
574
- const apiHandler = async (req, res) => {
575
- // Nitro v3's dev server is fetch-first, so adapt Vite's Node
576
- // request once and let Nitro respond with a standard Web Response.
577
- const response = await server.fetch(toWebRequest(req));
578
- await writeWebResponseToNode(res, response);
579
- };
580
- if (hasAPIDir) {
581
- viteServer.middlewares.use((req, res, next) => {
582
- if (req.url?.startsWith(`${prefix}${apiPrefix}`)) {
583
- void apiHandler(req, res).catch((error) => next(error));
584
- return;
585
- }
586
- next();
587
- });
588
- }
589
- else {
590
- viteServer.middlewares.use(apiPrefix, (req, res, next) => {
591
- void apiHandler(req, res).catch((error) => next(error));
592
- });
593
- }
594
- viteServer.httpServer?.once('listening', () => {
595
- process.env['ANALOG_HOST'] = !viteServer.config.server.host
596
- ? 'localhost'
597
- : viteServer.config.server.host;
598
- process.env['ANALOG_PORT'] = `${viteServer.config.server.port}`;
599
- });
600
- // handle upgrades if websockets are enabled
601
- if (nitroOptions?.experimental?.websocket) {
602
- viteServer.httpServer?.on('upgrade', server.upgrade);
603
- }
604
- console.log(`\n\nThe server endpoints are accessible under the "${prefix}${apiPrefix}" path.`);
605
- }
606
- },
607
- async closeBundle() {
608
- // Skip when build is triggered by the Environment API
609
- if (environmentBuild) {
610
- return;
611
- }
612
- if (ssrBuild) {
613
- return;
614
- }
615
- if (isBuild) {
616
- if (options?.ssr) {
617
- console.log('Building SSR application...');
618
- await buildSSRApp(config, options);
619
- }
620
- if (nitroConfig.prerender?.routes?.length &&
621
- options?.prerender?.sitemap) {
622
- console.log('Building Sitemap...');
623
- // sitemap needs to be built after all directories are built
624
- await buildSitemap(config, options.prerender.sitemap, nitroConfig.prerender.routes, clientOutputPath, routeSitemaps);
625
- }
626
- applySsrEntryAlias(nitroConfig, options, workspaceRoot, rootDir);
627
- await buildServer(options, nitroConfig, routeSourceFiles);
628
- console.log(`\n\nThe '@analogjs/platform' server has been successfully built.`);
629
- }
630
- },
631
- },
632
- {
633
- name: '@analogjs/vite-plugin-nitro-api-prefix',
634
- config() {
635
- return {
636
- define: {
637
- ANALOG_API_PREFIX: `"${baseURL.substring(1)}${apiPrefix.substring(1)}"`,
638
- },
639
- };
640
- },
641
- },
642
- ];
143
+ function nitro(options, nitroOptions) {
144
+ const workspaceRoot = options?.workspaceRoot ?? process.cwd();
145
+ const sourceRoot = options?.sourceRoot ?? "src";
146
+ let isTest = process.env.NODE_ENV === "test" || !!process.env["VITEST"];
147
+ const baseURL = process.env["NITRO_APP_BASE_URL"] || "";
148
+ const prefix = baseURL ? baseURL.substring(0, baseURL.length - 1) : "";
149
+ const apiPrefix = `/${options?.apiPrefix || "api"}`;
150
+ const useAPIMiddleware = typeof options?.useAPIMiddleware !== "undefined" ? options?.useAPIMiddleware : true;
151
+ const viteRolldownOutput = options?.vite?.build?.rolldownOptions?.output;
152
+ const viteRolldownOutputConfig = viteRolldownOutput && !Array.isArray(viteRolldownOutput) ? viteRolldownOutput : void 0;
153
+ const codeSplitting = viteRolldownOutputConfig?.codeSplitting;
154
+ let isBuild = false;
155
+ let isServe = false;
156
+ let ssrBuild = false;
157
+ let config;
158
+ let nitroConfig;
159
+ let environmentBuild = false;
160
+ let hasAPIDir = false;
161
+ let clientOutputPath = "";
162
+ let rendererIndexEntry = "";
163
+ const rollupExternalEntries = [];
164
+ const routeSitemaps = {};
165
+ const routeSourceFiles = {};
166
+ let rootDir = workspaceRoot;
167
+ return [
168
+ options?.ssr ? devServerPlugin({
169
+ entryServer: options?.entryServer,
170
+ index: options?.index,
171
+ routeRules: nitroOptions?.routeRules
172
+ }) : false,
173
+ {
174
+ name: "@analogjs/vite-plugin-nitro",
175
+ async config(userConfig, { mode, command }) {
176
+ isServe = command === "serve";
177
+ isBuild = command === "build";
178
+ ssrBuild = userConfig.build?.ssr === true;
179
+ config = userConfig;
180
+ isTest = isTest ? isTest : mode === "test";
181
+ rootDir = relative(workspaceRoot, config.root || ".") || ".";
182
+ hasAPIDir = existsSync(resolve(workspaceRoot, rootDir, `${sourceRoot}/server/routes/${options?.apiPrefix || "api"}`));
183
+ const buildPreset = process.env["BUILD_PRESET"] ?? nitroOptions?.preset ?? (process.env["VERCEL"] ? "vercel" : void 0);
184
+ const pageHandlers = getPageHandlers({
185
+ workspaceRoot,
186
+ sourceRoot,
187
+ rootDir,
188
+ additionalPagesDirs: options?.additionalPagesDirs,
189
+ hasAPIDir
190
+ });
191
+ const resolvedClientOutputPath = resolveClientOutputPath(clientOutputPath, workspaceRoot, rootDir, config.build?.outDir, ssrBuild);
192
+ rendererIndexEntry = normalizePath(resolve(resolvedClientOutputPath, "index.html"));
193
+ nitroConfig = {
194
+ rootDir: normalizePath(rootDir),
195
+ preset: buildPreset,
196
+ compatibilityDate: "2025-11-19",
197
+ logLevel: nitroOptions?.logLevel || 0,
198
+ serverDir: normalizePath(`${sourceRoot}/server`),
199
+ scanDirs: [normalizePath(`${rootDir}/${sourceRoot}/server`), ...(options?.additionalAPIDirs || []).map((dir) => normalizePath(`${workspaceRoot}${dir}`))],
200
+ output: {
201
+ dir: normalizePath(resolve(workspaceRoot, "dist", rootDir, "analog")),
202
+ publicDir: normalizePath(resolve(workspaceRoot, "dist", rootDir, "analog/public"))
203
+ },
204
+ buildDir: normalizePath(resolve(workspaceRoot, "dist", rootDir, ".nitro")),
205
+ typescript: { generateTsConfig: false },
206
+ runtimeConfig: {
207
+ apiPrefix: apiPrefix.substring(1),
208
+ prefix
209
+ },
210
+ renderer: false,
211
+ imports: { autoImport: false },
212
+ hooks: { "rollup:before": createRollupBeforeHook(rollupExternalEntries) },
213
+ rollupConfig: {
214
+ onwarn(warning) {
215
+ if (warning.message.includes("empty chunk") && warning.message.endsWith(".server")) return;
216
+ },
217
+ plugins: [pageEndpointsPlugin()]
218
+ },
219
+ handlers: [...hasAPIDir ? [] : useAPIMiddleware ? [createNitroMiddlewareHandler("#ANALOG_API_MIDDLEWARE")] : [], ...pageHandlers],
220
+ routeRules: hasAPIDir ? void 0 : useAPIMiddleware ? void 0 : { [`${prefix}${apiPrefix}/**`]: { proxy: { to: "/**" } } },
221
+ virtual: {
222
+ "#ANALOG_SSR_RENDERER": ssrRenderer(rendererIndexEntry),
223
+ "#ANALOG_CLIENT_RENDERER": clientRenderer(rendererIndexEntry),
224
+ ...hasAPIDir ? {} : { "#ANALOG_API_MIDDLEWARE": apiMiddleware }
225
+ }
226
+ };
227
+ if (isVercelPreset(buildPreset)) nitroConfig = withVercelOutputAPI(nitroConfig, workspaceRoot);
228
+ if (isCloudflarePreset(buildPreset)) nitroConfig = withCloudflareOutput(nitroConfig);
229
+ if (isNetlifyPreset(buildPreset) && rootDir === "." && !existsSync(resolve(workspaceRoot, "netlify.toml"))) nitroConfig = withNetlifyOutputAPI(nitroConfig, workspaceRoot);
230
+ if (isFirebaseAppHosting()) nitroConfig = withAppHostingOutput(nitroConfig);
231
+ if (!ssrBuild && !isTest) clientOutputPath = resolvedClientOutputPath;
232
+ if (isBuild) {
233
+ nitroConfig.publicAssets = [{
234
+ dir: normalizePath(clientOutputPath),
235
+ maxAge: 0
236
+ }];
237
+ const rendererHandler = options?.ssr ? "#ANALOG_SSR_RENDERER" : "#ANALOG_CLIENT_RENDERER";
238
+ nitroConfig.handlers = [...nitroConfig.handlers || [], {
239
+ handler: rendererHandler,
240
+ route: "/**",
241
+ lazy: true
242
+ }];
243
+ if (isEmptyPrerenderRoutes(options)) {
244
+ nitroConfig.prerender = {};
245
+ nitroConfig.prerender.routes = ["/"];
246
+ }
247
+ if (options?.prerender) {
248
+ nitroConfig.prerender = nitroConfig.prerender ?? {};
249
+ nitroConfig.prerender.crawlLinks = options?.prerender?.discover;
250
+ let routes = [];
251
+ const prerenderRoutes = options?.prerender?.routes;
252
+ if (isArrayWithElements(prerenderRoutes)) routes = prerenderRoutes;
253
+ else if (typeof prerenderRoutes === "function") routes = await prerenderRoutes();
254
+ nitroConfig.prerender.routes = routes.reduce((prev, current) => {
255
+ if (!current) return prev;
256
+ if (typeof current === "string") {
257
+ prev.push(current);
258
+ return prev;
259
+ }
260
+ if ("route" in current) {
261
+ if (current.sitemap) routeSitemaps[current.route] = current.sitemap;
262
+ if (current.outputSourceFile) {
263
+ const sourcePath = resolve(workspaceRoot, rootDir, current.outputSourceFile);
264
+ routeSourceFiles[current.route] = readFileSync(sourcePath, "utf8");
265
+ }
266
+ prev.push(current.route);
267
+ if ("staticData" in current) prev.push(`${apiPrefix}/_analog/pages/${current.route}`);
268
+ return prev;
269
+ }
270
+ getMatchingContentFilesWithFrontMatter(workspaceRoot, rootDir, current.contentDir).forEach((f) => {
271
+ const result = current.transform(f);
272
+ if (result) {
273
+ if (current.sitemap) routeSitemaps[result] = current.sitemap && typeof current.sitemap === "function" ? current.sitemap?.(f) : current.sitemap;
274
+ if (current.outputSourceFile) {
275
+ const sourceContent = current.outputSourceFile(f);
276
+ if (sourceContent) routeSourceFiles[result] = sourceContent;
277
+ }
278
+ prev.push(result);
279
+ if ("staticData" in current) prev.push(`${apiPrefix}/_analog/pages/${result}`);
280
+ }
281
+ });
282
+ return prev;
283
+ }, []);
284
+ }
285
+ if (ssrBuild || options?.ssr || nitroConfig.prerender?.routes?.length) {
286
+ if (process.platform === "win32") nitroConfig.noExternals = appendNoExternals(nitroConfig.noExternals, "std-env");
287
+ rollupExternalEntries.push("rxjs", "node-fetch-native/dist/polyfill");
288
+ nitroConfig = {
289
+ ...nitroConfig,
290
+ moduleSideEffects: ["zone.js/node", "zone.js/fesm2015/zone-node"],
291
+ handlers: [
292
+ ...hasAPIDir ? [] : useAPIMiddleware ? [createNitroMiddlewareHandler("#ANALOG_API_MIDDLEWARE")] : [],
293
+ ...pageHandlers,
294
+ {
295
+ handler: rendererHandler,
296
+ route: "/**",
297
+ lazy: true
298
+ }
299
+ ]
300
+ };
301
+ }
302
+ }
303
+ nitroConfig = mergeConfig(nitroConfig, nitroOptions);
304
+ return {
305
+ environments: {
306
+ client: { build: {
307
+ outDir: config?.build?.outDir || resolve(workspaceRoot, "dist", rootDir, "client"),
308
+ ...isRolldown() && codeSplitting !== void 0 ? { rolldownOptions: { output: {
309
+ ...viteRolldownOutputConfig,
310
+ codeSplitting
311
+ } } } : {}
312
+ } },
313
+ ssr: { build: {
314
+ ssr: true,
315
+ [getBundleOptionsKey()]: { input: options?.entryServer || resolve(workspaceRoot, rootDir, `${sourceRoot}/main.server.ts`) },
316
+ outDir: options?.ssrBuildDir || resolve(workspaceRoot, "dist", rootDir, "ssr")
317
+ } }
318
+ },
319
+ builder: {
320
+ sharedPlugins: true,
321
+ buildApp: async (builder) => {
322
+ environmentBuild = true;
323
+ const builds = [builder.build(builder.environments["client"])];
324
+ if (options?.ssr || nitroConfig.prerender?.routes?.length) builds.push(builder.build(builder.environments["ssr"]));
325
+ await Promise.all(builds);
326
+ applySsrEntryAlias(nitroConfig, options, workspaceRoot, rootDir);
327
+ await buildServer(options, nitroConfig, routeSourceFiles);
328
+ if (nitroConfig.prerender?.routes?.length && options?.prerender?.sitemap) {
329
+ const publicDir = nitroConfig.output?.publicDir;
330
+ if (!publicDir) throw new Error("Nitro public output directory is required to build the sitemap.");
331
+ console.log("Building Sitemap...");
332
+ await buildSitemap(config, options.prerender.sitemap, nitroConfig.prerender.routes, publicDir, routeSitemaps);
333
+ }
334
+ console.log(`\n\nThe '@analogjs/platform' server has been successfully built.`);
335
+ }
336
+ }
337
+ };
338
+ },
339
+ async configureServer(viteServer) {
340
+ if (isServe && !isTest) {
341
+ const nitro = await createNitro({
342
+ dev: true,
343
+ builder: "rollup",
344
+ ...nitroConfig
345
+ });
346
+ const server = createDevServer(nitro);
347
+ await build(nitro);
348
+ const apiHandler = async (req, res) => {
349
+ await writeWebResponseToNode(res, await server.fetch(toWebRequest(req)));
350
+ };
351
+ if (hasAPIDir) viteServer.middlewares.use((req, res, next) => {
352
+ if (req.url?.startsWith(`${prefix}${apiPrefix}`)) {
353
+ apiHandler(req, res).catch((error) => next(error));
354
+ return;
355
+ }
356
+ next();
357
+ });
358
+ else viteServer.middlewares.use(apiPrefix, (req, res, next) => {
359
+ apiHandler(req, res).catch((error) => next(error));
360
+ });
361
+ viteServer.httpServer?.once("listening", () => {
362
+ process.env["ANALOG_HOST"] = !viteServer.config.server.host ? "localhost" : viteServer.config.server.host;
363
+ process.env["ANALOG_PORT"] = `${viteServer.config.server.port}`;
364
+ });
365
+ if (nitroOptions?.experimental?.websocket) viteServer.httpServer?.on("upgrade", server.upgrade);
366
+ console.log(`\n\nThe server endpoints are accessible under the "${prefix}${apiPrefix}" path.`);
367
+ }
368
+ },
369
+ async closeBundle() {
370
+ if (environmentBuild) return;
371
+ if (ssrBuild) return;
372
+ if (isBuild) {
373
+ if (options?.ssr) {
374
+ console.log("Building SSR application...");
375
+ await buildSSRApp(config, options);
376
+ }
377
+ if (nitroConfig.prerender?.routes?.length && options?.prerender?.sitemap) {
378
+ console.log("Building Sitemap...");
379
+ await buildSitemap(config, options.prerender.sitemap, nitroConfig.prerender.routes, clientOutputPath, routeSitemaps);
380
+ }
381
+ applySsrEntryAlias(nitroConfig, options, workspaceRoot, rootDir);
382
+ await buildServer(options, nitroConfig, routeSourceFiles);
383
+ console.log(`\n\nThe '@analogjs/platform' server has been successfully built.`);
384
+ }
385
+ }
386
+ },
387
+ {
388
+ name: "@analogjs/vite-plugin-nitro-api-prefix",
389
+ config() {
390
+ return { define: { ANALOG_API_PREFIX: `"${baseURL.substring(1)}${apiPrefix.substring(1)}"` } };
391
+ }
392
+ }
393
+ ];
643
394
  }
644
395
  function isEmptyPrerenderRoutes(options) {
645
- if (!options || isArrayWithElements(options?.prerender?.routes)) {
646
- return false;
647
- }
648
- return !options.prerender?.routes;
396
+ if (!options || isArrayWithElements(options?.prerender?.routes)) return false;
397
+ return !options.prerender?.routes;
649
398
  }
650
399
  function isArrayWithElements(arr) {
651
- return !!(Array.isArray(arr) && arr.length);
400
+ return !!(Array.isArray(arr) && arr.length);
652
401
  }
653
- const VERCEL_PRESET = 'vercel';
654
- // Nitro v3 consolidates the old `vercel-edge` preset into `vercel` with
655
- // fluid compute enabled by default, so a single preset covers both
656
- // serverless and edge deployments.
657
- const withVercelOutputAPI = (nitroConfig, workspaceRoot) => ({
658
- ...nitroConfig,
659
- preset: nitroConfig?.preset ?? 'vercel',
660
- vercel: {
661
- ...nitroConfig?.vercel,
662
- entryFormat: nitroConfig?.vercel?.entryFormat ?? 'node',
663
- functions: {
664
- runtime: nitroConfig?.vercel?.functions?.runtime ?? 'nodejs24.x',
665
- ...nitroConfig?.vercel?.functions,
666
- },
667
- },
668
- output: {
669
- ...nitroConfig?.output,
670
- dir: normalizePath(resolve(workspaceRoot, '.vercel', 'output')),
671
- publicDir: normalizePath(resolve(workspaceRoot, '.vercel', 'output/static')),
672
- },
402
+ var withVercelOutputAPI = (nitroConfig, workspaceRoot) => ({
403
+ ...nitroConfig,
404
+ preset: nitroConfig?.preset ?? "vercel",
405
+ vercel: {
406
+ ...nitroConfig?.vercel,
407
+ entryFormat: nitroConfig?.vercel?.entryFormat ?? "node",
408
+ functions: {
409
+ runtime: nitroConfig?.vercel?.functions?.runtime ?? "nodejs24.x",
410
+ ...nitroConfig?.vercel?.functions
411
+ }
412
+ },
413
+ output: {
414
+ ...nitroConfig?.output,
415
+ dir: normalizePath(resolve(workspaceRoot, ".vercel", "output")),
416
+ publicDir: normalizePath(resolve(workspaceRoot, ".vercel", "output/static"))
417
+ }
673
418
  });
674
- // Nitro v3 uses underscore-separated preset names (e.g. `cloudflare_pages`),
675
- // but we accept both hyphen and underscore forms for backwards compatibility.
676
- const isCloudflarePreset = (buildPreset) => process.env['CF_PAGES'] ||
677
- (buildPreset &&
678
- (buildPreset.toLowerCase().includes('cloudflare-pages') ||
679
- buildPreset.toLowerCase().includes('cloudflare_pages')));
680
- const withCloudflareOutput = (nitroConfig) => ({
681
- ...nitroConfig,
682
- output: {
683
- ...nitroConfig?.output,
684
- serverDir: '{{ output.publicDir }}/_worker.js',
685
- },
419
+ var isCloudflarePreset = (buildPreset) => process.env["CF_PAGES"] || buildPreset && (buildPreset.toLowerCase().includes("cloudflare-pages") || buildPreset.toLowerCase().includes("cloudflare_pages"));
420
+ var withCloudflareOutput = (nitroConfig) => ({
421
+ ...nitroConfig,
422
+ output: {
423
+ ...nitroConfig?.output,
424
+ serverDir: "{{ output.publicDir }}/_worker.js"
425
+ }
686
426
  });
687
- const isFirebaseAppHosting = () => !!process.env['NG_BUILD_LOGS_JSON'];
688
- const withAppHostingOutput = (nitroConfig) => {
689
- let hasOutput = false;
690
- return {
691
- ...nitroConfig,
692
- serveStatic: true,
693
- rollupConfig: {
694
- ...nitroConfig.rollupConfig,
695
- output: {
696
- ...nitroConfig.rollupConfig?.output,
697
- entryFileNames: 'server.mjs',
698
- },
699
- },
700
- hooks: {
701
- ...nitroConfig.hooks,
702
- compiled: () => {
703
- if (!hasOutput) {
704
- const buildOutput = {
705
- errors: [],
706
- warnings: [],
707
- outputPaths: {
708
- root: pathToFileURL(`${nitroConfig.output?.dir}`),
709
- browser: pathToFileURL(`${nitroConfig.output?.publicDir}`),
710
- server: pathToFileURL(`${nitroConfig.output?.dir}/server`),
711
- },
712
- };
713
- // Log the build output for Firebase App Hosting to pick up
714
- console.log(JSON.stringify(buildOutput, null, 2));
715
- hasOutput = true;
716
- }
717
- },
718
- },
719
- };
427
+ var isFirebaseAppHosting = () => !!process.env["NG_BUILD_LOGS_JSON"];
428
+ var withAppHostingOutput = (nitroConfig) => {
429
+ let hasOutput = false;
430
+ return {
431
+ ...nitroConfig,
432
+ serveStatic: true,
433
+ rollupConfig: {
434
+ ...nitroConfig.rollupConfig,
435
+ output: {
436
+ ...nitroConfig.rollupConfig?.output,
437
+ entryFileNames: "server.mjs"
438
+ }
439
+ },
440
+ hooks: {
441
+ ...nitroConfig.hooks,
442
+ compiled: () => {
443
+ if (!hasOutput) {
444
+ const buildOutput = {
445
+ errors: [],
446
+ warnings: [],
447
+ outputPaths: {
448
+ root: pathToFileURL(`${nitroConfig.output?.dir}`),
449
+ browser: pathToFileURL(`${nitroConfig.output?.publicDir}`),
450
+ server: pathToFileURL(`${nitroConfig.output?.dir}/server`)
451
+ }
452
+ };
453
+ console.log(JSON.stringify(buildOutput, null, 2));
454
+ hasOutput = true;
455
+ }
456
+ }
457
+ }
458
+ };
720
459
  };
721
- const isNetlifyPreset = (buildPreset) => process.env['NETLIFY'] ||
722
- (buildPreset && buildPreset.toLowerCase().includes('netlify'));
723
- const withNetlifyOutputAPI = (nitroConfig, workspaceRoot) => ({
724
- ...nitroConfig,
725
- output: {
726
- ...nitroConfig?.output,
727
- dir: normalizePath(resolve(workspaceRoot, 'netlify/functions')),
728
- },
460
+ var isNetlifyPreset = (buildPreset) => process.env["NETLIFY"] || buildPreset && buildPreset.toLowerCase().includes("netlify");
461
+ var withNetlifyOutputAPI = (nitroConfig, workspaceRoot) => ({
462
+ ...nitroConfig,
463
+ output: {
464
+ ...nitroConfig?.output,
465
+ dir: normalizePath(resolve(workspaceRoot, "netlify/functions"))
466
+ }
729
467
  });
468
+ //#endregion
469
+ export { nitro };
470
+
730
471
  //# sourceMappingURL=vite-plugin-nitro.js.map