rsbuild-plugin-react-router 0.2.0 → 0.3.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/README.md +185 -204
- package/dist/451.js +1087 -0
- package/dist/bounded-cache.d.ts +1 -0
- package/dist/build-manifest.d.ts +1 -1
- package/dist/build-output-transforms.d.ts +30 -0
- package/dist/concurrency.d.ts +2 -0
- package/dist/config-imports.d.ts +3 -0
- package/dist/constants.d.ts +3 -0
- package/dist/dev-generation.d.ts +25 -0
- package/dist/dev-runtime-artifacts.d.ts +39 -0
- package/dist/dev-runtime-compilation.d.ts +32 -0
- package/dist/dev-runtime-controller.d.ts +14 -0
- package/dist/dev-runtime-session.d.ts +34 -0
- package/dist/dev-server.d.ts +11 -2
- package/dist/export-utils.d.ts +15 -7
- package/dist/index.cjs +3050 -1244
- package/dist/index.d.ts +4 -1
- package/dist/index.js +2015 -1279
- package/dist/lazy-compilation.d.ts +5 -0
- package/dist/manifest.d.ts +32 -9
- package/dist/modify-browser-manifest.d.ts +30 -13
- package/dist/parallel-route-transform-protocol.d.ts +25 -0
- package/dist/parallel-route-transform-worker.d.ts +1 -0
- package/dist/parallel-route-transform-worker.js +38 -0
- package/dist/parallel-route-transforms.d.ts +14 -0
- package/dist/performance.d.ts +26 -0
- package/dist/plugin-utils.d.ts +2 -12
- package/dist/prerender-build.d.ts +32 -0
- package/dist/prerender.d.ts +28 -2
- package/dist/react-router-config.d.ts +8 -1
- package/dist/route-artifacts.d.ts +33 -0
- package/dist/route-ast.d.ts +21 -0
- package/dist/route-chunks.d.ts +9 -1
- package/dist/route-component-transform.d.ts +5 -0
- package/dist/route-export-pruning.d.ts +6 -0
- package/dist/route-export-resolution.d.ts +5 -0
- package/dist/route-transform-tasks.d.ts +47 -0
- package/dist/route-watch.d.ts +27 -0
- package/dist/server-build-plan.d.ts +22 -0
- package/dist/server-utils.d.ts +3 -2
- package/dist/templates/entry.server.cjs +3 -3
- package/dist/templates/entry.server.js +3 -3
- package/dist/typegen.d.ts +2 -0
- package/dist/types.d.ts +30 -12
- package/dist/virtual-modules.d.ts +2 -0
- package/dist/warnings/warn-on-client-source-maps.d.ts +1 -0
- package/dist/yuku.d.ts +15 -0
- package/package.json +22 -19
- package/src/bounded-cache.ts +18 -0
- package/src/build-manifest.ts +168 -0
- package/src/build-output-transforms.ts +273 -0
- package/src/concurrency.ts +34 -0
- package/src/config-imports.ts +45 -0
- package/src/constants.ts +91 -0
- package/src/dev-generation.ts +700 -0
- package/src/dev-runtime-artifacts.ts +207 -0
- package/src/dev-runtime-compilation.ts +92 -0
- package/src/dev-runtime-controller.ts +462 -0
- package/src/dev-runtime-session.ts +184 -0
- package/src/dev-server.ts +58 -0
- package/src/export-utils.ts +219 -0
- package/src/index.ts +1025 -0
- package/src/lazy-compilation.ts +94 -0
- package/src/manifest.ts +480 -0
- package/src/modify-browser-manifest.ts +245 -0
- package/src/parallel-route-transform-protocol.ts +35 -0
- package/src/parallel-route-transform-worker.ts +82 -0
- package/src/parallel-route-transforms.ts +332 -0
- package/src/performance.ts +254 -0
- package/src/plugin-utils.ts +82 -0
- package/src/prerender-build.ts +623 -0
- package/src/prerender.ts +367 -0
- package/src/react-router-config.ts +220 -0
- package/src/route-artifacts.ts +155 -0
- package/src/route-ast.ts +163 -0
- package/src/route-chunks.ts +857 -0
- package/src/route-component-transform.ts +314 -0
- package/src/route-config.ts +106 -0
- package/src/route-export-pruning.ts +668 -0
- package/src/route-export-resolution.ts +329 -0
- package/src/route-transform-tasks.ts +249 -0
- package/src/route-watch.ts +322 -0
- package/src/server-build-plan.ts +91 -0
- package/src/server-utils.ts +210 -0
- package/src/ssr-externals.ts +59 -0
- package/src/templates/context.ts +12 -0
- package/src/templates/entry.client.tsx +12 -0
- package/src/templates/entry.server.tsx +76 -0
- package/src/typegen.ts +49 -0
- package/src/types.ts +82 -0
- package/src/validation/validate-plugin-order.ts +76 -0
- package/src/virtual-modules.ts +30 -0
- package/src/warnings/warn-on-client-source-maps.ts +96 -0
- package/src/yuku.ts +67 -0
- package/dist/0~rslib-runtime.js +0 -16
- package/dist/babel.d.ts +0 -8
package/dist/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { RsbuildConfig } from '@rsbuild/core';
|
|
1
2
|
export type Route = {
|
|
2
3
|
id: string;
|
|
3
4
|
parentId?: string;
|
|
@@ -24,18 +25,35 @@ export type PluginOptions = {
|
|
|
24
25
|
* Federation mode configuration
|
|
25
26
|
*/
|
|
26
27
|
federation?: boolean;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Opt in to Rsbuild's dev-only lazy compilation behavior.
|
|
30
|
+
*
|
|
31
|
+
* React Router hydration modules remain eager so initial dev requests can
|
|
32
|
+
* load the browser manifest and route modules without lazy proxy delays.
|
|
33
|
+
*
|
|
34
|
+
* @default undefined
|
|
35
|
+
*/
|
|
36
|
+
lazyCompilation?: NonNullable<RsbuildConfig['dev']>['lazyCompilation'];
|
|
37
|
+
/**
|
|
38
|
+
* Emit structured React Router plugin timing logs.
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
logPerformance?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Run route transforms in a worker-thread pool.
|
|
44
|
+
* Pass `true` to force the default worker count, a positive integer to set
|
|
45
|
+
* the worker count, or `false` to disable.
|
|
46
|
+
* @default Automatically enabled for 256+ resolved routes. The automatic
|
|
47
|
+
* pool uses available CPU cores minus 2.
|
|
48
|
+
*/
|
|
49
|
+
parallelRouteTransform?: boolean | number;
|
|
50
|
+
/**
|
|
51
|
+
* Called when the route graph changes during development.
|
|
52
|
+
* Programmatic/custom servers can use this to recreate their Rsbuild server;
|
|
53
|
+
* the CLI uses its built-in reload-server watcher when this is omitted. This
|
|
54
|
+
* notification is not awaited, so it may safely close the current server.
|
|
55
|
+
*/
|
|
56
|
+
onRouteTopologyChange?: () => void | Promise<void>;
|
|
39
57
|
};
|
|
40
58
|
export type RouteManifestItem = Omit<Route, 'file' | 'children'> & {
|
|
41
59
|
module: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NormalizedConfig } from '@rsbuild/core';
|
|
2
2
|
type Warn = (message: string) => void;
|
|
3
|
+
export declare function isSourceMapEnabled(value: unknown): boolean;
|
|
3
4
|
export declare function getClientSourceMapSetting(normalized: NormalizedConfig, clientEnvName?: string): unknown;
|
|
4
5
|
export declare function getClientDevtoolSetting(normalized: NormalizedConfig, clientEnvName?: string): unknown;
|
|
5
6
|
export declare function warnOnClientSourceMaps(normalized: NormalizedConfig, warn: Warn, clientEnvName?: string): void;
|
package/dist/yuku.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { walk, type ParseOptions, type ParseResult } from 'yuku-parser';
|
|
2
|
+
import type { Rspack } from '@rsbuild/core';
|
|
3
|
+
export declare const parse: (code: string, options?: ParseOptions) => ParseResult;
|
|
4
|
+
export declare const traverse: typeof walk;
|
|
5
|
+
export declare const generate: (ast: ParseResult | {
|
|
6
|
+
type: "Program";
|
|
7
|
+
}, options?: {
|
|
8
|
+
sourceMaps?: boolean;
|
|
9
|
+
filename?: string;
|
|
10
|
+
sourceFileName?: string;
|
|
11
|
+
}) => {
|
|
12
|
+
code: string;
|
|
13
|
+
map: Rspack.RawSourceMap | null;
|
|
14
|
+
};
|
|
15
|
+
export type { ParseResult };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rsbuild-plugin-react-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "React Router plugin for Rsbuild",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -42,22 +42,16 @@
|
|
|
42
42
|
"main": "./dist/index.cjs",
|
|
43
43
|
"types": "./dist/index.d.ts",
|
|
44
44
|
"files": [
|
|
45
|
-
"dist"
|
|
45
|
+
"dist",
|
|
46
|
+
"src"
|
|
46
47
|
],
|
|
47
48
|
"publint": {
|
|
48
49
|
"ignoreMissingDts": true
|
|
49
50
|
},
|
|
50
51
|
"dependencies": {
|
|
51
|
-
"@babel/core": "^7.28.6",
|
|
52
|
-
"@babel/generator": "^7.28.6",
|
|
53
|
-
"@babel/parser": "^7.28.6",
|
|
54
|
-
"@babel/traverse": "^7.28.6",
|
|
55
|
-
"@babel/types": "^7.28.6",
|
|
56
52
|
"@react-router/node": "^7.13.0",
|
|
57
53
|
"@remix-run/node-fetch-server": "^0.13.0",
|
|
58
54
|
"@rspack/plugin-react-refresh": "^2.0.2",
|
|
59
|
-
"babel-dead-code-elimination": "^1.0.12",
|
|
60
|
-
"esbuild": "^0.27.2",
|
|
61
55
|
"execa": "^9.6.1",
|
|
62
56
|
"fs-extra": "11.3.3",
|
|
63
57
|
"isbot": "5.1.34",
|
|
@@ -65,20 +59,20 @@
|
|
|
65
59
|
"jsesc": "^3.1.0",
|
|
66
60
|
"pathe": "^2.0.3",
|
|
67
61
|
"react-refresh": "^0.18.0",
|
|
68
|
-
"
|
|
62
|
+
"yuku-analyzer": "0.5.39",
|
|
63
|
+
"yuku-codegen": "0.5.39",
|
|
64
|
+
"yuku-parser": "0.5.39"
|
|
69
65
|
},
|
|
70
66
|
"devDependencies": {
|
|
71
67
|
"@changesets/cli": "^2.29.8",
|
|
72
|
-
"@react-router/dev": "^
|
|
73
|
-
"@rsbuild/core": "2.0
|
|
68
|
+
"@react-router/dev": "^8.0.1",
|
|
69
|
+
"@rsbuild/core": "2.1.0",
|
|
70
|
+
"@rsbuild/plugin-react": "2.1.0",
|
|
74
71
|
"@rslib/core": "^0.22.1",
|
|
75
|
-
"@rspack/core": "2.0
|
|
76
|
-
"@swc/helpers": "^0.5.23",
|
|
72
|
+
"@rspack/core": "2.1.0",
|
|
77
73
|
"@rstest/core": "^0.8.1",
|
|
78
74
|
"@rstest/coverage-istanbul": "^0.2.0",
|
|
79
|
-
"@
|
|
80
|
-
"@types/babel__generator": "^7.27.0",
|
|
81
|
-
"@types/babel__traverse": "^7.28.0",
|
|
75
|
+
"@swc/helpers": "^0.5.23",
|
|
82
76
|
"@types/fs-extra": "11.0.4",
|
|
83
77
|
"@types/jsesc": "^3.0.3",
|
|
84
78
|
"@types/node": "^25.0.10",
|
|
@@ -86,7 +80,8 @@
|
|
|
86
80
|
"@types/react-dom": "^19.2.3",
|
|
87
81
|
"es-module-lexer": "1.7.0",
|
|
88
82
|
"kill-port": "^2.0.1",
|
|
89
|
-
"
|
|
83
|
+
"pkg-pr-new": "^0.0.75",
|
|
84
|
+
"playwright": "1.58.0",
|
|
90
85
|
"prettier": "3.8.1",
|
|
91
86
|
"react": "^19.2.4",
|
|
92
87
|
"react-dom": "^19.2.4",
|
|
@@ -105,13 +100,21 @@
|
|
|
105
100
|
},
|
|
106
101
|
"scripts": {
|
|
107
102
|
"build": "rslib build",
|
|
108
|
-
"
|
|
103
|
+
"bench": "node scripts/bench-builds.mjs",
|
|
104
|
+
"bench:ci-report": "node scripts/report-benchmark-ci.mjs",
|
|
105
|
+
"bench:compare": "node scripts/compare-benchmarks.mjs",
|
|
106
|
+
"bench:smoke": "node scripts/bench-builds.mjs --profile smoke --iterations 1 --warmup 0 --format both --out .benchmark/results/smoke",
|
|
107
|
+
"bench:baseline": "node scripts/bench-builds.mjs --profile default --iterations 5 --warmup 1 --clean build --format both --out .benchmark/results/baseline",
|
|
108
|
+
"bench:full": "node scripts/bench-builds.mjs --profile full --iterations 5 --warmup 1 --clean build --format both --out .benchmark/results/full",
|
|
109
|
+
"bench:large": "node scripts/bench-builds.mjs --profile large --iterations 1 --warmup 0 --clean cold --format both --out .benchmark/results/large",
|
|
110
|
+
"e2e": "pnpm build && pnpm test:package-interop && pnpm --filter './examples/{default-template,spa-mode,prerender,custom-node-server,cloudflare,client-only}' test:e2e",
|
|
109
111
|
"dev": "rslib build --watch",
|
|
110
112
|
"test": "rstest run",
|
|
111
113
|
"test:watch": "rstest watch",
|
|
112
114
|
"test:coverage": "rstest run --coverage",
|
|
113
115
|
"test:core": "rstest run -c ./rstest.config.ts",
|
|
114
116
|
"test:core:watch": "rstest watch -c ./rstest.config.ts",
|
|
117
|
+
"test:package-interop": "node scripts/test-package-interop.mjs",
|
|
115
118
|
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx}\"",
|
|
116
119
|
"format:check": "prettier --check \"src/**/*.{js,jsx,ts,tsx}\"",
|
|
117
120
|
"changeset": "changeset",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const setBoundedCacheEntry = <Key, Value>(
|
|
2
|
+
cache: Map<Key, Value>,
|
|
3
|
+
key: Key,
|
|
4
|
+
value: Value,
|
|
5
|
+
maxEntries: number
|
|
6
|
+
): void => {
|
|
7
|
+
if (maxEntries <= 0) {
|
|
8
|
+
cache.clear();
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (!cache.has(key) && cache.size >= maxEntries) {
|
|
12
|
+
const oldestEntry = cache.keys().next();
|
|
13
|
+
if (!oldestEntry.done) {
|
|
14
|
+
cache.delete(oldestEntry.value);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
cache.set(key, value);
|
|
18
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { relative, resolve } from 'pathe';
|
|
2
|
+
import type { Config } from './react-router-config.js';
|
|
3
|
+
import type { Route } from './types.js';
|
|
4
|
+
|
|
5
|
+
type BuildManifest =
|
|
6
|
+
| {
|
|
7
|
+
routes: Record<string, Route>;
|
|
8
|
+
}
|
|
9
|
+
| {
|
|
10
|
+
routes: Record<string, Route>;
|
|
11
|
+
serverBundles: Record<string, { id: string; file: string }>;
|
|
12
|
+
routeIdToServerBundleId: Record<string, string>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const normalizePath = (value: string): string => value.replace(/\\/g, '/');
|
|
16
|
+
|
|
17
|
+
const getAddressableRoutes = (routes: Record<string, Route>): Route[] => {
|
|
18
|
+
const nonAddressableIds = new Set<string>();
|
|
19
|
+
for (const id in routes) {
|
|
20
|
+
const route = routes[id];
|
|
21
|
+
if (route.index) {
|
|
22
|
+
if (!route.parentId) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
nonAddressableIds.add(route.parentId);
|
|
26
|
+
}
|
|
27
|
+
if (typeof route.path !== 'string' && !route.index) {
|
|
28
|
+
nonAddressableIds.add(id);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return Object.values(routes).filter(
|
|
32
|
+
route => !nonAddressableIds.has(route.id)
|
|
33
|
+
);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getRouteBranch = (
|
|
37
|
+
routes: Record<string, Route>,
|
|
38
|
+
routeId: string
|
|
39
|
+
): Route[] => {
|
|
40
|
+
const branch: Route[] = [];
|
|
41
|
+
let current = routeId;
|
|
42
|
+
while (current) {
|
|
43
|
+
const route = routes[current];
|
|
44
|
+
if (!route) {
|
|
45
|
+
throw new Error(`Missing route for ${current}`);
|
|
46
|
+
}
|
|
47
|
+
branch.push(route);
|
|
48
|
+
current = route.parentId || '';
|
|
49
|
+
}
|
|
50
|
+
return branch.reverse();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const configRouteToBranchRoute = (route: Route) => ({
|
|
54
|
+
id: route.id,
|
|
55
|
+
path: route.path,
|
|
56
|
+
file: route.file,
|
|
57
|
+
index: route.index,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const getBuildManifest = async ({
|
|
61
|
+
reactRouterConfig,
|
|
62
|
+
routes,
|
|
63
|
+
rootDirectory,
|
|
64
|
+
}: {
|
|
65
|
+
reactRouterConfig: Required<
|
|
66
|
+
Pick<
|
|
67
|
+
Config,
|
|
68
|
+
'appDirectory' | 'buildDirectory' | 'serverBuildFile' | 'future'
|
|
69
|
+
>
|
|
70
|
+
> &
|
|
71
|
+
Pick<Config, 'serverBundles'>;
|
|
72
|
+
routes: Record<string, Route>;
|
|
73
|
+
rootDirectory: string;
|
|
74
|
+
}): Promise<BuildManifest | undefined> => {
|
|
75
|
+
const {
|
|
76
|
+
serverBundles,
|
|
77
|
+
appDirectory,
|
|
78
|
+
buildDirectory,
|
|
79
|
+
serverBuildFile,
|
|
80
|
+
future,
|
|
81
|
+
} = reactRouterConfig;
|
|
82
|
+
|
|
83
|
+
if (!serverBundles) {
|
|
84
|
+
return { routes };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const rootRelativeRoutes = Object.fromEntries(
|
|
88
|
+
Object.entries(routes).map(([id, route]) => {
|
|
89
|
+
const filePath = resolve(appDirectory, route.file);
|
|
90
|
+
return [
|
|
91
|
+
id,
|
|
92
|
+
{ ...route, file: normalizePath(relative(rootDirectory, filePath)) },
|
|
93
|
+
];
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const serverBuildDirectory = resolve(buildDirectory, 'server');
|
|
98
|
+
|
|
99
|
+
const buildManifest: BuildManifest = {
|
|
100
|
+
routes: rootRelativeRoutes,
|
|
101
|
+
serverBundles: {},
|
|
102
|
+
routeIdToServerBundleId: {},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
await Promise.all(
|
|
106
|
+
getAddressableRoutes(routes).map(async route => {
|
|
107
|
+
const branch = getRouteBranch(routes, route.id);
|
|
108
|
+
const serverBundleId = await serverBundles({
|
|
109
|
+
branch: branch.map(branchRoute =>
|
|
110
|
+
configRouteToBranchRoute({
|
|
111
|
+
...branchRoute,
|
|
112
|
+
file: resolve(appDirectory, branchRoute.file),
|
|
113
|
+
})
|
|
114
|
+
),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
if (typeof serverBundleId !== 'string') {
|
|
118
|
+
throw new Error('The "serverBundles" function must return a string');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (future?.v8_viteEnvironmentApi) {
|
|
122
|
+
if (!/^[a-zA-Z0-9_]+$/.test(serverBundleId)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
'The "serverBundles" function must only return strings containing alphanumeric characters and underscores.'
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
} else if (!/^[a-zA-Z0-9-_]+$/.test(serverBundleId)) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
'The "serverBundles" function must only return strings containing alphanumeric characters, hyphens and underscores.'
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
buildManifest.routeIdToServerBundleId![route.id] = serverBundleId;
|
|
134
|
+
buildManifest.serverBundles![serverBundleId] ??= {
|
|
135
|
+
id: serverBundleId,
|
|
136
|
+
file: normalizePath(
|
|
137
|
+
relative(
|
|
138
|
+
rootDirectory,
|
|
139
|
+
resolve(serverBuildDirectory, serverBundleId, serverBuildFile)
|
|
140
|
+
)
|
|
141
|
+
),
|
|
142
|
+
};
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
return buildManifest;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const getRoutesByServerBundleId = (
|
|
150
|
+
buildManifest: BuildManifest | undefined,
|
|
151
|
+
sourceRoutes: Record<string, Route>
|
|
152
|
+
): Record<string, Record<string, Route>> => {
|
|
153
|
+
if (!buildManifest || !('routeIdToServerBundleId' in buildManifest)) {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const routesByServerBundleId: Record<string, Record<string, Route>> = {};
|
|
158
|
+
for (const [routeId, serverBundleId] of Object.entries(
|
|
159
|
+
buildManifest.routeIdToServerBundleId
|
|
160
|
+
)) {
|
|
161
|
+
routesByServerBundleId[serverBundleId] ??= {};
|
|
162
|
+
const branch = getRouteBranch(sourceRoutes, routeId);
|
|
163
|
+
for (const route of branch) {
|
|
164
|
+
routesByServerBundleId[serverBundleId][route.id] = route;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return routesByServerBundleId;
|
|
168
|
+
};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type { RsbuildPluginAPI, TransformHandler } from '@rsbuild/core';
|
|
2
|
+
import jsesc from 'jsesc';
|
|
3
|
+
import { relative } from 'pathe';
|
|
4
|
+
import { PLUGIN_NAME } from './constants.js';
|
|
5
|
+
import {
|
|
6
|
+
getReactRouterManifestForDev,
|
|
7
|
+
type ReactRouterManifestStats,
|
|
8
|
+
} from './manifest.js';
|
|
9
|
+
import type { RouteTransformExecutor } from './parallel-route-transforms.js';
|
|
10
|
+
import type { ReactRouterPerformanceProfiler } from './performance.js';
|
|
11
|
+
import { createBundlerRouteExportResolver } from './route-export-resolution.js';
|
|
12
|
+
import type { RouteChunkConfig } from './route-chunks.js';
|
|
13
|
+
import type { PluginOptions, Route } from './types.js';
|
|
14
|
+
import { isSourceMapEnabled } from './warnings/warn-on-client-source-maps.js';
|
|
15
|
+
|
|
16
|
+
type ReactRouterManifest = Awaited<
|
|
17
|
+
ReturnType<typeof getReactRouterManifestForDev>
|
|
18
|
+
>;
|
|
19
|
+
|
|
20
|
+
type RegisterBuildOutputTransformsOptions = {
|
|
21
|
+
api: RsbuildPluginAPI;
|
|
22
|
+
resolvedServerOutput: 'module' | 'commonjs';
|
|
23
|
+
performanceProfiler: ReactRouterPerformanceProfiler;
|
|
24
|
+
getLatestServerManifest: () => ReactRouterManifest | null;
|
|
25
|
+
getLatestServerManifestByBundleId: (
|
|
26
|
+
bundleId: string
|
|
27
|
+
) => ReactRouterManifest | undefined;
|
|
28
|
+
routes: Record<string, Route>;
|
|
29
|
+
pluginOptions: PluginOptions;
|
|
30
|
+
getClientStats: () => ReactRouterManifestStats | undefined;
|
|
31
|
+
appDirectory: string;
|
|
32
|
+
getAssetPrefix: () => string;
|
|
33
|
+
routeChunkOptions: Parameters<typeof getReactRouterManifestForDev>[5];
|
|
34
|
+
routeTransformExecutor: RouteTransformExecutor;
|
|
35
|
+
routeByFilePath: Map<string, Route>;
|
|
36
|
+
routeChunkConfig: RouteChunkConfig;
|
|
37
|
+
isBuild: boolean;
|
|
38
|
+
splitRouteModules: boolean;
|
|
39
|
+
ssr: boolean;
|
|
40
|
+
isSpaMode: boolean;
|
|
41
|
+
rootRoutePath: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const registerBuildOutputTransforms = ({
|
|
45
|
+
api,
|
|
46
|
+
resolvedServerOutput,
|
|
47
|
+
performanceProfiler,
|
|
48
|
+
getLatestServerManifest,
|
|
49
|
+
getLatestServerManifestByBundleId,
|
|
50
|
+
routes,
|
|
51
|
+
pluginOptions,
|
|
52
|
+
getClientStats,
|
|
53
|
+
appDirectory,
|
|
54
|
+
getAssetPrefix,
|
|
55
|
+
routeChunkOptions,
|
|
56
|
+
routeTransformExecutor,
|
|
57
|
+
routeByFilePath,
|
|
58
|
+
routeChunkConfig,
|
|
59
|
+
isBuild,
|
|
60
|
+
splitRouteModules,
|
|
61
|
+
ssr,
|
|
62
|
+
isSpaMode,
|
|
63
|
+
rootRoutePath,
|
|
64
|
+
}: RegisterBuildOutputTransformsOptions): void => {
|
|
65
|
+
const transformRouteModule = async (args: Parameters<TransformHandler>[0]) =>
|
|
66
|
+
performanceProfiler.record(
|
|
67
|
+
args.environment?.name,
|
|
68
|
+
'route:module',
|
|
69
|
+
args.resource,
|
|
70
|
+
async () =>
|
|
71
|
+
routeTransformExecutor.run({
|
|
72
|
+
kind: 'routeModule',
|
|
73
|
+
code: args.code,
|
|
74
|
+
resource: args.resource,
|
|
75
|
+
resourcePath: args.resourcePath,
|
|
76
|
+
environmentName: args.environment.name,
|
|
77
|
+
sourceMaps: isSourceMapEnabled(
|
|
78
|
+
args.environment.config.output.sourceMap
|
|
79
|
+
),
|
|
80
|
+
ssr,
|
|
81
|
+
isBuild,
|
|
82
|
+
isSpaMode,
|
|
83
|
+
rootRoutePath,
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
api.processAssets(
|
|
88
|
+
{ stage: 'additional', targets: ['node'] },
|
|
89
|
+
({ sources, compilation }) => {
|
|
90
|
+
const packageJsonPath = 'package.json';
|
|
91
|
+
const source = new sources.RawSource(
|
|
92
|
+
`{"type": "${resolvedServerOutput}"}`
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (compilation.getAsset(packageJsonPath)) {
|
|
96
|
+
compilation.updateAsset(packageJsonPath, source);
|
|
97
|
+
} else {
|
|
98
|
+
compilation.emitAsset(packageJsonPath, source);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
api.transform(
|
|
104
|
+
{
|
|
105
|
+
test: /virtual\/react-router\/(browser|server)-manifest/,
|
|
106
|
+
},
|
|
107
|
+
async args =>
|
|
108
|
+
performanceProfiler.record(
|
|
109
|
+
args.environment?.name,
|
|
110
|
+
'manifest:transform',
|
|
111
|
+
args.resource,
|
|
112
|
+
async () => {
|
|
113
|
+
if (args.environment.name === 'web') {
|
|
114
|
+
return {
|
|
115
|
+
code: `window.__reactRouterManifest = "PLACEHOLDER";`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const bundleMatch = args.resource.match(
|
|
120
|
+
/virtual\/react-router\/server-manifest(?:-([^?]+))?/
|
|
121
|
+
);
|
|
122
|
+
const bundleId = bundleMatch?.[1]?.replace(/\.js$/, '');
|
|
123
|
+
const latestServerManifest = getLatestServerManifest();
|
|
124
|
+
const manifest =
|
|
125
|
+
(latestServerManifest
|
|
126
|
+
? ((bundleId && getLatestServerManifestByBundleId(bundleId)) ??
|
|
127
|
+
latestServerManifest)
|
|
128
|
+
: null) ??
|
|
129
|
+
(await getReactRouterManifestForDev(
|
|
130
|
+
routes,
|
|
131
|
+
pluginOptions,
|
|
132
|
+
getClientStats(),
|
|
133
|
+
appDirectory,
|
|
134
|
+
getAssetPrefix(),
|
|
135
|
+
routeChunkOptions
|
|
136
|
+
));
|
|
137
|
+
return {
|
|
138
|
+
code: `export default ${jsesc(manifest, { es6: true })};`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
api.transform(
|
|
145
|
+
{
|
|
146
|
+
resourceQuery: /__react-router-build-client-route/,
|
|
147
|
+
},
|
|
148
|
+
async args =>
|
|
149
|
+
performanceProfiler.record(
|
|
150
|
+
args.environment?.name,
|
|
151
|
+
'route:client-entry',
|
|
152
|
+
args.resource,
|
|
153
|
+
async () =>
|
|
154
|
+
routeTransformExecutor.run({
|
|
155
|
+
kind: 'routeClientEntry',
|
|
156
|
+
code: args.code,
|
|
157
|
+
resourcePath: args.resourcePath,
|
|
158
|
+
environmentName: args.environment?.name,
|
|
159
|
+
isBuild,
|
|
160
|
+
routeChunkConfig,
|
|
161
|
+
})
|
|
162
|
+
)
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
api.transform(
|
|
166
|
+
{
|
|
167
|
+
resourceQuery: /route-chunk=/,
|
|
168
|
+
environments: ['web'],
|
|
169
|
+
},
|
|
170
|
+
async args =>
|
|
171
|
+
performanceProfiler.record(
|
|
172
|
+
args.environment?.name,
|
|
173
|
+
'route:chunk',
|
|
174
|
+
args.resource,
|
|
175
|
+
async () =>
|
|
176
|
+
routeTransformExecutor.run({
|
|
177
|
+
kind: 'routeChunk',
|
|
178
|
+
code: args.code,
|
|
179
|
+
resource: args.resource,
|
|
180
|
+
resourcePath: args.resourcePath,
|
|
181
|
+
isBuild,
|
|
182
|
+
routeChunkConfig,
|
|
183
|
+
})
|
|
184
|
+
)
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
if (isBuild && splitRouteModules) {
|
|
188
|
+
api.transform(
|
|
189
|
+
{
|
|
190
|
+
test: path => routeByFilePath.has(path),
|
|
191
|
+
resourceQuery: {
|
|
192
|
+
not: /__react-router-build-client-route|react-router-route|route-chunk=/,
|
|
193
|
+
},
|
|
194
|
+
environments: ['web'],
|
|
195
|
+
},
|
|
196
|
+
async args =>
|
|
197
|
+
performanceProfiler.record(
|
|
198
|
+
args.environment?.name,
|
|
199
|
+
'route:split-exports',
|
|
200
|
+
args.resource,
|
|
201
|
+
async () =>
|
|
202
|
+
routeTransformExecutor.run({
|
|
203
|
+
kind: 'splitRouteExports',
|
|
204
|
+
code: args.code,
|
|
205
|
+
resourcePath: args.resourcePath,
|
|
206
|
+
routeChunkConfig,
|
|
207
|
+
})
|
|
208
|
+
)
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
api.transform(
|
|
213
|
+
{
|
|
214
|
+
test: /[\\/]\.server[\\/]|\.server(\.[cm]?[jt]sx?)?$/,
|
|
215
|
+
environments: ['web'],
|
|
216
|
+
},
|
|
217
|
+
async args =>
|
|
218
|
+
performanceProfiler.record(
|
|
219
|
+
args.environment?.name,
|
|
220
|
+
'module:server-only-guard',
|
|
221
|
+
args.resource,
|
|
222
|
+
async () => {
|
|
223
|
+
const relativePath = relative(process.cwd(), args.resourcePath);
|
|
224
|
+
throw new Error(
|
|
225
|
+
`[${PLUGIN_NAME}] Server-only module referenced by client: ${relativePath}`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
api.transform(
|
|
232
|
+
{
|
|
233
|
+
test: /[\\/]\.client[\\/]|\.client(\.[cm]?[jt]sx?)?$/,
|
|
234
|
+
environments: ['node'],
|
|
235
|
+
},
|
|
236
|
+
async args =>
|
|
237
|
+
performanceProfiler.record(
|
|
238
|
+
args.environment?.name,
|
|
239
|
+
'module:client-only-stub',
|
|
240
|
+
args.resource,
|
|
241
|
+
async () => {
|
|
242
|
+
return routeTransformExecutor.run({
|
|
243
|
+
kind: 'clientOnlyStub',
|
|
244
|
+
code: args.code,
|
|
245
|
+
resourcePath: args.resourcePath,
|
|
246
|
+
resolveExportAllModule:
|
|
247
|
+
typeof args.resolve === 'function'
|
|
248
|
+
? createBundlerRouteExportResolver(args.resolve)
|
|
249
|
+
: undefined,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
)
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
api.transform(
|
|
256
|
+
{
|
|
257
|
+
resourceQuery: /\?react-router-route/,
|
|
258
|
+
order: 'post',
|
|
259
|
+
},
|
|
260
|
+
transformRouteModule
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
api.transform(
|
|
264
|
+
{
|
|
265
|
+
test: path => routeByFilePath.has(path),
|
|
266
|
+
resourceQuery: {
|
|
267
|
+
not: /__react-router-build-client-route|react-router-route|route-chunk=/,
|
|
268
|
+
},
|
|
269
|
+
order: 'post',
|
|
270
|
+
},
|
|
271
|
+
transformRouteModule
|
|
272
|
+
);
|
|
273
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { availableParallelism, cpus } from 'node:os';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_RESERVED_CORES = 2;
|
|
4
|
+
|
|
5
|
+
const getAvailableCpuCount = (): number =>
|
|
6
|
+
typeof availableParallelism === 'function'
|
|
7
|
+
? availableParallelism()
|
|
8
|
+
: cpus().length;
|
|
9
|
+
|
|
10
|
+
export const getDefaultConcurrency = (
|
|
11
|
+
cpuCount: number = getAvailableCpuCount()
|
|
12
|
+
): number => Math.max(0, Math.floor(cpuCount) - DEFAULT_RESERVED_CORES);
|
|
13
|
+
|
|
14
|
+
export const mapWithConcurrency = async <Item, Result>(
|
|
15
|
+
items: readonly Item[],
|
|
16
|
+
concurrency: number,
|
|
17
|
+
worker: (item: Item, index: number) => Promise<Result>
|
|
18
|
+
): Promise<Result[]> => {
|
|
19
|
+
const results = new Array<Result>(items.length);
|
|
20
|
+
let nextIndex = 0;
|
|
21
|
+
const workerCount = Math.max(1, Math.min(concurrency, items.length));
|
|
22
|
+
await Promise.all(
|
|
23
|
+
Array.from({ length: workerCount }, async () => {
|
|
24
|
+
while (true) {
|
|
25
|
+
const index = nextIndex++;
|
|
26
|
+
if (index >= items.length) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
results[index] = await worker(items[index], index);
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
);
|
|
33
|
+
return results;
|
|
34
|
+
};
|