UNPKG

rsbuild-plugin-react-router

Version:
279 lines (264 loc) 7.87 kB
import type { RsbuildPluginAPI, TransformHandler } from '@rsbuild/core'; import jsesc from 'jsesc'; import { relative } from 'pathe'; import { PLUGIN_NAME } from './constants.js'; import { getReactRouterManifestForDev, type ReactRouterManifestStats, } from './manifest.js'; import type { RouteTransformExecutor } from './parallel-route-transforms.js'; import type { ReactRouterPerformanceProfiler } from './performance.js'; import { createBundlerRouteExportResolver } from './route-export-resolution.js'; import type { RouteChunkConfig } from './route-chunks.js'; import type { PluginOptions, Route } from './types.js'; import { isSourceMapEnabled } from './warnings/warn-on-client-source-maps.js'; type ReactRouterManifest = Awaited< ReturnType<typeof getReactRouterManifestForDev> >; type RegisterBuildOutputTransformsOptions = { api: RsbuildPluginAPI; resolvedServerOutput: 'module' | 'commonjs'; performanceProfiler: ReactRouterPerformanceProfiler; getLatestServerManifest: () => ReactRouterManifest | null; getLatestServerManifestByBundleId: ( bundleId: string ) => ReactRouterManifest | undefined; routes: Record<string, Route>; pluginOptions: PluginOptions; getClientStats: () => ReactRouterManifestStats | undefined; appDirectory: string; getAssetPrefix: () => string; routeChunkOptions: Parameters<typeof getReactRouterManifestForDev>[5]; routeTransformExecutor: RouteTransformExecutor; routeByFilePath: Map<string, Route>; routeChunkConfig: RouteChunkConfig; isBuild: boolean; splitRouteModules: boolean; ssr: boolean; isSpaMode: boolean; rootRoutePath: string; isDevHmrEnabled?: () => boolean; }; export const registerBuildOutputTransforms = ({ api, resolvedServerOutput, performanceProfiler, getLatestServerManifest, getLatestServerManifestByBundleId, routes, pluginOptions, getClientStats, appDirectory, getAssetPrefix, routeChunkOptions, routeTransformExecutor, routeByFilePath, routeChunkConfig, isBuild, splitRouteModules, ssr, isSpaMode, rootRoutePath, isDevHmrEnabled = () => false, }: RegisterBuildOutputTransformsOptions): void => { const transformRouteModule = async (args: Parameters<TransformHandler>[0]) => performanceProfiler.record( args.environment?.name, 'route:module', args.resource, async () => routeTransformExecutor.run({ kind: 'routeModule', code: args.code, resource: args.resource, resourcePath: args.resourcePath, environmentName: args.environment.name, sourceMaps: isSourceMapEnabled( args.environment.config.output.sourceMap ), ssr, isBuild, isSpaMode, rootRoutePath, devHmr: isDevHmrEnabled(), }) ); api.processAssets( { stage: 'additional', targets: ['node'] }, ({ sources, compilation }) => { const packageJsonPath = 'package.json'; const source = new sources.RawSource( `{"type": "${resolvedServerOutput}"}` ); if (compilation.getAsset(packageJsonPath)) { compilation.updateAsset(packageJsonPath, source); } else { compilation.emitAsset(packageJsonPath, source); } } ); api.transform( { test: /virtual\/react-router\/(browser|server)-manifest/, }, async args => performanceProfiler.record( args.environment?.name, 'manifest:transform', args.resource, async () => { if (args.environment.name === 'web') { return { code: `window.__reactRouterManifest = "PLACEHOLDER";`, }; } const bundleMatch = args.resource.match( /virtual\/react-router\/server-manifest(?:-([^?]+))?/ ); const bundleId = bundleMatch?.[1]?.replace(/\.js$/, ''); const latestServerManifest = getLatestServerManifest(); const manifest = (latestServerManifest ? ((bundleId && getLatestServerManifestByBundleId(bundleId)) ?? latestServerManifest) : null) ?? (await getReactRouterManifestForDev( routes, pluginOptions, getClientStats(), appDirectory, getAssetPrefix(), routeChunkOptions )); return { code: `export default ${jsesc(manifest, { es6: true })};`, }; } ) ); api.transform( { resourceQuery: /__react-router-build-client-route/, }, async args => performanceProfiler.record( args.environment?.name, 'route:client-entry', args.resource, async () => routeTransformExecutor.run({ kind: 'routeClientEntry', code: args.code, resourcePath: args.resourcePath, environmentName: args.environment?.name, isBuild, routeChunkConfig, routeId: routeByFilePath.get(args.resourcePath)?.id, devHmr: isDevHmrEnabled(), }) ) ); api.transform( { resourceQuery: /route-chunk=/, environments: ['web'], }, async args => performanceProfiler.record( args.environment?.name, 'route:chunk', args.resource, async () => routeTransformExecutor.run({ kind: 'routeChunk', code: args.code, resource: args.resource, resourcePath: args.resourcePath, isBuild, routeChunkConfig, }) ) ); if (isBuild && splitRouteModules) { api.transform( { test: path => routeByFilePath.has(path), resourceQuery: { not: /__react-router-build-client-route|react-router-route|route-chunk=/, }, environments: ['web'], }, async args => performanceProfiler.record( args.environment?.name, 'route:split-exports', args.resource, async () => routeTransformExecutor.run({ kind: 'splitRouteExports', code: args.code, resourcePath: args.resourcePath, routeChunkConfig, }) ) ); } api.transform( { test: /[\\/]\.server[\\/]|\.server(\.[cm]?[jt]sx?)?$/, environments: ['web'], }, async args => performanceProfiler.record( args.environment?.name, 'module:server-only-guard', args.resource, async () => { const relativePath = relative(process.cwd(), args.resourcePath); throw new Error( `[${PLUGIN_NAME}] Server-only module referenced by client: ${relativePath}` ); } ) ); api.transform( { test: /[\\/]\.client[\\/]|\.client(\.[cm]?[jt]sx?)?$/, environments: ['node'], }, async args => performanceProfiler.record( args.environment?.name, 'module:client-only-stub', args.resource, async () => { return routeTransformExecutor.run({ kind: 'clientOnlyStub', code: args.code, resourcePath: args.resourcePath, resolveExportAllModule: typeof args.resolve === 'function' ? createBundlerRouteExportResolver(args.resolve) : undefined, }); } ) ); api.transform( { resourceQuery: /\?react-router-route/, order: 'post', }, transformRouteModule ); api.transform( { test: path => routeByFilePath.has(path), resourceQuery: { not: /__react-router-build-client-route|react-router-route|route-chunk=/, }, order: 'post', }, transformRouteModule ); };