rsbuild-plugin-react-router 0.7.2 → 0.8.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 +14 -2
- package/dist/468.js +48 -0
- package/dist/511.js +60 -86
- package/dist/819.js +64 -0
- package/dist/build-output-transforms.d.ts +3 -1
- package/dist/constants.d.ts +1 -0
- package/dist/dev-hdr-channel.d.ts +9 -0
- package/dist/dev-hmr.d.ts +1 -22
- package/dist/dev-runtime-controller.d.ts +1 -6
- package/dist/dev-server.d.ts +3 -1
- package/dist/index.cjs +5417 -5080
- package/dist/index.js +1701 -1441
- package/dist/manifest-assets.d.ts +34 -0
- package/dist/manifest-snapshot.d.ts +17 -0
- package/dist/manifest-state.d.ts +12 -0
- package/dist/manifest.d.ts +2 -22
- package/dist/modify-browser-manifest.d.ts +2 -0
- package/dist/node-only-manifest.d.ts +8 -0
- package/dist/plugin-utils.d.ts +1 -1
- package/dist/rsc-prerender.d.ts +3 -2
- package/dist/server-build-worker-client.d.ts +20 -0
- package/dist/server-build-worker-protocol.d.ts +84 -0
- package/dist/server-build-worker.d.ts +1 -0
- package/dist/server-build-worker.js +123 -0
- package/dist/server-utils.d.ts +1 -2
- package/dist/types.d.ts +6 -0
- package/package.json +4 -4
- package/src/build-output-transforms.ts +22 -1
- package/src/classic-mode.ts +0 -1
- package/src/constants.ts +3 -0
- package/src/dev-hdr-channel.ts +38 -0
- package/src/dev-hmr.ts +57 -100
- package/src/dev-runtime-controller.ts +23 -18
- package/src/dev-server.ts +24 -4
- package/src/index.ts +229 -144
- package/src/lazy-compilation.ts +7 -2
- package/src/manifest-assets.ts +228 -0
- package/src/manifest-snapshot.ts +80 -0
- package/src/manifest-state.ts +71 -0
- package/src/manifest.ts +23 -161
- package/src/mode-plan.ts +12 -4
- package/src/modify-browser-manifest.ts +47 -18
- package/src/node-only-manifest.ts +52 -0
- package/src/plugin-utils.ts +6 -2
- package/src/prerender-build.ts +76 -86
- package/src/route-chunks.ts +152 -63
- package/src/rsc-prerender.ts +7 -35
- package/src/server-build-resolution.ts +1 -2
- package/src/server-build-worker-client.ts +221 -0
- package/src/server-build-worker-protocol.ts +69 -0
- package/src/server-build-worker.ts +192 -0
- package/src/server-utils.ts +0 -2
- package/src/types.ts +7 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { BROWSER_MANIFEST_ENTRY_NAME } from './constants.js';
|
|
2
|
+
import { getManifestAssetType, stripAssetQuery } from './manifest-assets.js';
|
|
1
3
|
import { createHash } from 'node:crypto';
|
|
2
4
|
import type { Route, PluginOptions } from './types.js';
|
|
3
5
|
import type { RsbuildPluginAPI, Rspack } from '@rsbuild/core';
|
|
@@ -30,6 +32,8 @@ type CompilationAssetWithIntegrity = {
|
|
|
30
32
|
source?: { source(): string | Buffer };
|
|
31
33
|
info?: {
|
|
32
34
|
integrity?: unknown;
|
|
35
|
+
assetType?: string;
|
|
36
|
+
javascriptModule?: boolean;
|
|
33
37
|
};
|
|
34
38
|
};
|
|
35
39
|
|
|
@@ -79,14 +83,10 @@ const toManifestAssetUrl = (assetPrefix: string, assetName: string) => {
|
|
|
79
83
|
const addIntegrity = (
|
|
80
84
|
sri: Record<string, string>,
|
|
81
85
|
assetPrefix: string,
|
|
82
|
-
assetName:
|
|
86
|
+
assetName: string,
|
|
83
87
|
integrity: unknown
|
|
84
88
|
) => {
|
|
85
|
-
if (
|
|
86
|
-
typeof assetName !== 'string' ||
|
|
87
|
-
!isManifestJsAsset(assetName) ||
|
|
88
|
-
typeof integrity !== 'string'
|
|
89
|
-
) {
|
|
89
|
+
if (typeof integrity !== 'string') {
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
sri[toManifestAssetUrl(assetPrefix, assetName)] = integrity;
|
|
@@ -111,6 +111,9 @@ export const collectSubresourceIntegrity = (
|
|
|
111
111
|
const sri: Record<string, string> = {};
|
|
112
112
|
|
|
113
113
|
for (const asset of stats?.assets ?? []) {
|
|
114
|
+
if (typeof asset.name !== 'string' || !isManifestJsAsset(asset.name)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
114
117
|
addIntegrity(sri, assetPrefix, asset.name, asset.integrity);
|
|
115
118
|
}
|
|
116
119
|
|
|
@@ -118,7 +121,17 @@ export const collectSubresourceIntegrity = (
|
|
|
118
121
|
const assets =
|
|
119
122
|
compilation.getAssets() as readonly CompilationAssetWithIntegrity[];
|
|
120
123
|
for (const asset of assets) {
|
|
121
|
-
|
|
124
|
+
const assetType =
|
|
125
|
+
asset.info?.assetType ??
|
|
126
|
+
(typeof asset.info?.javascriptModule === 'boolean'
|
|
127
|
+
? 'javascript'
|
|
128
|
+
: undefined);
|
|
129
|
+
if (
|
|
130
|
+
getManifestAssetType(
|
|
131
|
+
asset.name,
|
|
132
|
+
assetType === undefined ? undefined : { [asset.name]: assetType }
|
|
133
|
+
) !== 'javascript'
|
|
134
|
+
) {
|
|
122
135
|
continue;
|
|
123
136
|
}
|
|
124
137
|
addIntegrity(
|
|
@@ -144,13 +157,15 @@ export function registerModifyBrowserManifestAssets(
|
|
|
144
157
|
): void {
|
|
145
158
|
const getAssetPrefix =
|
|
146
159
|
typeof assetPrefix === 'function' ? assetPrefix : () => assetPrefix;
|
|
147
|
-
const manifestChunkNames =
|
|
160
|
+
const manifestChunkNames = new Set(
|
|
148
161
|
options?.manifestChunkNames ??
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
getReactRouterManifestChunkNames(
|
|
163
|
+
routes,
|
|
164
|
+
appDirectory,
|
|
165
|
+
routeChunkOptions?.splitRouteModules
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
manifestChunkNames.add(BROWSER_MANIFEST_ENTRY_NAME);
|
|
154
169
|
const isBuild = Boolean(routeChunkOptions?.isBuild);
|
|
155
170
|
const finalizeSri = Boolean(
|
|
156
171
|
isBuild &&
|
|
@@ -165,6 +180,7 @@ export function registerModifyBrowserManifestAssets(
|
|
|
165
180
|
{ assets, sources, compilation }: ManifestProcessAssetsContext,
|
|
166
181
|
{ withSri }: { withSri: boolean }
|
|
167
182
|
): Promise<void> => {
|
|
183
|
+
if (compilation.errors?.length) return;
|
|
168
184
|
const currentAssetPrefix = getAssetPrefix();
|
|
169
185
|
const stats = createReactRouterManifestStats(
|
|
170
186
|
compilation,
|
|
@@ -185,8 +201,17 @@ export function registerModifyBrowserManifestAssets(
|
|
|
185
201
|
const browserManifest = { ...manifest };
|
|
186
202
|
delete browserManifest.sri;
|
|
187
203
|
|
|
188
|
-
const
|
|
189
|
-
|
|
204
|
+
const browserManifestPaths = stats?.assetsByChunkName?.[
|
|
205
|
+
BROWSER_MANIFEST_ENTRY_NAME
|
|
206
|
+
]?.filter(
|
|
207
|
+
name =>
|
|
208
|
+
getManifestAssetType(name, stats.assetTypesByName) === 'javascript'
|
|
209
|
+
) ?? [BROWSER_MANIFEST_ASSET];
|
|
210
|
+
// Production consumes the separately emitted versioned manifest. Leave the
|
|
211
|
+
// placeholder chunk unchanged: its content hash has already been finalized.
|
|
212
|
+
for (const browserManifestPath of isBuild ? [] : browserManifestPaths) {
|
|
213
|
+
const browserManifestAsset = assets[browserManifestPath];
|
|
214
|
+
if (!browserManifestAsset) continue;
|
|
190
215
|
const originalSource = browserManifestAsset.source().toString();
|
|
191
216
|
const serializedManifest = jsesc(browserManifest, { es6: true });
|
|
192
217
|
const newSource = originalSource.replace(
|
|
@@ -194,18 +219,22 @@ export function registerModifyBrowserManifestAssets(
|
|
|
194
219
|
() => serializedManifest
|
|
195
220
|
);
|
|
196
221
|
compilation.updateAsset(
|
|
197
|
-
|
|
222
|
+
browserManifestPath,
|
|
198
223
|
new sources.RawSource(newSource)
|
|
199
224
|
);
|
|
200
225
|
}
|
|
201
226
|
|
|
202
227
|
if (isBuild) {
|
|
203
228
|
const entryAssets = stats?.assetsByChunkName?.['entry.client'];
|
|
204
|
-
const entryJsAssets =
|
|
229
|
+
const entryJsAssets =
|
|
230
|
+
entryAssets?.filter(
|
|
231
|
+
name =>
|
|
232
|
+
getManifestAssetType(name, stats?.assetTypesByName) === 'javascript'
|
|
233
|
+
) || [];
|
|
205
234
|
const manifestPath = getReactRouterManifestPath({
|
|
206
235
|
version: manifest.version,
|
|
207
236
|
isBuild: true,
|
|
208
|
-
entryModulePath: entryJsAssets[0],
|
|
237
|
+
entryModulePath: stripAssetQuery(entryJsAssets[0] ?? ''),
|
|
209
238
|
});
|
|
210
239
|
const manifestSource = `window.__reactRouterManifest=${jsesc(
|
|
211
240
|
browserManifest,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { rspack, type RsbuildPluginAPI, type Rspack } from '@rsbuild/core';
|
|
2
|
+
import { resolve } from 'pathe';
|
|
3
|
+
import type { ReactRouterManifestSnapshot } from './manifest-snapshot.js';
|
|
4
|
+
import type { Route } from './types.js';
|
|
5
|
+
|
|
6
|
+
// Inspect the compiled graph rather than parsing source: loaders, re-exports,
|
|
7
|
+
// and cached modules must all participate in the compatibility check.
|
|
8
|
+
export const registerNodeOnlyManifestValidation = ({
|
|
9
|
+
api,
|
|
10
|
+
routeByFilePath,
|
|
11
|
+
getSnapshot,
|
|
12
|
+
}: {
|
|
13
|
+
api: RsbuildPluginAPI;
|
|
14
|
+
routeByFilePath: ReadonlyMap<string, Route>;
|
|
15
|
+
getSnapshot: () => ReactRouterManifestSnapshot | null;
|
|
16
|
+
}): void => {
|
|
17
|
+
api.modifyRspackConfig((config, { environment }) => {
|
|
18
|
+
if (environment.name !== 'node') return;
|
|
19
|
+
config.plugins ??= [];
|
|
20
|
+
config.plugins.push({
|
|
21
|
+
apply(compiler: Rspack.Compiler) {
|
|
22
|
+
const name = 'ReactRouterNodeOnlyManifestValidation';
|
|
23
|
+
compiler.hooks.thisCompilation.tap(name, compilation => {
|
|
24
|
+
compilation.hooks.afterOptimizeModules.tap(name, modules => {
|
|
25
|
+
const snapshot = getSnapshot();
|
|
26
|
+
if (!snapshot) return;
|
|
27
|
+
for (const module of modules) {
|
|
28
|
+
if (!(module instanceof rspack.NormalModule)) continue;
|
|
29
|
+
const route = routeByFilePath.get(
|
|
30
|
+
resolve(module.resource.split('?')[0])
|
|
31
|
+
);
|
|
32
|
+
if (!route) continue;
|
|
33
|
+
const exports =
|
|
34
|
+
compilation.moduleGraph.getProvidedExports(module);
|
|
35
|
+
const saved = snapshot.browser.routes[route.id];
|
|
36
|
+
if (
|
|
37
|
+
!Array.isArray(exports) ||
|
|
38
|
+
!saved ||
|
|
39
|
+
exports.includes('loader') !== saved.hasLoader ||
|
|
40
|
+
exports.includes('action') !== saved.hasAction
|
|
41
|
+
) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Run a full build before building only the node environment: loader/action exports changed for route "${route.id}".`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
};
|
package/src/plugin-utils.ts
CHANGED
|
@@ -6,10 +6,14 @@ import { JS_EXTENSIONS } from './constants.js';
|
|
|
6
6
|
const requireFromApp = createRequire(resolve(process.cwd(), 'package.json'));
|
|
7
7
|
|
|
8
8
|
export const resolveAppPackagePath = (
|
|
9
|
-
specifier: string
|
|
9
|
+
specifier: string,
|
|
10
|
+
rootPath?: string
|
|
10
11
|
): string | undefined => {
|
|
11
12
|
try {
|
|
12
|
-
|
|
13
|
+
const require = rootPath
|
|
14
|
+
? createRequire(resolve(rootPath, 'package.json'))
|
|
15
|
+
: requireFromApp;
|
|
16
|
+
return require.resolve(specifier);
|
|
13
17
|
} catch {
|
|
14
18
|
return undefined;
|
|
15
19
|
}
|
package/src/prerender-build.ts
CHANGED
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
-
import { pathToFileURL } from 'node:url';
|
|
4
3
|
import fsExtra from 'fs-extra';
|
|
5
4
|
import * as Effect from 'effect/Effect';
|
|
6
5
|
import type { RsbuildPluginAPI } from '@rsbuild/core';
|
|
7
|
-
import {
|
|
8
|
-
createRequestHandler,
|
|
9
|
-
matchRoutes,
|
|
10
|
-
type ServerBuild,
|
|
11
|
-
} from 'react-router';
|
|
6
|
+
import { matchRoutes } from 'react-router';
|
|
12
7
|
import { dirname, relative, resolve } from 'pathe';
|
|
13
8
|
import { PLUGIN_NAME, SPA_FALLBACK_HTML_FILE } from './constants.js';
|
|
14
9
|
import { getBuildManifest } from './build-manifest.js';
|
|
@@ -31,24 +26,11 @@ import type {
|
|
|
31
26
|
Config,
|
|
32
27
|
ResolvedReactRouterConfig,
|
|
33
28
|
} from './react-router-config.js';
|
|
34
|
-
import {
|
|
29
|
+
import { startServerBuildWorker } from './server-build-worker-client.js';
|
|
30
|
+
import type { ServerBuildDescription } from './server-build-worker-protocol.js';
|
|
35
31
|
import type { PluginOptions, Route } from './types.js';
|
|
36
32
|
import { runPluginEffect, tryPluginPromise } from './effect-runtime.js';
|
|
37
33
|
|
|
38
|
-
type BuildRouteModule = {
|
|
39
|
-
loader?: unknown;
|
|
40
|
-
default?: unknown;
|
|
41
|
-
ErrorBoundary?: unknown;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
type PrerenderServerBuild = ServerBuild & {
|
|
45
|
-
routes: Record<string, { id?: string; module?: BuildRouteModule }>;
|
|
46
|
-
assets?: {
|
|
47
|
-
routes?: Record<string, { hasLoader?: boolean }>;
|
|
48
|
-
};
|
|
49
|
-
prerender?: string[];
|
|
50
|
-
};
|
|
51
|
-
|
|
52
34
|
type PrerenderBuildApi = Pick<
|
|
53
35
|
RsbuildPluginAPI,
|
|
54
36
|
'logger' | 'getNormalizedConfig'
|
|
@@ -344,7 +326,7 @@ const handleSpaMode = async ({
|
|
|
344
326
|
api,
|
|
345
327
|
}: {
|
|
346
328
|
handler: (request: Request) => Promise<Response>;
|
|
347
|
-
build:
|
|
329
|
+
build: ServerBuildDescription;
|
|
348
330
|
clientBuildDir: string;
|
|
349
331
|
basename: string;
|
|
350
332
|
api: PrerenderBuildApi;
|
|
@@ -459,7 +441,7 @@ const createPrerenderPathEffect = ({
|
|
|
459
441
|
options,
|
|
460
442
|
}: {
|
|
461
443
|
path: string;
|
|
462
|
-
build:
|
|
444
|
+
build: ServerBuildDescription;
|
|
463
445
|
buildRoutes: ReturnType<typeof createPrerenderRoutes>;
|
|
464
446
|
requestHandler: (request: Request) => Promise<Response>;
|
|
465
447
|
clientBuildDir: string;
|
|
@@ -596,75 +578,83 @@ export const runReactRouterPrerenderBuild = async (
|
|
|
596
578
|
await mkdir(clientBuildDir, { recursive: true });
|
|
597
579
|
|
|
598
580
|
if (!ssr || isPrerenderEnabled) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
const generated = latestBrowserManifest
|
|
610
|
-
? {
|
|
611
|
-
manifest: latestBrowserManifest,
|
|
612
|
-
moduleExportsByRouteId: latestBrowserManifestModuleExports,
|
|
613
|
-
}
|
|
614
|
-
: await generateReactRouterManifestForDev(
|
|
615
|
-
routes,
|
|
616
|
-
pluginOptions,
|
|
617
|
-
clientStats,
|
|
618
|
-
appDirectory,
|
|
619
|
-
assetPrefix,
|
|
620
|
-
createReactRouterManifestOptions({
|
|
621
|
-
routeChunks: routeChunkOptions,
|
|
622
|
-
routeModuleAnalysis,
|
|
623
|
-
})
|
|
624
|
-
);
|
|
625
|
-
assertValidSsrFalsePrerenderExports({
|
|
626
|
-
routes,
|
|
627
|
-
manifestRoutes: generated.manifest.routes,
|
|
628
|
-
routeExports: generated.moduleExportsByRouteId,
|
|
629
|
-
prerenderPaths,
|
|
630
|
-
api,
|
|
631
|
-
});
|
|
581
|
+
const worker = await startServerBuildWorker({
|
|
582
|
+
serverBuildPath,
|
|
583
|
+
mode: 'classic',
|
|
584
|
+
});
|
|
585
|
+
try {
|
|
586
|
+
const build = worker.description;
|
|
587
|
+
if (!build) {
|
|
588
|
+
throw new Error(
|
|
589
|
+
`[${PLUGIN_NAME}] Server build worker returned no build description`
|
|
590
|
+
);
|
|
632
591
|
}
|
|
592
|
+
const requestHandler = worker.handler;
|
|
593
|
+
|
|
594
|
+
if (isPrerenderEnabled) {
|
|
595
|
+
if (!ssr) {
|
|
596
|
+
const generated = latestBrowserManifest
|
|
597
|
+
? {
|
|
598
|
+
manifest: latestBrowserManifest,
|
|
599
|
+
moduleExportsByRouteId: latestBrowserManifestModuleExports,
|
|
600
|
+
}
|
|
601
|
+
: await generateReactRouterManifestForDev(
|
|
602
|
+
routes,
|
|
603
|
+
pluginOptions,
|
|
604
|
+
clientStats,
|
|
605
|
+
appDirectory,
|
|
606
|
+
assetPrefix,
|
|
607
|
+
createReactRouterManifestOptions({
|
|
608
|
+
routeChunks: routeChunkOptions,
|
|
609
|
+
routeModuleAnalysis,
|
|
610
|
+
})
|
|
611
|
+
);
|
|
612
|
+
assertValidSsrFalsePrerenderExports({
|
|
613
|
+
routes,
|
|
614
|
+
manifestRoutes: generated.manifest.routes,
|
|
615
|
+
routeExports: generated.moduleExportsByRouteId,
|
|
616
|
+
prerenderPaths,
|
|
617
|
+
api,
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
validatePrerenderPathMatches(routes, prerenderPaths);
|
|
633
622
|
|
|
634
|
-
|
|
623
|
+
if (prerenderPaths.length > 0) {
|
|
624
|
+
api.logger.info(
|
|
625
|
+
`Prerender (html): ${prerenderPaths.length} path(s)...`
|
|
626
|
+
);
|
|
627
|
+
}
|
|
635
628
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
629
|
+
const buildRoutes = createPrerenderRoutes(build.routes);
|
|
630
|
+
await runPluginEffect(
|
|
631
|
+
createBoundedPrerenderTasksEffect(
|
|
632
|
+
prerenderPaths,
|
|
633
|
+
getPrerenderConcurrency(prerenderConfig),
|
|
634
|
+
path =>
|
|
635
|
+
createPrerenderPathEffect({
|
|
636
|
+
path,
|
|
637
|
+
build,
|
|
638
|
+
buildRoutes,
|
|
639
|
+
requestHandler,
|
|
640
|
+
clientBuildDir,
|
|
641
|
+
options,
|
|
642
|
+
})
|
|
643
|
+
)
|
|
639
644
|
);
|
|
640
645
|
}
|
|
641
646
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
clientBuildDir,
|
|
654
|
-
options,
|
|
655
|
-
})
|
|
656
|
-
)
|
|
657
|
-
);
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
if (!ssr) {
|
|
661
|
-
await handleSpaMode({
|
|
662
|
-
handler: requestHandler,
|
|
663
|
-
build,
|
|
664
|
-
clientBuildDir,
|
|
665
|
-
basename,
|
|
666
|
-
api,
|
|
667
|
-
});
|
|
647
|
+
if (!ssr) {
|
|
648
|
+
await handleSpaMode({
|
|
649
|
+
handler: requestHandler,
|
|
650
|
+
build,
|
|
651
|
+
clientBuildDir,
|
|
652
|
+
basename,
|
|
653
|
+
api,
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
} finally {
|
|
657
|
+
await worker.close();
|
|
668
658
|
}
|
|
669
659
|
}
|
|
670
660
|
|