vite-plugin-react-server
Version:
Vite plugin for React Server Components (RSC)
168 lines (154 loc) • 4.64 kB
text/typescript
import type { Manifest, ModuleGraph } from "vite";
import { createInputNormalizer } from "./helpers/inputNormalizer.js";
import { DEFAULT_CONFIG } from "./config/defaults.js";
export async function collectModuleGraphCss({
moduleGraph,
pagePath,
onCss,
parentUrl,
}: {
moduleGraph: ModuleGraph;
pagePath: string;
onCss?: (path: string, parentUrl: string) => void;
parentUrl?: string;
}) {
if (!pagePath) return new Map<string, string>();
const cssFiles = new Map<string, string>();
const pageModule = await moduleGraph.getModuleByUrl(pagePath, true);
if (!pageModule) {
return new Map<string, string>();
}
const seen = new Set<string>();
const walkModule = (mod: any) => {
if (!mod?.id || seen.has(mod.id)) return;
seen.add(mod.id);
if (mod?.id?.endsWith(".css")) {
cssFiles.set(mod?.url, mod?.id);
onCss?.(mod?.url, parentUrl ?? pagePath);
}
mod?.importedModules?.forEach((imp: any) => walkModule(imp));
};
walkModule(pageModule);
return cssFiles;
}
export function collectManifestClientFiles({
manifest,
root,
pagePath,
preserveModulesRoot,
moduleBase,
onCss,
onClientModule,
testClient = DEFAULT_CONFIG.AUTO_DISCOVER.clientComponents,
testJson = DEFAULT_CONFIG.AUTO_DISCOVER.jsonPattern,
}: {
manifest: Manifest;
root: string;
pagePath: string;
preserveModulesRoot?: boolean;
moduleBase?: string;
onCss?: (path: string, parentUrl: string) => void;
onClientModule?: (path: string, parentUrl: string) => void;
parentUrl?: string;
testClient?: (id: string) => boolean;
testJson?: (id: string) => boolean;
}) {
const normalizer = createInputNormalizer({
root,
removeExtension: true,
preserveModulesRoot: preserveModulesRoot ? moduleBase : undefined,
});
const [_, value] = normalizer(pagePath);
const cssFiles = new Map<string, string>();
const clientFiles = new Map<string, string>();
const seen = new Set<string>();
const manifestValues = Object.values(manifest);
// Try different variations of the path
const possibleKeys = [
value, // Relative path
];
const walkManifestEntry = (id: string, parentUrl: string) => {
if (seen.has(id)) return;
seen.add(id);
// Get the manifest entry
const entry = manifest[id] ?? manifestValues.find((e) => id === e.file);
if (!entry) {
const hasKey = Object.keys(manifest).find((key) => id === key);
if (hasKey) {
console.log(
`Manifest entry found for ${id}, but it is ${typeof entry}`
);
return;
} else {
console.log(
`No manifest entry found for ${id}, possible keys: ${Object.keys(
manifest
).join(", ")}`
);
}
return;
}
if (
(typeof testClient === "function" &&
typeof onClientModule === "function" &&
testClient(entry.file)) ||
(typeof testJson === "function" && testJson(entry.file))
) {
onClientModule?.(entry.file ?? "", parentUrl);
clientFiles.set(id, entry.name ?? "");
}
// Add direct CSS from the css array
if (entry.css) {
entry.css.forEach((css: string) => {
cssFiles.set(css, css);
onCss?.(css, id);
onClientModule?.(css, id);
});
}
// Walk imports recursively
if (entry.imports) {
entry.imports.forEach((imp: string) =>
walkManifestEntry(imp, entry.file)
);
}
// Also check dynamicImports
if (entry.dynamicImports) {
entry.dynamicImports.forEach((imp: string) =>
walkManifestEntry(imp, entry.file)
);
}
};
// Try all possible keys
for (const possibleKey of possibleKeys) {
if (manifest[possibleKey]) {
walkManifestEntry(possibleKey, pagePath);
break;
}
}
// If no entry found by key, try matching by file
if (cssFiles.size === 0) {
const entry = manifestValues.find(
(e) =>
possibleKeys.includes(e.file) ||
(e.src && possibleKeys.includes(e.src)) ||
(e.name && possibleKeys.includes(e.name))
);
if (entry) {
walkManifestEntry(value, pagePath);
} else {
const hasKey = Object.keys(manifest).find((key) => value === key);
if (hasKey) {
console.warn(
`Manifest entry found for ${value}, but it is ${typeof manifest[hasKey]}`
);
} else {
console.warn(
`No manifest entry found for ${value} (tried all possible keys: ${possibleKeys.join(
", "
)} for manifest keys: ${Object.keys(manifest).join(", ")})`
);
}
}
}
return { cssFiles, clientFiles };
}