@analogjs/vite-plugin-nitro 3.0.0-alpha.9 → 3.0.0-alpha.90
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 +30 -6
- package/src/index.d.ts +19 -9
- package/src/index.js +4 -1
- package/src/index.js.map +1 -1
- package/src/lib/build-server.d.ts +2 -2
- package/src/lib/build-server.js +1 -62
- package/src/lib/build-server.js.map +1 -1
- package/src/lib/build-sitemap.d.ts +23 -9
- package/src/lib/build-sitemap.js +118 -37
- package/src/lib/build-sitemap.js.map +1 -1
- package/src/lib/build-ssr.d.ts +3 -2
- package/src/lib/build-ssr.js +12 -2
- package/src/lib/build-ssr.js.map +1 -1
- package/src/lib/hooks/post-rendering-hook.d.ts +1 -1
- package/src/lib/hooks/post-rendering-hook.js.map +1 -1
- package/src/lib/options.d.ts +148 -114
- package/src/lib/plugins/dev-server-plugin.d.ts +3 -3
- package/src/lib/plugins/dev-server-plugin.js +9 -1
- package/src/lib/plugins/dev-server-plugin.js.map +1 -1
- package/src/lib/plugins/page-endpoints.d.ts +5 -5
- package/src/lib/plugins/page-endpoints.js.map +1 -1
- package/src/lib/plugins/server-fn-id-plugin.d.ts +11 -0
- package/src/lib/plugins/server-fn-id-plugin.js +27 -0
- package/src/lib/plugins/server-fn-id-plugin.js.map +1 -0
- package/src/lib/utils/debug.d.ts +5 -0
- package/src/lib/utils/debug.js +15 -0
- package/src/lib/utils/debug.js.map +1 -0
- package/src/lib/utils/derive-server-fn-id.d.ts +25 -0
- package/src/lib/utils/derive-server-fn-id.js +37 -0
- package/src/lib/utils/derive-server-fn-id.js.map +1 -0
- package/src/lib/utils/get-content-files.d.ts +55 -55
- package/src/lib/utils/get-content-files.js +12 -5
- package/src/lib/utils/get-content-files.js.map +1 -1
- package/src/lib/utils/get-page-handlers.d.ts +55 -58
- package/src/lib/utils/get-page-handlers.js +7 -10
- package/src/lib/utils/get-page-handlers.js.map +1 -1
- package/src/lib/utils/get-server-fn-handlers.d.ts +29 -0
- package/src/lib/utils/get-server-fn-handlers.js +66 -0
- package/src/lib/utils/get-server-fn-handlers.js.map +1 -0
- package/src/lib/utils/i18n-prerender.d.ts +36 -0
- package/src/lib/utils/i18n-prerender.js +23 -0
- package/src/lib/utils/i18n-prerender.js.map +1 -0
- package/src/lib/utils/inject-server-fn-ids.d.ts +17 -0
- package/src/lib/utils/inject-server-fn-ids.js +94 -0
- package/src/lib/utils/inject-server-fn-ids.js.map +1 -0
- package/src/lib/utils/load-esm.d.ts +18 -18
- package/src/lib/utils/node-web-bridge.d.ts +1 -1
- package/src/lib/utils/node-web-bridge.js +18 -2
- package/src/lib/utils/node-web-bridge.js.map +1 -1
- package/src/lib/utils/register-dev-middleware.d.ts +12 -12
- package/src/lib/utils/register-dev-middleware.js.map +1 -1
- package/src/lib/utils/register-i18n-watcher.d.ts +15 -0
- package/src/lib/utils/register-i18n-watcher.js +27 -0
- package/src/lib/utils/register-i18n-watcher.js.map +1 -0
- package/src/lib/utils/renderers.d.ts +29 -49
- package/src/lib/utils/renderers.js +5 -52
- package/src/lib/utils/renderers.js.map +1 -1
- package/src/lib/utils/rolldown.d.ts +1 -1
- package/src/lib/utils/rolldown.js.map +1 -1
- package/src/lib/utils/server-fn-endpoints.d.ts +41 -0
- package/src/lib/utils/server-fn-endpoints.js +57 -0
- package/src/lib/utils/server-fn-endpoints.js.map +1 -0
- package/src/lib/vite-plugin-nitro.d.ts +3 -3
- package/src/lib/vite-plugin-nitro.js +406 -53
- package/src/lib/vite-plugin-nitro.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@analogjs/vite-plugin-nitro",
|
|
3
|
-
"version": "3.0.0-alpha.
|
|
3
|
+
"version": "3.0.0-alpha.90",
|
|
4
4
|
"description": "A Vite plugin for adding a nitro API server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Brandon Roberts <robertsbt@gmail.com>",
|
|
7
7
|
"exports": {
|
|
8
|
-
".":
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./src/index.d.ts",
|
|
10
|
+
"import": "./src/index.js",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./internal": {
|
|
14
|
+
"types": "./src/lib/utils/debug.d.ts",
|
|
15
|
+
"import": "./src/lib/utils/debug.js",
|
|
16
|
+
"default": "./src/lib/utils/debug.js"
|
|
17
|
+
},
|
|
18
|
+
"./server-fn-id": {
|
|
19
|
+
"types": "./src/lib/utils/derive-server-fn-id.d.ts",
|
|
20
|
+
"import": "./src/lib/utils/derive-server-fn-id.js",
|
|
21
|
+
"default": "./src/lib/utils/derive-server-fn-id.js"
|
|
22
|
+
},
|
|
9
23
|
"./package.json": "./package.json"
|
|
10
24
|
},
|
|
11
25
|
"keywords": [
|
|
@@ -29,13 +43,23 @@
|
|
|
29
43
|
"type": "github",
|
|
30
44
|
"url": "https://github.com/sponsors/brandonroberts"
|
|
31
45
|
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"sharp": ">=0.32.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"sharp": {
|
|
51
|
+
"optional": true
|
|
52
|
+
}
|
|
53
|
+
},
|
|
32
54
|
"dependencies": {
|
|
33
|
-
"
|
|
55
|
+
"defu": "^6.1.4",
|
|
56
|
+
"h3": "^2.0.1-rc.31",
|
|
57
|
+
"nitro": "3.0.260903-beta",
|
|
58
|
+
"obug": "^2.1.1",
|
|
34
59
|
"ofetch": "2.0.0-alpha.3",
|
|
35
|
-
"
|
|
36
|
-
"oxc-parser": "^0.119.0",
|
|
60
|
+
"oxc-parser": "^0.124.0",
|
|
37
61
|
"radix3": "^1.1.2",
|
|
38
|
-
"
|
|
62
|
+
"xmlbuilder2": "^4.0.3"
|
|
39
63
|
},
|
|
40
64
|
"ng-update": {
|
|
41
65
|
"packageGroup": [
|
package/src/index.d.ts
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
|
-
import { nitro } from
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { nitro } from "./lib/vite-plugin-nitro.js";
|
|
2
|
+
export { debugInstances } from "./lib/utils/debug.js";
|
|
3
|
+
export { nitro } from "./lib/vite-plugin-nitro.js";
|
|
4
|
+
export type { Options, SitemapConfig, SitemapEntry, SitemapExcludeRule, SitemapPriority, SitemapRouteDefinition, SitemapRouteInput, SitemapRouteSource, SitemapTransform, PrerenderSitemapConfig, PrerenderRouteConfig, PrerenderContentDir, PrerenderContentFile, I18nPrerenderOptions } from "./lib/options.js";
|
|
5
|
+
export { deriveServerFnId, serverFnFileId } from "./lib/utils/derive-server-fn-id.js";
|
|
6
|
+
export { injectServerFnIds, type InjectServerFnIdsResult } from "./lib/utils/inject-server-fn-ids.js";
|
|
7
|
+
declare module "h3/rules" {
|
|
8
|
+
interface RouteRuleConfig {
|
|
9
|
+
ssr?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Disable progressive streaming SSR for matching routes (falls back to a
|
|
12
|
+
* buffered render). Only meaningful when `experimental.streaming` is on.
|
|
13
|
+
*/
|
|
14
|
+
streaming?: boolean;
|
|
15
|
+
}
|
|
16
|
+
interface RouteRules {
|
|
17
|
+
ssr?: boolean;
|
|
18
|
+
streaming?: boolean;
|
|
19
|
+
}
|
|
10
20
|
}
|
|
11
21
|
export default nitro;
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { deriveServerFnId, serverFnFileId } from "./lib/utils/derive-server-fn-id.js";
|
|
2
|
+
import { injectServerFnIds } from "./lib/utils/inject-server-fn-ids.js";
|
|
3
|
+
import { debugInstances } from "./lib/utils/debug.js";
|
|
1
4
|
import { nitro } from "./lib/vite-plugin-nitro.js";
|
|
2
5
|
//#region packages/vite-plugin-nitro/src/index.ts
|
|
3
6
|
var src_default = nitro;
|
|
4
7
|
//#endregion
|
|
5
|
-
export { src_default as default };
|
|
8
|
+
export { debugInstances, src_default as default, deriveServerFnId, injectServerFnIds, nitro, serverFnFileId };
|
|
6
9
|
|
|
7
10
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/index.ts"],"sourcesContent":["import { nitro } from './lib/vite-plugin-nitro.js';\nexport { debugInstances } from './lib/utils/debug.js';\nexport { nitro } from './lib/vite-plugin-nitro.js';\nexport type {\n Options,\n SitemapConfig,\n SitemapEntry,\n SitemapExcludeRule,\n SitemapPriority,\n SitemapRouteDefinition,\n SitemapRouteInput,\n SitemapRouteSource,\n SitemapTransform,\n PrerenderSitemapConfig,\n PrerenderRouteConfig,\n PrerenderContentDir,\n PrerenderContentFile,\n I18nPrerenderOptions,\n} from './lib/options.js';\n\n// Server-function id derivation, shared with @analogjs/platform's client scrub\n// so both sides compute identical opaque ids (single source of truth).\nexport {\n deriveServerFnId,\n serverFnFileId,\n} from './lib/utils/derive-server-fn-id.js';\nexport {\n injectServerFnIds,\n type InjectServerFnIdsResult,\n} from './lib/utils/inject-server-fn-ids.js';\n\n// Nitro's NitroRouteConfig and NitroRouteRules are aliases of h3's route rule\n// types, so custom rule names are declared on h3 as its docs describe.\ndeclare module 'h3/rules' {\n interface RouteRuleConfig {\n ssr?: boolean;\n /**\n * Disable progressive streaming SSR for matching routes (falls back to a\n * buffered render). Only meaningful when `experimental.streaming` is on.\n */\n streaming?: boolean;\n }\n\n interface RouteRules {\n ssr?: boolean;\n streaming?: boolean;\n }\n}\n\nexport default nitro;\n"],"mappings":";;;;;AAiDA,IAAA,cAAe"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { NitroConfig } from
|
|
2
|
-
import { Options } from
|
|
1
|
+
import type { NitroConfig } from "nitro/types";
|
|
2
|
+
import { Options } from "./options.js";
|
|
3
3
|
export declare function isVercelPreset(preset: string | undefined): boolean;
|
|
4
4
|
export declare function buildServer(options?: Options, nitroConfig?: NitroConfig, routeSourceFiles?: Record<string, string>): Promise<void>;
|
package/src/lib/build-server.js
CHANGED
|
@@ -1,71 +1,12 @@
|
|
|
1
1
|
import { addPostRenderingHooks } from "./hooks/post-rendering-hook.js";
|
|
2
2
|
import { build, copyPublicAssets, createNitro, prepare, prerender } from "nitro/builder";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
5
5
|
//#region packages/vite-plugin-nitro/src/lib/build-server.ts
|
|
6
|
-
/**
|
|
7
|
-
* Ensures a minimal `tsconfig.json` exists in the SSR output directory.
|
|
8
|
-
*
|
|
9
|
-
* Nitro v3's `nitro:oxc` Rollup plugin uses OXC's resolver for every
|
|
10
|
-
* file it transforms. OXC walks up the directory tree looking for a
|
|
11
|
-
* `tsconfig.json` to load path aliases. The SSR output directory
|
|
12
|
-
* (`dist/<app>/ssr/`) is outside the source tree, so no tsconfig
|
|
13
|
-
* exists there. On Windows, OXC's upward directory walk also fails to
|
|
14
|
-
* reach the project root's tsconfig due to path normalisation issues,
|
|
15
|
-
* causing:
|
|
16
|
-
*
|
|
17
|
-
* [TSCONFIG_ERROR] Failed to load tsconfig for
|
|
18
|
-
* '../../dist/apps/blog-app/ssr/main.server.js': Tsconfig not found
|
|
19
|
-
*
|
|
20
|
-
* Writing a minimal tsconfig satisfies the resolver without adding any
|
|
21
|
-
* path aliases that would interfere with Nitro's own module resolution.
|
|
22
|
-
*/
|
|
23
|
-
function ensureSsrTsconfig(nitroConfig) {
|
|
24
|
-
const ssrEntry = nitroConfig?.alias?.["#analog/ssr"];
|
|
25
|
-
if (!ssrEntry) return;
|
|
26
|
-
const tsconfigPath = join(dirname(ssrEntry.replace(/\//g, join("a", "b")[1])), "tsconfig.json");
|
|
27
|
-
if (existsSync(tsconfigPath)) return;
|
|
28
|
-
writeFileSync(tsconfigPath, JSON.stringify({ compilerOptions: {
|
|
29
|
-
module: "ESNext",
|
|
30
|
-
moduleResolution: "bundler"
|
|
31
|
-
} }, null, 2), "utf8");
|
|
32
|
-
}
|
|
33
6
|
function isVercelPreset(preset) {
|
|
34
7
|
return !!preset?.toLowerCase().includes("vercel");
|
|
35
8
|
}
|
|
36
|
-
function getVercelBuildConfigPath(nitro) {
|
|
37
|
-
return join(nitro.options.output.dir, "config.json");
|
|
38
|
-
}
|
|
39
|
-
function assertValidVercelBuildConfig(nitro) {
|
|
40
|
-
if (!isVercelPreset(nitro.options.preset)) return;
|
|
41
|
-
const buildConfigPath = getVercelBuildConfigPath(nitro);
|
|
42
|
-
if (!existsSync(buildConfigPath)) throw new Error(`Nitro did not generate the expected Vercel build output config at "${buildConfigPath}".`);
|
|
43
|
-
const buildConfig = readFileSync(buildConfigPath, "utf8").trim();
|
|
44
|
-
if (!buildConfig) throw new Error(`Nitro generated an empty Vercel build output config at "${buildConfigPath}".`);
|
|
45
|
-
try {
|
|
46
|
-
JSON.parse(buildConfig);
|
|
47
|
-
} catch (error) {
|
|
48
|
-
const symptomError = /* @__PURE__ */ new Error(`Nitro generated an invalid Vercel build output config at "${buildConfigPath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
49
|
-
symptomError.cause = error;
|
|
50
|
-
throw symptomError;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
function ensureVercelFunctionConfig(nitro) {
|
|
54
|
-
if (!isVercelPreset(nitro.options.preset)) return;
|
|
55
|
-
const serverDir = nitro.options.output.serverDir;
|
|
56
|
-
const functionConfigPath = join(serverDir, ".vc-config.json");
|
|
57
|
-
if (existsSync(functionConfigPath)) return;
|
|
58
|
-
mkdirSync(serverDir, { recursive: true });
|
|
59
|
-
writeFileSync(functionConfigPath, JSON.stringify({
|
|
60
|
-
handler: "index.mjs",
|
|
61
|
-
launcherType: "Nodejs",
|
|
62
|
-
shouldAddHelpers: false,
|
|
63
|
-
supportsResponseStreaming: true,
|
|
64
|
-
...nitro.options.vercel?.functions
|
|
65
|
-
}, null, 2), "utf8");
|
|
66
|
-
}
|
|
67
9
|
async function buildServer(options, nitroConfig, routeSourceFiles) {
|
|
68
|
-
ensureSsrTsconfig(nitroConfig);
|
|
69
10
|
const nitro = await createNitro({
|
|
70
11
|
dev: false,
|
|
71
12
|
preset: process.env["BUILD_PRESET"],
|
|
@@ -98,10 +39,8 @@ async function buildServer(options, nitroConfig, routeSourceFiles) {
|
|
|
98
39
|
if (!options?.static) {
|
|
99
40
|
console.log("Building Server...");
|
|
100
41
|
await build(nitro);
|
|
101
|
-
ensureVercelFunctionConfig(nitro);
|
|
102
42
|
}
|
|
103
43
|
await nitro.close();
|
|
104
|
-
assertValidVercelBuildConfig(nitro);
|
|
105
44
|
}
|
|
106
45
|
//#endregion
|
|
107
46
|
export { buildServer, isVercelPreset };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-server.js","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"build-server.js","names":[],"sources":["../../../src/lib/build-server.ts"],"sourcesContent":["import type { NitroConfig } from 'nitro/types';\nimport {\n build,\n copyPublicAssets,\n createNitro,\n prepare,\n prerender,\n} from 'nitro/builder';\nimport { mkdirSync, rmSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport { Options } from './options.js';\nimport { addPostRenderingHooks } from './hooks/post-rendering-hook.js';\n\nexport function isVercelPreset(preset: string | undefined): boolean {\n return !!preset?.toLowerCase().includes('vercel');\n}\n\nexport async function buildServer(\n options?: Options,\n nitroConfig?: NitroConfig,\n routeSourceFiles?: Record<string, string>,\n): Promise<void> {\n // ── Force Rollup as the server bundler ────────────────────────────\n //\n // Nitro v3 defaults to Rolldown when available. Rolldown is faster,\n // but its module resolver cannot resolve relative chunk imports\n // (e.g. `./assets/core-DTazUigR.js`) from a rebundled SSR entry on\n // Windows. The prerender build fails with:\n //\n // [RESOLVE_ERROR] Could not resolve './assets/core-DTazUigR.js'\n // in ../../dist/apps/blog-app/ssr/main.server.js\n //\n // This is a known Rolldown limitation with cross-directory relative\n // paths on Windows (backslash vs forward-slash normalisation).\n // Rollup handles these paths correctly on all platforms.\n //\n // The dev server already uses `builder: 'rollup'` for the same\n // reason. Default to Rollup here too until Rolldown's resolver\n // matures. The caller can still opt in to Rolldown explicitly via\n // nitroConfig.builder if their platform supports it.\n const nitro = await createNitro({\n dev: false,\n preset: process.env['BUILD_PRESET'],\n ...nitroConfig,\n builder: nitroConfig?.builder ?? 'rollup',\n });\n\n if (options?.prerender?.postRenderingHooks) {\n addPostRenderingHooks(nitro, options.prerender.postRenderingHooks);\n }\n\n await prepare(nitro);\n await copyPublicAssets(nitro);\n\n if (\n options?.ssr &&\n nitroConfig?.prerender?.routes &&\n (nitroConfig?.prerender?.routes.find((route) => route === '/') ||\n nitroConfig?.prerender?.routes?.length === 0)\n ) {\n const indexFileExts = ['', '.br', '.gz'];\n\n indexFileExts.forEach((fileExt) => {\n // Remove the root index.html(.br|.gz) files\n const indexFilePath = join(\n nitroConfig?.output?.publicDir ?? '',\n `index.html${fileExt}`,\n );\n\n rmSync(indexFilePath, { force: true });\n });\n }\n\n if (\n nitroConfig?.prerender?.routes &&\n nitroConfig?.prerender?.routes?.length > 0\n ) {\n console.log(`Prerendering static pages...`);\n await prerender(nitro);\n }\n\n if (routeSourceFiles && Object.keys(routeSourceFiles).length > 0) {\n const publicDir = nitroConfig?.output?.publicDir;\n if (!publicDir) {\n throw new Error(\n 'Nitro public output directory is required to write route source files.',\n );\n }\n\n for (const [route, content] of Object.entries(routeSourceFiles)) {\n const outputPath = join(publicDir, `${route}.md`);\n const outputDir = dirname(outputPath);\n mkdirSync(outputDir, { recursive: true });\n\n writeFileSync(outputPath, content, 'utf8');\n }\n }\n\n if (!options?.static) {\n console.log('Building Server...');\n await build(nitro);\n }\n\n await nitro.close();\n}\n"],"mappings":";;;;;AAcA,SAAgB,eAAe,QAAqC;AAClE,QAAO,CAAC,CAAC,QAAQ,aAAa,CAAC,SAAS,SAAS;;AAGnD,eAAsB,YACpB,SACA,aACA,kBACe;CAmBf,MAAM,QAAQ,MAAM,YAAY;EAC9B,KAAK;EACL,QAAQ,QAAQ,IAAI;EACpB,GAAG;EACH,SAAS,aAAa,WAAW;EAClC,CAAC;AAEF,KAAI,SAAS,WAAW,mBACtB,uBAAsB,OAAO,QAAQ,UAAU,mBAAmB;AAGpE,OAAM,QAAQ,MAAM;AACpB,OAAM,iBAAiB,MAAM;AAE7B,KACE,SAAS,OACT,aAAa,WAAW,WACvB,aAAa,WAAW,OAAO,MAAM,UAAU,UAAU,IAAI,IAC5D,aAAa,WAAW,QAAQ,WAAW,GAEvB;EAAC;EAAI;EAAO;EAAM,CAE1B,SAAS,YAAY;AAOjC,SALsB,KACpB,aAAa,QAAQ,aAAa,IAClC,aAAa,UACd,EAEqB,EAAE,OAAO,MAAM,CAAC;GACtC;AAGJ,KACE,aAAa,WAAW,UACxB,aAAa,WAAW,QAAQ,SAAS,GACzC;AACA,UAAQ,IAAI,+BAA+B;AAC3C,QAAM,UAAU,MAAM;;AAGxB,KAAI,oBAAoB,OAAO,KAAK,iBAAiB,CAAC,SAAS,GAAG;EAChE,MAAM,YAAY,aAAa,QAAQ;AACvC,MAAI,CAAC,UACH,OAAM,IAAI,MACR,yEACD;AAGH,OAAK,MAAM,CAAC,OAAO,YAAY,OAAO,QAAQ,iBAAiB,EAAE;GAC/D,MAAM,aAAa,KAAK,WAAW,GAAG,MAAM,KAAK;AAEjD,aADkB,QAAQ,WAAW,EAChB,EAAE,WAAW,MAAM,CAAC;AAEzC,iBAAc,YAAY,SAAS,OAAO;;;AAI9C,KAAI,CAAC,SAAS,QAAQ;AACpB,UAAQ,IAAI,qBAAqB;AACjC,QAAM,MAAM,MAAM;;AAGpB,OAAM,MAAM,OAAO"}
|
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { UserConfig } from
|
|
2
|
-
import { PrerenderSitemapConfig, SitemapConfig } from
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { UserConfig } from "vite";
|
|
2
|
+
import { I18nPrerenderOptions, PrerenderSitemapConfig, SitemapConfig, SitemapEntry } from "./options";
|
|
3
|
+
type RouteSitemapConfig = PrerenderSitemapConfig | (() => PrerenderSitemapConfig) | undefined;
|
|
4
|
+
export type PagesJson = SitemapEntry;
|
|
5
|
+
export interface BuildSitemapOptions {
|
|
6
|
+
apiPrefix?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function buildSitemap(_config: UserConfig, sitemapConfig: SitemapConfig, routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>), outputDir: string, routeSitemaps: Record<string, RouteSitemapConfig>, buildOptions?: BuildSitemapOptions): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* Generates hreflang alternate URLs for a given page URL.
|
|
11
|
+
* For a URL like `https://example.com/fr/about`, it produces alternates
|
|
12
|
+
* for all configured locales.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getHreflangAlternates(pageUrl: string, host: string, i18n: I18nPrerenderOptions): {
|
|
15
|
+
locale: string;
|
|
16
|
+
href: string;
|
|
17
|
+
}[];
|
|
18
|
+
/**
|
|
19
|
+
* Strips a locale prefix from a URL path.
|
|
20
|
+
* E.g., '/fr/about' -> '/about', '/en' -> '/'
|
|
21
|
+
*/
|
|
22
|
+
export declare function stripLocalePrefix(path: string, locales: string[]): string;
|
|
23
|
+
export {};
|
package/src/lib/build-sitemap.js
CHANGED
|
@@ -1,55 +1,136 @@
|
|
|
1
1
|
import { resolve } from "node:path";
|
|
2
|
-
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { create } from "xmlbuilder2";
|
|
4
4
|
//#region packages/vite-plugin-nitro/src/lib/build-sitemap.ts
|
|
5
|
-
async function buildSitemap(
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
5
|
+
async function buildSitemap(_config, sitemapConfig, routes, outputDir, routeSitemaps, buildOptions = {}) {
|
|
6
|
+
const host = normalizeSitemapHost(sitemapConfig.host);
|
|
7
|
+
const sitemapData = await resolveSitemapEntries(await collectSitemapRoutes(routes, sitemapConfig.include), host, routeSitemaps, sitemapConfig, buildOptions);
|
|
8
|
+
if (!sitemapData.length) return;
|
|
9
|
+
const sitemap = createXml("urlset");
|
|
10
|
+
for (const item of sitemapData) {
|
|
11
|
+
const page = sitemap.ele("url");
|
|
12
|
+
page.ele("loc").txt(item.loc);
|
|
13
|
+
if (item.lastmod) page.ele("lastmod").txt(item.lastmod);
|
|
14
|
+
if (item.changefreq) page.ele("changefreq").txt(item.changefreq);
|
|
15
|
+
if (item.priority !== void 0) page.ele("priority").txt(String(item.priority));
|
|
16
|
+
}
|
|
17
|
+
const resolvedOutputDir = resolve(outputDir);
|
|
18
|
+
const mapPath = resolve(resolvedOutputDir, "sitemap.xml");
|
|
19
|
+
try {
|
|
20
|
+
if (!resolvedOutputDir || resolvedOutputDir === resolve()) throw new Error("Refusing to write the sitemap to the current working directory. Expected the Nitro public output directory instead.");
|
|
21
|
+
if (!existsSync(resolvedOutputDir)) mkdirSync(resolvedOutputDir, { recursive: true });
|
|
22
|
+
console.log(`Writing sitemap at ${mapPath}`);
|
|
23
|
+
writeFileSync(mapPath, sitemap.end({ prettyPrint: true }));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
console.error(`Unable to write file at ${mapPath}`, e);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function resolveSitemapEntries(routes, host, routeSitemaps, sitemapConfig, buildOptions) {
|
|
29
|
+
const defaults = sitemapConfig.defaults ?? {};
|
|
30
|
+
const seen = /* @__PURE__ */ new Set();
|
|
31
|
+
const entries = [];
|
|
32
|
+
for (const route of routes) {
|
|
33
|
+
const entry = await toSitemapEntry(route, host, routeSitemaps, defaults, sitemapConfig.transform);
|
|
34
|
+
if (!entry) continue;
|
|
35
|
+
if (isInternalSitemapRoute(entry.route, buildOptions.apiPrefix) || await isExcludedSitemapRoute(entry, sitemapConfig.exclude)) continue;
|
|
36
|
+
if (seen.has(entry.loc)) continue;
|
|
37
|
+
seen.add(entry.loc);
|
|
38
|
+
entries.push(entry);
|
|
39
|
+
}
|
|
40
|
+
return entries;
|
|
41
|
+
}
|
|
42
|
+
async function toSitemapEntry(route, host, routeSitemaps, defaults, transform) {
|
|
43
|
+
const normalizedRoute = normalizeSitemapRoute(typeof route === "string" ? route : route?.route);
|
|
44
|
+
if (!normalizedRoute) return;
|
|
45
|
+
const baseEntry = createSitemapEntry({
|
|
46
|
+
...defaults,
|
|
47
|
+
...resolveRouteSitemapConfig(routeSitemaps[normalizedRoute]),
|
|
48
|
+
...typeof route === "object" ? route : {},
|
|
49
|
+
route: normalizedRoute
|
|
50
|
+
}, host);
|
|
51
|
+
if (!transform) return baseEntry;
|
|
52
|
+
const transformed = await transform(baseEntry);
|
|
53
|
+
if (!transformed) return;
|
|
54
|
+
return createSitemapEntry({
|
|
55
|
+
...baseEntry,
|
|
56
|
+
...transformed
|
|
57
|
+
}, host);
|
|
58
|
+
}
|
|
59
|
+
function createSitemapEntry(routeDefinition, host) {
|
|
60
|
+
const route = normalizeSitemapRoute(routeDefinition.route) ?? "/";
|
|
61
|
+
return {
|
|
62
|
+
route,
|
|
63
|
+
loc: new URL(route, ensureTrailingSlash(host)).toString(),
|
|
64
|
+
lastmod: routeDefinition.lastmod,
|
|
65
|
+
changefreq: routeDefinition.changefreq,
|
|
66
|
+
priority: routeDefinition.priority
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function resolveRouteSitemapConfig(config) {
|
|
70
|
+
if (!config) return {};
|
|
71
|
+
return typeof config === "function" ? config() : config;
|
|
72
|
+
}
|
|
73
|
+
function normalizeSitemapHost(host) {
|
|
74
|
+
const resolvedHost = new URL(host);
|
|
75
|
+
resolvedHost.hash = "";
|
|
76
|
+
return resolvedHost.toString();
|
|
77
|
+
}
|
|
78
|
+
function ensureTrailingSlash(host) {
|
|
79
|
+
return host.endsWith("/") ? host : `${host}/`;
|
|
80
|
+
}
|
|
81
|
+
function normalizeSitemapRoute(route) {
|
|
82
|
+
if (!route) return;
|
|
83
|
+
const trimmedRoute = route.trim();
|
|
84
|
+
if (!trimmedRoute) return;
|
|
85
|
+
const [pathname, search] = (trimmedRoute.split("#", 1)[0] ?? "").split("?", 2);
|
|
86
|
+
const normalizedPathname = pathname ? `/${pathname.replace(/^\/+/, "").replace(/\/{2,}/g, "/")}` : "/";
|
|
87
|
+
return search ? `${normalizedPathname}?${search}` : normalizedPathname;
|
|
88
|
+
}
|
|
89
|
+
function isInternalSitemapRoute(route, apiPrefix = "api") {
|
|
90
|
+
const normalizedApiPrefix = normalizeSitemapRoute(`/${apiPrefix}`) ?? "/api";
|
|
91
|
+
return route === `${normalizedApiPrefix}/_analog/pages` || route.startsWith(`${normalizedApiPrefix}/_analog/pages/`);
|
|
92
|
+
}
|
|
93
|
+
async function isExcludedSitemapRoute(entry, excludeRules) {
|
|
94
|
+
if (!excludeRules?.length) return false;
|
|
95
|
+
for (const rule of excludeRules) {
|
|
96
|
+
if (typeof rule === "function") {
|
|
97
|
+
if (await rule(entry)) return true;
|
|
98
|
+
continue;
|
|
27
99
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
writeFileSync(mapPath, sitemap.end({ prettyPrint: true }));
|
|
32
|
-
} catch (e) {
|
|
33
|
-
console.error(`Unable to write file at ${mapPath}`, e);
|
|
100
|
+
if (rule instanceof RegExp) {
|
|
101
|
+
if (rule.test(entry.route)) return true;
|
|
102
|
+
continue;
|
|
34
103
|
}
|
|
104
|
+
if (toGlobRegExp(rule).test(entry.route)) return true;
|
|
35
105
|
}
|
|
106
|
+
return false;
|
|
36
107
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
108
|
+
function toGlobRegExp(pattern) {
|
|
109
|
+
const doubleStarToken = "__ANALOG_DOUBLE_STAR__";
|
|
110
|
+
const singleStarToken = "__ANALOG_SINGLE_STAR__";
|
|
111
|
+
const regexPattern = pattern.replace(/\*\*/g, doubleStarToken).replace(/\*/g, singleStarToken).replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(new RegExp(doubleStarToken, "g"), ".*").replace(new RegExp(singleStarToken, "g"), "[^/]*");
|
|
112
|
+
return new RegExp(`^${regexPattern}$`);
|
|
42
113
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
114
|
+
async function collectSitemapRoutes(routes, include) {
|
|
115
|
+
const routeList = await resolveRouteInputs(routes);
|
|
116
|
+
const includedRoutes = include ? await resolveRouteInputs(include) : [];
|
|
117
|
+
return [...routeList, ...includedRoutes];
|
|
45
118
|
}
|
|
46
|
-
async function
|
|
119
|
+
async function resolveRouteInputs(routes) {
|
|
47
120
|
let routeList;
|
|
48
121
|
if (typeof routes === "function") routeList = await routes();
|
|
49
122
|
else if (Array.isArray(routes)) routeList = routes;
|
|
50
123
|
else routeList = [];
|
|
51
124
|
return routeList.filter(Boolean);
|
|
52
125
|
}
|
|
126
|
+
function createXml(elementName, includeXhtml = false) {
|
|
127
|
+
const attrs = { xmlns: "https://www.sitemaps.org/schemas/sitemap/0.9" };
|
|
128
|
+
if (includeXhtml) attrs["xmlns:xhtml"] = "https://www.w3.org/1999/xhtml";
|
|
129
|
+
return create({
|
|
130
|
+
version: "1.0",
|
|
131
|
+
encoding: "UTF-8"
|
|
132
|
+
}).ele(elementName, attrs).com(`This file was automatically generated by Analog.`);
|
|
133
|
+
}
|
|
53
134
|
//#endregion
|
|
54
135
|
export { buildSitemap };
|
|
55
136
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-sitemap.js","names":[],"sources":["../../../../../packages/vite-plugin-nitro/src/lib/build-sitemap.ts"],"sourcesContent":["import { writeFileSync } from 'node:fs';\nimport { XMLBuilder } from 'xmlbuilder2/lib/interfaces';\nimport { create } from 'xmlbuilder2';\nimport { UserConfig } from 'vite';\nimport { resolve } from 'node:path';\nimport {\n PrerenderContentFile,\n PrerenderSitemapConfig,\n SitemapConfig,\n} from './options';\n\nexport type PagesJson = {\n page: string;\n lastMod: string;\n changefreq?: string;\n priority?: string;\n};\n\nexport async function buildSitemap(\n config: UserConfig,\n sitemapConfig: SitemapConfig,\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n outputDir: string,\n routeSitemaps: Record<\n string,\n PrerenderSitemapConfig | (() => PrerenderSitemapConfig) | undefined\n >,\n): Promise<void> {\n const routeList: string[] = await optionHasRoutes(routes);\n\n if (routeList.length) {\n const slash = checkSlash(sitemapConfig.host || '');\n const sitemapData: PagesJson[] = routeList.map((page: string) => {\n const url = `${slash}${page.replace(/^\\/+/g, '')}`;\n const config = routeSitemaps[url];\n const props = typeof config === 'object' ? config : config?.();\n\n return {\n page: `${sitemapConfig.host}${url}`,\n lastMod: props?.lastmod ?? new Date().toISOString().split('T')[0],\n changefreq: props?.changefreq,\n priority: props?.priority,\n };\n });\n\n const sitemap = createXml('urlset');\n\n for (const item of sitemapData) {\n const page = sitemap.ele('url');\n page.ele('loc').txt(item.page);\n page.ele('lastmod').txt(item.lastMod);\n\n if (item.changefreq) {\n page.ele('changefreq').txt(item.changefreq);\n }\n\n if (item.priority) {\n page.ele('priority').txt(item.priority);\n }\n }\n\n const mapPath = `${resolve(outputDir)}/sitemap.xml`;\n try {\n console.log(`Writing sitemap at ${mapPath}`);\n writeFileSync(mapPath, sitemap.end({ prettyPrint: true }));\n } catch (e) {\n console.error(`Unable to write file at ${mapPath}`, e);\n }\n }\n}\n\nfunction createXml(elementName: 'urlset' | 'sitemapindex'): XMLBuilder {\n return create({ version: '1.0', encoding: 'UTF-8' })\n .ele(elementName, {\n xmlns: 'https://www.sitemaps.org/schemas/sitemap/0.9',\n })\n .com(`This file was automatically generated by Analog.`);\n}\n\nfunction checkSlash(host: string): string {\n const finalChar = host.slice(-1);\n return finalChar === '/' ? '' : '/';\n}\n\nasync function optionHasRoutes(\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n): Promise<string[]> {\n let routeList: (string | undefined)[];\n\n if (typeof routes === 'function') {\n // returns an array or undefined\n routeList = await routes();\n } else if (Array.isArray(routes)) {\n // returns an array of strings\n routeList = routes;\n } else {\n // default it to an empty of array\n routeList = [];\n }\n\n return routeList.filter(Boolean) as string[];\n}\n"],"mappings":";;;;AAkBA,eAAsB,aACpB,QACA,eACA,QACA,WACA,eAIe;CACf,MAAM,YAAsB,MAAM,gBAAgB,OAAO;AAEzD,KAAI,UAAU,QAAQ;EACpB,MAAM,QAAQ,WAAW,cAAc,QAAQ,GAAG;EAClD,MAAM,cAA2B,UAAU,KAAK,SAAiB;GAC/D,MAAM,MAAM,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;GAChD,MAAM,SAAS,cAAc;GAC7B,MAAM,QAAQ,OAAO,WAAW,WAAW,SAAS,UAAU;AAE9D,UAAO;IACL,MAAM,GAAG,cAAc,OAAO;IAC9B,SAAS,OAAO,4BAAW,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,IAAI,CAAC;IAC/D,YAAY,OAAO;IACnB,UAAU,OAAO;IAClB;IACD;EAEF,MAAM,UAAU,UAAU,SAAS;AAEnC,OAAK,MAAM,QAAQ,aAAa;GAC9B,MAAM,OAAO,QAAQ,IAAI,MAAM;AAC/B,QAAK,IAAI,MAAM,CAAC,IAAI,KAAK,KAAK;AAC9B,QAAK,IAAI,UAAU,CAAC,IAAI,KAAK,QAAQ;AAErC,OAAI,KAAK,WACP,MAAK,IAAI,aAAa,CAAC,IAAI,KAAK,WAAW;AAG7C,OAAI,KAAK,SACP,MAAK,IAAI,WAAW,CAAC,IAAI,KAAK,SAAS;;EAI3C,MAAM,UAAU,GAAG,QAAQ,UAAU,CAAC;AACtC,MAAI;AACF,WAAQ,IAAI,sBAAsB,UAAU;AAC5C,iBAAc,SAAS,QAAQ,IAAI,EAAE,aAAa,MAAM,CAAC,CAAC;WACnD,GAAG;AACV,WAAQ,MAAM,2BAA2B,WAAW,EAAE;;;;AAK5D,SAAS,UAAU,aAAoD;AACrE,QAAO,OAAO;EAAE,SAAS;EAAO,UAAU;EAAS,CAAC,CACjD,IAAI,aAAa,EAChB,OAAO,gDACR,CAAC,CACD,IAAI,mDAAmD;;AAG5D,SAAS,WAAW,MAAsB;AAExC,QADkB,KAAK,MAAM,GAAG,KACX,MAAM,KAAK;;AAGlC,eAAe,gBACb,QACmB;CACnB,IAAI;AAEJ,KAAI,OAAO,WAAW,WAEpB,aAAY,MAAM,QAAQ;UACjB,MAAM,QAAQ,OAAO,CAE9B,aAAY;KAGZ,aAAY,EAAE;AAGhB,QAAO,UAAU,OAAO,QAAQ"}
|
|
1
|
+
{"version":3,"file":"build-sitemap.js","names":[],"sources":["../../../src/lib/build-sitemap.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { create } from 'xmlbuilder2';\nimport { XMLBuilder } from 'xmlbuilder2/lib/interfaces';\nimport { UserConfig } from 'vite';\nimport {\n I18nPrerenderOptions,\n PrerenderSitemapConfig,\n SitemapConfig,\n SitemapEntry,\n SitemapExcludeRule,\n SitemapRouteDefinition,\n SitemapRouteInput,\n SitemapRouteSource,\n} from './options';\n\ntype RouteSitemapConfig =\n | PrerenderSitemapConfig\n | (() => PrerenderSitemapConfig)\n | undefined;\n\nexport type PagesJson = SitemapEntry;\n\nexport interface BuildSitemapOptions {\n apiPrefix?: string;\n}\n\nexport async function buildSitemap(\n _config: UserConfig,\n sitemapConfig: SitemapConfig,\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n outputDir: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n buildOptions: BuildSitemapOptions = {},\n): Promise<void> {\n const host = normalizeSitemapHost(sitemapConfig.host);\n const routeList = await collectSitemapRoutes(routes, sitemapConfig.include);\n const sitemapData = await resolveSitemapEntries(\n routeList,\n host,\n routeSitemaps,\n sitemapConfig,\n buildOptions,\n );\n\n if (!sitemapData.length) {\n return;\n }\n\n const sitemap = createXml('urlset');\n\n for (const item of sitemapData) {\n const page = sitemap.ele('url');\n page.ele('loc').txt(item.loc);\n\n if (item.lastmod) {\n page.ele('lastmod').txt(item.lastmod);\n }\n\n if (item.changefreq) {\n page.ele('changefreq').txt(item.changefreq);\n }\n\n if (item.priority !== undefined) {\n page.ele('priority').txt(String(item.priority));\n }\n }\n\n const resolvedOutputDir = resolve(outputDir);\n const mapPath = resolve(resolvedOutputDir, 'sitemap.xml');\n try {\n if (!resolvedOutputDir || resolvedOutputDir === resolve()) {\n throw new Error(\n 'Refusing to write the sitemap to the current working directory. Expected the Nitro public output directory instead.',\n );\n }\n\n if (!existsSync(resolvedOutputDir)) {\n mkdirSync(resolvedOutputDir, { recursive: true });\n }\n console.log(`Writing sitemap at ${mapPath}`);\n writeFileSync(mapPath, sitemap.end({ prettyPrint: true }));\n } catch (e) {\n console.error(`Unable to write file at ${mapPath}`, e);\n }\n}\n\nasync function resolveSitemapEntries(\n routes: SitemapRouteInput[],\n host: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n sitemapConfig: SitemapConfig,\n buildOptions: BuildSitemapOptions,\n): Promise<SitemapEntry[]> {\n const defaults = sitemapConfig.defaults ?? {};\n const seen = new Set<string>();\n const entries: SitemapEntry[] = [];\n\n for (const route of routes) {\n const entry = await toSitemapEntry(\n route,\n host,\n routeSitemaps,\n defaults,\n sitemapConfig.transform,\n );\n\n if (!entry) {\n continue;\n }\n\n if (\n isInternalSitemapRoute(entry.route, buildOptions.apiPrefix) ||\n (await isExcludedSitemapRoute(entry, sitemapConfig.exclude))\n ) {\n continue;\n }\n\n if (seen.has(entry.loc)) {\n continue;\n }\n\n seen.add(entry.loc);\n entries.push(entry);\n }\n\n return entries;\n}\n\nasync function toSitemapEntry(\n route: SitemapRouteInput,\n host: string,\n routeSitemaps: Record<string, RouteSitemapConfig>,\n defaults: PrerenderSitemapConfig,\n transform: SitemapConfig['transform'],\n): Promise<SitemapEntry | undefined> {\n const normalizedRoute = normalizeSitemapRoute(\n typeof route === 'string' ? route : route?.route,\n );\n if (!normalizedRoute) {\n return undefined;\n }\n\n const baseEntry = createSitemapEntry(\n {\n ...defaults,\n ...resolveRouteSitemapConfig(routeSitemaps[normalizedRoute]),\n ...(typeof route === 'object' ? route : {}),\n route: normalizedRoute,\n },\n host,\n );\n\n if (!transform) {\n return baseEntry;\n }\n\n const transformed = await transform(baseEntry);\n if (!transformed) {\n return undefined;\n }\n\n return createSitemapEntry(\n {\n ...baseEntry,\n ...transformed,\n },\n host,\n );\n}\n\nfunction createSitemapEntry(\n routeDefinition: SitemapRouteDefinition,\n host: string,\n): SitemapEntry {\n const route = normalizeSitemapRoute(routeDefinition.route) ?? '/';\n\n return {\n route,\n loc: new URL(route, ensureTrailingSlash(host)).toString(),\n lastmod: routeDefinition.lastmod,\n changefreq: routeDefinition.changefreq,\n priority: routeDefinition.priority,\n };\n}\n\nfunction resolveRouteSitemapConfig(\n config: RouteSitemapConfig,\n): PrerenderSitemapConfig {\n if (!config) {\n return {};\n }\n\n return typeof config === 'function' ? config() : config;\n}\n\nfunction normalizeSitemapHost(host: string): string {\n const resolvedHost = new URL(host);\n resolvedHost.hash = '';\n return resolvedHost.toString();\n}\n\nfunction ensureTrailingSlash(host: string): string {\n return host.endsWith('/') ? host : `${host}/`;\n}\n\nfunction normalizeSitemapRoute(route: string | undefined): string | undefined {\n if (!route) {\n return undefined;\n }\n\n const trimmedRoute = route.trim();\n if (!trimmedRoute) {\n return undefined;\n }\n\n const pathWithQuery = trimmedRoute.split('#', 1)[0] ?? '';\n const [pathname, search] = pathWithQuery.split('?', 2);\n const normalizedPathname = pathname\n ? `/${pathname.replace(/^\\/+/, '').replace(/\\/{2,}/g, '/')}`\n : '/';\n\n return search ? `${normalizedPathname}?${search}` : normalizedPathname;\n}\n\nfunction isInternalSitemapRoute(route: string, apiPrefix = 'api'): boolean {\n const normalizedApiPrefix = normalizeSitemapRoute(`/${apiPrefix}`) ?? '/api';\n return (\n route === `${normalizedApiPrefix}/_analog/pages` ||\n route.startsWith(`${normalizedApiPrefix}/_analog/pages/`)\n );\n}\n\nasync function isExcludedSitemapRoute(\n entry: SitemapEntry,\n excludeRules: SitemapExcludeRule[] | undefined,\n): Promise<boolean> {\n if (!excludeRules?.length) {\n return false;\n }\n\n for (const rule of excludeRules) {\n if (typeof rule === 'function') {\n if (await rule(entry)) {\n return true;\n }\n continue;\n }\n\n if (rule instanceof RegExp) {\n if (rule.test(entry.route)) {\n return true;\n }\n continue;\n }\n\n if (toGlobRegExp(rule).test(entry.route)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction toGlobRegExp(pattern: string): RegExp {\n const doubleStarToken = '__ANALOG_DOUBLE_STAR__';\n const singleStarToken = '__ANALOG_SINGLE_STAR__';\n const escapedPattern = pattern\n .replace(/\\*\\*/g, doubleStarToken)\n .replace(/\\*/g, singleStarToken)\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n const regexPattern = escapedPattern\n .replace(new RegExp(doubleStarToken, 'g'), '.*')\n .replace(new RegExp(singleStarToken, 'g'), '[^/]*');\n return new RegExp(`^${regexPattern}$`);\n}\n\nasync function collectSitemapRoutes(\n routes: (string | undefined)[] | (() => Promise<(string | undefined)[]>),\n include?: SitemapRouteSource,\n): Promise<SitemapRouteInput[]> {\n const routeList = await resolveRouteInputs(routes);\n const includedRoutes = include ? await resolveRouteInputs(include) : [];\n return [...routeList, ...includedRoutes];\n}\n\nasync function resolveRouteInputs(\n routes:\n | SitemapRouteSource\n | (string | undefined)[]\n | (() => Promise<(string | undefined)[]>),\n): Promise<SitemapRouteInput[]> {\n let routeList: SitemapRouteInput[];\n\n if (typeof routes === 'function') {\n routeList = await routes();\n } else if (Array.isArray(routes)) {\n routeList = routes;\n } else {\n routeList = [];\n }\n\n return routeList.filter(Boolean);\n}\n\n/**\n * Generates hreflang alternate URLs for a given page URL.\n * For a URL like `https://example.com/fr/about`, it produces alternates\n * for all configured locales.\n */\nexport function getHreflangAlternates(\n pageUrl: string,\n host: string,\n i18n: I18nPrerenderOptions,\n): { locale: string; href: string }[] {\n const alternates: { locale: string; href: string }[] = [];\n const normalizedHost = host.replace(/\\/+$/, '');\n\n // Extract the path portion after the host\n const path = pageUrl.replace(normalizedHost, '');\n\n // Strip locale prefix to get the base path\n const basePath = stripLocalePrefix(path, i18n.locales);\n\n for (const locale of i18n.locales) {\n const localizedPath =\n basePath === '/' || basePath === ''\n ? `/${locale}`\n : `/${locale}${basePath}`;\n alternates.push({\n locale,\n href: `${normalizedHost}${localizedPath}`,\n });\n }\n\n // Add x-default pointing to the default locale variant\n const defaultPath =\n basePath === '/' || basePath === ''\n ? `/${i18n.defaultLocale}`\n : `/${i18n.defaultLocale}${basePath}`;\n alternates.push({\n locale: 'x-default',\n href: `${normalizedHost}${defaultPath}`,\n });\n\n return alternates;\n}\n\n/**\n * Strips a locale prefix from a URL path.\n * E.g., '/fr/about' -> '/about', '/en' -> '/'\n */\nexport function stripLocalePrefix(path: string, locales: string[]): string {\n const segments = path.split('/').filter(Boolean);\n if (segments.length > 0 && locales.includes(segments[0])) {\n const rest = segments.slice(1).join('/');\n return rest ? `/${rest}` : '/';\n }\n return path || '/';\n}\n\nfunction createXml(\n elementName: 'urlset' | 'sitemapindex',\n includeXhtml = false,\n): XMLBuilder {\n const attrs: Record<string, string> = {\n xmlns: 'https://www.sitemaps.org/schemas/sitemap/0.9',\n };\n if (includeXhtml) {\n attrs['xmlns:xhtml'] = 'https://www.w3.org/1999/xhtml';\n }\n\n return create({ version: '1.0', encoding: 'UTF-8' })\n .ele(elementName, attrs)\n .com(`This file was automatically generated by Analog.`);\n}\n"],"mappings":"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"}
|
package/src/lib/build-ssr.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import { UserConfig } from
|
|
2
|
-
import { Options } from
|
|
1
|
+
import { UserConfig } from "vite";
|
|
2
|
+
import { Options } from "./options.js";
|
|
3
|
+
export declare function buildClientApp(config: UserConfig, options?: Options): Promise<void>;
|
|
3
4
|
export declare function buildSSRApp(config: UserConfig, options?: Options): Promise<void>;
|
package/src/lib/build-ssr.js
CHANGED
|
@@ -2,6 +2,15 @@ import { getBundleOptionsKey } from "./utils/rolldown.js";
|
|
|
2
2
|
import { build, mergeConfig } from "vite";
|
|
3
3
|
import { relative, resolve } from "node:path";
|
|
4
4
|
//#region packages/vite-plugin-nitro/src/lib/build-ssr.ts
|
|
5
|
+
async function buildClientApp(config, options) {
|
|
6
|
+
const workspaceRoot = options?.workspaceRoot ?? process.cwd();
|
|
7
|
+
const rootDir = relative(workspaceRoot, config.root || ".") || ".";
|
|
8
|
+
await build(mergeConfig(config, { build: {
|
|
9
|
+
ssr: false,
|
|
10
|
+
outDir: config.build?.outDir || resolve(workspaceRoot, "dist", rootDir, "client"),
|
|
11
|
+
emptyOutDir: true
|
|
12
|
+
} }));
|
|
13
|
+
}
|
|
5
14
|
async function buildSSRApp(config, options) {
|
|
6
15
|
const workspaceRoot = options?.workspaceRoot ?? process.cwd();
|
|
7
16
|
const sourceRoot = options?.sourceRoot ?? "src";
|
|
@@ -9,10 +18,11 @@ async function buildSSRApp(config, options) {
|
|
|
9
18
|
await build(mergeConfig(config, { build: {
|
|
10
19
|
ssr: true,
|
|
11
20
|
[getBundleOptionsKey()]: { input: options?.entryServer || resolve(workspaceRoot, rootDir, `${sourceRoot}/main.server.ts`) },
|
|
12
|
-
outDir: options?.ssrBuildDir || resolve(workspaceRoot, "dist", rootDir, "ssr")
|
|
21
|
+
outDir: options?.ssrBuildDir || resolve(workspaceRoot, "dist", rootDir, "ssr"),
|
|
22
|
+
emptyOutDir: false
|
|
13
23
|
} }));
|
|
14
24
|
}
|
|
15
25
|
//#endregion
|
|
16
|
-
export { buildSSRApp };
|
|
26
|
+
export { buildClientApp, buildSSRApp };
|
|
17
27
|
|
|
18
28
|
//# sourceMappingURL=build-ssr.js.map
|
package/src/lib/build-ssr.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-ssr.js","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"build-ssr.js","names":[],"sources":["../../../src/lib/build-ssr.ts"],"sourcesContent":["import { build, mergeConfig, UserConfig } from 'vite';\nimport { relative, resolve } from 'node:path';\n\nimport { Options } from './options.js';\nimport { getBundleOptionsKey } from './utils/rolldown.js';\n\nexport async function buildClientApp(\n config: UserConfig,\n options?: Options,\n): Promise<void> {\n const workspaceRoot = options?.workspaceRoot ?? process.cwd();\n const rootDir = relative(workspaceRoot, config.root || '.') || '.';\n const clientBuildConfig = mergeConfig(config, <UserConfig>{\n build: {\n ssr: false,\n outDir:\n config.build?.outDir ||\n resolve(workspaceRoot, 'dist', rootDir, 'client'),\n emptyOutDir: true,\n },\n });\n\n await build(clientBuildConfig);\n}\n\nexport async function buildSSRApp(\n config: UserConfig,\n options?: Options,\n): Promise<void> {\n const workspaceRoot = options?.workspaceRoot ?? process.cwd();\n const sourceRoot = options?.sourceRoot ?? 'src';\n const rootDir = relative(workspaceRoot, config.root || '.') || '.';\n const bundleOptionsKey = getBundleOptionsKey();\n\n /**\n * SSR is built as a second pass from the already prepared Analog/Vite config.\n *\n * That means we intentionally start from the same base config used for the\n * client build and then merge only the SSR-specific overrides (entry, outDir,\n * `build.ssr`, etc).\n *\n * A side effect of this design is that the resolved SSR config can expose the\n * same high-level Analog plugin chain more than once when Vite/Nitro replays\n * shared plugins for the server environment. In particular,\n * `@analogjs/vite-plugin-angular` may appear twice in `config.plugins` during\n * SSR resolution:\n * - once from the normal Analog platform plugin expansion\n * - once from the reused/shared plugin graph for the SSR pass\n *\n * That does NOT imply the client build has two competing style registries.\n * The client-side duplicate-registration guard in `vite-plugin-angular`\n * therefore explicitly ignores `build.ssr === true` to avoid treating this\n * valid SSR orchestration detail as a style-map bug.\n */\n const ssrBuildConfig = mergeConfig(config, <UserConfig>{\n build: {\n ssr: true,\n [bundleOptionsKey]: {\n input:\n options?.entryServer ||\n resolve(workspaceRoot, rootDir, `${sourceRoot}/main.server.ts`),\n },\n outDir:\n options?.ssrBuildDir || resolve(workspaceRoot, 'dist', rootDir, 'ssr'),\n // Preserve the client build output. The client pass already handled its\n // own cleanup, and on Windows this nested SSR build can otherwise remove\n // sibling artifacts that Nitro needs to read immediately afterward.\n emptyOutDir: false,\n },\n });\n\n await build(ssrBuildConfig);\n}\n"],"mappings":";;;;AAMA,eAAsB,eACpB,QACA,SACe;CACf,MAAM,gBAAgB,SAAS,iBAAiB,QAAQ,KAAK;CAC7D,MAAM,UAAU,SAAS,eAAe,OAAO,QAAQ,IAAI,IAAI;AAW/D,OAAM,MAVoB,YAAY,QAAoB,EACxD,OAAO;EACL,KAAK;EACL,QACE,OAAO,OAAO,UACd,QAAQ,eAAe,QAAQ,SAAS,SAAS;EACnD,aAAa;EACd,EACF,CAAC,CAE4B;;AAGhC,eAAsB,YACpB,QACA,SACe;CACf,MAAM,gBAAgB,SAAS,iBAAiB,QAAQ,KAAK;CAC7D,MAAM,aAAa,SAAS,cAAc;CAC1C,MAAM,UAAU,SAAS,eAAe,OAAO,QAAQ,IAAI,IAAI;AAwC/D,OAAM,MAjBiB,YAAY,QAAoB,EACrD,OAAO;EACL,KAAK;GAxBgB,qBAAqB,GAyBtB,EAClB,OACE,SAAS,eACT,QAAQ,eAAe,SAAS,GAAG,WAAW,iBAAiB,EAClE;EACD,QACE,SAAS,eAAe,QAAQ,eAAe,QAAQ,SAAS,MAAM;EAIxE,aAAa;EACd,EACF,CAAC,CAEyB"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Nitro, PrerenderRoute } from
|
|
1
|
+
import type { Nitro, PrerenderRoute } from "nitro/types";
|
|
2
2
|
export declare function addPostRenderingHooks(nitro: Nitro, hooks: ((pr: PrerenderRoute) => Promise<void>)[]): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-rendering-hook.js","names":[],"sources":["
|
|
1
|
+
{"version":3,"file":"post-rendering-hook.js","names":[],"sources":["../../../../src/lib/hooks/post-rendering-hook.ts"],"sourcesContent":["import type { Nitro, PrerenderRoute } from 'nitro/types';\n\nexport function addPostRenderingHooks(\n nitro: Nitro,\n hooks: ((pr: PrerenderRoute) => Promise<void>)[],\n): void {\n hooks.forEach((hook: (preRoute: PrerenderRoute) => void) => {\n nitro.hooks.hook('prerender:generate', (route: PrerenderRoute) => {\n hook(route);\n });\n });\n}\n"],"mappings":";AAEA,SAAgB,sBACd,OACA,OACM;AACN,OAAM,SAAS,SAA6C;AAC1D,QAAM,MAAM,KAAK,uBAAuB,UAA0B;AAChE,QAAK,MAAM;IACX;GACF"}
|