@analogjs/platform 3.0.0-alpha.55 → 3.0.0-alpha.57
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/migrations/migration.json +7 -1
- package/package.json +8 -5
- package/src/index.d.ts +2 -0
- package/src/index.js +2 -1
- package/src/index.js.map +1 -1
- package/src/lib/deps-plugin.js +2 -5
- package/src/lib/deps-plugin.js.map +1 -1
- package/src/lib/discover-library-routes.d.ts +9 -0
- package/src/lib/discover-library-routes.js +12 -1
- package/src/lib/discover-library-routes.js.map +1 -1
- package/src/lib/nitro/analog-nitro-plugin.d.ts +8 -0
- package/src/lib/nitro/analog-nitro-plugin.js +502 -0
- package/src/lib/nitro/analog-nitro-plugin.js.map +1 -0
- package/src/lib/nitro/angular-linker-plugin.d.ts +17 -0
- package/src/lib/nitro/angular-linker-plugin.js +51 -0
- package/src/lib/nitro/angular-linker-plugin.js.map +1 -0
- package/src/lib/nitro/build-sitemap.d.ts +23 -0
- package/src/lib/nitro/build-sitemap.js +137 -0
- package/src/lib/nitro/build-sitemap.js.map +1 -0
- package/src/lib/nitro/debug.d.ts +5 -0
- package/src/lib/nitro/debug.js +11 -0
- package/src/lib/nitro/debug.js.map +1 -0
- package/src/lib/nitro/get-content-files.d.ts +13 -0
- package/src/lib/nitro/get-content-files.js +52 -0
- package/src/lib/nitro/get-content-files.js.map +1 -0
- package/src/lib/nitro/get-page-handlers.d.ts +23 -0
- package/src/lib/nitro/get-page-handlers.js +34 -0
- package/src/lib/nitro/get-page-handlers.js.map +1 -0
- package/src/lib/nitro/i18n-prerender.d.ts +29 -0
- package/src/lib/nitro/i18n-prerender.js +61 -0
- package/src/lib/nitro/i18n-prerender.js.map +1 -0
- package/src/lib/nitro/page-endpoints-plugin.d.ts +7 -0
- package/src/lib/nitro/page-endpoints-plugin.js +72 -0
- package/src/lib/nitro/page-endpoints-plugin.js.map +1 -0
- package/src/lib/nitro/post-rendering-hook.d.ts +2 -0
- package/src/lib/nitro/post-rendering-hook.js +10 -0
- package/src/lib/nitro/post-rendering-hook.js.map +1 -0
- package/src/lib/nitro/renderers.d.ts +53 -0
- package/src/lib/nitro/renderers.js +22 -0
- package/src/lib/nitro/renderers.js.map +1 -0
- package/src/lib/nitro/types.d.ts +122 -0
- package/src/lib/nx-plugin/_virtual/_rolldown/runtime.js +23 -0
- package/src/lib/nx-plugin/builders.json +10 -2
- package/src/lib/nx-plugin/executors.json +10 -7
- package/src/lib/nx-plugin/src/builders/vite/schema.json +39 -0
- package/src/lib/nx-plugin/src/builders/vite/vite-build.d.ts +11 -0
- package/src/lib/nx-plugin/src/builders/vite/vite-build.js +18 -0
- package/src/lib/nx-plugin/src/builders/vite/vite-build.js.map +1 -0
- package/src/lib/nx-plugin/src/builders/vite-dev-server/dev-server.d.ts +7 -0
- package/src/lib/nx-plugin/src/builders/vite-dev-server/dev-server.js +14 -0
- package/src/lib/nx-plugin/src/builders/vite-dev-server/dev-server.js.map +1 -0
- package/src/lib/nx-plugin/src/builders/vite-dev-server/schema.json +27 -0
- package/src/lib/nx-plugin/src/executors/vite-dev-server/compat.js +1 -0
- package/src/lib/nx-plugin/src/executors/vite-dev-server/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/executors/vite-dev-server/vite-dev-server.impl.js +1 -0
- package/src/lib/nx-plugin/src/executors/vite-dev-server/vite-dev-server.impl.js.map +1 -1
- package/src/lib/nx-plugin/src/executors/vitest/compat.js +1 -0
- package/src/lib/nx-plugin/src/executors/vitest/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/executors/vitest/vitest.impl.js +1 -0
- package/src/lib/nx-plugin/src/executors/vitest/vitest.impl.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/compat.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/generator.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-analog-project-config.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-analog-project-config.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-angular-app.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-angular-app.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-files.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-files.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-home-page.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-home-page.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-config.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/lib/add-tailwind-helpers.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/lib/update-index-html.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/lib/update-index-html.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/minimum-supported-versions.js +1 -0
- package/src/lib/nx-plugin/src/generators/app/versions/minimum-supported-versions.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +5 -5
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/compat.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/generator.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/add-analog-dependencies.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/add-analog-dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-app-tsconfig.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-app-tsconfig.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-build-target.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-build-target.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-index-html.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-index-html.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-main.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-main.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-package-json.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-package-json.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-serve-target.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-serve-target.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-target.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-target.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-tsconfig.js +1 -0
- package/src/lib/nx-plugin/src/generators/init/lib/update-test-tsconfig.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/page/compat.js +1 -0
- package/src/lib/nx-plugin/src/generators/page/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/page/generator.js +1 -0
- package/src/lib/nx-plugin/src/generators/page/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/preset/generator.js +1 -0
- package/src/lib/nx-plugin/src/generators/preset/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/setup-vitest/compat.js +1 -0
- package/src/lib/nx-plugin/src/generators/setup-vitest/compat.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/setup-vitest/generator.js +1 -0
- package/src/lib/nx-plugin/src/generators/setup-vitest/generator.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/setup-vitest/lib/add-analog-dependencies.js +1 -0
- package/src/lib/nx-plugin/src/generators/setup-vitest/lib/add-analog-dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/setup-vitest/lib/update-test-target.js +1 -0
- package/src/lib/nx-plugin/src/generators/setup-vitest/lib/update-test-target.js.map +1 -1
- package/src/lib/nx-plugin/src/generators/setup-vitest/lib/update-tsconfig.js +1 -0
- package/src/lib/nx-plugin/src/generators/setup-vitest/lib/update-tsconfig.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/version-utils.js +1 -0
- package/src/lib/nx-plugin/src/utils/version-utils.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/dependencies.js +1 -0
- package/src/lib/nx-plugin/src/utils/versions/dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/dev-dependencies.js +1 -0
- package/src/lib/nx-plugin/src/utils/versions/dev-dependencies.js.map +1 -1
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.d.ts +5 -5
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js +5 -5
- package/src/lib/nx-plugin/src/utils/versions/ng_19_X/versions.js.map +1 -1
- package/src/lib/options.d.ts +9 -146
- package/src/lib/platform-plugin.js +3 -40
- package/src/lib/platform-plugin.js.map +1 -1
- package/src/lib/style-pipeline.d.ts +0 -1
- package/src/lib/style-pipeline.js.map +1 -1
- package/src/lib/utils/debug.js +2 -2
- package/src/lib/utils/debug.js.map +1 -1
- package/src/lib/nx-plugin/src/executors/vite/compat.d.ts +0 -3
- package/src/lib/nx-plugin/src/executors/vite/compat.js +0 -7
- package/src/lib/nx-plugin/src/executors/vite/compat.js.map +0 -1
- package/src/lib/nx-plugin/src/executors/vite/schema.d.ts +0 -2
- package/src/lib/nx-plugin/src/executors/vite/schema.json +0 -147
- package/src/lib/nx-plugin/src/executors/vite/vite.impl.d.ts +0 -2
- package/src/lib/nx-plugin/src/executors/vite/vite.impl.js +0 -6
- package/src/lib/nx-plugin/src/executors/vite/vite.impl.js.map +0 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
//#region packages/platform/src/lib/nitro/angular-linker-plugin.ts
|
|
2
|
+
/**
|
|
3
|
+
* Rolldown plugin that runs the Angular Linker against partially-compiled
|
|
4
|
+
* Angular npm packages.
|
|
5
|
+
*
|
|
6
|
+
* Wired into `ssr.optimizeDeps.rolldownOptions.plugins` so the SSR /
|
|
7
|
+
* `nitro` environment's dep optimizer turns `ɵɵngDeclare*` partial
|
|
8
|
+
* declarations into fully-compiled definitions. Without this, the
|
|
9
|
+
* server bundle would need JIT (eval) at runtime — forbidden on
|
|
10
|
+
* `workerd` / edge runtimes and unnecessary anywhere else.
|
|
11
|
+
*
|
|
12
|
+
* Loaded lazily so apps that never trigger the SSR optimizer don't
|
|
13
|
+
* incur the babel + compiler-cli/linker cost.
|
|
14
|
+
*/
|
|
15
|
+
function angularLinkerPlugin() {
|
|
16
|
+
let linkerBabelPlugin;
|
|
17
|
+
let needsLinkingFn;
|
|
18
|
+
let transformAsyncFn;
|
|
19
|
+
async function ensureLoaded() {
|
|
20
|
+
if (linkerBabelPlugin && needsLinkingFn && transformAsyncFn) return;
|
|
21
|
+
needsLinkingFn = (await import("@angular/compiler-cli/linker")).needsLinking;
|
|
22
|
+
const linkerBabel = await import("@angular/compiler-cli/linker/babel");
|
|
23
|
+
linkerBabelPlugin = linkerBabel.default ?? linkerBabel;
|
|
24
|
+
transformAsyncFn = (await import("@babel/core")).transformAsync;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
name: "analogjs-platform-angular-linker",
|
|
28
|
+
async transform(code, id) {
|
|
29
|
+
if (!id.endsWith(".mjs") && !id.endsWith(".js")) return;
|
|
30
|
+
if (!code.includes("ɵɵngDeclare")) return;
|
|
31
|
+
await ensureLoaded();
|
|
32
|
+
if (!needsLinkingFn(id, code)) return;
|
|
33
|
+
const result = await transformAsyncFn(code, {
|
|
34
|
+
filename: id,
|
|
35
|
+
plugins: [linkerBabelPlugin],
|
|
36
|
+
sourceMaps: true,
|
|
37
|
+
compact: false,
|
|
38
|
+
configFile: false,
|
|
39
|
+
babelrc: false
|
|
40
|
+
});
|
|
41
|
+
if (result?.code) return {
|
|
42
|
+
code: result.code,
|
|
43
|
+
map: result.map ?? null
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
export { angularLinkerPlugin };
|
|
50
|
+
|
|
51
|
+
//# sourceMappingURL=angular-linker-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"angular-linker-plugin.js","names":[],"sources":["../../../../src/lib/nitro/angular-linker-plugin.ts"],"sourcesContent":["/**\n * Rolldown plugin that runs the Angular Linker against partially-compiled\n * Angular npm packages.\n *\n * Wired into `ssr.optimizeDeps.rolldownOptions.plugins` so the SSR /\n * `nitro` environment's dep optimizer turns `ɵɵngDeclare*` partial\n * declarations into fully-compiled definitions. Without this, the\n * server bundle would need JIT (eval) at runtime — forbidden on\n * `workerd` / edge runtimes and unnecessary anywhere else.\n *\n * Loaded lazily so apps that never trigger the SSR optimizer don't\n * incur the babel + compiler-cli/linker cost.\n */\nexport function angularLinkerPlugin() {\n let linkerBabelPlugin: unknown;\n let needsLinkingFn: ((id: string, code: string) => boolean) | undefined;\n let transformAsyncFn:\n | ((\n code: string,\n options: Record<string, unknown>,\n ) => Promise<{ code?: string; map?: unknown } | null>)\n | undefined;\n\n async function ensureLoaded() {\n if (linkerBabelPlugin && needsLinkingFn && transformAsyncFn) return;\n\n const linker = await import('@angular/compiler-cli/linker');\n needsLinkingFn = linker.needsLinking;\n\n const linkerBabel = await import('@angular/compiler-cli/linker/babel');\n linkerBabelPlugin =\n (linkerBabel as { default?: unknown }).default ?? linkerBabel;\n\n // @ts-expect-error — @babel/core ships without bundled type declarations\n const babel = await import('@babel/core');\n transformAsyncFn = babel.transformAsync;\n }\n\n return {\n name: 'analogjs-platform-angular-linker',\n async transform(code: string, id: string) {\n if (!id.endsWith('.mjs') && !id.endsWith('.js')) return;\n\n // Cheap pre-check before pulling babel/compiler-cli into memory.\n if (!code.includes('ɵɵngDeclare')) return;\n\n await ensureLoaded();\n if (!needsLinkingFn!(id, code)) return;\n\n const result = await transformAsyncFn!(code, {\n filename: id,\n plugins: [linkerBabelPlugin],\n sourceMaps: true,\n compact: false,\n configFile: false,\n babelrc: false,\n });\n\n if (result?.code) {\n return { code: result.code, map: result.map ?? null };\n }\n return;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;AAaA,SAAgB,sBAAsB;CACpC,IAAI;CACJ,IAAI;CACJ,IAAI;CAOJ,eAAe,eAAe;AAC5B,MAAI,qBAAqB,kBAAkB,iBAAkB;AAG7D,oBADe,MAAM,OAAO,iCACJ;EAExB,MAAM,cAAc,MAAM,OAAO;AACjC,sBACG,YAAsC,WAAW;AAIpD,sBADc,MAAM,OAAO,gBACF;;AAG3B,QAAO;EACL,MAAM;EACN,MAAM,UAAU,MAAc,IAAY;AACxC,OAAI,CAAC,GAAG,SAAS,OAAO,IAAI,CAAC,GAAG,SAAS,MAAM,CAAE;AAGjD,OAAI,CAAC,KAAK,SAAS,cAAc,CAAE;AAEnC,SAAM,cAAc;AACpB,OAAI,CAAC,eAAgB,IAAI,KAAK,CAAE;GAEhC,MAAM,SAAS,MAAM,iBAAkB,MAAM;IAC3C,UAAU;IACV,SAAS,CAAC,kBAAkB;IAC5B,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,SAAS;IACV,CAAC;AAEF,OAAI,QAAQ,KACV,QAAO;IAAE,MAAM,OAAO;IAAM,KAAK,OAAO,OAAO;IAAM;;EAI1D"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { UserConfig } from "vite";
|
|
2
|
+
import type { I18nPrerenderOptions, PrerenderSitemapConfig, SitemapConfig, SitemapEntry } from "./types.js";
|
|
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 {};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { create } from "xmlbuilder2";
|
|
4
|
+
//#region packages/platform/src/lib/nitro/build-sitemap.ts
|
|
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;
|
|
99
|
+
}
|
|
100
|
+
if (rule instanceof RegExp) {
|
|
101
|
+
if (rule.test(entry.route)) return true;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (toGlobRegExp(rule).test(entry.route)) return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
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}$`);
|
|
113
|
+
}
|
|
114
|
+
async function collectSitemapRoutes(routes, include) {
|
|
115
|
+
const routeList = await resolveRouteInputs(routes);
|
|
116
|
+
const includedRoutes = include ? await resolveRouteInputs(include) : [];
|
|
117
|
+
return [...routeList, ...includedRoutes];
|
|
118
|
+
}
|
|
119
|
+
async function resolveRouteInputs(routes) {
|
|
120
|
+
let routeList;
|
|
121
|
+
if (typeof routes === "function") routeList = await routes();
|
|
122
|
+
else if (Array.isArray(routes)) routeList = routes;
|
|
123
|
+
else routeList = [];
|
|
124
|
+
return routeList.filter(Boolean);
|
|
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
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { buildSitemap };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=build-sitemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-sitemap.js","names":[],"sources":["../../../../src/lib/nitro/build-sitemap.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { create } from 'xmlbuilder2';\nimport type { XMLBuilder } from 'xmlbuilder2/lib/interfaces';\nimport { UserConfig } from 'vite';\nimport type {\n I18nPrerenderOptions,\n PrerenderSitemapConfig,\n SitemapConfig,\n SitemapEntry,\n SitemapExcludeRule,\n SitemapRouteDefinition,\n SitemapRouteInput,\n SitemapRouteSource,\n} from './types.js';\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 const path = pageUrl.replace(normalizedHost, '');\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 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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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createDebug } from "obug";
|
|
2
|
+
/** All Nitro-related debug instances, for external wrapping (e.g. file logging). */
|
|
3
|
+
var nitroDebugInstances = [
|
|
4
|
+
createDebug("analog:nitro"),
|
|
5
|
+
createDebug("analog:nitro:ssr"),
|
|
6
|
+
createDebug("analog:nitro:prerender")
|
|
7
|
+
];
|
|
8
|
+
//#endregion
|
|
9
|
+
export { nitroDebugInstances };
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=debug.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/nitro/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\n\nexport const debugNitro = createDebug('analog:nitro');\nexport const debugSsr = createDebug('analog:nitro:ssr');\nexport const debugPrerender = createDebug('analog:nitro:prerender');\n\n/** All Nitro-related debug instances, for external wrapping (e.g. file logging). */\nexport const nitroDebugInstances = [debugNitro, debugSsr, debugPrerender];\n"],"mappings":";;AAOA,IAAa,sBAAsB;CALT,YAAY,eAAe;CAC7B,YAAY,mBAAmB;CACzB,YAAY,yBAAyB;CAGM"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PrerenderContentFile } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Discovers content files with front matter and extracts metadata for prerendering.
|
|
4
|
+
*
|
|
5
|
+
* Globs the resolved content directory, reads each match, parses YAML/TOML
|
|
6
|
+
* front matter via `front-matter`, and returns `PrerenderContentFile`
|
|
7
|
+
* entries used by `PrerenderContentDir.transform()` to map files to routes.
|
|
8
|
+
*
|
|
9
|
+
* When `recursive` is enabled, `relativePath` on each result captures the
|
|
10
|
+
* file's directory relative to `contentDir` so transforms can disambiguate
|
|
11
|
+
* identically-named files across subdirectories.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getMatchingContentFilesWithFrontMatter(workspaceRoot: string, rootDir: string, glob: string, recursive?: boolean): PrerenderContentFile[];
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { basename, join, relative, resolve } from "node:path";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { normalizePath } from "vite";
|
|
4
|
+
import { globSync } from "tinyglobby";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
//#region packages/platform/src/lib/nitro/get-content-files.ts
|
|
7
|
+
var require = createRequire(import.meta.url);
|
|
8
|
+
/**
|
|
9
|
+
* Discovers content files with front matter and extracts metadata for prerendering.
|
|
10
|
+
*
|
|
11
|
+
* Globs the resolved content directory, reads each match, parses YAML/TOML
|
|
12
|
+
* front matter via `front-matter`, and returns `PrerenderContentFile`
|
|
13
|
+
* entries used by `PrerenderContentDir.transform()` to map files to routes.
|
|
14
|
+
*
|
|
15
|
+
* When `recursive` is enabled, `relativePath` on each result captures the
|
|
16
|
+
* file's directory relative to `contentDir` so transforms can disambiguate
|
|
17
|
+
* identically-named files across subdirectories.
|
|
18
|
+
*/
|
|
19
|
+
function getMatchingContentFilesWithFrontMatter(workspaceRoot, rootDir, glob, recursive = false) {
|
|
20
|
+
const fm = require("front-matter");
|
|
21
|
+
const root = normalizePath(resolve(workspaceRoot, rootDir));
|
|
22
|
+
const resolvedDir = normalizePath(relative(root, join(root, glob)));
|
|
23
|
+
const contentFiles = globSync([recursive ? `${root}/${resolvedDir}/**/*` : `${root}/${resolvedDir}/*`], {
|
|
24
|
+
dot: true,
|
|
25
|
+
absolute: true,
|
|
26
|
+
onlyFiles: true
|
|
27
|
+
});
|
|
28
|
+
const dirPrefix = `${root}/${resolvedDir}`;
|
|
29
|
+
return contentFiles.map((f) => {
|
|
30
|
+
const fileContents = readFileSync(f, "utf8");
|
|
31
|
+
const raw = fm(fileContents);
|
|
32
|
+
const filename = basename(normalizePath(f));
|
|
33
|
+
const dot = filename.lastIndexOf(".");
|
|
34
|
+
const name = dot === -1 ? filename : filename.slice(0, dot);
|
|
35
|
+
const extension = dot === -1 ? "" : filename.slice(dot + 1);
|
|
36
|
+
const relativeDir = normalizePath(relative(dirPrefix, f));
|
|
37
|
+
const lastSlash = relativeDir.lastIndexOf("/");
|
|
38
|
+
const relativePath = lastSlash === -1 ? "" : relativeDir.slice(0, lastSlash);
|
|
39
|
+
return {
|
|
40
|
+
name,
|
|
41
|
+
extension,
|
|
42
|
+
path: resolvedDir,
|
|
43
|
+
attributes: raw.attributes,
|
|
44
|
+
content: fileContents,
|
|
45
|
+
relativePath
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
//#endregion
|
|
50
|
+
export { getMatchingContentFilesWithFrontMatter };
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=get-content-files.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-content-files.js","names":[],"sources":["../../../../src/lib/nitro/get-content-files.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { basename, join, relative, resolve } from 'node:path';\nimport { normalizePath } from 'vite';\nimport { createRequire } from 'node:module';\nimport { globSync } from 'tinyglobby';\n\nimport type { PrerenderContentFile } from './types.js';\n\nconst require = createRequire(import.meta.url);\n\n/**\n * Discovers content files with front matter and extracts metadata for prerendering.\n *\n * Globs the resolved content directory, reads each match, parses YAML/TOML\n * front matter via `front-matter`, and returns `PrerenderContentFile`\n * entries used by `PrerenderContentDir.transform()` to map files to routes.\n *\n * When `recursive` is enabled, `relativePath` on each result captures the\n * file's directory relative to `contentDir` so transforms can disambiguate\n * identically-named files across subdirectories.\n */\nexport function getMatchingContentFilesWithFrontMatter(\n workspaceRoot: string,\n rootDir: string,\n glob: string,\n recursive = false,\n): PrerenderContentFile[] {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const fm = require('front-matter');\n\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n const resolvedDir = normalizePath(relative(root, join(root, glob)));\n\n const pattern = recursive\n ? `${root}/${resolvedDir}/**/*`\n : `${root}/${resolvedDir}/*`;\n const contentFiles: string[] = globSync([pattern], {\n dot: true,\n absolute: true,\n onlyFiles: true,\n });\n\n const dirPrefix = `${root}/${resolvedDir}`;\n\n const mappedFilesWithFm: PrerenderContentFile[] = contentFiles.map((f) => {\n const fileContents = readFileSync(f, 'utf8');\n const raw = fm(fileContents);\n\n // Split the basename on the LAST dot so file names that contain\n // additional dots (e.g. locale suffixes like `post.en.md`) keep the\n // inner dots in `name` and only the trailing segment becomes the\n // extension. The previous regex stopped at the first dot and\n // discarded everything between it and the extension.\n const filename = basename(normalizePath(f));\n const dot = filename.lastIndexOf('.');\n const name = dot === -1 ? filename : filename.slice(0, dot);\n const extension = dot === -1 ? '' : filename.slice(dot + 1);\n\n const relativeDir = normalizePath(relative(dirPrefix, f));\n const lastSlash = relativeDir.lastIndexOf('/');\n const relativePath =\n lastSlash === -1 ? '' : relativeDir.slice(0, lastSlash);\n\n return {\n name,\n extension,\n path: resolvedDir,\n attributes: raw.attributes as { attributes: Record<string, any> },\n content: fileContents,\n relativePath,\n };\n });\n\n return mappedFilesWithFm;\n}\n"],"mappings":";;;;;;AAQA,IAAM,UAAU,cAAc,OAAO,KAAK,IAAI;;;;;;;;;;;;AAa9C,SAAgB,uCACd,eACA,SACA,MACA,YAAY,OACY;CAExB,MAAM,KAAK,QAAQ,eAAe;CAElC,MAAM,OAAO,cAAc,QAAQ,eAAe,QAAQ,CAAC;CAC3D,MAAM,cAAc,cAAc,SAAS,MAAM,KAAK,MAAM,KAAK,CAAC,CAAC;CAKnE,MAAM,eAAyB,SAAS,CAHxB,YACZ,GAAG,KAAK,GAAG,YAAY,SACvB,GAAG,KAAK,GAAG,YAAY,IACsB,EAAE;EACjD,KAAK;EACL,UAAU;EACV,WAAW;EACZ,CAAC;CAEF,MAAM,YAAY,GAAG,KAAK,GAAG;AA+B7B,QA7BkD,aAAa,KAAK,MAAM;EACxE,MAAM,eAAe,aAAa,GAAG,OAAO;EAC5C,MAAM,MAAM,GAAG,aAAa;EAO5B,MAAM,WAAW,SAAS,cAAc,EAAE,CAAC;EAC3C,MAAM,MAAM,SAAS,YAAY,IAAI;EACrC,MAAM,OAAO,QAAQ,KAAK,WAAW,SAAS,MAAM,GAAG,IAAI;EAC3D,MAAM,YAAY,QAAQ,KAAK,KAAK,SAAS,MAAM,MAAM,EAAE;EAE3D,MAAM,cAAc,cAAc,SAAS,WAAW,EAAE,CAAC;EACzD,MAAM,YAAY,YAAY,YAAY,IAAI;EAC9C,MAAM,eACJ,cAAc,KAAK,KAAK,YAAY,MAAM,GAAG,UAAU;AAEzD,SAAO;GACL;GACA;GACA,MAAM;GACN,YAAY,IAAI;GAChB,SAAS;GACT;GACD;GACD"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { NitroEventHandler } from "nitro/types";
|
|
2
|
+
type GetHandlersArgs = {
|
|
3
|
+
workspaceRoot: string;
|
|
4
|
+
sourceRoot: string;
|
|
5
|
+
rootDir: string;
|
|
6
|
+
additionalPagesDirs?: string[];
|
|
7
|
+
hasAPIDir?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Discovers and generates Nitro event handlers for server-side page routes.
|
|
11
|
+
*
|
|
12
|
+
* Discovers all `.server.ts` files under `app/pages/**` and any additional
|
|
13
|
+
* pages directories, then maps each file to a Nitro route pattern under
|
|
14
|
+
* `/_analog/pages/...` (prefixed with `/api` when the project has an API dir).
|
|
15
|
+
*
|
|
16
|
+
* Route transformation examples:
|
|
17
|
+
* - index.server.ts → /_analog/pages/index
|
|
18
|
+
* - users/[id].server.ts → /_analog/pages/users/:id
|
|
19
|
+
* - products/[...slug].server.ts → /_analog/pages/products/**:slug
|
|
20
|
+
* - (auth)/login.server.ts → /_analog/pages/-auth-/login
|
|
21
|
+
*/
|
|
22
|
+
export declare function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs, hasAPIDir }: GetHandlersArgs): NitroEventHandler[];
|
|
23
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { normalizePath } from "vite";
|
|
3
|
+
import { globSync } from "tinyglobby";
|
|
4
|
+
//#region packages/platform/src/lib/nitro/get-page-handlers.ts
|
|
5
|
+
/**
|
|
6
|
+
* Discovers and generates Nitro event handlers for server-side page routes.
|
|
7
|
+
*
|
|
8
|
+
* Discovers all `.server.ts` files under `app/pages/**` and any additional
|
|
9
|
+
* pages directories, then maps each file to a Nitro route pattern under
|
|
10
|
+
* `/_analog/pages/...` (prefixed with `/api` when the project has an API dir).
|
|
11
|
+
*
|
|
12
|
+
* Route transformation examples:
|
|
13
|
+
* - index.server.ts → /_analog/pages/index
|
|
14
|
+
* - users/[id].server.ts → /_analog/pages/users/:id
|
|
15
|
+
* - products/[...slug].server.ts → /_analog/pages/products/**:slug
|
|
16
|
+
* - (auth)/login.server.ts → /_analog/pages/-auth-/login
|
|
17
|
+
*/
|
|
18
|
+
function getPageHandlers({ workspaceRoot, sourceRoot, rootDir, additionalPagesDirs, hasAPIDir }) {
|
|
19
|
+
return globSync([`${normalizePath(resolve(workspaceRoot, rootDir))}/${sourceRoot}/app/pages/**/*.server.ts`, ...(additionalPagesDirs || []).map((dir) => `${workspaceRoot}${dir}/**/*.server.ts`)], {
|
|
20
|
+
dot: true,
|
|
21
|
+
absolute: true
|
|
22
|
+
}).map((endpointFile) => {
|
|
23
|
+
const route = normalizePath(endpointFile).replace(/^(.*?)\/pages/, "/pages").replace(/\.server\.ts$/, "").replace(/\[\.{3}(.+)\]/g, "**:$1").replace(/\[\.{3}(\w+)\]/g, "**:$1").replace(/\/\(([^/]+)\)/g, "/-$1-").replace(/\[(\w+)\]/g, ":$1").replace(/\./g, "/");
|
|
24
|
+
return {
|
|
25
|
+
handler: endpointFile,
|
|
26
|
+
route: `${hasAPIDir ? "/api" : ""}/_analog${route}`,
|
|
27
|
+
lazy: true
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//#endregion
|
|
32
|
+
export { getPageHandlers };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=get-page-handlers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-page-handlers.js","names":[],"sources":["../../../../src/lib/nitro/get-page-handlers.ts"],"sourcesContent":["import { resolve } from 'node:path';\nimport { globSync } from 'tinyglobby';\n\nimport type { NitroEventHandler } from 'nitro/types';\nimport { normalizePath } from 'vite';\n\ntype GetHandlersArgs = {\n workspaceRoot: string;\n sourceRoot: string;\n rootDir: string;\n additionalPagesDirs?: string[];\n hasAPIDir?: boolean;\n};\n\n/**\n * Discovers and generates Nitro event handlers for server-side page routes.\n *\n * Discovers all `.server.ts` files under `app/pages/**` and any additional\n * pages directories, then maps each file to a Nitro route pattern under\n * `/_analog/pages/...` (prefixed with `/api` when the project has an API dir).\n *\n * Route transformation examples:\n * - index.server.ts → /_analog/pages/index\n * - users/[id].server.ts → /_analog/pages/users/:id\n * - products/[...slug].server.ts → /_analog/pages/products/**:slug\n * - (auth)/login.server.ts → /_analog/pages/-auth-/login\n */\nexport function getPageHandlers({\n workspaceRoot,\n sourceRoot,\n rootDir,\n additionalPagesDirs,\n hasAPIDir,\n}: GetHandlersArgs): NitroEventHandler[] {\n const root = normalizePath(resolve(workspaceRoot, rootDir));\n\n const endpointFiles: string[] = globSync(\n [\n `${root}/${sourceRoot}/app/pages/**/*.server.ts`,\n ...(additionalPagesDirs || []).map(\n (dir) => `${workspaceRoot}${dir}/**/*.server.ts`,\n ),\n ],\n { dot: true, absolute: true },\n );\n\n const handlers: NitroEventHandler[] = endpointFiles.map((endpointFile) => {\n const normalized = normalizePath(endpointFile);\n const route = normalized\n .replace(/^(.*?)\\/pages/, '/pages')\n .replace(/\\.server\\.ts$/, '')\n .replace(/\\[\\.{3}(.+)\\]/g, '**:$1')\n .replace(/\\[\\.{3}(\\w+)\\]/g, '**:$1')\n // Strip Angular Router group syntax `(group)` from any segment, not\n // just trailing ones. Routes like `(auth)/login.server.ts` need to\n // become `/-auth-/login`, otherwise the literal parens leak through\n // and the handler is mounted under an invalid Nitro path.\n .replace(/\\/\\(([^/]+)\\)/g, '/-$1-')\n .replace(/\\[(\\w+)\\]/g, ':$1')\n .replace(/\\./g, '/');\n\n return {\n handler: endpointFile,\n route: `${hasAPIDir ? '/api' : ''}/_analog${route}`,\n lazy: true,\n };\n });\n\n return handlers;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AA2BA,SAAgB,gBAAgB,EAC9B,eACA,YACA,SACA,qBACA,aACuC;AAmCvC,QAhCgC,SAC9B,CACE,GAJS,cAAc,QAAQ,eAAe,QAAQ,CAAC,CAI/C,GAAG,WAAW,4BACtB,IAAI,uBAAuB,EAAE,EAAE,KAC5B,QAAQ,GAAG,gBAAgB,IAAI,iBACjC,CACF,EACD;EAAE,KAAK;EAAM,UAAU;EAAM,CAC9B,CAEmD,KAAK,iBAAiB;EAExE,MAAM,QADa,cAAc,aAAa,CAE3C,QAAQ,iBAAiB,SAAS,CAClC,QAAQ,iBAAiB,GAAG,CAC5B,QAAQ,kBAAkB,QAAQ,CAClC,QAAQ,mBAAmB,QAAQ,CAKnC,QAAQ,kBAAkB,QAAQ,CAClC,QAAQ,cAAc,MAAM,CAC5B,QAAQ,OAAO,IAAI;AAEtB,SAAO;GACL,SAAS;GACT,OAAO,GAAG,YAAY,SAAS,GAAG,UAAU;GAC5C,MAAM;GACP;GACD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { PrerenderRoute } from "nitro/types";
|
|
2
|
+
import type { I18nPrerenderOptions } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Expands a list of routes to include locale-prefixed variants.
|
|
5
|
+
*
|
|
6
|
+
* For each route and each locale, generates a prefixed route:
|
|
7
|
+
* '/' + locale + route
|
|
8
|
+
*
|
|
9
|
+
* The default locale's routes are included both with and without the prefix
|
|
10
|
+
* so that `/about` and `/en/about` both render.
|
|
11
|
+
*/
|
|
12
|
+
export declare function expandRoutesWithLocales(routes: string[], i18n: I18nPrerenderOptions): string[];
|
|
13
|
+
/**
|
|
14
|
+
* Creates a post-rendering hook that injects the `lang` attribute
|
|
15
|
+
* into the `<html>` tag of prerendered pages based on the route's
|
|
16
|
+
* locale prefix.
|
|
17
|
+
*/
|
|
18
|
+
export declare function createI18nPostRenderingHook(i18n: I18nPrerenderOptions): (route: PrerenderRoute) => Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Detects the locale from a prerendered route path by checking
|
|
21
|
+
* the first path segment against the configured locales.
|
|
22
|
+
*/
|
|
23
|
+
export declare function detectLocaleFromRoute(route: string, i18n: I18nPrerenderOptions): string;
|
|
24
|
+
/**
|
|
25
|
+
* Sets the `lang` attribute on the `<html>` tag in an HTML string.
|
|
26
|
+
* If a `lang` attribute already exists, it is replaced.
|
|
27
|
+
* If no `lang` attribute exists, it is added.
|
|
28
|
+
*/
|
|
29
|
+
export declare function setHtmlLang(html: string, locale: string): string;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
//#region packages/platform/src/lib/nitro/i18n-prerender.ts
|
|
2
|
+
/**
|
|
3
|
+
* Expands a list of routes to include locale-prefixed variants.
|
|
4
|
+
*
|
|
5
|
+
* For each route and each locale, generates a prefixed route:
|
|
6
|
+
* '/' + locale + route
|
|
7
|
+
*
|
|
8
|
+
* The default locale's routes are included both with and without the prefix
|
|
9
|
+
* so that `/about` and `/en/about` both render.
|
|
10
|
+
*/
|
|
11
|
+
function expandRoutesWithLocales(routes, i18n) {
|
|
12
|
+
const expanded = [];
|
|
13
|
+
for (const route of routes) {
|
|
14
|
+
if (route.includes("/_analog/") || route === "/api" || route.startsWith("/api/")) {
|
|
15
|
+
expanded.push(route);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
for (const locale of i18n.locales) {
|
|
19
|
+
const prefix = `/${locale}`;
|
|
20
|
+
const localizedRoute = route === "/" ? prefix : `${prefix}${route}`;
|
|
21
|
+
expanded.push(localizedRoute);
|
|
22
|
+
}
|
|
23
|
+
if (!expanded.includes(route)) expanded.push(route);
|
|
24
|
+
}
|
|
25
|
+
return expanded;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Creates a post-rendering hook that injects the `lang` attribute
|
|
29
|
+
* into the `<html>` tag of prerendered pages based on the route's
|
|
30
|
+
* locale prefix.
|
|
31
|
+
*/
|
|
32
|
+
function createI18nPostRenderingHook(i18n) {
|
|
33
|
+
return async (route) => {
|
|
34
|
+
if (!route.contents || typeof route.contents !== "string") return;
|
|
35
|
+
const locale = detectLocaleFromRoute(route.route, i18n);
|
|
36
|
+
if (!locale) return;
|
|
37
|
+
route.contents = setHtmlLang(route.contents, locale);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Detects the locale from a prerendered route path by checking
|
|
42
|
+
* the first path segment against the configured locales.
|
|
43
|
+
*/
|
|
44
|
+
function detectLocaleFromRoute(route, i18n) {
|
|
45
|
+
const firstSegment = route.split("/").filter(Boolean)[0];
|
|
46
|
+
if (firstSegment && i18n.locales.includes(firstSegment)) return firstSegment;
|
|
47
|
+
return i18n.defaultLocale;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Sets the `lang` attribute on the `<html>` tag in an HTML string.
|
|
51
|
+
* If a `lang` attribute already exists, it is replaced.
|
|
52
|
+
* If no `lang` attribute exists, it is added.
|
|
53
|
+
*/
|
|
54
|
+
function setHtmlLang(html, locale) {
|
|
55
|
+
if (/<html[^>]*\slang\s*=\s*["'][^"']*["']/i.test(html)) return html.replace(/(<html[^>]*\s)lang\s*=\s*["'][^"']*["']/i, `$1lang="${locale}"`);
|
|
56
|
+
return html.replace(/<html/i, `<html lang="${locale}"`);
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { createI18nPostRenderingHook, expandRoutesWithLocales };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=i18n-prerender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n-prerender.js","names":[],"sources":["../../../../src/lib/nitro/i18n-prerender.ts"],"sourcesContent":["import type { PrerenderRoute } from 'nitro/types';\nimport type { I18nPrerenderOptions } from './types.js';\n\n/**\n * Expands a list of routes to include locale-prefixed variants.\n *\n * For each route and each locale, generates a prefixed route:\n * '/' + locale + route\n *\n * The default locale's routes are included both with and without the prefix\n * so that `/about` and `/en/about` both render.\n */\nexport function expandRoutesWithLocales(\n routes: string[],\n i18n: I18nPrerenderOptions,\n): string[] {\n const expanded: string[] = [];\n\n for (const route of routes) {\n // Skip locale expansion for internal analog endpoints and API routes.\n // `startsWith('/api/')` alone misses the bare `/api` path, which\n // would otherwise get locale-prefixed and produce phantom routes like\n // `/en/api`.\n if (\n route.includes('/_analog/') ||\n route === '/api' ||\n route.startsWith('/api/')\n ) {\n expanded.push(route);\n continue;\n }\n\n for (const locale of i18n.locales) {\n const prefix = `/${locale}`;\n const localizedRoute = route === '/' ? prefix : `${prefix}${route}`;\n expanded.push(localizedRoute);\n }\n\n if (!expanded.includes(route)) {\n expanded.push(route);\n }\n }\n\n return expanded;\n}\n\n/**\n * Creates a post-rendering hook that injects the `lang` attribute\n * into the `<html>` tag of prerendered pages based on the route's\n * locale prefix.\n */\nexport function createI18nPostRenderingHook(\n i18n: I18nPrerenderOptions,\n): (route: PrerenderRoute) => Promise<void> {\n return async (route: PrerenderRoute) => {\n if (!route.contents || typeof route.contents !== 'string') {\n return;\n }\n\n const locale = detectLocaleFromRoute(route.route, i18n);\n if (!locale) {\n return;\n }\n\n route.contents = setHtmlLang(route.contents, locale);\n };\n}\n\n/**\n * Detects the locale from a prerendered route path by checking\n * the first path segment against the configured locales.\n */\nexport function detectLocaleFromRoute(\n route: string,\n i18n: I18nPrerenderOptions,\n): string {\n const segments = route.split('/').filter(Boolean);\n const firstSegment = segments[0];\n\n if (firstSegment && i18n.locales.includes(firstSegment)) {\n return firstSegment;\n }\n\n return i18n.defaultLocale;\n}\n\n/**\n * Sets the `lang` attribute on the `<html>` tag in an HTML string.\n * If a `lang` attribute already exists, it is replaced.\n * If no `lang` attribute exists, it is added.\n */\nexport function setHtmlLang(html: string, locale: string): string {\n if (/<html[^>]*\\slang\\s*=\\s*[\"'][^\"']*[\"']/i.test(html)) {\n return html.replace(\n /(<html[^>]*\\s)lang\\s*=\\s*[\"'][^\"']*[\"']/i,\n `$1lang=\"${locale}\"`,\n );\n }\n\n return html.replace(/<html/i, `<html lang=\"${locale}\"`);\n}\n"],"mappings":";;;;;;;;;;AAYA,SAAgB,wBACd,QACA,MACU;CACV,MAAM,WAAqB,EAAE;AAE7B,MAAK,MAAM,SAAS,QAAQ;AAK1B,MACE,MAAM,SAAS,YAAY,IAC3B,UAAU,UACV,MAAM,WAAW,QAAQ,EACzB;AACA,YAAS,KAAK,MAAM;AACpB;;AAGF,OAAK,MAAM,UAAU,KAAK,SAAS;GACjC,MAAM,SAAS,IAAI;GACnB,MAAM,iBAAiB,UAAU,MAAM,SAAS,GAAG,SAAS;AAC5D,YAAS,KAAK,eAAe;;AAG/B,MAAI,CAAC,SAAS,SAAS,MAAM,CAC3B,UAAS,KAAK,MAAM;;AAIxB,QAAO;;;;;;;AAQT,SAAgB,4BACd,MAC0C;AAC1C,QAAO,OAAO,UAA0B;AACtC,MAAI,CAAC,MAAM,YAAY,OAAO,MAAM,aAAa,SAC/C;EAGF,MAAM,SAAS,sBAAsB,MAAM,OAAO,KAAK;AACvD,MAAI,CAAC,OACH;AAGF,QAAM,WAAW,YAAY,MAAM,UAAU,OAAO;;;;;;;AAQxD,SAAgB,sBACd,OACA,MACQ;CAER,MAAM,eADW,MAAM,MAAM,IAAI,CAAC,OAAO,QAAQ,CACnB;AAE9B,KAAI,gBAAgB,KAAK,QAAQ,SAAS,aAAa,CACrD,QAAO;AAGT,QAAO,KAAK;;;;;;;AAQd,SAAgB,YAAY,MAAc,QAAwB;AAChE,KAAI,yCAAyC,KAAK,KAAK,CACrD,QAAO,KAAK,QACV,4CACA,WAAW,OAAO,GACnB;AAGH,QAAO,KAAK,QAAQ,UAAU,eAAe,OAAO,GAAG"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { SERVER_FETCH_FACTORY_SNIPPET } from "./renderers.js";
|
|
2
|
+
import { normalizePath } from "vite";
|
|
3
|
+
import { parseSync } from "oxc-parser";
|
|
4
|
+
//#region packages/platform/src/lib/nitro/page-endpoints-plugin.ts
|
|
5
|
+
function pageEndpointsPlugin() {
|
|
6
|
+
return {
|
|
7
|
+
name: "analogjs-platform-rollup-page-endpoint",
|
|
8
|
+
async transform(_code, id) {
|
|
9
|
+
if (normalizePath(id).includes("/pages/") && id.endsWith(".server.ts")) {
|
|
10
|
+
const fileExports = parseSync(id, _code, {
|
|
11
|
+
sourceType: "module",
|
|
12
|
+
lang: "ts"
|
|
13
|
+
}).module.staticExports.flatMap((e) => e.entries.filter((entry) => entry.exportName.name !== null).map((entry) => entry.exportName.name));
|
|
14
|
+
return {
|
|
15
|
+
code: `
|
|
16
|
+
import { defineHandler, fetchWithEvent } from 'nitro/h3';
|
|
17
|
+
import { createFetch } from 'ofetch';
|
|
18
|
+
|
|
19
|
+
${fileExports.includes("load") ? _code : `
|
|
20
|
+
${_code}
|
|
21
|
+
export const load = () => {
|
|
22
|
+
return {};
|
|
23
|
+
}`}
|
|
24
|
+
|
|
25
|
+
${fileExports.includes("action") ? "" : `
|
|
26
|
+
export const action = () => {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
`}
|
|
30
|
+
|
|
31
|
+
export default defineHandler(async(event) => {
|
|
32
|
+
${SERVER_FETCH_FACTORY_SNIPPET}
|
|
33
|
+
|
|
34
|
+
if (event.method === 'GET') {
|
|
35
|
+
try {
|
|
36
|
+
return await load({
|
|
37
|
+
params: event.context?.params,
|
|
38
|
+
req: event.node?.req,
|
|
39
|
+
res: event.node?.res,
|
|
40
|
+
fetch: serverFetch,
|
|
41
|
+
event
|
|
42
|
+
});
|
|
43
|
+
} catch(e) {
|
|
44
|
+
console.error(\` An error occurred: \${e}\`)
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
try {
|
|
49
|
+
return await action({
|
|
50
|
+
params: event.context?.params,
|
|
51
|
+
req: event.node?.req,
|
|
52
|
+
res: event.node?.res,
|
|
53
|
+
fetch: serverFetch,
|
|
54
|
+
event
|
|
55
|
+
});
|
|
56
|
+
} catch(e) {
|
|
57
|
+
console.error(\` An error occurred: \${e}\`)
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
`,
|
|
63
|
+
map: null
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
export { pageEndpointsPlugin };
|
|
71
|
+
|
|
72
|
+
//# sourceMappingURL=page-endpoints-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"page-endpoints-plugin.js","names":[],"sources":["../../../../src/lib/nitro/page-endpoints-plugin.ts"],"sourcesContent":["import { parseSync } from 'oxc-parser';\nimport { normalizePath } from 'vite';\nimport { SERVER_FETCH_FACTORY_SNIPPET } from './renderers.js';\n\nexport function pageEndpointsPlugin() {\n return {\n name: 'analogjs-platform-rollup-page-endpoint',\n async transform(\n _code: string,\n id: string,\n ): Promise<{ code: string; map: null } | undefined> {\n if (normalizePath(id).includes('/pages/') && id.endsWith('.server.ts')) {\n const result = parseSync(id, _code, {\n sourceType: 'module',\n lang: 'ts',\n });\n\n const fileExports: string[] = result.module.staticExports.flatMap((e) =>\n e.entries\n .filter((entry) => entry.exportName.name !== null)\n .map((entry) => entry.exportName.name as string),\n );\n\n // In h3 v2 / Nitro v3, event.node is undefined during prerendering\n // (which uses the fetch-based pipeline, not Node.js http), and\n // event.context is undefined for sub-handler dispatches that\n // didn't go through the router params layer. We use optional\n // chaining on both so page endpoints work in Node.js server,\n // fetch-based prerender, and internal `fetchWithEvent` paths.\n //\n // Page loaders expect Nitro-style `$fetch` semantics (parsed data plus\n // internal relative-route support), so we construct a request-local\n // fetch using `createFetch` from ofetch + `fetchWithEvent` from h3.\n const code = `\n import { defineHandler, fetchWithEvent } from 'nitro/h3';\n import { createFetch } from 'ofetch';\n\n ${\n fileExports.includes('load')\n ? _code\n : `\n ${_code}\n export const load = () => {\n return {};\n }`\n }\n\n ${\n fileExports.includes('action')\n ? ''\n : `\n export const action = () => {\n return {};\n }\n `\n }\n\n export default defineHandler(async(event) => {\n ${SERVER_FETCH_FACTORY_SNIPPET}\n\n if (event.method === 'GET') {\n try {\n return await load({\n params: event.context?.params,\n req: event.node?.req,\n res: event.node?.res,\n fetch: serverFetch,\n event\n });\n } catch(e) {\n console.error(\\` An error occurred: \\${e}\\`)\n throw e;\n }\n } else {\n try {\n return await action({\n params: event.context?.params,\n req: event.node?.req,\n res: event.node?.res,\n fetch: serverFetch,\n event\n });\n } catch(e) {\n console.error(\\` An error occurred: \\${e}\\`)\n throw e;\n }\n }\n });\n `;\n\n return {\n code,\n map: null,\n };\n }\n\n return;\n },\n };\n}\n"],"mappings":";;;;AAIA,SAAgB,sBAAsB;AACpC,QAAO;EACL,MAAM;EACN,MAAM,UACJ,OACA,IACkD;AAClD,OAAI,cAAc,GAAG,CAAC,SAAS,UAAU,IAAI,GAAG,SAAS,aAAa,EAAE;IAMtE,MAAM,cALS,UAAU,IAAI,OAAO;KAClC,YAAY;KACZ,MAAM;KACP,CAAC,CAEmC,OAAO,cAAc,SAAS,MACjE,EAAE,QACC,QAAQ,UAAU,MAAM,WAAW,SAAS,KAAK,CACjD,KAAK,UAAU,MAAM,WAAW,KAAe,CACnD;AAqED,WAAO;KACL,MA1DW;;;;cAKP,YAAY,SAAS,OAAO,GACxB,QACA;kBACA,MAAM;;;mBAIX;;cAGC,YAAY,SAAS,SAAS,GAC1B,KACA;;;;gBAKL;;;gBAGG,6BAA6B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KAkCnC,KAAK;KACN;;;EAKN"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region packages/platform/src/lib/nitro/post-rendering-hook.ts
|
|
2
|
+
function addPostRenderingHooks(nitro, hooks) {
|
|
3
|
+
for (const hook of hooks) nitro.hooks.hook("prerender:generate", async (route) => {
|
|
4
|
+
await hook(route);
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { addPostRenderingHooks };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=post-rendering-hook.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"post-rendering-hook.js","names":[],"sources":["../../../../src/lib/nitro/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> | void)[],\n): void {\n for (const hook of hooks) {\n nitro.hooks.hook('prerender:generate', async (route: PrerenderRoute) => {\n await hook(route);\n });\n }\n}\n"],"mappings":";AAEA,SAAgB,sBACd,OACA,OACM;AACN,MAAK,MAAM,QAAQ,MACjB,OAAM,MAAM,KAAK,sBAAsB,OAAO,UAA0B;AACtE,QAAM,KAAK,MAAM;GACjB"}
|