rsbuild-plugin-react-router 0.3.0 → 0.3.1
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 +55 -24
- package/dist/451.js +36 -20
- package/dist/build-manifest.d.ts +6 -3
- package/dist/concurrency.d.ts +2 -1
- package/dist/config-imports.d.ts +7 -0
- package/dist/dev-background-resources.d.ts +38 -0
- package/dist/dev-generation.d.ts +2 -2
- package/dist/dev-runtime-artifacts.d.ts +9 -1
- package/dist/dev-runtime-compilation.d.ts +17 -2
- package/dist/dev-server.d.ts +5 -0
- package/dist/effect-runtime.d.ts +18 -0
- package/dist/export-utils.d.ts +2 -2
- package/dist/index.cjs +11448 -891
- package/dist/index.d.ts +4 -0
- package/dist/index.js +7950 -758
- package/dist/lazy-compilation-prewarm.d.ts +25 -0
- package/dist/lazy-compilation.d.ts +2 -1
- package/dist/manifest.d.ts +19 -4
- package/dist/parallel-route-transforms.d.ts +17 -2
- package/dist/prerender-build.d.ts +4 -0
- package/dist/prerender.d.ts +0 -1
- package/dist/react-router-config.d.ts +8 -5
- package/dist/server-build-resolution.d.ts +3 -0
- package/dist/ssr-externals.d.ts +1 -0
- package/dist/typegen.d.ts +14 -1
- package/dist/types.d.ts +15 -6
- package/package.json +11 -9
- package/src/build-manifest.ts +110 -73
- package/src/concurrency.ts +3 -22
- package/src/config-imports.ts +38 -0
- package/src/dev-background-resources.ts +255 -0
- package/src/dev-generation.ts +43 -53
- package/src/dev-runtime-artifacts.ts +50 -18
- package/src/dev-runtime-compilation.ts +80 -1
- package/src/dev-runtime-controller.ts +111 -31
- package/src/dev-runtime-session.ts +18 -11
- package/src/dev-server.ts +31 -1
- package/src/effect-runtime.ts +130 -0
- package/src/export-utils.ts +82 -23
- package/src/index.ts +118 -153
- package/src/lazy-compilation-prewarm.ts +279 -0
- package/src/lazy-compilation.ts +12 -5
- package/src/manifest.ts +366 -255
- package/src/modify-browser-manifest.ts +2 -1
- package/src/parallel-route-transforms.ts +195 -69
- package/src/prerender-build.ts +125 -61
- package/src/prerender.ts +0 -18
- package/src/react-router-config.ts +98 -73
- package/src/route-artifacts.ts +2 -2
- package/src/route-export-resolution.ts +3 -3
- package/src/route-watch.ts +119 -84
- package/src/server-build-resolution.ts +131 -0
- package/src/server-utils.ts +7 -106
- package/src/ssr-externals.ts +1 -1
- package/src/typegen.ts +162 -33
- package/src/types.ts +16 -6
package/src/server-utils.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { resolve } from 'pathe';
|
|
2
2
|
import type { ServerBuild } from 'react-router';
|
|
3
|
+
import { runPluginEffect } from './effect-runtime.js';
|
|
4
|
+
import { resolveServerBuildModuleEffect } from './server-build-resolution.js';
|
|
3
5
|
import type { Route } from './types.js';
|
|
4
6
|
|
|
5
7
|
/**
|
|
@@ -91,120 +93,19 @@ function generateServerBuild(
|
|
|
91
93
|
return generateStaticTemplate(routes, options);
|
|
92
94
|
}
|
|
93
95
|
|
|
94
|
-
|
|
95
|
-
'allowedActionOrigins',
|
|
96
|
-
'assets',
|
|
97
|
-
'assetsBuildDirectory',
|
|
98
|
-
'basename',
|
|
99
|
-
'entry',
|
|
100
|
-
'future',
|
|
101
|
-
'isSpaMode',
|
|
102
|
-
'prerender',
|
|
103
|
-
'publicPath',
|
|
104
|
-
'routeDiscovery',
|
|
105
|
-
'routes',
|
|
106
|
-
'ssr',
|
|
107
|
-
]);
|
|
108
|
-
|
|
109
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
110
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function isPromiseLike(value: unknown): value is PromiseLike<unknown> {
|
|
114
|
-
return isRecord(value) && typeof value.then === 'function';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function isRouteDiscovery(value: unknown): boolean {
|
|
118
|
-
return (
|
|
119
|
-
isRecord(value) &&
|
|
120
|
-
(value.mode === 'initial' ||
|
|
121
|
-
(value.mode === 'lazy' &&
|
|
122
|
-
(value.manifestPath === undefined ||
|
|
123
|
-
typeof value.manifestPath === 'string')))
|
|
124
|
-
);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
async function resolveBuildExports(
|
|
128
|
-
build: Record<string, unknown>
|
|
129
|
-
): Promise<Record<string, unknown>> {
|
|
130
|
-
const resolved = { ...build };
|
|
131
|
-
for (const key of Object.keys(build)) {
|
|
132
|
-
if (!RESOLVABLE_BUILD_EXPORTS.has(key)) {
|
|
133
|
-
continue;
|
|
134
|
-
}
|
|
135
|
-
const value = build[key];
|
|
136
|
-
if (typeof value === 'function' && value.length === 0) {
|
|
137
|
-
const result = value();
|
|
138
|
-
resolved[key] = isPromiseLike(result) ? await result : result;
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (isPromiseLike(value)) {
|
|
142
|
-
resolved[key] = await value;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
return resolved;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function isServerBuild(value: unknown): value is ServerBuild {
|
|
149
|
-
return Boolean(
|
|
150
|
-
isRecord(value) &&
|
|
151
|
-
isRecord(value.entry) &&
|
|
152
|
-
isRecord(value.entry.module) &&
|
|
153
|
-
typeof value.entry.module.default === 'function' &&
|
|
154
|
-
isRecord(value.routes) &&
|
|
155
|
-
isRecord(value.assets) &&
|
|
156
|
-
typeof value.assetsBuildDirectory === 'string' &&
|
|
157
|
-
(value.basename === undefined || typeof value.basename === 'string') &&
|
|
158
|
-
isRecord(value.future) &&
|
|
159
|
-
typeof value.isSpaMode === 'boolean' &&
|
|
160
|
-
Array.isArray(value.prerender) &&
|
|
161
|
-
typeof value.publicPath === 'string' &&
|
|
162
|
-
isRouteDiscovery(value.routeDiscovery) &&
|
|
163
|
-
typeof value.ssr === 'boolean'
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async function resolveServerBuildCandidate(
|
|
168
|
-
candidate: unknown
|
|
169
|
-
): Promise<ServerBuild | undefined> {
|
|
170
|
-
if (!isRecord(candidate)) {
|
|
171
|
-
return undefined;
|
|
172
|
-
}
|
|
173
|
-
const resolved = await resolveBuildExports(candidate);
|
|
174
|
-
return isServerBuild(resolved) ? resolved : undefined;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export async function resolveServerBuildModule(
|
|
96
|
+
export function resolveServerBuildModule(
|
|
178
97
|
buildModule: unknown,
|
|
179
98
|
source: string
|
|
180
99
|
): Promise<ServerBuild> {
|
|
181
|
-
|
|
182
|
-
const candidates = [() => moduleValue];
|
|
183
|
-
if (isRecord(moduleValue)) {
|
|
184
|
-
if ('default' in moduleValue) {
|
|
185
|
-
candidates.push(() => moduleValue.default);
|
|
186
|
-
}
|
|
187
|
-
if ('module.exports' in moduleValue) {
|
|
188
|
-
candidates.push(() => moduleValue['module.exports']);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
for (const getCandidate of candidates) {
|
|
193
|
-
const candidate = await getCandidate();
|
|
194
|
-
const serverBuild = await resolveServerBuildCandidate(candidate);
|
|
195
|
-
if (serverBuild) {
|
|
196
|
-
return serverBuild;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
throw new Error(
|
|
200
|
-
`[rsbuild-plugin-react-router] ${source} did not contain a valid React Router ServerBuild.`
|
|
201
|
-
);
|
|
100
|
+
return runPluginEffect(resolveServerBuildModuleEffect(buildModule, source));
|
|
202
101
|
}
|
|
203
102
|
|
|
204
103
|
export function resolveReactRouterServerBuild(
|
|
205
104
|
buildModule: unknown
|
|
206
105
|
): Promise<ServerBuild> {
|
|
207
|
-
return
|
|
106
|
+
return runPluginEffect(
|
|
107
|
+
resolveServerBuildModuleEffect(buildModule, 'Imported module')
|
|
108
|
+
);
|
|
208
109
|
}
|
|
209
110
|
|
|
210
111
|
export { generateServerBuild };
|
package/src/ssr-externals.ts
CHANGED
package/src/typegen.ts
CHANGED
|
@@ -1,49 +1,178 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
1
3
|
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
2
4
|
import type { ResultPromise } from 'execa';
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import { createDelayedPluginTask, tryPluginPromise } from './effect-runtime.js';
|
|
7
|
+
import { resolvePackageJson } from './ssr-externals.js';
|
|
3
8
|
|
|
4
|
-
|
|
5
|
-
|
|
9
|
+
// Quiet period with no dev compiles before the typegen watch starts. Long
|
|
10
|
+
// enough that route-load and HMR compile bursts (each rescheduling the task)
|
|
11
|
+
// finish before the typegen process competes for CPU on small machines.
|
|
12
|
+
const TYPEGEN_IDLE_DELAY_MS = 10_000;
|
|
13
|
+
|
|
14
|
+
type Execa = typeof import('execa').execa;
|
|
15
|
+
type LoadExeca = () => Promise<Execa>;
|
|
16
|
+
|
|
17
|
+
export type ReactRouterTypegenRunner = {
|
|
18
|
+
startWatch(): Promise<void>;
|
|
19
|
+
closeWatch(): Promise<void>;
|
|
20
|
+
runBuild(): Promise<void>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const loadDefaultExeca: LoadExeca = async () => {
|
|
24
|
+
const { execa } = await import('execa');
|
|
25
|
+
return execa;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type TypegenCommand = {
|
|
29
|
+
command: string;
|
|
30
|
+
args: string[];
|
|
31
|
+
};
|
|
6
32
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
33
|
+
// The `react-router` CLI bin is provided by `@react-router/dev`. Spawning it
|
|
34
|
+
// directly through `process.execPath` skips the npx bootstrap (npm config
|
|
35
|
+
// load and package resolution), which otherwise costs several hundred ms of
|
|
36
|
+
// CPU during dev-server startup and every production build.
|
|
37
|
+
const resolveDirectTypegenCommand = (
|
|
38
|
+
appDirectory: string
|
|
39
|
+
): TypegenCommand | undefined => {
|
|
40
|
+
const packageJsonPath = resolvePackageJson('@react-router/dev', appDirectory);
|
|
41
|
+
if (!packageJsonPath) {
|
|
42
|
+
// `@react-router/dev` is not resolvable from the app directory; the
|
|
43
|
+
// caller falls back to spawning through npx.
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as {
|
|
48
|
+
bin?: string | Record<string, string>;
|
|
49
|
+
};
|
|
50
|
+
const binRelativePath =
|
|
51
|
+
typeof packageJson.bin === 'string'
|
|
52
|
+
? packageJson.bin
|
|
53
|
+
: packageJson.bin?.['react-router'];
|
|
54
|
+
if (!binRelativePath) {
|
|
55
|
+
return undefined;
|
|
10
56
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
57
|
+
return {
|
|
58
|
+
command: process.execPath,
|
|
59
|
+
args: [resolve(dirname(packageJsonPath), binRelativePath)],
|
|
60
|
+
};
|
|
61
|
+
} catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const createReactRouterTypegenRunner = (
|
|
67
|
+
loadExeca: LoadExeca = loadDefaultExeca,
|
|
68
|
+
appDirectory?: string
|
|
69
|
+
): ReactRouterTypegenRunner => {
|
|
70
|
+
let typegenProcess: ResultPromise | undefined;
|
|
71
|
+
let typegenCommand: TypegenCommand | undefined;
|
|
72
|
+
|
|
73
|
+
const getTypegenCommand = (): TypegenCommand => {
|
|
74
|
+
typegenCommand ??= (appDirectory
|
|
75
|
+
? resolveDirectTypegenCommand(appDirectory)
|
|
76
|
+
: undefined) ?? {
|
|
77
|
+
command: 'npx',
|
|
78
|
+
args: ['--yes', 'react-router'],
|
|
79
|
+
};
|
|
80
|
+
return typegenCommand;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const observeWatchExit = (process: ResultPromise): void => {
|
|
84
|
+
void process
|
|
85
|
+
.catch(() => undefined)
|
|
26
86
|
.finally(() => {
|
|
27
87
|
if (typegenProcess === process) {
|
|
28
88
|
typegenProcess = undefined;
|
|
29
89
|
}
|
|
30
90
|
});
|
|
31
|
-
}
|
|
91
|
+
};
|
|
32
92
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
93
|
+
return {
|
|
94
|
+
async startWatch(): Promise<void> {
|
|
95
|
+
if (typegenProcess) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const execa = await loadExeca();
|
|
100
|
+
const { command, args } = getTypegenCommand();
|
|
101
|
+
const process = execa(command, [...args, 'typegen', '--watch'], {
|
|
102
|
+
stdio: 'inherit',
|
|
103
|
+
detached: false,
|
|
104
|
+
cleanup: true,
|
|
105
|
+
});
|
|
106
|
+
typegenProcess = process;
|
|
107
|
+
observeWatchExit(process);
|
|
108
|
+
},
|
|
109
|
+
|
|
110
|
+
async closeWatch(): Promise<void> {
|
|
111
|
+
const process = typegenProcess;
|
|
112
|
+
typegenProcess = undefined;
|
|
113
|
+
if (!process) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
process.kill('SIGTERM');
|
|
118
|
+
await process.catch(() => undefined);
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
async runBuild(): Promise<void> {
|
|
122
|
+
const execa = await loadExeca();
|
|
123
|
+
const { command, args } = getTypegenCommand();
|
|
124
|
+
await execa(command, [...args, 'typegen'], {
|
|
125
|
+
stdio: 'inherit',
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export const registerReactRouterTypegen = (
|
|
132
|
+
api: RsbuildPluginAPI,
|
|
133
|
+
{
|
|
134
|
+
runner,
|
|
135
|
+
devWatchDelayMs = TYPEGEN_IDLE_DELAY_MS,
|
|
136
|
+
appDirectory,
|
|
137
|
+
}: {
|
|
138
|
+
runner?: ReactRouterTypegenRunner;
|
|
139
|
+
devWatchDelayMs?: number;
|
|
140
|
+
appDirectory?: string;
|
|
141
|
+
} = {}
|
|
142
|
+
): void => {
|
|
143
|
+
const resolvedRunner =
|
|
144
|
+
runner ?? createReactRouterTypegenRunner(loadDefaultExeca, appDirectory);
|
|
145
|
+
let devWatchStarted = false;
|
|
146
|
+
const devWatchTask = createDelayedPluginTask({
|
|
147
|
+
delayMs: devWatchDelayMs,
|
|
148
|
+
run: () =>
|
|
149
|
+
tryPluginPromise(() => {
|
|
150
|
+
devWatchStarted = true;
|
|
151
|
+
return resolvedRunner.startWatch();
|
|
152
|
+
}).pipe(Effect.asVoid),
|
|
153
|
+
onError(error) {
|
|
154
|
+
api.logger.warn(
|
|
155
|
+
`[react-router] Failed to start React Router typegen watch: ${error}`
|
|
156
|
+
);
|
|
157
|
+
},
|
|
41
158
|
});
|
|
42
159
|
|
|
43
|
-
api.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
160
|
+
if (api.context.action !== 'build') {
|
|
161
|
+
// Reschedule on every compile so the typegen watch only starts after a
|
|
162
|
+
// quiet period with no compiles. Starting it during the initial compile
|
|
163
|
+
// burst competes with HMR rebuilds for CPU on small machines.
|
|
164
|
+
api.onAfterDevCompile(() => {
|
|
165
|
+
if (devWatchStarted) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
devWatchTask.reschedule();
|
|
47
169
|
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
api.onCloseDevServer(async () => {
|
|
173
|
+
await devWatchTask.cancel();
|
|
174
|
+
await resolvedRunner.closeWatch();
|
|
48
175
|
});
|
|
176
|
+
|
|
177
|
+
api.onBeforeBuild(() => resolvedRunner.runBuild());
|
|
49
178
|
};
|
package/src/types.ts
CHANGED
|
@@ -20,8 +20,8 @@ export type PluginOptions = {
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* The output format for server builds.
|
|
23
|
-
* When
|
|
24
|
-
*
|
|
23
|
+
* When omitted, React Router's `serverModuleFormat` selects the emitted
|
|
24
|
+
* Rsbuild format (`"esm"` -> `"module"`, `"cjs"` -> `"commonjs"`).
|
|
25
25
|
*/
|
|
26
26
|
serverOutput?: 'module' | 'commonjs';
|
|
27
27
|
|
|
@@ -31,15 +31,25 @@ export type PluginOptions = {
|
|
|
31
31
|
federation?: boolean;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
|
-
*
|
|
34
|
+
* Rsbuild dev-only lazy compilation behavior.
|
|
35
35
|
*
|
|
36
|
-
* React Router
|
|
37
|
-
*
|
|
36
|
+
* React Router's browser manifest remains eager so initial dev requests can
|
|
37
|
+
* discover browser assets without lazy proxy delays.
|
|
38
38
|
*
|
|
39
|
-
*
|
|
39
|
+
* Pass `false` to disable.
|
|
40
|
+
* @default true
|
|
40
41
|
*/
|
|
41
42
|
lazyCompilation?: NonNullable<RsbuildConfig['dev']>['lazyCompilation'];
|
|
42
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Prewarm Rspack lazy-compilation proxy modules after dev compiles.
|
|
46
|
+
* This depends on Rspack's generated lazy-compilation client shape and should
|
|
47
|
+
* be treated as experimental.
|
|
48
|
+
*
|
|
49
|
+
* @default false
|
|
50
|
+
*/
|
|
51
|
+
unstableLazyCompilationPrewarm?: boolean;
|
|
52
|
+
|
|
43
53
|
/**
|
|
44
54
|
* Emit structured React Router plugin timing logs.
|
|
45
55
|
* @default false
|