vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
123 lines (109 loc) • 4 kB
text/typescript
import type {
OutputBundle,
PluginContext,
OutputChunk,
} from "rollup";
import { createInputNormalizer } from "./inputNormalizer.js";
import { DEFAULT_CONFIG } from "../config/index.js";
interface BundleManifestEntry {
file: string;
name: string;
src?: string;
isEntry?: boolean;
imports?: string[];
dynamicImports?: string[];
css?: string[];
}
/**
* Get the bundle manifest from the plugin context. Will only work during production build
* @param pluginContext - The plugin context
* @param bundle - The bundle
* @param preserveModulesRoot - The preserve modules root
* @returns The bundle manifest
*/
export function getBundleManifest({
pluginContext,
bundle,
moduleBase,
preserveModulesRoot,
}: {
pluginContext: PluginContext,
bundle: OutputBundle,
moduleBase?: string,
preserveModulesRoot?: boolean,
}): Record<string, BundleManifestEntry> {
const normalizer = createInputNormalizer({
root: pluginContext.environment.config.root,
removeExtension: DEFAULT_CONFIG.FILE_REGEX,
preserveModulesRoot: preserveModulesRoot === true ? moduleBase : undefined,
});
if (!bundle) return {};
// Track virtual modules to prevent duplicates
const virtualModules = new Map<string, string>();
const bundleManifest = Object.fromEntries(
Object.entries(bundle)
.map(([fileName, chunk]) => {
if (chunk.type !== "chunk") return null as never;
const chunkWithFacade = chunk as OutputChunk;
// Get the module ID, preferring facadeModuleId
const moduleId = chunkWithFacade.facadeModuleId || chunkWithFacade.moduleIds[0] || fileName;
// Handle commonjs helpers specially - must be done before normalization
if (moduleId.includes('commonjsHelpers')) {
return [
moduleId,
{
file: 'commonjs-runtime.js',
name: 'commonjsHelpers',
src: moduleId,
isEntry: false
}
];
}
// Normalize both paths, removing the root prefix
let [normalizedId, sourcePath] = normalizer(moduleId);
// For virtual modules, use a consistent naming scheme
let finalFileName = fileName;
if (moduleId.includes('?')) {
const [basePath, query] = moduleId.split('?');
const virtualPath = basePath.includes('node_modules')
? basePath.split('node_modules/')[1]
: basePath;
// Create a unique key for this virtual module
const virtualKey = `${virtualPath}?${query}`;
if (!virtualModules.has(virtualKey)) {
// First time seeing this virtual module
const virtualFileName = `${virtualPath.replace(/\.js$/, '')}.${query}.js`;
virtualModules.set(virtualKey, virtualFileName);
}
finalFileName = virtualModules.get(virtualKey)!;
}
// handle preserveModulesRoot
if(normalizedId.startsWith('\x00')){
normalizedId = normalizedId.slice(1);
}
if(sourcePath.startsWith('/')){
sourcePath = sourcePath.slice(1);
}
if(moduleBase && preserveModulesRoot && normalizedId?.startsWith(moduleBase + '/')) {
normalizedId = normalizedId.slice(moduleBase.length + 1);
}
const bundleManifestEntry = [
sourcePath,
{
file: finalFileName,
name: normalizedId,
src: sourcePath,
isEntry: chunk.isEntry,
...(chunk.imports?.length > 0 ? { imports: chunk.imports } : {}),
...(chunk.dynamicImports?.length > 0 ? { dynamicImports: chunk.dynamicImports } : {}),
...(chunk.viteMetadata?.importedCss?.size ? {
css: Array.from(chunk.viteMetadata.importedCss),
} : {}),
},
];
return bundleManifestEntry;
})
.filter(Boolean)
);
return bundleManifest;
}