@rangojs/router 0.5.2 → 0.7.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/dist/bin/rango.js +343 -125
- package/dist/types/browser/react/use-router.d.ts +10 -3
- package/dist/types/browser/react/use-search-params.d.ts +57 -10
- package/dist/types/browser/types.d.ts +22 -0
- package/dist/types/build/merge-full-manifests.d.ts +3 -0
- package/dist/types/build/route-trie.d.ts +4 -73
- package/dist/types/build/route-types/per-module-writer.d.ts +6 -4
- package/dist/types/build/route-types/router-processing.d.ts +2 -3
- package/dist/types/cache/cache-exec-scope.d.ts +31 -0
- package/dist/types/cache/taint.d.ts +12 -6
- package/dist/types/client-urls/client-root.d.ts +38 -0
- package/dist/types/client-urls/client-urls.d.ts +5 -0
- package/dist/types/client-urls/navigation.d.ts +38 -0
- package/dist/types/client-urls/revalidation-protocol.d.ts +25 -0
- package/dist/types/client-urls/server-projection.d.ts +71 -0
- package/dist/types/client-urls/types.d.ts +147 -0
- package/dist/types/client.d.ts +12 -4
- package/dist/types/client.rsc.d.ts +4 -1
- package/dist/types/decode-loader-results.d.ts +37 -0
- package/dist/types/errors.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/loader-redirect.d.ts +27 -0
- package/dist/types/outlet-context.d.ts +12 -0
- package/dist/types/outlet-provider.d.ts +3 -1
- package/dist/types/redirect-origin.d.ts +4 -0
- package/dist/types/route-content-wrapper.d.ts +42 -1
- package/dist/types/route-definition/helpers-types.d.ts +13 -2
- package/dist/types/router/error-handling.d.ts +35 -1
- package/dist/types/router/intercept-resolution.d.ts +12 -0
- package/dist/types/router/loader-resolution.d.ts +24 -2
- package/dist/types/router/revalidation.d.ts +7 -0
- package/dist/types/router/route-trie-builder.d.ts +77 -0
- package/dist/types/router/router-interfaces.d.ts +20 -0
- package/dist/types/router/segment-resolution/helpers.d.ts +1 -1
- package/dist/types/router/segment-resolution/loader-mask.d.ts +9 -9
- package/dist/types/router/trie-matching.d.ts +1 -1
- package/dist/types/rsc/manifest-init.d.ts +5 -5
- package/dist/types/rsc/shell-capture.d.ts +9 -0
- package/dist/types/rsc/shell-serve.d.ts +11 -0
- package/dist/types/rsc/types.d.ts +30 -0
- package/dist/types/segment-system.d.ts +2 -0
- package/dist/types/server/context.d.ts +10 -0
- package/dist/types/server/handle-store.d.ts +34 -3
- package/dist/types/server/request-context.d.ts +11 -1
- package/dist/types/server.d.ts +1 -0
- package/dist/types/ssr/index.d.ts +22 -0
- package/dist/types/ssr/ssr-root.d.ts +10 -0
- package/dist/types/testing/dom.entry.d.ts +1 -1
- package/dist/types/testing/render-route.d.ts +16 -6
- package/dist/types/testing/run-loader.d.ts +9 -0
- package/dist/types/types/boundaries.d.ts +22 -0
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/types/loader-types.d.ts +57 -5
- package/dist/types/types/segments.d.ts +7 -0
- package/dist/types/urls/path-helper-types.d.ts +13 -4
- package/dist/types/vite/discovery/client-urls-projection.d.ts +53 -0
- package/dist/types/vite/discovery/discover-routers.d.ts +1 -1
- package/dist/types/vite/discovery/state.d.ts +8 -1
- package/dist/types/vite/plugins/client-ref-dedup.d.ts +0 -11
- package/dist/vite/index.js +6159 -2959
- package/package.json +6 -5
- package/skills/breadcrumbs/SKILL.md +39 -9
- package/skills/catalog.json +7 -1
- package/skills/client-urls/SKILL.md +338 -0
- package/skills/comparison/references/framework-comparison.md +23 -9
- package/skills/hooks/SKILL.md +2 -2
- package/skills/hooks/data.md +11 -2
- package/skills/hooks/handle-and-actions.md +7 -0
- package/skills/hooks/outlets.md +26 -5
- package/skills/hooks/urls.md +40 -3
- package/skills/loader/SKILL.md +132 -20
- package/skills/migrate-nextjs/SKILL.md +70 -10
- package/skills/migrate-react-router/SKILL.md +49 -13
- package/skills/migrate-react-router/component-migration.md +18 -13
- package/skills/migrate-react-router/data-and-actions.md +14 -3
- package/skills/migrate-react-router/route-mapping.md +15 -2
- package/skills/parallel/SKILL.md +32 -1
- package/skills/ppr/SKILL.md +16 -6
- package/skills/prerender/SKILL.md +8 -4
- package/skills/rango/SKILL.md +21 -17
- package/skills/react-compiler/SKILL.md +3 -3
- package/skills/route/SKILL.md +5 -2
- package/skills/router-setup/SKILL.md +16 -2
- package/skills/scripts/SKILL.md +16 -6
- package/skills/shell-manifest/SKILL.md +16 -7
- package/skills/testing/SKILL.md +2 -2
- package/skills/testing/client-components.md +6 -0
- package/skills/testing/handles.md +30 -8
- package/skills/testing/loader.md +51 -49
- package/skills/testing/middleware.md +1 -1
- package/skills/theme/SKILL.md +8 -5
- package/src/bin/rango.ts +7 -3
- package/src/browser/navigation-bridge.ts +6 -0
- package/src/browser/navigation-client.ts +5 -0
- package/src/browser/partial-update.ts +65 -13
- package/src/browser/react/use-router.ts +40 -11
- package/src/browser/react/use-search-params.ts +140 -17
- package/src/browser/rsc-router.tsx +59 -0
- package/src/browser/server-action-bridge.ts +26 -0
- package/src/browser/types.ts +22 -0
- package/src/build/merge-full-manifests.ts +161 -0
- package/src/build/route-trie.ts +9 -332
- package/src/build/route-types/include-resolution.ts +66 -11
- package/src/build/route-types/per-module-writer.ts +11 -6
- package/src/build/route-types/router-processing.ts +184 -153
- package/src/build/runtime-discovery.ts +23 -12
- package/src/cache/cache-exec-scope.ts +47 -0
- package/src/cache/cache-runtime.ts +24 -25
- package/src/cache/taint.ts +28 -9
- package/src/client-urls/client-root.tsx +168 -0
- package/src/client-urls/client-urls.ts +776 -0
- package/src/client-urls/navigation.ts +237 -0
- package/src/client-urls/revalidation-protocol.ts +56 -0
- package/src/client-urls/server-projection.ts +670 -0
- package/src/client-urls/types.ts +201 -0
- package/src/client.rsc.tsx +12 -0
- package/src/client.tsx +49 -6
- package/src/decode-loader-results.ts +113 -0
- package/src/errors.ts +14 -0
- package/src/handles/deferred-resolution.ts +14 -7
- package/src/index.ts +1 -0
- package/src/loader-redirect.tsx +64 -0
- package/src/outlet-context.ts +12 -0
- package/src/outlet-provider.tsx +15 -1
- package/src/redirect-origin.ts +29 -0
- package/src/route-content-wrapper.tsx +96 -3
- package/src/route-definition/dsl-helpers.ts +28 -3
- package/src/route-definition/helpers-types.ts +13 -0
- package/src/route-definition/redirect.ts +17 -18
- package/src/router/error-handling.ts +65 -11
- package/src/router/intercept-resolution.ts +29 -0
- package/src/router/loader-resolution.ts +261 -28
- package/src/router/match-result.ts +7 -0
- package/src/router/revalidation.ts +24 -11
- package/src/router/route-trie-builder.ts +334 -0
- package/src/router/router-interfaces.ts +38 -0
- package/src/router/segment-resolution/fresh.ts +55 -2
- package/src/router/segment-resolution/helpers.ts +9 -11
- package/src/router/segment-resolution/loader-cache.ts +34 -23
- package/src/router/segment-resolution/loader-mask.ts +9 -9
- package/src/router/segment-resolution/revalidation.ts +23 -2
- package/src/router/trie-matching.ts +3 -3
- package/src/router.ts +46 -1
- package/src/rsc/full-payload.ts +6 -0
- package/src/rsc/handler.ts +10 -7
- package/src/rsc/loader-fetch.ts +2 -2
- package/src/rsc/manifest-init.ts +28 -9
- package/src/rsc/rsc-rendering.ts +15 -1
- package/src/rsc/shell-capture.ts +12 -0
- package/src/rsc/shell-serve.ts +15 -2
- package/src/rsc/ssr-setup.ts +10 -1
- package/src/rsc/types.ts +31 -2
- package/src/segment-system.tsx +83 -26
- package/src/server/context.ts +10 -0
- package/src/server/cookie-store.ts +19 -19
- package/src/server/handle-store.ts +185 -48
- package/src/server/request-context.ts +30 -6
- package/src/server.ts +7 -0
- package/src/ssr/index.tsx +37 -2
- package/src/ssr/ssr-root.tsx +29 -2
- package/src/testing/dom.entry.ts +1 -1
- package/src/testing/render-route.tsx +22 -8
- package/src/testing/run-loader.ts +51 -13
- package/src/types/boundaries.ts +19 -0
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +60 -5
- package/src/types/segments.ts +7 -0
- package/src/urls/include-helper.ts +22 -4
- package/src/urls/path-helper-types.ts +17 -1
- package/src/use-loader.tsx +67 -6
- package/src/vite/discovery/client-urls-projection.ts +322 -0
- package/src/vite/discovery/discover-routers.ts +43 -17
- package/src/vite/discovery/state.ts +11 -1
- package/src/vite/discovery/virtual-module-codegen.ts +20 -0
- package/src/vite/plugins/client-ref-dedup.ts +281 -19
- package/src/vite/plugins/expose-action-id.ts +45 -23
- package/src/vite/plugins/virtual-entries.ts +12 -3
- package/src/vite/router-discovery.ts +163 -12
|
@@ -44,6 +44,7 @@ export function generateRoutesManifestModule(state: DiscoveryState): string {
|
|
|
44
44
|
const hasManifest =
|
|
45
45
|
state.mergedRouteManifest &&
|
|
46
46
|
Object.keys(state.mergedRouteManifest).length > 0;
|
|
47
|
+
const hasClientUrlModules = Boolean(state.clientUrlSourceByReferenceId?.size);
|
|
47
48
|
|
|
48
49
|
if (hasManifest) {
|
|
49
50
|
// Build gen file import statements for each router with a sourceFile.
|
|
@@ -83,6 +84,9 @@ export function generateRoutesManifestModule(state: DiscoveryState): string {
|
|
|
83
84
|
"setRouterManifest",
|
|
84
85
|
...(state.isBuildMode ? ["registerRouterManifestLoader"] : []),
|
|
85
86
|
"clearAllRouterData",
|
|
87
|
+
...(hasClientUrlModules
|
|
88
|
+
? ["clearClientUrlProjections", "setClientUrlProjection"]
|
|
89
|
+
: []),
|
|
86
90
|
];
|
|
87
91
|
const lines = [
|
|
88
92
|
`import { ${serverImports.join(", ")} } from "@rangojs/router/server";`,
|
|
@@ -95,6 +99,16 @@ export function generateRoutesManifestModule(state: DiscoveryState): string {
|
|
|
95
99
|
`clearAllRouterData();`,
|
|
96
100
|
];
|
|
97
101
|
|
|
102
|
+
if (hasClientUrlModules) {
|
|
103
|
+
lines.push(`clearClientUrlProjections();`);
|
|
104
|
+
for (const [referenceId, projection] of state.clientUrlProjectionMap ??
|
|
105
|
+
[]) {
|
|
106
|
+
lines.push(
|
|
107
|
+
`setClientUrlProjection(${JSON.stringify(referenceId)}, ${jsonParseExpression(projection)});`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
98
112
|
if (varIdx > 0) {
|
|
99
113
|
lines.push(
|
|
100
114
|
`function __flat(r) { const o = {}; for (const [k, v] of Object.entries(r)) o[k] = typeof v === "string" ? v : v.path; return o; }`,
|
|
@@ -169,6 +183,12 @@ export function generateRoutesManifestModule(state: DiscoveryState): string {
|
|
|
169
183
|
}
|
|
170
184
|
|
|
171
185
|
const lines: string[] = [];
|
|
186
|
+
if (hasClientUrlModules) {
|
|
187
|
+
lines.push(
|
|
188
|
+
`import { clearClientUrlProjections } from "@rangojs/router/server";`,
|
|
189
|
+
`clearClientUrlProjections();`,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
172
192
|
if (!state.isBuildMode) {
|
|
173
193
|
const origin =
|
|
174
194
|
state.devServerOrigin ||
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, join, normalize, relative } from "node:path";
|
|
1
3
|
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
4
|
import { createRangoDebugger, NS } from "../debug.js";
|
|
3
5
|
|
|
@@ -5,23 +7,21 @@ const debug = createRangoDebugger(NS.transform);
|
|
|
5
7
|
|
|
6
8
|
const CLIENT_IN_SERVER_PROXY_PREFIX =
|
|
7
9
|
"virtual:vite-rsc/client-in-server-package-proxy/";
|
|
10
|
+
const DEDUP_PREFIX = "\0rango:dedup/";
|
|
11
|
+
|
|
12
|
+
interface PackageMetadata {
|
|
13
|
+
root: string;
|
|
14
|
+
selfName: string;
|
|
15
|
+
exports: unknown;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type PackageResolver = ReturnType<ResolvedConfig["createResolver"]>;
|
|
8
19
|
|
|
9
20
|
/**
|
|
10
21
|
* Extract the bare package name from an absolute node_modules path.
|
|
11
22
|
* Handles scoped packages (@org/name) and nested node_modules.
|
|
12
23
|
* Returns null if the path doesn't contain a valid package reference.
|
|
13
24
|
*
|
|
14
|
-
* NOTE: This is a lossy transformation. It maps a specific submodule path
|
|
15
|
-
* (e.g., pkg/internal/context.js) to the package root (pkg). The load()
|
|
16
|
-
* hook then re-exports via the bare specifier, which resolves to the
|
|
17
|
-
* package entry point. This works for packages that barrel-export their
|
|
18
|
-
* "use client" symbols from the root, which covers the common case
|
|
19
|
-
* (component libraries like @mantine/core, @chakra-ui/react, etc.).
|
|
20
|
-
* Packages whose client symbols are only available from deep subpaths
|
|
21
|
-
* (not re-exported from the root) would lose those symbols after the
|
|
22
|
-
* rewrite. A more precise approach would resolve through the package's
|
|
23
|
-
* exports map to find the correct entry point, but that adds significant
|
|
24
|
-
* complexity for a rare edge case.
|
|
25
25
|
* See: https://github.com/cloudflare/vinext/pull/413
|
|
26
26
|
*/
|
|
27
27
|
export function extractPackageName(absolutePath: string): string | null {
|
|
@@ -44,6 +44,219 @@ export function extractPackageName(absolutePath: string): string | null {
|
|
|
44
44
|
return name || null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function stripQueryAndHash(id: string): string {
|
|
48
|
+
const suffixIndex = id.search(/[?#]/);
|
|
49
|
+
return suffixIndex === -1 ? id : id.slice(0, suffixIndex);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function getPackageRoot(source: string): string | undefined {
|
|
53
|
+
const packageName = extractPackageName(source);
|
|
54
|
+
if (!packageName) return;
|
|
55
|
+
|
|
56
|
+
const marker = "/node_modules/";
|
|
57
|
+
const markerIndex = source.lastIndexOf(marker);
|
|
58
|
+
return source.slice(0, markerIndex + marker.length) + packageName;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readPackageMetadata(
|
|
62
|
+
source: string,
|
|
63
|
+
cache: Map<string, PackageMetadata | null>,
|
|
64
|
+
): PackageMetadata | undefined {
|
|
65
|
+
const root = getPackageRoot(source);
|
|
66
|
+
if (!root) return;
|
|
67
|
+
|
|
68
|
+
const cached = cache.get(root);
|
|
69
|
+
if (cached !== undefined) return cached ?? undefined;
|
|
70
|
+
|
|
71
|
+
const packageJsonPath = join(root, "package.json");
|
|
72
|
+
if (!existsSync(packageJsonPath)) {
|
|
73
|
+
cache.set(root, null);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8")) as {
|
|
79
|
+
name?: unknown;
|
|
80
|
+
exports?: unknown;
|
|
81
|
+
};
|
|
82
|
+
if (typeof packageJson.name !== "string" || !packageJson.name) {
|
|
83
|
+
cache.set(root, null);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const metadata = {
|
|
87
|
+
root,
|
|
88
|
+
selfName: packageJson.name,
|
|
89
|
+
exports: packageJson.exports,
|
|
90
|
+
} satisfies PackageMetadata;
|
|
91
|
+
cache.set(root, metadata);
|
|
92
|
+
return metadata;
|
|
93
|
+
} catch {
|
|
94
|
+
cache.set(root, null);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function collectStringTargets(value: unknown, targets: string[]): void {
|
|
100
|
+
if (typeof value === "string") {
|
|
101
|
+
targets.push(value);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(value)) {
|
|
105
|
+
for (const item of value) collectStringTargets(item, targets);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (typeof value !== "object" || value === null) return;
|
|
109
|
+
for (const target of Object.values(value)) {
|
|
110
|
+
collectStringTargets(target, targets);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function singleStarParts(value: string): [string, string] | undefined {
|
|
115
|
+
const starIndex = value.indexOf("*");
|
|
116
|
+
if (starIndex === -1 || value.indexOf("*", starIndex + 1) !== -1) return;
|
|
117
|
+
return [value.slice(0, starIndex), value.slice(starIndex + 1)];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function targetCanMapToSource(target: string, sourceRelative: string): boolean {
|
|
121
|
+
const targetStar = singleStarParts(target);
|
|
122
|
+
if (!targetStar) return target === sourceRelative;
|
|
123
|
+
const [prefix, suffix] = targetStar;
|
|
124
|
+
return (
|
|
125
|
+
sourceRelative.startsWith(prefix) &&
|
|
126
|
+
sourceRelative.endsWith(suffix) &&
|
|
127
|
+
sourceRelative.length >= prefix.length + suffix.length
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function getPublicSpecifierCandidates(
|
|
132
|
+
metadata: PackageMetadata,
|
|
133
|
+
packageSpecifier: string,
|
|
134
|
+
source: string,
|
|
135
|
+
): string[] {
|
|
136
|
+
if (
|
|
137
|
+
typeof metadata.exports !== "object" ||
|
|
138
|
+
metadata.exports === null ||
|
|
139
|
+
Array.isArray(metadata.exports)
|
|
140
|
+
) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const candidates = new Set<string>();
|
|
145
|
+
const sourceRelative = `./${relative(metadata.root, source).replaceAll("\\", "/")}`;
|
|
146
|
+
if (
|
|
147
|
+
sourceRelative.startsWith("./../") ||
|
|
148
|
+
isAbsolute(sourceRelative.slice(2))
|
|
149
|
+
) {
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const [exportKey, target] of Object.entries(metadata.exports)) {
|
|
154
|
+
if (!exportKey.startsWith("./") || exportKey === ".") continue;
|
|
155
|
+
|
|
156
|
+
const keyStar = singleStarParts(exportKey);
|
|
157
|
+
if (!keyStar) {
|
|
158
|
+
const targets: string[] = [];
|
|
159
|
+
collectStringTargets(target, targets);
|
|
160
|
+
if (
|
|
161
|
+
targets.some((target) => targetCanMapToSource(target, sourceRelative))
|
|
162
|
+
) {
|
|
163
|
+
candidates.add(`${packageSpecifier}${exportKey.slice(1)}`);
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const captures = new Set<string>();
|
|
169
|
+
const targets: string[] = [];
|
|
170
|
+
collectStringTargets(target, targets);
|
|
171
|
+
for (const targetPattern of targets) {
|
|
172
|
+
const targetStar = singleStarParts(targetPattern);
|
|
173
|
+
if (!targetStar) continue;
|
|
174
|
+
const [prefix, suffix] = targetStar;
|
|
175
|
+
if (
|
|
176
|
+
!sourceRelative.startsWith(prefix) ||
|
|
177
|
+
!sourceRelative.endsWith(suffix) ||
|
|
178
|
+
sourceRelative.length < prefix.length + suffix.length
|
|
179
|
+
) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
captures.add(
|
|
183
|
+
sourceRelative.slice(
|
|
184
|
+
prefix.length,
|
|
185
|
+
sourceRelative.length - suffix.length,
|
|
186
|
+
),
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (captures.size === 1) {
|
|
191
|
+
const capture = captures.values().next().value;
|
|
192
|
+
if (capture !== undefined) {
|
|
193
|
+
const publicSubpath = `${keyStar[0]}${capture}${keyStar[1]}`;
|
|
194
|
+
candidates.add(`${packageSpecifier}${publicSubpath.slice(1)}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return [...candidates];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function belongsToPackage(resolvedId: string, packageRoot: string): boolean {
|
|
203
|
+
return normalize(getPackageRoot(resolvedId) ?? "") === normalize(packageRoot);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function resolvePublicSpecifier(
|
|
207
|
+
source: string,
|
|
208
|
+
metadata: PackageMetadata,
|
|
209
|
+
installedSpecifier: string,
|
|
210
|
+
resolvePackage: PackageResolver,
|
|
211
|
+
importer: string,
|
|
212
|
+
): Promise<string | undefined> {
|
|
213
|
+
const packageSpecifiers =
|
|
214
|
+
installedSpecifier === metadata.selfName
|
|
215
|
+
? [installedSpecifier]
|
|
216
|
+
: [installedSpecifier, metadata.selfName];
|
|
217
|
+
|
|
218
|
+
for (const packageSpecifier of packageSpecifiers) {
|
|
219
|
+
for (const candidate of getPublicSpecifierCandidates(
|
|
220
|
+
metadata,
|
|
221
|
+
packageSpecifier,
|
|
222
|
+
source,
|
|
223
|
+
)) {
|
|
224
|
+
try {
|
|
225
|
+
const resolved = await resolvePackage(candidate, importer);
|
|
226
|
+
if (!resolved) continue;
|
|
227
|
+
const resolvedId = stripQueryAndHash(resolved);
|
|
228
|
+
if (
|
|
229
|
+
normalize(resolvedId) === normalize(source) &&
|
|
230
|
+
belongsToPackage(resolvedId, metadata.root)
|
|
231
|
+
) {
|
|
232
|
+
return candidate;
|
|
233
|
+
}
|
|
234
|
+
} catch {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Fallback: the pre-exports-map behavior, and it is LOSSY. When no public
|
|
240
|
+
// subpath maps back to this exact file, re-export from the bare package
|
|
241
|
+
// root — correct only for packages that barrel-export their "use client"
|
|
242
|
+
// symbols from the entry point (the common component-library shape).
|
|
243
|
+
// A deep module whose symbols are NOT re-exported from the root loses
|
|
244
|
+
// them silently after this rewrite; the exports-map resolution above
|
|
245
|
+
// exists precisely to make that case rare.
|
|
246
|
+
try {
|
|
247
|
+
const rootResolved = await resolvePackage(packageSpecifier, importer);
|
|
248
|
+
if (
|
|
249
|
+
rootResolved &&
|
|
250
|
+
belongsToPackage(stripQueryAndHash(rootResolved), metadata.root)
|
|
251
|
+
) {
|
|
252
|
+
return packageSpecifier;
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
47
260
|
/**
|
|
48
261
|
* Vite plugin that deduplicates client references from third-party packages
|
|
49
262
|
* in dev mode.
|
|
@@ -65,7 +278,11 @@ export function extractPackageName(absolutePath: string): string | null {
|
|
|
65
278
|
*/
|
|
66
279
|
export function clientRefDedup(): Plugin {
|
|
67
280
|
let clientExclude: string[] = [];
|
|
281
|
+
let rootImporter = "";
|
|
282
|
+
let resolvePackage: PackageResolver | undefined;
|
|
68
283
|
const dedupedPackages = new Set<string>();
|
|
284
|
+
const packageMetadataCache = new Map<string, PackageMetadata | null>();
|
|
285
|
+
const publicSpecifierCache = new Map<string, Promise<string | undefined>>();
|
|
69
286
|
|
|
70
287
|
return {
|
|
71
288
|
name: "@rangojs/router:client-ref-dedup",
|
|
@@ -76,6 +293,8 @@ export function clientRefDedup(): Plugin {
|
|
|
76
293
|
const clientEnv = config.environments?.["client"];
|
|
77
294
|
clientExclude =
|
|
78
295
|
clientEnv?.optimizeDeps?.exclude ?? config.optimizeDeps?.exclude ?? [];
|
|
296
|
+
rootImporter = join(config.root, "index.html");
|
|
297
|
+
resolvePackage = config.createResolver({ scan: true });
|
|
79
298
|
},
|
|
80
299
|
|
|
81
300
|
buildEnd() {
|
|
@@ -95,24 +314,67 @@ export function clientRefDedup(): Plugin {
|
|
|
95
314
|
|
|
96
315
|
if (!source.includes("/node_modules/")) return;
|
|
97
316
|
|
|
98
|
-
const
|
|
99
|
-
|
|
317
|
+
const cleanSource = stripQueryAndHash(source);
|
|
318
|
+
const packageName = extractPackageName(cleanSource);
|
|
319
|
+
if (!packageName || !resolvePackage) return;
|
|
100
320
|
|
|
101
321
|
if (clientExclude.includes(packageName)) return;
|
|
102
322
|
|
|
103
|
-
|
|
323
|
+
const metadata = readPackageMetadata(cleanSource, packageMetadataCache);
|
|
324
|
+
if (!metadata || clientExclude.includes(metadata.selfName)) return;
|
|
325
|
+
|
|
326
|
+
let specifierPromise = publicSpecifierCache.get(cleanSource);
|
|
327
|
+
if (!specifierPromise) {
|
|
328
|
+
specifierPromise = resolvePublicSpecifier(
|
|
329
|
+
cleanSource,
|
|
330
|
+
metadata,
|
|
331
|
+
packageName,
|
|
332
|
+
resolvePackage,
|
|
333
|
+
rootImporter,
|
|
334
|
+
);
|
|
335
|
+
publicSpecifierCache.set(cleanSource, specifierPromise);
|
|
336
|
+
// Only SUCCESSFUL resolutions stay cached. A failed one is evicted so
|
|
337
|
+
// the next resolveId retries — deliberate: dev-time failures can be
|
|
338
|
+
// transient (a dependency installed mid-session, an exports map fixed
|
|
339
|
+
// on disk). The cost is a repeated candidate scan per unresolvable
|
|
340
|
+
// module, bounded by resolveId call frequency in dev.
|
|
341
|
+
void specifierPromise.then(
|
|
342
|
+
(specifier) => {
|
|
343
|
+
if (
|
|
344
|
+
!specifier &&
|
|
345
|
+
publicSpecifierCache.get(cleanSource) === specifierPromise
|
|
346
|
+
) {
|
|
347
|
+
publicSpecifierCache.delete(cleanSource);
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
() => {
|
|
351
|
+
if (publicSpecifierCache.get(cleanSource) === specifierPromise) {
|
|
352
|
+
publicSpecifierCache.delete(cleanSource);
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
);
|
|
356
|
+
}
|
|
104
357
|
|
|
105
|
-
return
|
|
358
|
+
return specifierPromise.then((specifier) => {
|
|
359
|
+
if (!specifier) return;
|
|
360
|
+
if (debug) dedupedPackages.add(packageName);
|
|
361
|
+
return `${DEDUP_PREFIX}${encodeURIComponent(specifier)}`;
|
|
362
|
+
});
|
|
106
363
|
},
|
|
107
364
|
|
|
108
365
|
load(id) {
|
|
109
|
-
if (!id.startsWith(
|
|
366
|
+
if (!id.startsWith(DEDUP_PREFIX)) return;
|
|
110
367
|
|
|
111
|
-
|
|
368
|
+
let specifier: string;
|
|
369
|
+
try {
|
|
370
|
+
specifier = decodeURIComponent(id.slice(DEDUP_PREFIX.length));
|
|
371
|
+
} catch {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
112
374
|
|
|
113
375
|
return [
|
|
114
|
-
`export * from ${JSON.stringify(
|
|
115
|
-
`import * as __all__ from ${JSON.stringify(
|
|
376
|
+
`export * from ${JSON.stringify(specifier)};`,
|
|
377
|
+
`import * as __all__ from ${JSON.stringify(specifier)};`,
|
|
116
378
|
`export default __all__.default;`,
|
|
117
379
|
].join("\n");
|
|
118
380
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Plugin, ResolvedConfig } from "vite";
|
|
2
|
+
import { getPluginApi } from "@vitejs/plugin-rsc";
|
|
2
3
|
import MagicString from "magic-string";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import fs from "node:fs";
|
|
@@ -8,15 +9,15 @@ import { createRangoDebugger, createCounter, NS } from "../debug.js";
|
|
|
8
9
|
|
|
9
10
|
const debug = createRangoDebugger(NS.transform);
|
|
10
11
|
|
|
12
|
+
interface ServerReferenceMeta {
|
|
13
|
+
importId: string;
|
|
14
|
+
referenceKey: string;
|
|
15
|
+
exportNames: string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
11
18
|
interface RscPluginManager {
|
|
12
|
-
serverReferenceMetaMap
|
|
13
|
-
|
|
14
|
-
{
|
|
15
|
-
importId: string;
|
|
16
|
-
referenceKey: string;
|
|
17
|
-
exportNames: string[];
|
|
18
|
-
}
|
|
19
|
-
>;
|
|
19
|
+
serverReferenceMetaMap?: Record<string, ServerReferenceMeta>;
|
|
20
|
+
serverReferences?: { metaMap: Map<string, ServerReferenceMeta> };
|
|
20
21
|
config: ResolvedConfig;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -24,21 +25,44 @@ interface RscPluginApi {
|
|
|
24
25
|
manager: RscPluginManager;
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function getServerReferenceMetaEntries(
|
|
29
|
+
manager: RscPluginManager | undefined,
|
|
30
|
+
): Iterable<[string, ServerReferenceMeta]> {
|
|
31
|
+
if (manager?.serverReferences?.metaMap) {
|
|
32
|
+
return manager.serverReferences.metaMap;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (manager?.serverReferenceMetaMap) {
|
|
36
|
+
return Object.entries(manager.serverReferenceMetaMap);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
throw new Error(
|
|
40
|
+
"[rango] Unsupported @vitejs/plugin-rsc server reference metadata shape. " +
|
|
41
|
+
"Expected manager.serverReferences.metaMap or manager.serverReferenceMetaMap.",
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
27
45
|
function getRscPluginApi(config: ResolvedConfig): RscPluginApi | undefined {
|
|
28
|
-
|
|
46
|
+
const pluginApi = getPluginApi(config) as RscPluginApi | undefined;
|
|
47
|
+
if (pluginApi) {
|
|
48
|
+
return pluginApi;
|
|
49
|
+
}
|
|
29
50
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
(
|
|
33
|
-
(p.api as RscPluginApi | undefined)?.manager
|
|
34
|
-
undefined,
|
|
35
|
-
);
|
|
36
|
-
if (plugin) {
|
|
37
|
-
console.warn(
|
|
38
|
-
`[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). ` +
|
|
39
|
-
`Consider updating the name lookup if the plugin was renamed.`,
|
|
51
|
+
const plugin = config.plugins.find((p) => {
|
|
52
|
+
try {
|
|
53
|
+
getServerReferenceMetaEntries(
|
|
54
|
+
(p.api as RscPluginApi | undefined)?.manager,
|
|
40
55
|
);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
41
59
|
}
|
|
60
|
+
});
|
|
61
|
+
if (plugin) {
|
|
62
|
+
console.warn(
|
|
63
|
+
`[rango:expose-action-id] RSC plugin found by API structure (name: "${plugin.name}"). ` +
|
|
64
|
+
`Consider updating the name lookup if the plugin was renamed.`,
|
|
65
|
+
);
|
|
42
66
|
}
|
|
43
67
|
|
|
44
68
|
return plugin?.api as RscPluginApi | undefined;
|
|
@@ -233,10 +257,8 @@ export function exposeActionId(): Plugin {
|
|
|
233
257
|
if (!isBuild) return;
|
|
234
258
|
|
|
235
259
|
hashToFileMap = new Map();
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
for (const [absolutePath, meta] of Object.entries(
|
|
239
|
-
serverReferenceMetaMap,
|
|
260
|
+
for (const [absolutePath, meta] of getServerReferenceMetaEntries(
|
|
261
|
+
rscPluginApi.manager,
|
|
240
262
|
)) {
|
|
241
263
|
// Only include module-level "use server" files
|
|
242
264
|
// Inline actions (defined in RSC components) should keep hashed IDs for client security
|
|
@@ -154,14 +154,23 @@ import {
|
|
|
154
154
|
decodeAction,
|
|
155
155
|
decodeFormState,
|
|
156
156
|
} from "@rangojs/router/internal/deps/rsc";
|
|
157
|
-
import { router } from "${routerPath}";
|
|
158
|
-
import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
|
|
159
|
-
import { VERSION } from "@rangojs/router:version";
|
|
160
157
|
|
|
161
158
|
// Startup bootstrap imports (routes + loader manifests). See
|
|
162
159
|
// RSC_ENTRY_BOOTSTRAP_IMPORTS — the same list the custom-entry injector uses.
|
|
160
|
+
//
|
|
161
|
+
// ORDER IS LOAD-BEARING: the bootstrap imports MUST precede the router import.
|
|
162
|
+
// The routes-manifest module installs client URL projections
|
|
163
|
+
// (setClientUrlProjection), and createRouter().routes(clientUrlsReference)
|
|
164
|
+
// consults them at router-module evaluation. Router-first would silently push
|
|
165
|
+
// every clientUrls() app onto the deferred-subscription path and change mount
|
|
166
|
+
// registration order with no error. The custom-entry injector keeps the same
|
|
167
|
+
// guarantee by PREPENDING this list at file top (version-injector.ts).
|
|
163
168
|
${bootstrapImports}
|
|
164
169
|
|
|
170
|
+
import { router } from "${routerPath}";
|
|
171
|
+
import { createRSCHandler } from "@rangojs/router/internal/rsc-handler";
|
|
172
|
+
import { VERSION } from "@rangojs/router:version";
|
|
173
|
+
|
|
165
174
|
// Lazily create the handler on first request so that ESM live bindings
|
|
166
175
|
// have resolved by the time we read \`router\`. During HMR the module may
|
|
167
176
|
// re-evaluate before router.tsx finishes, leaving the import undefined.
|