@analogjs/platform 3.0.0-alpha.90 → 3.0.0-alpha.92
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/migrate-to-separated-plugins/migrate-to-separated-plugins.d.ts +2 -0
- package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.js +203 -0
- package/migrations/migrate-to-separated-plugins/migrate-to-separated-plugins.js.map +1 -0
- package/package.json +11 -7
- package/src/index.d.ts +1 -2
- package/src/index.js +1 -2
- package/src/index.js.map +1 -1
- package/src/lib/clear-client-page-endpoint.js +1 -0
- package/src/lib/clear-client-page-endpoint.js.map +1 -1
- package/src/lib/deps-plugin.js +17 -6
- package/src/lib/deps-plugin.js.map +1 -1
- package/src/lib/nitro/analog-nitro-plugin.js +102 -45
- package/src/lib/nitro/analog-nitro-plugin.js.map +1 -1
- package/src/lib/nitro/get-server-fn-handlers.d.ts +29 -0
- package/src/lib/nitro/get-server-fn-handlers.js +66 -0
- package/src/lib/nitro/get-server-fn-handlers.js.map +1 -0
- package/src/lib/nitro/server-fn-endpoints.d.ts +41 -0
- package/src/lib/nitro/server-fn-endpoints.js +57 -0
- package/src/lib/nitro/server-fn-endpoints.js.map +1 -0
- package/src/lib/nx-plugin/src/generators/app/files/template-angular/src/server/routes/api/v1/hello.ts__template__ +2 -2
- package/src/lib/nx-plugin/src/generators/app/files/template-angular/vite.config.ts__template__ +4 -0
- package/src/lib/nx-plugin/src/generators/app/versions/nx_18_X/versions.d.ts +7 -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/preset/__snapshots__/generator.spec.ts.snap +3 -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 -49
- package/src/lib/platform-plugin.js +42 -6
- package/src/lib/platform-plugin.js.map +1 -1
- package/src/lib/route-file-discovery.d.ts +3 -1
- package/src/lib/route-file-discovery.js +19 -8
- package/src/lib/route-file-discovery.js.map +1 -1
- package/src/lib/route-manifest.d.ts +22 -26
- package/src/lib/route-manifest.js +58 -189
- package/src/lib/route-manifest.js.map +1 -1
- package/src/lib/ssr/defer-streaming-plugin.js +97 -0
- package/src/lib/ssr/defer-streaming-plugin.js.map +1 -0
- package/src/lib/typed-routes-plugin.d.ts +6 -43
- package/src/lib/typed-routes-plugin.js +80 -195
- package/src/lib/typed-routes-plugin.js.map +1 -1
- package/src/lib/utils/debug.js +4 -7
- package/src/lib/utils/debug.js.map +1 -1
- package/src/lib/utils/rolldown.js +1 -1
- package/src/server-mode-plugin.d.ts +1 -1
- package/src/server-mode-plugin.js +13 -1
- package/src/server-mode-plugin.js.map +1 -1
- package/src/lib/json-ld-manifest-plugin.d.ts +0 -41
- package/src/lib/json-ld-manifest-plugin.js +0 -84
- package/src/lib/json-ld-manifest-plugin.js.map +0 -1
- package/src/lib/route-generation-plugin.d.ts +0 -3
- package/src/lib/route-generation-plugin.js +0 -45
- package/src/lib/route-generation-plugin.js.map +0 -1
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether the streaming SSR patch can be applied to the installed Angular.
|
|
3
|
+
* A `null` major (version undetectable) returns `true`: don't block on a failed
|
|
4
|
+
* detection — the plugin's anchor-drift detection catches a real mismatch at
|
|
5
|
+
* build time and falls back to buffered rendering.
|
|
6
|
+
*/
|
|
7
|
+
function streamingSupportedOnAngular(major) {
|
|
8
|
+
return major === null || major >= 21;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Pure transform that injects the streaming-SSR per-block resolution hook into
|
|
12
|
+
* `@angular/core`'s compiled output. It is the same class of transform as
|
|
13
|
+
* `i18nDefRegistryPlugin` in ./ssr-build-plugin — a string patch of Angular's
|
|
14
|
+
* bundle applied only to SSR builds. Exported separately so it can be unit
|
|
15
|
+
* tested against a bundle string.
|
|
16
|
+
*
|
|
17
|
+
* Two edits, both keyed on single-occurrence anchors in the module that holds
|
|
18
|
+
* the defer runtime:
|
|
19
|
+
* 1. Inside `applyDeferBlockState`, fire `globalThis.__analogSsrDeferCapture`
|
|
20
|
+
* when a `@defer` block reaches `Complete` on the server, passing the
|
|
21
|
+
* block's live `lContainer` — this is the per-block resolution signal a
|
|
22
|
+
* streaming renderer consumes.
|
|
23
|
+
* 2. Expose `collectNativeNodesInLContainer` via `globalThis.__analogSsrInternals`
|
|
24
|
+
* so the renderer can serialize a block's subtree.
|
|
25
|
+
*
|
|
26
|
+
* Returns `null` when the module is not the one carrying the defer runtime, so
|
|
27
|
+
* it is a no-op on every other Angular module.
|
|
28
|
+
*/
|
|
29
|
+
function injectDeferStreamingHook(code) {
|
|
30
|
+
const END_ANCHOR = "profiler(ProfilerEvent.DeferBlockStateEnd);";
|
|
31
|
+
if (!code.includes("function applyDeferBlockState(") || !code.includes(END_ANCHOR)) return null;
|
|
32
|
+
let out = code.replace(END_ANCHOR, "try { if (newState === DeferBlockState.Complete && typeof ngServerMode !== 'undefined' && ngServerMode && typeof globalThis.__analogSsrDeferCapture === 'function') { globalThis.__analogSsrDeferCapture({ ssrUniqueId: lDetails[SSR_UNIQUE_ID], lContainer, hostLView }); } } catch (e) {}\n " + END_ANCHOR);
|
|
33
|
+
if (code.includes("function collectNativeNodesInLContainer(")) out += "\nglobalThis.__analogSsrInternals = Object.assign(globalThis.__analogSsrInternals || {}, { collectNativeNodesInLContainer });\n";
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Classify an `@angular/core` module for the streaming patch. The patch anchors
|
|
38
|
+
* on internal symbol names, so when Angular changes those the transform would
|
|
39
|
+
* otherwise become a silent no-op and streaming would degrade to buffered with
|
|
40
|
+
* no signal. This distinguishes "not the target module" (skip quietly) from
|
|
41
|
+
* "this IS the `@defer` runtime module but the anchors drifted" (worth warning).
|
|
42
|
+
*/
|
|
43
|
+
function inspectAngularCoreModule(code) {
|
|
44
|
+
if (!code.includes("function applyDeferBlockState(")) return { kind: "not-target" };
|
|
45
|
+
const missing = [];
|
|
46
|
+
if (!code.includes("profiler(ProfilerEvent.DeferBlockStateEnd);")) missing.push("DeferBlockStateEnd profiler anchor");
|
|
47
|
+
if (!code.includes("function collectNativeNodesInLContainer(")) missing.push("collectNativeNodesInLContainer");
|
|
48
|
+
return missing.length === 0 ? { kind: "patchable" } : {
|
|
49
|
+
kind: "drifted",
|
|
50
|
+
reason: `missing ${missing.join(", ")}`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Vite plugin that applies {@link injectDeferStreamingHook} to `@angular/core`
|
|
55
|
+
* during SSR builds, so `@analogjs/router`'s `renderStream` can flush `@defer`
|
|
56
|
+
* blocks as they resolve on the server — without hand-patching node_modules.
|
|
57
|
+
*
|
|
58
|
+
* Only active for SSR. Mirrors the enforce/filter/ssr-gate shape of
|
|
59
|
+
* `i18nDefRegistryPlugin`. If the `@defer` runtime module is found but its
|
|
60
|
+
* anchors have drifted (Angular changed internals), or if it is never
|
|
61
|
+
* encountered at all, the plugin warns rather than silently producing a build
|
|
62
|
+
* that falls back to buffered rendering.
|
|
63
|
+
*/
|
|
64
|
+
function deferStreamingPlugin() {
|
|
65
|
+
let applied = false;
|
|
66
|
+
let warnedDrift = false;
|
|
67
|
+
return {
|
|
68
|
+
name: "analogjs-defer-streaming",
|
|
69
|
+
enforce: "post",
|
|
70
|
+
transform: {
|
|
71
|
+
filter: { id: /\/@angular\/core\// },
|
|
72
|
+
handler(code, _id, options) {
|
|
73
|
+
if (!options?.ssr) return;
|
|
74
|
+
const info = inspectAngularCoreModule(code);
|
|
75
|
+
if (info.kind === "not-target") return;
|
|
76
|
+
if (info.kind === "drifted") {
|
|
77
|
+
if (!warnedDrift) {
|
|
78
|
+
warnedDrift = true;
|
|
79
|
+
this.warn(`experimental streaming SSR: found @angular/core's @defer runtime but could not apply the resolution hook (${info.reason}). The installed Angular version likely changed internals the patch depends on; streaming will fall back to buffered rendering.`);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const out = injectDeferStreamingHook(code);
|
|
84
|
+
if (!out) return;
|
|
85
|
+
applied = true;
|
|
86
|
+
return { code: out };
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
buildEnd() {
|
|
90
|
+
if (!applied && !warnedDrift) this.warn("experimental streaming SSR is enabled but @angular/core's @defer runtime module was never encountered during the SSR build, so the resolution hook was not injected. Streaming will fall back to buffered rendering.");
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//#endregion
|
|
95
|
+
export { deferStreamingPlugin, streamingSupportedOnAngular };
|
|
96
|
+
|
|
97
|
+
//# sourceMappingURL=defer-streaming-plugin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defer-streaming-plugin.js","names":[],"sources":["../../../../src/lib/ssr/defer-streaming-plugin.ts"],"sourcesContent":["import type { Plugin } from 'vite';\n\n/**\n * Minimum Angular major whose compiled `@angular/core` FESM matches the\n * streaming patch anchors. Incremental hydration is a stable public API from\n * v20 (`withIncrementalHydration`, `@publicApi 20.0`), but v20's FESM inlines\n * the injection anchor's `DeferBlockStateEnd` profiler event to its numeric\n * ordinal, whereas v21+ keeps the symbolic `ProfilerEvent.DeferBlockStateEnd`\n * form the anchor matches on. Keying on the literal ordinal would be fragile\n * (enum values shift between versions), so v21 is the floor.\n */\nexport const MIN_STREAMING_ANGULAR_MAJOR = 21;\n\n/**\n * Whether the streaming SSR patch can be applied to the installed Angular.\n * A `null` major (version undetectable) returns `true`: don't block on a failed\n * detection — the plugin's anchor-drift detection catches a real mismatch at\n * build time and falls back to buffered rendering.\n */\nexport function streamingSupportedOnAngular(major: number | null): boolean {\n return major === null || major >= MIN_STREAMING_ANGULAR_MAJOR;\n}\n\n/**\n * Pure transform that injects the streaming-SSR per-block resolution hook into\n * `@angular/core`'s compiled output. It is the same class of transform as\n * `i18nDefRegistryPlugin` in ./ssr-build-plugin — a string patch of Angular's\n * bundle applied only to SSR builds. Exported separately so it can be unit\n * tested against a bundle string.\n *\n * Two edits, both keyed on single-occurrence anchors in the module that holds\n * the defer runtime:\n * 1. Inside `applyDeferBlockState`, fire `globalThis.__analogSsrDeferCapture`\n * when a `@defer` block reaches `Complete` on the server, passing the\n * block's live `lContainer` — this is the per-block resolution signal a\n * streaming renderer consumes.\n * 2. Expose `collectNativeNodesInLContainer` via `globalThis.__analogSsrInternals`\n * so the renderer can serialize a block's subtree.\n *\n * Returns `null` when the module is not the one carrying the defer runtime, so\n * it is a no-op on every other Angular module.\n */\nexport function injectDeferStreamingHook(code: string): string | null {\n const END_ANCHOR = 'profiler(ProfilerEvent.DeferBlockStateEnd);';\n if (\n !code.includes('function applyDeferBlockState(') ||\n !code.includes(END_ANCHOR)\n ) {\n return null;\n }\n\n // Wrap the entire guard — not just the call — so a drifted Angular runtime\n // that still matches the anchors but renamed these locals fails as a silent\n // no-op instead of throwing a ReferenceError inside applyDeferBlockState.\n const capture =\n `try { if (newState === DeferBlockState.Complete && ` +\n `typeof ngServerMode !== 'undefined' && ngServerMode && ` +\n `typeof globalThis.__analogSsrDeferCapture === 'function') { ` +\n `globalThis.__analogSsrDeferCapture({ ssrUniqueId: lDetails[SSR_UNIQUE_ID], lContainer, hostLView }); } } catch (e) {}\\n `;\n\n let out = code.replace(END_ANCHOR, capture + END_ANCHOR);\n\n if (code.includes('function collectNativeNodesInLContainer(')) {\n out +=\n '\\nglobalThis.__analogSsrInternals = Object.assign(globalThis.__analogSsrInternals || {}, { collectNativeNodesInLContainer });\\n';\n }\n\n return out;\n}\n\n/**\n * Classify an `@angular/core` module for the streaming patch. The patch anchors\n * on internal symbol names, so when Angular changes those the transform would\n * otherwise become a silent no-op and streaming would degrade to buffered with\n * no signal. This distinguishes \"not the target module\" (skip quietly) from\n * \"this IS the `@defer` runtime module but the anchors drifted\" (worth warning).\n */\nexport function inspectAngularCoreModule(\n code: string,\n):\n | { kind: 'not-target' }\n | { kind: 'patchable' }\n | { kind: 'drifted'; reason: string } {\n if (!code.includes('function applyDeferBlockState(')) {\n return { kind: 'not-target' };\n }\n const missing: string[] = [];\n if (!code.includes('profiler(ProfilerEvent.DeferBlockStateEnd);')) {\n missing.push('DeferBlockStateEnd profiler anchor');\n }\n if (!code.includes('function collectNativeNodesInLContainer(')) {\n missing.push('collectNativeNodesInLContainer');\n }\n return missing.length === 0\n ? { kind: 'patchable' }\n : { kind: 'drifted', reason: `missing ${missing.join(', ')}` };\n}\n\n/**\n * Vite plugin that applies {@link injectDeferStreamingHook} to `@angular/core`\n * during SSR builds, so `@analogjs/router`'s `renderStream` can flush `@defer`\n * blocks as they resolve on the server — without hand-patching node_modules.\n *\n * Only active for SSR. Mirrors the enforce/filter/ssr-gate shape of\n * `i18nDefRegistryPlugin`. If the `@defer` runtime module is found but its\n * anchors have drifted (Angular changed internals), or if it is never\n * encountered at all, the plugin warns rather than silently producing a build\n * that falls back to buffered rendering.\n */\nexport function deferStreamingPlugin(): Plugin {\n let applied = false;\n let warnedDrift = false;\n return {\n name: 'analogjs-defer-streaming',\n enforce: 'post',\n transform: {\n filter: {\n id: /\\/@angular\\/core\\//,\n },\n handler(code, _id, options) {\n if (!options?.ssr) return;\n const info = inspectAngularCoreModule(code);\n if (info.kind === 'not-target') return;\n if (info.kind === 'drifted') {\n if (!warnedDrift) {\n warnedDrift = true;\n this.warn(\n `experimental streaming SSR: found @angular/core's @defer runtime ` +\n `but could not apply the resolution hook (${info.reason}). The ` +\n `installed Angular version likely changed internals the patch ` +\n `depends on; streaming will fall back to buffered rendering.`,\n );\n }\n return;\n }\n const out = injectDeferStreamingHook(code);\n if (!out) return;\n applied = true;\n return { code: out };\n },\n },\n buildEnd() {\n if (!applied && !warnedDrift) {\n this.warn(\n `experimental streaming SSR is enabled but @angular/core's @defer ` +\n `runtime module was never encountered during the SSR build, so the ` +\n `resolution hook was not injected. Streaming will fall back to ` +\n `buffered rendering.`,\n );\n }\n },\n };\n}\n"],"mappings":";;;;;;AAmBA,SAAgB,4BAA4B,OAA+B;AACzE,QAAO,UAAU,QAAQ,SAAA;;;;;;;;;;;;;;;;;;;;;AAsB3B,SAAgB,yBAAyB,MAA6B;CACpE,MAAM,aAAa;AACnB,KACE,CAAC,KAAK,SAAS,iCAAiC,IAChD,CAAC,KAAK,SAAS,WAAW,CAE1B,QAAO;CAYT,IAAI,MAAM,KAAK,QAAQ,YALrB,oSAK2C,WAAW;AAExD,KAAI,KAAK,SAAS,2CAA2C,CAC3D,QACE;AAGJ,QAAO;;;;;;;;;AAUT,SAAgB,yBACd,MAIsC;AACtC,KAAI,CAAC,KAAK,SAAS,iCAAiC,CAClD,QAAO,EAAE,MAAM,cAAc;CAE/B,MAAM,UAAoB,EAAE;AAC5B,KAAI,CAAC,KAAK,SAAS,8CAA8C,CAC/D,SAAQ,KAAK,qCAAqC;AAEpD,KAAI,CAAC,KAAK,SAAS,2CAA2C,CAC5D,SAAQ,KAAK,iCAAiC;AAEhD,QAAO,QAAQ,WAAW,IACtB,EAAE,MAAM,aAAa,GACrB;EAAE,MAAM;EAAW,QAAQ,WAAW,QAAQ,KAAK,KAAK;EAAI;;;;;;;;;;;;;AAclE,SAAgB,uBAA+B;CAC7C,IAAI,UAAU;CACd,IAAI,cAAc;AAClB,QAAO;EACL,MAAM;EACN,SAAS;EACT,WAAW;GACT,QAAQ,EACN,IAAI,sBACL;GACD,QAAQ,MAAM,KAAK,SAAS;AAC1B,QAAI,CAAC,SAAS,IAAK;IACnB,MAAM,OAAO,yBAAyB,KAAK;AAC3C,QAAI,KAAK,SAAS,aAAc;AAChC,QAAI,KAAK,SAAS,WAAW;AAC3B,SAAI,CAAC,aAAa;AAChB,oBAAc;AACd,WAAK,KACH,6GAC8C,KAAK,OAAO,iIAG3D;;AAEH;;IAEF,MAAM,MAAM,yBAAyB,KAAK;AAC1C,QAAI,CAAC,IAAK;AACV,cAAU;AACV,WAAO,EAAE,MAAM,KAAK;;GAEvB;EACD,WAAW;AACT,OAAI,CAAC,WAAW,CAAC,YACf,MAAK,KACH,uNAID;;EAGN"}
|
|
@@ -1,50 +1,13 @@
|
|
|
1
1
|
import { type Plugin } from "vite";
|
|
2
|
-
export interface
|
|
3
|
-
/**
|
|
4
|
-
* Output path for the single generated route module,
|
|
5
|
-
* relative to the app root.
|
|
6
|
-
*
|
|
7
|
-
* @default 'src/routeTree.gen.ts'
|
|
8
|
-
*/
|
|
2
|
+
export interface TypedRoutingOptions {
|
|
3
|
+
/** Generated declaration path (.d.ts), relative to the app root. */
|
|
9
4
|
outFile?: string;
|
|
10
|
-
/**
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
5
|
+
/** Fail builds when a checked-in route table is stale. Defaults to true. */
|
|
6
|
+
verifyOnBuild?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface TypedRoutesPluginOptions extends TypedRoutingOptions {
|
|
15
9
|
workspaceRoot?: string;
|
|
16
|
-
/**
|
|
17
|
-
* Additional page directories to scan for `.page.ts` files.
|
|
18
|
-
*/
|
|
19
10
|
additionalPagesDirs?: string[];
|
|
20
|
-
/**
|
|
21
|
-
* Additional content directories to scan for `.md` files.
|
|
22
|
-
*/
|
|
23
11
|
additionalContentDirs?: string[];
|
|
24
|
-
/**
|
|
25
|
-
* Include generated `routeJsonLdManifest` exports in the generated route file.
|
|
26
|
-
*
|
|
27
|
-
* @default true
|
|
28
|
-
*/
|
|
29
|
-
jsonLdManifest?: boolean;
|
|
30
|
-
/**
|
|
31
|
-
* When true, compare generated output against the existing file and
|
|
32
|
-
* throw an error if they differ instead of writing. Useful for CI to
|
|
33
|
-
* detect stale checked-in route files.
|
|
34
|
-
*
|
|
35
|
-
* @default false
|
|
36
|
-
*/
|
|
37
|
-
verify?: boolean;
|
|
38
|
-
/**
|
|
39
|
-
* When true, production builds fail after regenerating a stale checked-in
|
|
40
|
-
* route file. This preserves self-healing writes in development while making
|
|
41
|
-
* build-time freshness issues visible by default.
|
|
42
|
-
*
|
|
43
|
-
* @default true
|
|
44
|
-
*/
|
|
45
|
-
verifyOnBuild?: boolean;
|
|
46
12
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Vite plugin that generates a single typed route module for Analog file routes.
|
|
49
|
-
*/
|
|
50
13
|
export declare function typedRoutes(options?: TypedRoutesPluginOptions): Plugin;
|
|
@@ -1,232 +1,117 @@
|
|
|
1
|
-
import { debugTypedRouter } from "./utils/debug.js";
|
|
2
|
-
import { detectSchemaExports, filenameToRoutePath, formatManifestSummary, generateRouteManifest, generateRouteTableDeclaration, generateRouteTreeDeclaration } from "./route-manifest.js";
|
|
3
|
-
import { detectJsonLdModuleExports, extractMarkdownJsonLd, generateJsonLdManifestSource } from "./json-ld-manifest-plugin.js";
|
|
4
1
|
import { createRouteFileDiscovery } from "./route-file-discovery.js";
|
|
2
|
+
import { generateRouteManifest, generateRouteTableDeclaration } from "./route-manifest.js";
|
|
5
3
|
import { dirname, join, relative, resolve } from "node:path";
|
|
6
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
5
|
import { normalizePath } from "vite";
|
|
6
|
+
import { parseSync } from "oxc-parser";
|
|
7
|
+
import { readConfiguration } from "@angular/compiler-cli";
|
|
8
8
|
//#region packages/platform/src/lib/typed-routes-plugin.ts
|
|
9
|
-
var DEFAULT_OUT_FILE = "src/routeTree.gen.ts";
|
|
10
|
-
function resolvePluginOptions(options = {}) {
|
|
11
|
-
return {
|
|
12
|
-
outFile: options.outFile ?? DEFAULT_OUT_FILE,
|
|
13
|
-
workspaceRoot: options.workspaceRoot ?? process.env["NX_WORKSPACE_ROOT"] ?? process.cwd(),
|
|
14
|
-
additionalPagesDirs: options.additionalPagesDirs ?? [],
|
|
15
|
-
additionalContentDirs: options.additionalContentDirs ?? [],
|
|
16
|
-
jsonLdManifest: options.jsonLdManifest ?? true,
|
|
17
|
-
verify: options.verify ?? false,
|
|
18
|
-
verifyOnBuild: options.verifyOnBuild ?? true
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Vite plugin that generates a single typed route module for Analog file routes.
|
|
23
|
-
*/
|
|
24
9
|
function typedRoutes(options = {}) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
let
|
|
10
|
+
const workspaceRoot = normalizePath(options.workspaceRoot ?? process.cwd());
|
|
11
|
+
const outFile = options.outFile ?? "src/routeTree.gen.d.ts";
|
|
12
|
+
if (!outFile.endsWith(".d.ts")) throw new Error("[analog] Typed routing outFile must end in .d.ts. Include this declaration in your application tsconfig.");
|
|
13
|
+
let root;
|
|
14
|
+
let command;
|
|
29
15
|
let discovery;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
hasParamsSchema: false,
|
|
41
|
-
hasQuerySchema: false
|
|
42
|
-
};
|
|
43
|
-
try {
|
|
44
|
-
return detectSchemaExports(readFileSync(resolveDiscoveredFile(relativeFilename), "utf-8"));
|
|
45
|
-
} catch {
|
|
46
|
-
return {
|
|
47
|
-
hasParamsSchema: false,
|
|
48
|
-
hasQuerySchema: false
|
|
49
|
-
};
|
|
16
|
+
let getTsConfigPath;
|
|
17
|
+
function verifyTypeInclusion() {
|
|
18
|
+
if (!getTsConfigPath) return;
|
|
19
|
+
const tsconfig = getTsConfigPath();
|
|
20
|
+
const { rootNames, errors } = readConfiguration(tsconfig);
|
|
21
|
+
if (errors.length) throw new Error(`[analog] Cannot verify typed routing: unable to read ${tsconfig}.`);
|
|
22
|
+
const outputPath = normalizePath(resolve(root, outFile));
|
|
23
|
+
if (!rootNames.some((file) => normalizePath(resolve(file)) === outputPath)) {
|
|
24
|
+
const declaration = normalizePath(relative(dirname(tsconfig), outputPath));
|
|
25
|
+
throw new Error(`[analog] Typed route declaration is not included in ${tsconfig}. Add "${declaration}" to its "files" or "include" list so typed routing is available to the compiler and editor.`);
|
|
50
26
|
}
|
|
51
27
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Ensures the generated route file is imported from an app entry file
|
|
54
|
-
* so the module augmentation is always part of the TypeScript program.
|
|
55
|
-
*/
|
|
56
|
-
function ensureEntryImport() {
|
|
57
|
-
const entryFiles = ["src/main.ts", "src/main.server.ts"];
|
|
58
|
-
function importSpecifierFor(entryFile) {
|
|
59
|
-
const rel = relative(dirname(entryFile), resolvedOptions.outFile).replace(/\.ts$/, "").replace(/\\/g, "/");
|
|
60
|
-
return rel.startsWith(".") ? rel : "./" + rel;
|
|
61
|
-
}
|
|
62
|
-
for (const entryFile of entryFiles) {
|
|
63
|
-
const entryPath = join(root, entryFile);
|
|
64
|
-
if (!existsSync(entryPath)) continue;
|
|
65
|
-
const content = readFileSync(entryPath, "utf-8");
|
|
66
|
-
const specifier = importSpecifierFor(entryFile);
|
|
67
|
-
const escaped = specifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68
|
-
if (new RegExp(`import\\s+['"]${escaped}(\\.ts|\\.js)?['"]`).test(content)) return;
|
|
69
|
-
if (isFreshnessCheck()) return;
|
|
70
|
-
const importLine = `import '${specifier}';`;
|
|
71
|
-
const lines = content.split("\n");
|
|
72
|
-
let lastImportLine = -1;
|
|
73
|
-
for (let i = 0; i < lines.length; i++) if (/^import\s/.test(lines[i])) lastImportLine = i;
|
|
74
|
-
if (lastImportLine >= 0) lines.splice(lastImportLine + 1, 0, importLine);
|
|
75
|
-
else lines.unshift(importLine);
|
|
76
|
-
writeFileSync(entryPath, lines.join("\n"), "utf-8");
|
|
77
|
-
console.log(`[analog] Added route tree import to ${entryFile}`);
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
const specifier = importSpecifierFor("src/main.ts");
|
|
81
|
-
if (isFreshnessCheck()) return;
|
|
82
|
-
console.warn(`[analog] Could not find an entry file (src/main.ts or src/main.server.ts) to add the route tree import. Add \`import '${specifier}';\` to your app entry file to ensure typed routing is active.`);
|
|
83
|
-
}
|
|
84
28
|
function generate() {
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
path: c.fullPath,
|
|
102
|
-
kept: c.keptFile,
|
|
103
|
-
dropped: c.droppedFile
|
|
104
|
-
}))
|
|
29
|
+
const manifest = generateRouteManifest([...discovery.getRouteFiles(), ...discovery.getContentFiles()], (filename) => discovery.isAppLocal(filename) ? 0 : 1);
|
|
30
|
+
const collisions = manifest.collisions.filter((collision) => collision.samePriority);
|
|
31
|
+
if (command === "build" && collisions.length) throw new Error(`[analog] Route collisions detected: ${collisions.map((c) => `${c.fullPath}: ${c.keptFile}, ${c.droppedFile}`).join("; ")}`);
|
|
32
|
+
const outputPath = join(root, outFile);
|
|
33
|
+
const output = generateRouteTableDeclaration(manifest, (filename) => {
|
|
34
|
+
const pageFile = [
|
|
35
|
+
join(root, filename),
|
|
36
|
+
join(workspaceRoot, filename),
|
|
37
|
+
filename
|
|
38
|
+
].find((file) => existsSync(file));
|
|
39
|
+
if (!pageFile) return void 0;
|
|
40
|
+
const serverFile = pageFile.replace(/\.page\.ts$/, ".server.ts");
|
|
41
|
+
return {
|
|
42
|
+
routeMeta: hasNamedExport(pageFile, "routeMeta") ? toImportSpecifier(dirname(outputPath), pageFile) : void 0,
|
|
43
|
+
load: serverFile !== pageFile && existsSync(serverFile) && hasNamedExport(serverFile, "load") ? toImportSpecifier(dirname(outputPath), serverFile) : void 0
|
|
44
|
+
};
|
|
105
45
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const outDir = dirname(outPath);
|
|
113
|
-
const hadExistingOutput = existsSync(outPath);
|
|
114
|
-
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
|
|
115
|
-
let existing = "";
|
|
116
|
-
try {
|
|
117
|
-
existing = readFileSync(outPath, "utf-8");
|
|
118
|
-
} catch {}
|
|
119
|
-
if (output.includes(root)) console.warn(`[analog] Generated route output contains an absolute path prefix (${root}). Route keys and sourceFile values should be workspace-relative.`);
|
|
120
|
-
const normalizeEndings = (s) => s.replace(/\r\n/g, "\n");
|
|
121
|
-
if (normalizeEndings(existing) !== normalizeEndings(output)) {
|
|
122
|
-
debugTypedRouter("route file changed", {
|
|
123
|
-
outFile: resolvedOptions.outFile,
|
|
124
|
-
routes: manifest.routes.length,
|
|
125
|
-
verify: resolvedOptions.verify,
|
|
126
|
-
verifyOnBuild: resolvedOptions.verifyOnBuild,
|
|
127
|
-
command
|
|
128
|
-
});
|
|
129
|
-
if (resolvedOptions.verify) throw new Error(`[analog] Stale route file detected: ${resolvedOptions.outFile}\nThe checked-in generated route file does not match the current route sources.\nRegenerate route files and commit the updated output.`);
|
|
130
|
-
writeFileSync(outPath, output, "utf-8");
|
|
131
|
-
if (command === "build" && resolvedOptions.verifyOnBuild && hadExistingOutput) throw new Error(`[analog] Stale route file detected during build: ${resolvedOptions.outFile}\nThe generated route file was updated to match the current route sources.\nReview the updated output, commit it if it is checked in, and rerun the build.`);
|
|
46
|
+
const exists = existsSync(outputPath);
|
|
47
|
+
const current = exists ? readFileSync(outputPath, "utf8") : "";
|
|
48
|
+
if (current !== output && !sameDeclarations(current, output)) {
|
|
49
|
+
if (exists && command === "build" && (options.verifyOnBuild ?? true)) throw new Error(`[analog] Stale route file: ${outFile}. Run the dev server to regenerate it before building.`);
|
|
50
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
51
|
+
writeFileSync(outputPath, output);
|
|
132
52
|
}
|
|
133
53
|
}
|
|
134
54
|
return {
|
|
135
55
|
name: "analog-typed-routes",
|
|
136
56
|
config(config, env) {
|
|
137
57
|
command = env.command;
|
|
138
|
-
root = normalizePath(resolve(workspaceRoot, config.root
|
|
58
|
+
root = normalizePath(resolve(workspaceRoot, config.root ?? "."));
|
|
139
59
|
discovery = createRouteFileDiscovery({
|
|
140
60
|
root,
|
|
141
61
|
workspaceRoot,
|
|
142
|
-
additionalPagesDirs:
|
|
143
|
-
additionalContentDirs:
|
|
62
|
+
additionalPagesDirs: options.additionalPagesDirs ?? [],
|
|
63
|
+
additionalContentDirs: options.additionalContentDirs ?? []
|
|
144
64
|
});
|
|
65
|
+
generate();
|
|
66
|
+
},
|
|
67
|
+
configResolved(config) {
|
|
68
|
+
const compiler = config.plugins.find((plugin) => plugin.name === "@analogjs/vite-plugin-angular" || plugin.name === "@analogjs/vite-plugin-angular-fast-compile" || plugin.name === "@analogjs/vite-plugin-angular-compilation-api");
|
|
69
|
+
getTsConfigPath = compiler?.api?.getTsConfigPath;
|
|
70
|
+
if (compiler && !getTsConfigPath) throw new Error("[analog] Typed routing requires a matching @analogjs/vite-plugin-angular version to verify the application tsconfig.");
|
|
71
|
+
verifyTypeInclusion();
|
|
145
72
|
},
|
|
146
73
|
buildStart() {
|
|
74
|
+
discovery.reset();
|
|
147
75
|
generate();
|
|
148
|
-
|
|
76
|
+
verifyTypeInclusion();
|
|
149
77
|
},
|
|
150
78
|
configureServer(server) {
|
|
151
|
-
|
|
79
|
+
server.watcher.add(join(root, outFile));
|
|
80
|
+
for (const event of [
|
|
81
|
+
"add",
|
|
82
|
+
"change",
|
|
83
|
+
"unlink"
|
|
84
|
+
]) server.watcher.on(event, (path) => {
|
|
152
85
|
if (!discovery.getDiscoveredFileKind(path)) return;
|
|
153
|
-
debugTypedRouter("watch regenerate", {
|
|
154
|
-
event,
|
|
155
|
-
path
|
|
156
|
-
});
|
|
157
86
|
discovery.updateDiscoveredFile(path, event);
|
|
158
87
|
generate();
|
|
159
|
-
};
|
|
160
|
-
server.watcher.on("add", (path) => regenerate(path, "add"));
|
|
161
|
-
server.watcher.on("change", (path) => regenerate(path, "change"));
|
|
162
|
-
server.watcher.on("unlink", (path) => regenerate(path, "unlink"));
|
|
88
|
+
});
|
|
163
89
|
}
|
|
164
90
|
};
|
|
165
91
|
}
|
|
166
|
-
function
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
routeFiles.forEach((filename) => {
|
|
170
|
-
try {
|
|
171
|
-
if (!detectJsonLdModuleExports(readFileSync(resolveFile(filename), "utf-8"))) return;
|
|
172
|
-
entries.push({
|
|
173
|
-
kind: "module",
|
|
174
|
-
routePath: filenameToRoutePath(filename),
|
|
175
|
-
sourceFile: filename,
|
|
176
|
-
importAlias: `routeModule${importIndex++}`
|
|
177
|
-
});
|
|
178
|
-
} catch {}
|
|
179
|
-
});
|
|
180
|
-
contentFiles.forEach((filename) => {
|
|
181
|
-
try {
|
|
182
|
-
const jsonLd = extractMarkdownJsonLd(readFileSync(resolveFile(filename), "utf-8"));
|
|
183
|
-
if (jsonLd.length === 0) return;
|
|
184
|
-
entries.push({
|
|
185
|
-
kind: "content",
|
|
186
|
-
routePath: filenameToRoutePath(filename),
|
|
187
|
-
sourceFile: filename,
|
|
188
|
-
jsonLd
|
|
189
|
-
});
|
|
190
|
-
} catch {}
|
|
191
|
-
});
|
|
192
|
-
return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));
|
|
92
|
+
function toImportSpecifier(fromDir, file) {
|
|
93
|
+
const specifier = normalizePath(relative(fromDir, file)).replace(/\.ts$/, "");
|
|
94
|
+
return specifier.startsWith(".") ? specifier : `./${specifier}`;
|
|
193
95
|
}
|
|
194
|
-
function
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
return [
|
|
207
|
-
"// This file is auto-generated by @analogjs/platform",
|
|
208
|
-
"// Do not edit manually",
|
|
209
|
-
"",
|
|
210
|
-
...imports.length > 0 ? [...imports, ""] : [],
|
|
211
|
-
bodies.join("\n\n"),
|
|
212
|
-
""
|
|
213
|
-
].join("\n");
|
|
96
|
+
function hasNamedExport(file, name) {
|
|
97
|
+
const source = readFileSync(file, "utf8");
|
|
98
|
+
if (!source.includes(name)) return false;
|
|
99
|
+
const { program, errors } = parseSync(file, source);
|
|
100
|
+
if (errors.length) return false;
|
|
101
|
+
return program.body.some((statement) => {
|
|
102
|
+
if (statement.type !== "ExportNamedDeclaration" || statement.exportKind === "type") return false;
|
|
103
|
+
const declaration = statement.declaration;
|
|
104
|
+
if (declaration?.type === "VariableDeclaration") return declaration.declarations.some((item) => item.id.type === "Identifier" && item.id.name === name);
|
|
105
|
+
if (declaration?.type === "FunctionDeclaration") return declaration.id?.name === name;
|
|
106
|
+
return statement.specifiers.some((specifier) => specifier.exportKind !== "type" && (specifier.exported.type === "Identifier" ? specifier.exported.name : specifier.exported.value) === name);
|
|
107
|
+
});
|
|
214
108
|
}
|
|
215
|
-
function
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
while (index < lines.length && lines[index].startsWith("import ")) {
|
|
222
|
-
importLines.push(lines[index]);
|
|
223
|
-
index++;
|
|
224
|
-
}
|
|
225
|
-
while (index < lines.length && lines[index] === "") index++;
|
|
226
|
-
return {
|
|
227
|
-
importLines,
|
|
228
|
-
body: lines.slice(index).join("\n")
|
|
229
|
-
};
|
|
109
|
+
function sameDeclarations(current, output) {
|
|
110
|
+
const left = parseSync("routeTree.gen.d.ts", current);
|
|
111
|
+
const right = parseSync("routeTree.gen.d.ts", output);
|
|
112
|
+
if (left.errors.length || right.errors.length) return false;
|
|
113
|
+
const withoutFormatting = (key, value) => key === "start" || key === "end" || key === "raw" ? void 0 : value;
|
|
114
|
+
return JSON.stringify(left.program, withoutFormatting) === JSON.stringify(right.program, withoutFormatting);
|
|
230
115
|
}
|
|
231
116
|
//#endregion
|
|
232
117
|
export { typedRoutes };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { normalizePath, type Plugin } from 'vite';\nimport { resolve, join, dirname, relative } from 'node:path';\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from 'node:fs';\n\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n generateRouteTreeDeclaration,\n detectSchemaExports,\n formatManifestSummary,\n filenameToRoutePath,\n} from './route-manifest.js';\nimport type { RouteSchemaInfo } from './route-manifest.js';\nimport {\n detectJsonLdModuleExports,\n extractMarkdownJsonLd,\n generateJsonLdManifestSource,\n type JsonLdManifestEntry,\n} from './json-ld-manifest-plugin.js';\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\nimport { debugTypedRouter } from './utils/debug.js';\n\nconst DEFAULT_OUT_FILE = 'src/routeTree.gen.ts';\n\nexport interface TypedRoutesPluginOptions {\n /**\n * Output path for the single generated route module,\n * relative to the app root.\n *\n * @default 'src/routeTree.gen.ts'\n */\n outFile?: string;\n /**\n * Workspace root used to resolve additional route/content directories.\n *\n * @default process.env['NX_WORKSPACE_ROOT'] ?? process.cwd()\n */\n workspaceRoot?: string;\n /**\n * Additional page directories to scan for `.page.ts` files.\n */\n additionalPagesDirs?: string[];\n /**\n * Additional content directories to scan for `.md` files.\n */\n additionalContentDirs?: string[];\n /**\n * Include generated `routeJsonLdManifest` exports in the generated route file.\n *\n * @default true\n */\n jsonLdManifest?: boolean;\n /**\n * When true, compare generated output against the existing file and\n * throw an error if they differ instead of writing. Useful for CI to\n * detect stale checked-in route files.\n *\n * @default false\n */\n verify?: boolean;\n /**\n * When true, production builds fail after regenerating a stale checked-in\n * route file. This preserves self-healing writes in development while making\n * build-time freshness issues visible by default.\n *\n * @default true\n */\n verifyOnBuild?: boolean;\n}\n\nfunction resolvePluginOptions(\n options: TypedRoutesPluginOptions = {},\n): Required<TypedRoutesPluginOptions> {\n return {\n outFile: options.outFile ?? DEFAULT_OUT_FILE,\n workspaceRoot:\n options.workspaceRoot ??\n process.env['NX_WORKSPACE_ROOT'] ??\n process.cwd(),\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n jsonLdManifest: options.jsonLdManifest ?? true,\n verify: options.verify ?? false,\n verifyOnBuild: options.verifyOnBuild ?? true,\n };\n}\n\n/**\n * Vite plugin that generates a single typed route module for Analog file routes.\n */\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const resolvedOptions = resolvePluginOptions(options);\n const workspaceRoot = normalizePath(resolvedOptions.workspaceRoot);\n let root = '';\n let command: 'build' | 'serve' = 'serve';\n let discovery: RouteFileDiscovery;\n\n function isFreshnessCheck(): boolean {\n return (\n resolvedOptions.verify ||\n (command === 'build' && resolvedOptions.verifyOnBuild)\n );\n }\n\n function resolveDiscoveredFile(filename: string): string {\n const fromRoot = join(root, filename);\n if (existsSync(fromRoot)) return fromRoot;\n return join(workspaceRoot, filename);\n }\n\n function detectSchemas(relativeFilename: string): RouteSchemaInfo {\n if (!relativeFilename.endsWith('.ts')) {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n\n try {\n const absPath = resolveDiscoveredFile(relativeFilename);\n const content = readFileSync(absPath, 'utf-8');\n return detectSchemaExports(content);\n } catch {\n return { hasParamsSchema: false, hasQuerySchema: false };\n }\n }\n\n /**\n * Ensures the generated route file is imported from an app entry file\n * so the module augmentation is always part of the TypeScript program.\n */\n function ensureEntryImport(): void {\n const entryFiles = ['src/main.ts', 'src/main.server.ts'];\n\n // Compute the import specifier relative to the entry file\n function importSpecifierFor(entryFile: string): string {\n const rel = relative(dirname(entryFile), resolvedOptions.outFile)\n .replace(/\\.ts$/, '')\n .replace(/\\\\/g, '/');\n return rel.startsWith('.') ? rel : './' + rel;\n }\n\n for (const entryFile of entryFiles) {\n const entryPath = join(root, entryFile);\n if (!existsSync(entryPath)) continue;\n\n const content = readFileSync(entryPath, 'utf-8');\n const specifier = importSpecifierFor(entryFile);\n\n // Check if any variation of the import already exists\n const escaped = specifier.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const pattern = new RegExp(`import\\\\s+['\"]${escaped}(\\\\.ts|\\\\.js)?['\"]`);\n if (pattern.test(content)) {\n return;\n }\n\n if (isFreshnessCheck()) {\n return;\n }\n\n // Insert the import after the last existing import line\n const importLine = `import '${specifier}';`;\n const lines = content.split('\\n');\n let lastImportLine = -1;\n\n for (let i = 0; i < lines.length; i++) {\n if (/^import\\s/.test(lines[i])) {\n lastImportLine = i;\n }\n }\n\n if (lastImportLine >= 0) {\n lines.splice(lastImportLine + 1, 0, importLine);\n } else {\n lines.unshift(importLine);\n }\n\n writeFileSync(entryPath, lines.join('\\n'), 'utf-8');\n console.log(`[analog] Added route tree import to ${entryFile}`);\n return;\n }\n\n // No suitable entry file found\n const specifier = importSpecifierFor('src/main.ts');\n if (isFreshnessCheck()) {\n return;\n }\n console.warn(\n `[analog] Could not find an entry file (src/main.ts or src/main.server.ts) ` +\n `to add the route tree import. Add \\`import '${specifier}';\\` ` +\n `to your app entry file to ensure typed routing is active.`,\n );\n }\n\n function generate(): void {\n const routeFiles = discovery.getRouteFiles();\n const contentFiles = discovery.getContentFiles();\n debugTypedRouter('discovered files', {\n routeFiles: routeFiles.length,\n contentFiles: contentFiles.length,\n });\n const allFiles = [...routeFiles, ...contentFiles];\n const manifest = generateRouteManifest(\n allFiles,\n detectSchemas,\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const declaration = generateRouteTableDeclaration(manifest);\n const canonicalFiles = new Set(\n manifest.routes.map((route) => route.filename),\n );\n const jsonLdEntries = buildJsonLdEntries(\n resolveDiscoveredFile,\n routeFiles.filter((filename) => canonicalFiles.has(filename)),\n contentFiles.filter((filename) => canonicalFiles.has(filename)),\n );\n const routeTree = generateRouteTreeDeclaration(manifest, {\n jsonLdFiles: jsonLdEntries.map((entry) => entry.sourceFile),\n });\n const output = combineGeneratedModules(\n declaration,\n routeTree,\n resolvedOptions.jsonLdManifest && jsonLdEntries.length > 0\n ? generateJsonLdManifestSource(jsonLdEntries, resolvedOptions.outFile)\n : '',\n );\n\n const hardCollisions = manifest.collisions.filter((c) => c.samePriority);\n if (manifest.collisions.length > 0) {\n debugTypedRouter('route collisions', {\n total: manifest.collisions.length,\n hard: hardCollisions.length,\n collisions: manifest.collisions.map((c) => ({\n path: c.fullPath,\n kept: c.keptFile,\n dropped: c.droppedFile,\n })),\n });\n }\n if (hardCollisions.length > 0 && command === 'build') {\n const details = hardCollisions\n .map((c) => ` '${c.fullPath}': '${c.keptFile}' vs '${c.droppedFile}'`)\n .join('\\n');\n throw new Error(\n `[analog] Route collisions detected during build:\\n${details}\\n\\n` +\n `Each route path must be defined by exactly one source file. ` +\n `Remove or rename the conflicting files to resolve the collision.`,\n );\n }\n\n if (manifest.routes.length > 0) {\n console.log(formatManifestSummary(manifest));\n }\n\n const outPath = join(root, resolvedOptions.outFile);\n const outDir = dirname(outPath);\n const hadExistingOutput = existsSync(outPath);\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true });\n }\n\n let existing = '';\n\n try {\n existing = readFileSync(outPath, 'utf-8');\n } catch {\n // file does not exist yet\n }\n\n // Build-time guard: detect absolute path leaks in generated output.\n // Machine-specific prefixes must never appear in route keys or sourceFile values.\n if (output.includes(root)) {\n console.warn(\n `[analog] Generated route output contains an absolute path prefix (${root}). ` +\n `Route keys and sourceFile values should be workspace-relative.`,\n );\n }\n\n // Normalize line endings before comparison so that files checked in\n // with LF don't appear stale on Windows where readFileSync may return CRLF.\n const normalizeEndings = (s: string) => s.replace(/\\r\\n/g, '\\n');\n if (normalizeEndings(existing) !== normalizeEndings(output)) {\n debugTypedRouter('route file changed', {\n outFile: resolvedOptions.outFile,\n routes: manifest.routes.length,\n verify: resolvedOptions.verify,\n verifyOnBuild: resolvedOptions.verifyOnBuild,\n command,\n });\n if (resolvedOptions.verify) {\n throw new Error(\n `[analog] Stale route file detected: ${resolvedOptions.outFile}\\n` +\n `The checked-in generated route file does not match the current route sources.\\n` +\n `Regenerate route files and commit the updated output.`,\n );\n }\n\n writeFileSync(outPath, output, 'utf-8');\n\n if (\n command === 'build' &&\n resolvedOptions.verifyOnBuild &&\n hadExistingOutput\n ) {\n throw new Error(\n `[analog] Stale route file detected during build: ${resolvedOptions.outFile}\\n` +\n `The generated route file was updated to match the current route sources.\\n` +\n `Review the updated output, commit it if it is checked in, and rerun the build.`,\n );\n }\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root || '.') || '.');\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: resolvedOptions.additionalPagesDirs,\n additionalContentDirs: resolvedOptions.additionalContentDirs,\n });\n },\n buildStart() {\n generate();\n if (!isFreshnessCheck()) {\n ensureEntryImport();\n }\n },\n configureServer(server) {\n const regenerate = (path: string, event: 'add' | 'change' | 'unlink') => {\n // Reuse the discovery matcher so watch-time updates stay in sync with\n // the initial scan and don't pull Nitro server routes into routeTree.gen.ts.\n if (!discovery.getDiscoveredFileKind(path)) {\n return;\n }\n\n debugTypedRouter('watch regenerate', { event, path });\n discovery.updateDiscoveredFile(path, event);\n generate();\n };\n\n server.watcher.on('add', (path) => regenerate(path, 'add'));\n server.watcher.on('change', (path) => regenerate(path, 'change'));\n server.watcher.on('unlink', (path) => regenerate(path, 'unlink'));\n },\n };\n}\n\nfunction buildJsonLdEntries(\n resolveFile: (filename: string) => string,\n routeFiles: string[],\n contentFiles: string[],\n): JsonLdManifestEntry[] {\n const entries: JsonLdManifestEntry[] = [];\n let importIndex = 0;\n\n routeFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n if (!detectJsonLdModuleExports(source)) {\n return;\n }\n\n entries.push({\n kind: 'module',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n importAlias: `routeModule${importIndex++}`,\n });\n } catch {\n // ignore unreadable route file\n }\n });\n\n contentFiles.forEach((filename) => {\n try {\n const source = readFileSync(resolveFile(filename), 'utf-8');\n const jsonLd = extractMarkdownJsonLd(source);\n\n if (jsonLd.length === 0) {\n return;\n }\n\n entries.push({\n kind: 'content',\n routePath: filenameToRoutePath(filename),\n sourceFile: filename,\n jsonLd,\n });\n } catch {\n // ignore unreadable content file\n }\n });\n\n return entries.sort((a, b) => a.routePath.localeCompare(b.routePath));\n}\n\nfunction combineGeneratedModules(...sources: string[]): string {\n const imports: string[] = [];\n const seenImports = new Set<string>();\n const bodies: string[] = [];\n\n for (const source of sources) {\n const { body, importLines } = splitGeneratedModule(source);\n for (const importLine of importLines) {\n if (!seenImports.has(importLine)) {\n seenImports.add(importLine);\n imports.push(importLine);\n }\n }\n if (body.trim()) {\n bodies.push(body.trim());\n }\n }\n\n return [\n '// This file is auto-generated by @analogjs/platform',\n '// Do not edit manually',\n '',\n ...(imports.length > 0 ? [...imports, ''] : []),\n bodies.join('\\n\\n'),\n '',\n ].join('\\n');\n}\n\nfunction splitGeneratedModule(source: string): {\n importLines: string[];\n body: string;\n} {\n const lines = source.split('\\n');\n let index = 0;\n\n while (index < lines.length && lines[index].startsWith('//')) {\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n const importLines: string[] = [];\n while (index < lines.length && lines[index].startsWith('import ')) {\n importLines.push(lines[index]);\n index++;\n }\n\n while (index < lines.length && lines[index] === '') {\n index++;\n }\n\n return {\n importLines,\n body: lines.slice(index).join('\\n'),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"typed-routes-plugin.js","names":[],"sources":["../../../src/lib/typed-routes-plugin.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { readConfiguration } from '@angular/compiler-cli';\nimport { parseSync } from 'oxc-parser';\nimport { normalizePath, type Plugin } from 'vite';\n\nimport {\n createRouteFileDiscovery,\n type RouteFileDiscovery,\n} from './route-file-discovery.js';\nimport {\n generateRouteManifest,\n generateRouteTableDeclaration,\n} from './route-manifest.js';\n\nexport interface TypedRoutingOptions {\n /** Generated declaration path (.d.ts), relative to the app root. */\n outFile?: string;\n /** Fail builds when a checked-in route table is stale. Defaults to true. */\n verifyOnBuild?: boolean;\n}\n\nexport interface TypedRoutesPluginOptions extends TypedRoutingOptions {\n workspaceRoot?: string;\n additionalPagesDirs?: string[];\n additionalContentDirs?: string[];\n}\n\nexport function typedRoutes(options: TypedRoutesPluginOptions = {}): Plugin {\n const workspaceRoot = normalizePath(options.workspaceRoot ?? process.cwd());\n const outFile = options.outFile ?? 'src/routeTree.gen.d.ts';\n if (!outFile.endsWith('.d.ts')) {\n throw new Error(\n '[analog] Typed routing outFile must end in .d.ts. Include this declaration in your application tsconfig.',\n );\n }\n let root: string;\n let command: 'build' | 'serve';\n let discovery: RouteFileDiscovery;\n\n let getTsConfigPath: (() => string) | undefined;\n\n function verifyTypeInclusion(): void {\n if (!getTsConfigPath) return;\n const tsconfig = getTsConfigPath();\n const { rootNames, errors } = readConfiguration(tsconfig);\n if (errors.length) {\n throw new Error(\n `[analog] Cannot verify typed routing: unable to read ${tsconfig}.`,\n );\n }\n const outputPath = normalizePath(resolve(root, outFile));\n if (\n !rootNames.some((file) => normalizePath(resolve(file)) === outputPath)\n ) {\n const declaration = normalizePath(\n relative(dirname(tsconfig), outputPath),\n );\n throw new Error(\n `[analog] Typed route declaration is not included in ${tsconfig}. Add \"${declaration}\" to its \"files\" or \"include\" list so typed routing is available to the compiler and editor.`,\n );\n }\n }\n\n function generate(): void {\n const manifest = generateRouteManifest(\n [...discovery.getRouteFiles(), ...discovery.getContentFiles()],\n (filename) => (discovery.isAppLocal(filename) ? 0 : 1),\n );\n const collisions = manifest.collisions.filter(\n (collision) => collision.samePriority,\n );\n if (command === 'build' && collisions.length) {\n throw new Error(\n `[analog] Route collisions detected: ${collisions.map((c) => `${c.fullPath}: ${c.keptFile}, ${c.droppedFile}`).join('; ')}`,\n );\n }\n const outputPath = join(root, outFile);\n const output = generateRouteTableDeclaration(manifest, (filename) => {\n const pageFile = [\n join(root, filename),\n join(workspaceRoot, filename),\n filename,\n ].find((file) => existsSync(file));\n if (!pageFile) return undefined;\n const serverFile = pageFile.replace(/\\.page\\.ts$/, '.server.ts');\n return {\n routeMeta: hasNamedExport(pageFile, 'routeMeta')\n ? toImportSpecifier(dirname(outputPath), pageFile)\n : undefined,\n load:\n serverFile !== pageFile &&\n existsSync(serverFile) &&\n hasNamedExport(serverFile, 'load')\n ? toImportSpecifier(dirname(outputPath), serverFile)\n : undefined,\n };\n });\n const exists = existsSync(outputPath);\n const current = exists ? readFileSync(outputPath, 'utf8') : '';\n if (current !== output && !sameDeclarations(current, output)) {\n if (exists && command === 'build' && (options.verifyOnBuild ?? true)) {\n throw new Error(\n `[analog] Stale route file: ${outFile}. Run the dev server to regenerate it before building.`,\n );\n }\n mkdirSync(dirname(outputPath), { recursive: true });\n writeFileSync(outputPath, output);\n }\n }\n\n return {\n name: 'analog-typed-routes',\n config(config, env) {\n command = env.command;\n root = normalizePath(resolve(workspaceRoot, config.root ?? '.'));\n discovery = createRouteFileDiscovery({\n root,\n workspaceRoot,\n additionalPagesDirs: options.additionalPagesDirs ?? [],\n additionalContentDirs: options.additionalContentDirs ?? [],\n });\n // Generate before Angular reads the application tsconfig.\n generate();\n },\n configResolved(config) {\n const compiler = config.plugins.find(\n (plugin) =>\n plugin.name === '@analogjs/vite-plugin-angular' ||\n plugin.name === '@analogjs/vite-plugin-angular-fast-compile' ||\n plugin.name === '@analogjs/vite-plugin-angular-compilation-api',\n );\n getTsConfigPath = compiler?.api?.getTsConfigPath;\n if (compiler && !getTsConfigPath) {\n throw new Error(\n '[analog] Typed routing requires a matching @analogjs/vite-plugin-angular version to verify the application tsconfig.',\n );\n }\n verifyTypeInclusion();\n },\n buildStart() {\n discovery.reset();\n generate();\n verifyTypeInclusion();\n },\n configureServer(server) {\n server.watcher.add(join(root, outFile));\n for (const event of ['add', 'change', 'unlink'] as const) {\n server.watcher.on(event, (path) => {\n if (!discovery.getDiscoveredFileKind(path)) return;\n discovery.updateDiscoveredFile(path, event);\n generate();\n });\n }\n },\n };\n}\n\nfunction toImportSpecifier(fromDir: string, file: string): string {\n const specifier = normalizePath(relative(fromDir, file)).replace(/\\.ts$/, '');\n return specifier.startsWith('.') ? specifier : `./${specifier}`;\n}\n\nfunction hasNamedExport(file: string, name: string): boolean {\n const source = readFileSync(file, 'utf8');\n if (!source.includes(name)) return false;\n const { program, errors } = parseSync(file, source);\n if (errors.length) return false;\n return program.body.some((statement) => {\n if (\n statement.type !== 'ExportNamedDeclaration' ||\n statement.exportKind === 'type'\n ) {\n return false;\n }\n const declaration = statement.declaration;\n if (declaration?.type === 'VariableDeclaration') {\n return declaration.declarations.some(\n (item) => item.id.type === 'Identifier' && item.id.name === name,\n );\n }\n if (declaration?.type === 'FunctionDeclaration') {\n return declaration.id?.name === name;\n }\n return statement.specifiers.some(\n (specifier) =>\n specifier.exportKind !== 'type' &&\n (specifier.exported.type === 'Identifier'\n ? specifier.exported.name\n : specifier.exported.value) === name,\n );\n });\n}\n\nfunction sameDeclarations(current: string, output: string): boolean {\n const left = parseSync('routeTree.gen.d.ts', current);\n const right = parseSync('routeTree.gen.d.ts', output);\n if (left.errors.length || right.errors.length) return false;\n const withoutFormatting = (key: string, value: unknown) =>\n key === 'start' || key === 'end' || key === 'raw' ? undefined : value;\n return (\n JSON.stringify(left.program, withoutFormatting) ===\n JSON.stringify(right.program, withoutFormatting)\n );\n}\n"],"mappings":"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"}
|
package/src/lib/utils/debug.js
CHANGED
|
@@ -3,22 +3,19 @@ import { createDebugHarness } from "./debug-harness.js";
|
|
|
3
3
|
import { createDebug } from "obug";
|
|
4
4
|
//#region packages/platform/src/lib/utils/debug.ts
|
|
5
5
|
var debugPlatform = createDebug("analog:platform");
|
|
6
|
-
var debugRoutes = createDebug("analog:platform:routes");
|
|
7
|
-
var debugContent = createDebug("analog:platform:content");
|
|
8
|
-
var debugTypedRouter = createDebug("analog:platform:typed-router");
|
|
9
6
|
var harness = createDebugHarness({
|
|
10
7
|
fallbackNamespace: "analog:*",
|
|
11
8
|
instanceGroups: [[
|
|
12
9
|
debugPlatform,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
createDebug("analog:platform:routes"),
|
|
11
|
+
createDebug("analog:platform:content"),
|
|
12
|
+
createDebug("analog:platform:typed-router")
|
|
16
13
|
], nitroDebugInstances]
|
|
17
14
|
});
|
|
18
15
|
var applyDebugOption = harness.applyDebugOption;
|
|
19
16
|
var activateDeferredDebug = harness.activateDeferredDebug;
|
|
20
17
|
harness._resetDeferredDebug;
|
|
21
18
|
//#endregion
|
|
22
|
-
export { activateDeferredDebug, applyDebugOption, debugPlatform
|
|
19
|
+
export { activateDeferredDebug, applyDebugOption, debugPlatform };
|
|
23
20
|
|
|
24
21
|
//# sourceMappingURL=debug.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/utils/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\nimport { nitroDebugInstances } from '../nitro/debug.js';\nimport { createDebugHarness } from './debug-harness.js';\n\nexport const debugPlatform = createDebug('analog:platform');\nexport const debugRoutes = createDebug('analog:platform:routes');\nexport const debugContent = createDebug('analog:platform:content');\nexport const debugTypedRouter = createDebug('analog:platform:typed-router');\n\nconst platformDebugInstances = [\n debugPlatform,\n debugRoutes,\n debugContent,\n debugTypedRouter,\n];\n\nexport type DebugScope =\n | 'analog:*'\n | 'analog:platform'\n | 'analog:platform:*'\n | 'analog:platform:routes'\n | 'analog:platform:content'\n | 'analog:platform:typed-router'\n | 'analog:angular:*'\n | 'analog:angular:hmr'\n | 'analog:angular:styles'\n | 'analog:angular:compiler'\n | 'analog:angular:compilation-api'\n | 'analog:angular:style-pipeline'\n | 'analog:nitro'\n | 'analog:nitro:*'\n | 'analog:nitro:ssr'\n | 'analog:nitro:prerender'\n | (string & {});\n\nexport type DebugMode = 'build' | 'dev';\n\nexport interface DebugModeOptions {\n scopes?: boolean | DebugScope[];\n mode?: DebugMode;\n /**\n * Write debug output to log files under `tmp/debug/` in the workspace root.\n * - `true` or `'single'` — all output to `tmp/debug/analog.log`\n * - `'scoped'` — one file per scope, e.g. `tmp/debug/analog.angular.hmr.log`\n */\n logFile?: boolean | 'single' | 'scoped';\n}\n\nexport type DebugOption =\n | boolean\n | DebugScope[]\n | DebugModeOptions\n | DebugModeOptions[];\n\nconst harness = createDebugHarness({\n fallbackNamespace: 'analog:*',\n instanceGroups: [platformDebugInstances, nitroDebugInstances],\n});\n\nexport const applyDebugOption: (\n debug: DebugOption | undefined,\n workspaceRoot?: string,\n) => void = harness.applyDebugOption;\nexport const activateDeferredDebug: (command: 'build' | 'serve') => void =\n harness.activateDeferredDebug;\nexport const _resetDeferredDebug: () => void = harness._resetDeferredDebug;\n"],"mappings":";;;;AAIA,IAAa,gBAAgB,YAAY,kBAAkB;
|
|
1
|
+
{"version":3,"file":"debug.js","names":[],"sources":["../../../../src/lib/utils/debug.ts"],"sourcesContent":["import { createDebug } from 'obug';\nimport { nitroDebugInstances } from '../nitro/debug.js';\nimport { createDebugHarness } from './debug-harness.js';\n\nexport const debugPlatform = createDebug('analog:platform');\nexport const debugRoutes = createDebug('analog:platform:routes');\nexport const debugContent = createDebug('analog:platform:content');\nexport const debugTypedRouter = createDebug('analog:platform:typed-router');\n\nconst platformDebugInstances = [\n debugPlatform,\n debugRoutes,\n debugContent,\n debugTypedRouter,\n];\n\nexport type DebugScope =\n | 'analog:*'\n | 'analog:platform'\n | 'analog:platform:*'\n | 'analog:platform:routes'\n | 'analog:platform:content'\n | 'analog:platform:typed-router'\n | 'analog:angular:*'\n | 'analog:angular:hmr'\n | 'analog:angular:styles'\n | 'analog:angular:compiler'\n | 'analog:angular:compilation-api'\n | 'analog:angular:style-pipeline'\n | 'analog:nitro'\n | 'analog:nitro:*'\n | 'analog:nitro:ssr'\n | 'analog:nitro:prerender'\n | (string & {});\n\nexport type DebugMode = 'build' | 'dev';\n\nexport interface DebugModeOptions {\n scopes?: boolean | DebugScope[];\n mode?: DebugMode;\n /**\n * Write debug output to log files under `tmp/debug/` in the workspace root.\n * - `true` or `'single'` — all output to `tmp/debug/analog.log`\n * - `'scoped'` — one file per scope, e.g. `tmp/debug/analog.angular.hmr.log`\n */\n logFile?: boolean | 'single' | 'scoped';\n}\n\nexport type DebugOption =\n | boolean\n | DebugScope[]\n | DebugModeOptions\n | DebugModeOptions[];\n\nconst harness = createDebugHarness({\n fallbackNamespace: 'analog:*',\n instanceGroups: [platformDebugInstances, nitroDebugInstances],\n});\n\nexport const applyDebugOption: (\n debug: DebugOption | undefined,\n workspaceRoot?: string,\n) => void = harness.applyDebugOption;\nexport const activateDeferredDebug: (command: 'build' | 'serve') => void =\n harness.activateDeferredDebug;\nexport const _resetDeferredDebug: () => void = harness._resetDeferredDebug;\n"],"mappings":";;;;AAIA,IAAa,gBAAgB,YAAY,kBAAkB;AAkD3D,IAAM,UAAU,mBAAmB;CACjC,mBAAmB;CACnB,gBAAgB,CA/Ca;EAC7B;EALyB,YAAY,yBAAyB;EACpC,YAAY,0BAA0B;EAClC,YAAY,+BAA+B;EAO1E,EA0C0C,oBAAoB;CAC9D,CAAC;AAEF,IAAa,mBAGD,QAAQ;AACpB,IAAa,wBACX,QAAQ;AACqC,QAAQ"}
|
|
@@ -10,6 +10,6 @@ function getBundleOptionsKey() {
|
|
|
10
10
|
return isRolldown() ? "rolldownOptions" : "rollupOptions";
|
|
11
11
|
}
|
|
12
12
|
//#endregion
|
|
13
|
-
export { getBundleOptionsKey, getJsTransformConfigKey };
|
|
13
|
+
export { getBundleOptionsKey, getJsTransformConfigKey, isRolldown };
|
|
14
14
|
|
|
15
15
|
//# sourceMappingURL=rolldown.js.map
|
|
@@ -10,4 +10,4 @@ export declare const SERVER_MODE_ID = "virtual:@analogjs/platform/server-mode";
|
|
|
10
10
|
* first so the flag is set before any application module evaluates. A text
|
|
11
11
|
* replacement cannot do this because Angular is pre-bundled for SSR.
|
|
12
12
|
*/
|
|
13
|
-
export declare function serverModePlugin(): Plugin[];
|
|
13
|
+
export declare function serverModePlugin(entryServer?: string): Plugin[];
|