@reactra/vite-plugin 0.1.0-alpha.0
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/LICENSE +21 -0
- package/README.md +17 -0
- package/dist/esbuild-prescan.d.ts +31 -0
- package/dist/esbuild-prescan.d.ts.map +1 -0
- package/dist/esbuild-prescan.js +75 -0
- package/dist/esbuild-prescan.js.map +1 -0
- package/dist/extract.d.ts +128 -0
- package/dist/extract.d.ts.map +1 -0
- package/dist/extract.js +155 -0
- package/dist/extract.js.map +1 -0
- package/dist/fingerprint.d.ts +28 -0
- package/dist/fingerprint.d.ts.map +1 -0
- package/dist/fingerprint.js +68 -0
- package/dist/fingerprint.js.map +1 -0
- package/dist/graph-cache.d.ts +23 -0
- package/dist/graph-cache.d.ts.map +1 -0
- package/dist/graph-cache.js +52 -0
- package/dist/graph-cache.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +379 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +57 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +147 -0
- package/dist/manifest.js.map +1 -0
- package/dist/resource-names.d.ts +49 -0
- package/dist/resource-names.d.ts.map +1 -0
- package/dist/resource-names.js +128 -0
- package/dist/resource-names.js.map +1 -0
- package/dist/sidecar-writer.d.ts +60 -0
- package/dist/sidecar-writer.d.ts.map +1 -0
- package/dist/sidecar-writer.js +114 -0
- package/dist/sidecar-writer.js.map +1 -0
- package/dist/walker.d.ts +332 -0
- package/dist/walker.d.ts.map +1 -0
- package/dist/walker.js +1070 -0
- package/dist/walker.js.map +1 -0
- package/package.json +39 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Akhil Shastri and the Reactra contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @reactra/vite-plugin
|
|
2
|
+
|
|
3
|
+
> Reactra Vite plugin — wires the Reactra compiler and file-based route manifest into Vite.
|
|
4
|
+
|
|
5
|
+
**Alpha** — published under the npm dist-tag `alpha`. APIs may change before 1.0.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @reactra/vite-plugin@alpha
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Part of [Reactra](https://github.com/akhilshastri/reactra) — a compiler-first,
|
|
12
|
+
React-19-compatible framework. See the [documentation](https://reactra-docs.vercel.app) to get
|
|
13
|
+
started.
|
|
14
|
+
|
|
15
|
+
## License
|
|
16
|
+
|
|
17
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural type for esbuild's `Plugin` interface — we don't
|
|
3
|
+
* import esbuild at type-position to keep this file resolvable under
|
|
4
|
+
* Bun without esbuild's full type tree.
|
|
5
|
+
*/
|
|
6
|
+
export interface EsbuildPluginBuild {
|
|
7
|
+
onLoad(opts: {
|
|
8
|
+
filter: RegExp;
|
|
9
|
+
namespace?: string;
|
|
10
|
+
}, cb: (args: {
|
|
11
|
+
path: string;
|
|
12
|
+
}) => {
|
|
13
|
+
contents: string;
|
|
14
|
+
loader: string;
|
|
15
|
+
} | null): void;
|
|
16
|
+
}
|
|
17
|
+
export interface EsbuildPlugin {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
setup(build: EsbuildPluginBuild): void;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* esbuild plugin factory — registers an `onLoad` callback that
|
|
23
|
+
* preprocesses Reactra DSL files before esbuild's parser sees them.
|
|
24
|
+
*
|
|
25
|
+
* The Vite plugin's `config()` hook injects this into
|
|
26
|
+
* `config.optimizeDeps.esbuildOptions.plugins` so the dep-scan phase
|
|
27
|
+
* runs through it. Once Vite's dev server is up, the regular
|
|
28
|
+
* `transform()` hook takes over for per-request compiles.
|
|
29
|
+
*/
|
|
30
|
+
export declare const reactraEsbuildPlugin: EsbuildPlugin;
|
|
31
|
+
//# sourceMappingURL=esbuild-prescan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esbuild-prescan.d.ts","sourceRoot":"","sources":["../src/esbuild-prescan.ts"],"names":[],"mappings":"AA4BA;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,MAAM,CACJ,IAAI,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,EAC5C,EAAE,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAK;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAC1E,IAAI,CAAA;CACR;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,KAAK,CAAC,KAAK,EAAE,kBAAkB,GAAG,IAAI,CAAA;CACvC;AAID;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,EAAE,aAmClC,CAAA"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// esbuild plugin for Vite's dep-scan pre-bundling phase (Day 24 / `#15`).
|
|
2
|
+
//
|
|
3
|
+
// Vite's `optimizeDeps` runs esbuild over the entry's import graph
|
|
4
|
+
// BEFORE the dev server starts serving requests — so the Vite
|
|
5
|
+
// `transform()` hook hasn't fired yet. esbuild's scanner sees raw
|
|
6
|
+
// DSL keywords (`export component Foo {`, `route store X {`, etc.)
|
|
7
|
+
// and bails with "Unexpected component" / "Expected ';' but found
|
|
8
|
+
// 'store'" parse errors. Result: the dreaded "Failed to run
|
|
9
|
+
// dependency scan" warning at first dev-server boot. On-demand
|
|
10
|
+
// serving still works because the transform pipeline does fire on
|
|
11
|
+
// each request, but pre-bundling is skipped and the user sees a
|
|
12
|
+
// scary-looking stack trace they have to learn to ignore.
|
|
13
|
+
//
|
|
14
|
+
// Fix: register an esbuild `onLoad` callback for `.tsx/.ts/.jsx/.js`
|
|
15
|
+
// files. When the file's source contains a Reactra container keyword,
|
|
16
|
+
// run `compile()` to produce React-19 TSX before esbuild's parser
|
|
17
|
+
// sees it. Otherwise return `null` so esbuild's default loaders take
|
|
18
|
+
// over. Cheap regex gate — same `containsReactraDsl` predicate the
|
|
19
|
+
// Vite `transform` hook uses, so the per-file cost is the same.
|
|
20
|
+
//
|
|
21
|
+
// This module exports a plain esbuild Plugin (not a Vite plugin);
|
|
22
|
+
// the Vite plugin's `config()` hook wires it into
|
|
23
|
+
// `config.optimizeDeps.esbuildOptions.plugins`.
|
|
24
|
+
import { readFileSync } from "node:fs";
|
|
25
|
+
import { compile, containsReactraDsl } from "@reactra/babel-plugin";
|
|
26
|
+
const SUPPORTED_RE = /\.(?:tsx|jsx|ts|js)$/;
|
|
27
|
+
/**
|
|
28
|
+
* esbuild plugin factory — registers an `onLoad` callback that
|
|
29
|
+
* preprocesses Reactra DSL files before esbuild's parser sees them.
|
|
30
|
+
*
|
|
31
|
+
* The Vite plugin's `config()` hook injects this into
|
|
32
|
+
* `config.optimizeDeps.esbuildOptions.plugins` so the dep-scan phase
|
|
33
|
+
* runs through it. Once Vite's dev server is up, the regular
|
|
34
|
+
* `transform()` hook takes over for per-request compiles.
|
|
35
|
+
*/
|
|
36
|
+
export const reactraEsbuildPlugin = {
|
|
37
|
+
name: "@reactra/esbuild-prescan",
|
|
38
|
+
setup(build) {
|
|
39
|
+
build.onLoad({ filter: SUPPORTED_RE }, (args) => {
|
|
40
|
+
let source;
|
|
41
|
+
try {
|
|
42
|
+
source = readFileSync(args.path, "utf8");
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// File unreadable — let esbuild's default loader try (and
|
|
46
|
+
// emit its own clearer error).
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (!containsReactraDsl(source))
|
|
50
|
+
return null;
|
|
51
|
+
// The pre-scan phase only needs source good enough for esbuild
|
|
52
|
+
// to discover IMPORTS — it doesn't actually execute the code.
|
|
53
|
+
// We still run the full compile() because:
|
|
54
|
+
// (1) it's already cheap on Reactra files (no IR, no Babel
|
|
55
|
+
// round-trip — single-pass preprocess + template emit);
|
|
56
|
+
// (2) the resulting React-19 TSX has the same import-set as
|
|
57
|
+
// the original source, so esbuild scans the right deps;
|
|
58
|
+
// (3) handing esbuild anything less risks subtle scan-vs-
|
|
59
|
+
// transform divergence at runtime (different deps pre-
|
|
60
|
+
// bundled than the page actually uses).
|
|
61
|
+
try {
|
|
62
|
+
const { code } = compile(source, { sourceFile: args.path });
|
|
63
|
+
return { contents: code, loader: "tsx" };
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// If our compile fails here, fall through to esbuild's
|
|
67
|
+
// default loader — it'll produce a similar error but at the
|
|
68
|
+
// correct phase (the regular transform also runs compile,
|
|
69
|
+
// so a real syntax error surfaces there with our diagnostics).
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
//# sourceMappingURL=esbuild-prescan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"esbuild-prescan.js","sourceRoot":"","sources":["../src/esbuild-prescan.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,EAAE;AACF,mEAAmE;AACnE,8DAA8D;AAC9D,kEAAkE;AAClE,mEAAmE;AACnE,kEAAkE;AAClE,4DAA4D;AAC5D,+DAA+D;AAC/D,kEAAkE;AAClE,gEAAgE;AAChE,0DAA0D;AAC1D,EAAE;AACF,qEAAqE;AACrE,sEAAsE;AACtE,kEAAkE;AAClE,qEAAqE;AACrE,mEAAmE;AACnE,gEAAgE;AAChE,EAAE;AACF,kEAAkE;AAClE,kDAAkD;AAClD,gDAAgD;AAEhD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEtC,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAmBnE,MAAM,YAAY,GAAG,sBAAsB,CAAA;AAE3C;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAkB;IACjD,IAAI,EAAE,0BAA0B;IAChC,KAAK,CAAC,KAAK;QACT,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE;YAC9C,IAAI,MAAc,CAAA;YAClB,IAAI,CAAC;gBACH,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,0DAA0D;gBAC1D,+BAA+B;gBAC/B,OAAO,IAAI,CAAA;YACb,CAAC;YACD,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAA;YAC5C,+DAA+D;YAC/D,8DAA8D;YAC9D,2CAA2C;YAC3C,6DAA6D;YAC7D,8DAA8D;YAC9D,8DAA8D;YAC9D,8DAA8D;YAC9D,4DAA4D;YAC5D,6DAA6D;YAC7D,8CAA8C;YAC9C,IAAI,CAAC;gBACH,MAAM,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;gBAC3D,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;YAC1C,CAAC;YAAC,MAAM,CAAC;gBACP,uDAAuD;gBACvD,4DAA4D;gBAC5D,0DAA0D;gBAC1D,+DAA+D;gBAC/D,OAAO,IAAI,CAAA;YACb,CAAC;QACH,CAAC,CAAC,CAAA;IACJ,CAAC;CACF,CAAA"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { ContainerNode } from "@reactra/babel-plugin";
|
|
2
|
+
/**
|
|
3
|
+
* One discovered container (component / store / service) extracted from a
|
|
4
|
+
* single source file via {@link extractContainers}.
|
|
5
|
+
*/
|
|
6
|
+
export interface ContainerInfo {
|
|
7
|
+
/** `"component" | "store" | "service"` — derived from the marker name. */
|
|
8
|
+
readonly kind: "component" | "store" | "service";
|
|
9
|
+
readonly name: string;
|
|
10
|
+
/** True when the declaration carries `export` (v2 independent export token). */
|
|
11
|
+
readonly exported: boolean;
|
|
12
|
+
/**
|
|
13
|
+
* Service modifier (`"scoped" | "server"`) when present; undefined for
|
|
14
|
+
* plain `service X {}` and for stores/components.
|
|
15
|
+
*/
|
|
16
|
+
readonly serviceModifier?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Names of every `inject service X` site inside this container's body —
|
|
19
|
+
* used by {@link collectServiceInjectionDiagnostics} to find injection
|
|
20
|
+
* sites that reference undeclared services.
|
|
21
|
+
*/
|
|
22
|
+
readonly injectedServiceNames: string[];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Project the already-compiled container graph into the three-way
|
|
26
|
+
* {@link ContainerInfo}. The pure half of {@link extractContainers}, split out
|
|
27
|
+
* so the A3 graph cache can derive from a single per-file compile (the scanner
|
|
28
|
+
* passes a cached `ContainerNode[]` instead of re-reading + re-compiling).
|
|
29
|
+
*/
|
|
30
|
+
export declare const containersToInfo: (containers: readonly ContainerNode[]) => ContainerInfo[];
|
|
31
|
+
/**
|
|
32
|
+
* Extract all containers (components, stores, services) from a raw DSL source
|
|
33
|
+
* file via the compiled graph. `injectedServiceNames` is read from each
|
|
34
|
+
* container's `injects` list (no body-boundary guessing — a mis-attribution-
|
|
35
|
+
* proof replacement for the old "next top-level const" slice heuristic).
|
|
36
|
+
*
|
|
37
|
+
* Returns `[]` on malformed source — callers must treat an empty result as
|
|
38
|
+
* "no containers" rather than an error.
|
|
39
|
+
*/
|
|
40
|
+
export declare const extractContainers: (source: string) => ContainerInfo[];
|
|
41
|
+
/**
|
|
42
|
+
* Extract the first `export component <Name>` declaration name from `source`.
|
|
43
|
+
* Returns `null` when no exported component is found (plain React TSX, no
|
|
44
|
+
* DSL container, or a malformed file).
|
|
45
|
+
*
|
|
46
|
+
* Replaces `extractPageComponentName` in walker.ts — fixes the R2 regression
|
|
47
|
+
* where the old regex missed `export component Foo uses traceable {` (the
|
|
48
|
+
* header-`uses` form). The graph normalises all header forms before we read.
|
|
49
|
+
*/
|
|
50
|
+
export declare const extractComponentName: (source: string) => string | null;
|
|
51
|
+
/** Container-graph half of {@link extractComponentName} (A3 cache path). */
|
|
52
|
+
export declare const componentNameFromContainers: (containers: readonly ContainerNode[]) => string | null;
|
|
53
|
+
/**
|
|
54
|
+
* Extract the FIRST component's `prefetch` trigger from `source` via the
|
|
55
|
+
* compiled graph (`container.prefetch.trigger`). Returns the trigger word, or
|
|
56
|
+
* `undefined` when the file has no component, no `prefetch` declaration, or is
|
|
57
|
+
* malformed.
|
|
58
|
+
*
|
|
59
|
+
* First-component-wins matches the existing manifest semantics (one prefetch
|
|
60
|
+
* policy per page). Reads the graph rather than line-regexing the raw DSL, so
|
|
61
|
+
* the header-`uses` form (`export component X uses traceable { prefetch … }`)
|
|
62
|
+
* is recognised — the same regression class that produced the header-`uses`
|
|
63
|
+
* walker bug (B5).
|
|
64
|
+
*/
|
|
65
|
+
export declare const extractPrefetch: (source: string) => "hover" | "visible" | "mount" | "none" | undefined;
|
|
66
|
+
/** Container-graph half of {@link extractPrefetch} (A3 cache path). */
|
|
67
|
+
export declare const prefetchFromContainers: (containers: readonly ContainerNode[]) => "hover" | "visible" | "mount" | "none" | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* One `query` declaration extracted from a page component. Shape is unchanged
|
|
70
|
+
* from the existing `RouteQueryDecl` in walker.ts — only the extraction
|
|
71
|
+
* mechanism changes.
|
|
72
|
+
*/
|
|
73
|
+
export interface QueryDecl {
|
|
74
|
+
readonly name: string;
|
|
75
|
+
readonly tsType: string;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Extract `query` declarations from `source` via the compiled graph
|
|
79
|
+
* (`container.queries`, source-ordered). Returns `[]` for files with no
|
|
80
|
+
* queries, malformed files, or non-component files.
|
|
81
|
+
*
|
|
82
|
+
* C5 (B5): folded off `QUERY_MARKER_RE` onto the graph — `QueryNode.tsType` is
|
|
83
|
+
* the already-decoded StringLiteral value from Pass 2, so the v2 semicolon leak
|
|
84
|
+
* and the manual `\"`-unescaping the old regex carried are both gone for free.
|
|
85
|
+
* `tsType` defaults to `"string"` in Pass 2, matching the prior fallback. Inline
|
|
86
|
+
* `inject store s(query foo)` args never produce a `QueryNode`, so they're
|
|
87
|
+
* excluded as before.
|
|
88
|
+
*/
|
|
89
|
+
export declare const extractQueries: (source: string) => QueryDecl[];
|
|
90
|
+
/** Container-graph half of {@link extractQueries} (A3 cache path). */
|
|
91
|
+
export declare const queriesFromContainers: (containers: readonly ContainerNode[]) => QueryDecl[];
|
|
92
|
+
/**
|
|
93
|
+
* Extract every store declaration from `source`. Returns `{ name, exported }`
|
|
94
|
+
* pairs. Non-exported stores are included so callers can filter by `exported`.
|
|
95
|
+
*
|
|
96
|
+
* Replaces `STORE_DECL_RE` / `extractStoreNames` in walker.ts with a
|
|
97
|
+
* graph-based approach that correctly handles:
|
|
98
|
+
* - v2 `export app store X` / `export subtree store X "/p"` forms
|
|
99
|
+
* - header-`uses` `export store X uses undoable`
|
|
100
|
+
* - non-exported `app store X` / `route store X`
|
|
101
|
+
* - keyed `route store X(key: string)`
|
|
102
|
+
*/
|
|
103
|
+
export declare const extractStoreNames: (source: string) => {
|
|
104
|
+
name: string;
|
|
105
|
+
exported: boolean;
|
|
106
|
+
}[];
|
|
107
|
+
/** Container-graph half of {@link extractStoreNames} (A3 cache path). */
|
|
108
|
+
export declare const storeNamesFromContainers: (containers: readonly ContainerNode[]) => {
|
|
109
|
+
name: string;
|
|
110
|
+
exported: boolean;
|
|
111
|
+
}[];
|
|
112
|
+
/**
|
|
113
|
+
* Extract every service declaration from `source`. Returns
|
|
114
|
+
* `{ name, exported, serviceModifier? }` triples. Non-exported services are
|
|
115
|
+
* included so callers can filter by `exported`.
|
|
116
|
+
*/
|
|
117
|
+
export declare const extractServiceNames: (source: string) => {
|
|
118
|
+
name: string;
|
|
119
|
+
exported: boolean;
|
|
120
|
+
serviceModifier?: string;
|
|
121
|
+
}[];
|
|
122
|
+
/** Container-graph half of {@link extractServiceNames} (A3 cache path). */
|
|
123
|
+
export declare const serviceNamesFromContainers: (containers: readonly ContainerNode[]) => {
|
|
124
|
+
name: string;
|
|
125
|
+
exported: boolean;
|
|
126
|
+
serviceModifier?: string;
|
|
127
|
+
}[];
|
|
128
|
+
//# sourceMappingURL=extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.d.ts","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAIzE;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,SAAS,CAAA;IAChD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;IACjC;;;;OAIG;IACH,QAAQ,CAAC,oBAAoB,EAAE,MAAM,EAAE,CAAA;CACxC;AAoDD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAC3B,YAAY,SAAS,aAAa,EAAE,KACnC,aAAa,EAOX,CAAA;AAEL;;;;;;;;GAQG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,aAAa,EACtB,CAAA;AAI1C;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,KAAG,MAAM,GAAG,IACV,CAAA;AAErD,4EAA4E;AAC5E,eAAO,MAAM,2BAA2B,GACtC,YAAY,SAAS,aAAa,EAAE,KACnC,MAAM,GAAG,IAIX,CAAA;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,MAAM,KACb,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SACI,CAAA;AAEhD,uEAAuE;AACvE,eAAO,MAAM,sBAAsB,GACjC,YAAY,SAAS,aAAa,EAAE,KACnC,OAAO,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SACuB,CAAA;AAEnE;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,cAAc,GAAI,QAAQ,MAAM,KAAG,SAAS,EACV,CAAA;AAE/C,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,GAChC,YAAY,SAAS,aAAa,EAAE,KACnC,SAAS,EAGT,CAAA;AAEH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,EACpC,CAAA;AAElD,yEAAyE;AACzE,eAAO,MAAM,wBAAwB,GACnC,YAAY,SAAS,aAAa,EAAE,KACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,EAGgB,CAAA;AAEtD;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAC9B,QAAQ,MAAM,KACb;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,EACZ,CAAA;AAEpD,2EAA2E;AAC3E,eAAO,MAAM,0BAA0B,GACrC,YAAY,SAAS,aAAa,EAAE,KACnC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,EAGwB,CAAA"}
|
package/dist/extract.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// @reactra/vite-plugin — parse-based container extraction
|
|
2
|
+
//
|
|
3
|
+
// Owner: reactra-compiler-spec.md §4 Pass 1.1 (preprocess markers) +
|
|
4
|
+
// reactra-router-spec.md §5.1 (manifest format) +
|
|
5
|
+
// Phase 3 ADR-1 (reuse the compiler — single grammar source of truth).
|
|
6
|
+
//
|
|
7
|
+
// Each exported function calls `compileToGraph()` once per file and reads the
|
|
8
|
+
// intermediate FileGraph (Pass 2 extract). This replaces the hand-rolled regex
|
|
9
|
+
// passes in walker.ts AND the marker-regex / body-slice heuristics this file
|
|
10
|
+
// used to carry — the R2/B5 regression mitigation: the walker now shares the
|
|
11
|
+
// compiler's exact grammar for header-`uses` forms, v2 store/service lifetimes,
|
|
12
|
+
// and `inject` site classification (no body-boundary guessing).
|
|
13
|
+
import { compileToGraph } from "@reactra/babel-plugin";
|
|
14
|
+
// ─── graph helper ─────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* Run `compileToGraph()` on `source` and return its `containers`, or `[]` on
|
|
17
|
+
* any failure (malformed / mid-edit file). The try/catch is intentional: a
|
|
18
|
+
* broken source file must not crash the manifest regen loop — every public
|
|
19
|
+
* function below degrades to its documented empty / null fallback.
|
|
20
|
+
*
|
|
21
|
+
* READ-ONLY (C1): consumers read existing graph fields; codegen is never run.
|
|
22
|
+
*/
|
|
23
|
+
const containerNodes = (source) => {
|
|
24
|
+
try {
|
|
25
|
+
return compileToGraph(source).graph.containers;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Collapse the compiler's five-way {@link ContainerKind} into the three-way
|
|
33
|
+
* `ContainerInfo.kind` the walker consumes. The three store lifetimes
|
|
34
|
+
* (`route-store` / `session-store` / `export-store`) all surface as `"store"`.
|
|
35
|
+
*
|
|
36
|
+
* Exhaustive by construction: a 4th store kind added to {@link ContainerKind}
|
|
37
|
+
* makes the `default` branch a `never`-assignment compile error (C2) — so a
|
|
38
|
+
* future store lifetime can never be silently dropped from the manifest.
|
|
39
|
+
*/
|
|
40
|
+
const toInfoKind = (kind) => {
|
|
41
|
+
switch (kind) {
|
|
42
|
+
case "component":
|
|
43
|
+
return "component";
|
|
44
|
+
case "service":
|
|
45
|
+
return "service";
|
|
46
|
+
case "route-store":
|
|
47
|
+
case "session-store":
|
|
48
|
+
case "export-store":
|
|
49
|
+
return "store";
|
|
50
|
+
default: {
|
|
51
|
+
const _exhaustive = kind;
|
|
52
|
+
return _exhaustive;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
/** Service-injection-site names for a container, read straight from the graph. */
|
|
57
|
+
const injectedServiceNamesOf = (c) => c.injects.filter((i) => i.serviceKind === "service").map((i) => i.name);
|
|
58
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
59
|
+
/**
|
|
60
|
+
* Project the already-compiled container graph into the three-way
|
|
61
|
+
* {@link ContainerInfo}. The pure half of {@link extractContainers}, split out
|
|
62
|
+
* so the A3 graph cache can derive from a single per-file compile (the scanner
|
|
63
|
+
* passes a cached `ContainerNode[]` instead of re-reading + re-compiling).
|
|
64
|
+
*/
|
|
65
|
+
export const containersToInfo = (containers) => containers.map((c) => ({
|
|
66
|
+
kind: toInfoKind(c.kind),
|
|
67
|
+
name: c.name,
|
|
68
|
+
exported: c.exported,
|
|
69
|
+
serviceModifier: c.serviceModifier,
|
|
70
|
+
injectedServiceNames: injectedServiceNamesOf(c),
|
|
71
|
+
}));
|
|
72
|
+
/**
|
|
73
|
+
* Extract all containers (components, stores, services) from a raw DSL source
|
|
74
|
+
* file via the compiled graph. `injectedServiceNames` is read from each
|
|
75
|
+
* container's `injects` list (no body-boundary guessing — a mis-attribution-
|
|
76
|
+
* proof replacement for the old "next top-level const" slice heuristic).
|
|
77
|
+
*
|
|
78
|
+
* Returns `[]` on malformed source — callers must treat an empty result as
|
|
79
|
+
* "no containers" rather than an error.
|
|
80
|
+
*/
|
|
81
|
+
export const extractContainers = (source) => containersToInfo(containerNodes(source));
|
|
82
|
+
// ─── Focused extract fns (used by walker.ts) ──────────────────────────────────
|
|
83
|
+
/**
|
|
84
|
+
* Extract the first `export component <Name>` declaration name from `source`.
|
|
85
|
+
* Returns `null` when no exported component is found (plain React TSX, no
|
|
86
|
+
* DSL container, or a malformed file).
|
|
87
|
+
*
|
|
88
|
+
* Replaces `extractPageComponentName` in walker.ts — fixes the R2 regression
|
|
89
|
+
* where the old regex missed `export component Foo uses traceable {` (the
|
|
90
|
+
* header-`uses` form). The graph normalises all header forms before we read.
|
|
91
|
+
*/
|
|
92
|
+
export const extractComponentName = (source) => componentNameFromContainers(containerNodes(source));
|
|
93
|
+
/** Container-graph half of {@link extractComponentName} (A3 cache path). */
|
|
94
|
+
export const componentNameFromContainers = (containers) => {
|
|
95
|
+
// Route pages are `export component` — only EXPORTED components qualify.
|
|
96
|
+
const component = containers.find((c) => c.kind === "component" && c.exported);
|
|
97
|
+
return component?.name ?? null;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Extract the FIRST component's `prefetch` trigger from `source` via the
|
|
101
|
+
* compiled graph (`container.prefetch.trigger`). Returns the trigger word, or
|
|
102
|
+
* `undefined` when the file has no component, no `prefetch` declaration, or is
|
|
103
|
+
* malformed.
|
|
104
|
+
*
|
|
105
|
+
* First-component-wins matches the existing manifest semantics (one prefetch
|
|
106
|
+
* policy per page). Reads the graph rather than line-regexing the raw DSL, so
|
|
107
|
+
* the header-`uses` form (`export component X uses traceable { prefetch … }`)
|
|
108
|
+
* is recognised — the same regression class that produced the header-`uses`
|
|
109
|
+
* walker bug (B5).
|
|
110
|
+
*/
|
|
111
|
+
export const extractPrefetch = (source) => prefetchFromContainers(containerNodes(source));
|
|
112
|
+
/** Container-graph half of {@link extractPrefetch} (A3 cache path). */
|
|
113
|
+
export const prefetchFromContainers = (containers) => containers.find((c) => c.kind === "component")?.prefetch?.trigger;
|
|
114
|
+
/**
|
|
115
|
+
* Extract `query` declarations from `source` via the compiled graph
|
|
116
|
+
* (`container.queries`, source-ordered). Returns `[]` for files with no
|
|
117
|
+
* queries, malformed files, or non-component files.
|
|
118
|
+
*
|
|
119
|
+
* C5 (B5): folded off `QUERY_MARKER_RE` onto the graph — `QueryNode.tsType` is
|
|
120
|
+
* the already-decoded StringLiteral value from Pass 2, so the v2 semicolon leak
|
|
121
|
+
* and the manual `\"`-unescaping the old regex carried are both gone for free.
|
|
122
|
+
* `tsType` defaults to `"string"` in Pass 2, matching the prior fallback. Inline
|
|
123
|
+
* `inject store s(query foo)` args never produce a `QueryNode`, so they're
|
|
124
|
+
* excluded as before.
|
|
125
|
+
*/
|
|
126
|
+
export const extractQueries = (source) => queriesFromContainers(containerNodes(source));
|
|
127
|
+
/** Container-graph half of {@link extractQueries} (A3 cache path). */
|
|
128
|
+
export const queriesFromContainers = (containers) => containers.flatMap((c) => c.queries.map((q) => ({ name: q.name, tsType: q.tsType.trim() || "string" })));
|
|
129
|
+
/**
|
|
130
|
+
* Extract every store declaration from `source`. Returns `{ name, exported }`
|
|
131
|
+
* pairs. Non-exported stores are included so callers can filter by `exported`.
|
|
132
|
+
*
|
|
133
|
+
* Replaces `STORE_DECL_RE` / `extractStoreNames` in walker.ts with a
|
|
134
|
+
* graph-based approach that correctly handles:
|
|
135
|
+
* - v2 `export app store X` / `export subtree store X "/p"` forms
|
|
136
|
+
* - header-`uses` `export store X uses undoable`
|
|
137
|
+
* - non-exported `app store X` / `route store X`
|
|
138
|
+
* - keyed `route store X(key: string)`
|
|
139
|
+
*/
|
|
140
|
+
export const extractStoreNames = (source) => storeNamesFromContainers(containerNodes(source));
|
|
141
|
+
/** Container-graph half of {@link extractStoreNames} (A3 cache path). */
|
|
142
|
+
export const storeNamesFromContainers = (containers) => containersToInfo(containers)
|
|
143
|
+
.filter((c) => c.kind === "store")
|
|
144
|
+
.map(({ name, exported }) => ({ name, exported }));
|
|
145
|
+
/**
|
|
146
|
+
* Extract every service declaration from `source`. Returns
|
|
147
|
+
* `{ name, exported, serviceModifier? }` triples. Non-exported services are
|
|
148
|
+
* included so callers can filter by `exported`.
|
|
149
|
+
*/
|
|
150
|
+
export const extractServiceNames = (source) => serviceNamesFromContainers(containerNodes(source));
|
|
151
|
+
/** Container-graph half of {@link extractServiceNames} (A3 cache path). */
|
|
152
|
+
export const serviceNamesFromContainers = (containers) => containersToInfo(containers)
|
|
153
|
+
.filter((c) => c.kind === "service")
|
|
154
|
+
.map(({ name, exported, serviceModifier }) => ({ name, exported, serviceModifier }));
|
|
155
|
+
//# sourceMappingURL=extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract.js","sourceRoot":"","sources":["../src/extract.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,EAAE;AACF,qEAAqE;AACrE,yDAAyD;AACzD,8EAA8E;AAC9E,EAAE;AACF,8EAA8E;AAC9E,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,gFAAgF;AAChF,gEAAgE;AAEhE,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AA4BtD,iFAAiF;AAEjF;;;;;;;GAOG;AACH,MAAM,cAAc,GAAG,CAAC,MAAc,EAA4B,EAAE;IAClE,IAAI,CAAC;QACH,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,UAAU,CAAA;IAChD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAA;IACX,CAAC;AACH,CAAC,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAG,CAAC,IAAmB,EAAyB,EAAE;IAChE,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,WAAW;YACd,OAAO,WAAW,CAAA;QACpB,KAAK,SAAS;YACZ,OAAO,SAAS,CAAA;QAClB,KAAK,aAAa,CAAC;QACnB,KAAK,eAAe,CAAC;QACrB,KAAK,cAAc;YACjB,OAAO,OAAO,CAAA;QAChB,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,WAAW,GAAU,IAAI,CAAA;YAC/B,OAAO,WAAW,CAAA;QACpB,CAAC;IACH,CAAC;AACH,CAAC,CAAA;AAED,kFAAkF;AAClF,MAAM,sBAAsB,GAAG,CAAC,CAAgB,EAAY,EAAE,CAC5D,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AAEzE,iFAAiF;AAEjF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,UAAoC,EACnB,EAAE,CACnB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrB,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC;IACxB,IAAI,EAAE,CAAC,CAAC,IAAI;IACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;IACpB,eAAe,EAAE,CAAC,CAAC,eAAe;IAClC,oBAAoB,EAAE,sBAAsB,CAAC,CAAC,CAAC;CAChD,CAAC,CAAC,CAAA;AAEL;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAmB,EAAE,CACnE,gBAAgB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;AAE1C,iFAAiF;AAEjF;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAc,EAAiB,EAAE,CACpE,2BAA2B,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;AAErD,4EAA4E;AAC5E,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,UAAoC,EACrB,EAAE;IACjB,yEAAyE;IACzE,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAA;IAC9E,OAAO,SAAS,EAAE,IAAI,IAAI,IAAI,CAAA;AAChC,CAAC,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC7B,MAAc,EACsC,EAAE,CACtD,sBAAsB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;AAEhD,uEAAuE;AACvE,MAAM,CAAC,MAAM,sBAAsB,GAAG,CACpC,UAAoC,EACgB,EAAE,CACtD,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAA;AAYnE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,MAAc,EAAe,EAAE,CAC5D,qBAAqB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;AAE/C,sEAAsE;AACtE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,UAAoC,EACvB,EAAE,CACf,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CACvB,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC,CAAC,CAC9E,CAAA;AAEH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAc,EAAyC,EAAE,CACzF,wBAAwB,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;AAElD,yEAAyE;AACzE,MAAM,CAAC,MAAM,wBAAwB,GAAG,CACtC,UAAoC,EACG,EAAE,CACzC,gBAAgB,CAAC,UAAU,CAAC;KACzB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC;KACjC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAA;AAEtD;;;;GAIG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,MAAc,EACmD,EAAE,CACnE,0BAA0B,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAA;AAEpD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,UAAoC,EAC6B,EAAE,CACnE,gBAAgB,CAAC,UAAU,CAAC;KACzB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC;KACnC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ContainerNode } from "@reactra/babel-plugin";
|
|
2
|
+
/**
|
|
3
|
+
* The declaration set of one source file that the manifest + resource-names
|
|
4
|
+
* output depends on. All arrays are sorted for order-insensitive comparison
|
|
5
|
+
* (declaration order in the file doesn't affect the generated output —
|
|
6
|
+
* scanStores/scanServices/collectResourceNames all sort their results).
|
|
7
|
+
*/
|
|
8
|
+
export interface DeclFingerprint {
|
|
9
|
+
/** Sorted store binding names declared in the file. */
|
|
10
|
+
readonly stores: string[];
|
|
11
|
+
/** Sorted service binding names declared in the file. */
|
|
12
|
+
readonly services: string[];
|
|
13
|
+
/** Sorted `name:modifier` pairs (modifier = "" when none) — flips a regen. */
|
|
14
|
+
readonly serviceModifiers: string[];
|
|
15
|
+
/** Sorted resource names declared across the file's containers. */
|
|
16
|
+
readonly resources: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Derive a {@link DeclFingerprint} from a file's compiled containers. Pure:
|
|
20
|
+
* same containers → identical fingerprint. Caller supplies the cached
|
|
21
|
+
* `containersFor(path)` result so no extra compile is paid.
|
|
22
|
+
*/
|
|
23
|
+
export declare const fingerprintOf: (containers: readonly ContainerNode[]) => DeclFingerprint;
|
|
24
|
+
/** True when two fingerprints declare the same set of stores/services/resources. */
|
|
25
|
+
export declare const fingerprintsEqual: (a: DeclFingerprint, b: DeclFingerprint) => boolean;
|
|
26
|
+
/** True when `path` is a page file (under `src/pages/`) — routes/queries/prefetch live here. */
|
|
27
|
+
export declare const isPagesFile: (projectRoot: string, eventPath: string) => boolean;
|
|
28
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAA;IACzB,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;IAC3B,8EAA8E;IAC9E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,EAAE,CAAA;IACnC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAC7B;AAQD;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,YAAY,SAAS,aAAa,EAAE,KACnC,eAoBF,CAAA;AAED,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,GAAI,GAAG,eAAe,EAAE,GAAG,eAAe,KAAG,OAS1E,CAAA;AAED,gGAAgG;AAChG,eAAO,MAAM,WAAW,GAAI,aAAa,MAAM,EAAE,WAAW,MAAM,KAAG,OAKpE,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// @reactra/vite-plugin — per-file declaration fingerprint (A3 increment 2)
|
|
2
|
+
//
|
|
3
|
+
// Owner: backlogs/framework-review-2026-06-12.md §A3 +
|
|
4
|
+
// reactra-compiler-spec.md §7.1 (vite plugin file-watcher hook).
|
|
5
|
+
//
|
|
6
|
+
// A3 increment 2: on a `change` event we want to skip the src/-wide store/
|
|
7
|
+
// service/resource rescan when the edit only touched a body — the declared SET
|
|
8
|
+
// (store names, service names + modifiers, resource names) is what the manifest
|
|
9
|
+
// + resource-names files are derived from, so if that set is unchanged the wide
|
|
10
|
+
// rescan can't change the generated output.
|
|
11
|
+
//
|
|
12
|
+
// We deliberately do NOT implement the backlog's literal "skip store/service
|
|
13
|
+
// rescan on every change" — a `change` CAN rename a store, add/remove one, or
|
|
14
|
+
// flip a service modifier (and a `[id]`→`[customerId]` param rename arrives as
|
|
15
|
+
// `change` too). The fingerprint differ catches all of those: any decl-set
|
|
16
|
+
// change forces a full regen.
|
|
17
|
+
//
|
|
18
|
+
// The fingerprint is cheap strings derived from the already-cached compile —
|
|
19
|
+
// no extra read, no extra compile.
|
|
20
|
+
const STORE_KINDS = new Set([
|
|
21
|
+
"route-store",
|
|
22
|
+
"session-store",
|
|
23
|
+
"export-store",
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Derive a {@link DeclFingerprint} from a file's compiled containers. Pure:
|
|
27
|
+
* same containers → identical fingerprint. Caller supplies the cached
|
|
28
|
+
* `containersFor(path)` result so no extra compile is paid.
|
|
29
|
+
*/
|
|
30
|
+
export const fingerprintOf = (containers) => {
|
|
31
|
+
const stores = [];
|
|
32
|
+
const services = [];
|
|
33
|
+
const serviceModifiers = [];
|
|
34
|
+
const resources = [];
|
|
35
|
+
for (const c of containers) {
|
|
36
|
+
if (STORE_KINDS.has(c.kind))
|
|
37
|
+
stores.push(c.name);
|
|
38
|
+
if (c.kind === "service") {
|
|
39
|
+
services.push(c.name);
|
|
40
|
+
serviceModifiers.push(`${c.name}:${c.serviceModifier ?? ""}`);
|
|
41
|
+
}
|
|
42
|
+
for (const r of c.resources)
|
|
43
|
+
resources.push(r.name);
|
|
44
|
+
}
|
|
45
|
+
const sort = (xs) => xs.sort((a, b) => a.localeCompare(b));
|
|
46
|
+
return {
|
|
47
|
+
stores: sort(stores),
|
|
48
|
+
services: sort(services),
|
|
49
|
+
serviceModifiers: sort(serviceModifiers),
|
|
50
|
+
resources: sort(resources),
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/** True when two fingerprints declare the same set of stores/services/resources. */
|
|
54
|
+
export const fingerprintsEqual = (a, b) => {
|
|
55
|
+
const sameList = (xs, ys) => xs.length === ys.length && xs.every((x, i) => x === ys[i]);
|
|
56
|
+
return (sameList(a.stores, b.stores) &&
|
|
57
|
+
sameList(a.services, b.services) &&
|
|
58
|
+
sameList(a.serviceModifiers, b.serviceModifiers) &&
|
|
59
|
+
sameList(a.resources, b.resources));
|
|
60
|
+
};
|
|
61
|
+
/** True when `path` is a page file (under `src/pages/`) — routes/queries/prefetch live here. */
|
|
62
|
+
export const isPagesFile = (projectRoot, eventPath) => {
|
|
63
|
+
const root = projectRoot.replace(/\\/g, "/");
|
|
64
|
+
const target = eventPath.replace(/\\/g, "/");
|
|
65
|
+
const prefix = (root.endsWith("/") ? root + "src/pages/" : root + "/src/pages/");
|
|
66
|
+
return target.startsWith(prefix);
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../src/fingerprint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ContainerNode } from "@reactra/babel-plugin";
|
|
2
|
+
/**
|
|
3
|
+
* A per-regen cache of compiled container graphs, keyed by absolute file path.
|
|
4
|
+
* One instance lives for the duration of a single manifest regeneration.
|
|
5
|
+
*/
|
|
6
|
+
export interface GraphCache {
|
|
7
|
+
/**
|
|
8
|
+
* Read + parse `path` once per regen; returns `graph.containers`.
|
|
9
|
+
* Returns `[]` on any read OR compile failure (malformed / mid-edit file),
|
|
10
|
+
* so callers degrade to their documented empty result rather than throwing.
|
|
11
|
+
*
|
|
12
|
+
* @param path absolute file path (the cache key — NO content-hash / mtime).
|
|
13
|
+
*/
|
|
14
|
+
containersFor: (path: string) => readonly ContainerNode[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build a fresh {@link GraphCache}. Memoizes by absolute path: the first
|
|
18
|
+
* `containersFor(path)` reads + compiles; subsequent calls for the same path
|
|
19
|
+
* return the stashed result. Discard the instance when the regen completes —
|
|
20
|
+
* it never sees stale content because a new cache is built per regen.
|
|
21
|
+
*/
|
|
22
|
+
export declare const createGraphCache: () => GraphCache;
|
|
23
|
+
//# sourceMappingURL=graph-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph-cache.d.ts","sourceRoot":"","sources":["../src/graph-cache.ts"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAE1D;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB;;;;;;OAMG;IACH,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,SAAS,aAAa,EAAE,CAAA;CAC1D;AAED;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAO,UAqBnC,CAAA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// @reactra/vite-plugin — per-regen graph cache
|
|
2
|
+
//
|
|
3
|
+
// Owner: reactra-compiler-spec.md §7.1 (vite plugin file-watcher hook) +
|
|
4
|
+
// backlogs/framework-review-2026-06-12.md §A3 (one walk, one preprocess).
|
|
5
|
+
//
|
|
6
|
+
// A3 increment 1: every container-reading scanner (scanPages, scanStores,
|
|
7
|
+
// scanServices, collectServiceInjectionDiagnostics, collectResourceNames) used
|
|
8
|
+
// to read + `compileToGraph` each file independently — after B5 landed, a 200-
|
|
9
|
+
// file regen compiled each file 4–5×. This cache folds the read + compile +
|
|
10
|
+
// try/catch fallback into ONE place, memoized by absolute path, so each file
|
|
11
|
+
// compiles AT MOST once per regen.
|
|
12
|
+
//
|
|
13
|
+
// Lifetime: ONE instance per regen (created at the top of regenerateRouteManifest
|
|
14
|
+
// and the watcher event handler, then thrown away). NO module-level instance,
|
|
15
|
+
// NO content-hash / mtime key, NO eviction (C1) — a regen is short-lived and a
|
|
16
|
+
// fresh cache each time is correct by construction. The fingerprint differ
|
|
17
|
+
// (increment 2) is the only module-level state.
|
|
18
|
+
//
|
|
19
|
+
// READ-ONLY (C3): reads `graph.containers` only; codegen is never run. Returns
|
|
20
|
+
// `[]` on read OR compile failure, preserving every scanner's documented empty
|
|
21
|
+
// degradation. Node-portable (C7): `node:fs` + plain `Map` only.
|
|
22
|
+
import { readFileSync } from "node:fs";
|
|
23
|
+
import { compileToGraph } from "@reactra/babel-plugin";
|
|
24
|
+
/**
|
|
25
|
+
* Build a fresh {@link GraphCache}. Memoizes by absolute path: the first
|
|
26
|
+
* `containersFor(path)` reads + compiles; subsequent calls for the same path
|
|
27
|
+
* return the stashed result. Discard the instance when the regen completes —
|
|
28
|
+
* it never sees stale content because a new cache is built per regen.
|
|
29
|
+
*/
|
|
30
|
+
export const createGraphCache = () => {
|
|
31
|
+
const byPath = new Map();
|
|
32
|
+
const containersFor = (path) => {
|
|
33
|
+
const cached = byPath.get(path);
|
|
34
|
+
if (cached)
|
|
35
|
+
return cached;
|
|
36
|
+
let containers;
|
|
37
|
+
try {
|
|
38
|
+
const source = readFileSync(path, "utf8");
|
|
39
|
+
// READ-ONLY: consume only `.graph.containers`; codegen is never run.
|
|
40
|
+
containers = compileToGraph(source).graph.containers;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Read OR compile failure → degrade to empty (C3). The next regen
|
|
44
|
+
// (a fresh cache) recovers once the file is well-formed / readable.
|
|
45
|
+
containers = [];
|
|
46
|
+
}
|
|
47
|
+
byPath.set(path, containers);
|
|
48
|
+
return containers;
|
|
49
|
+
};
|
|
50
|
+
return { containersFor };
|
|
51
|
+
};
|
|
52
|
+
//# sourceMappingURL=graph-cache.js.map
|