astro
Version:
Astro is a modern site builder with web best practices, performance, and DX front-of-mind.
115 lines (114 loc) • 3.93 kB
JavaScript
import fs from "node:fs";
import { ASTRO_VITE_ENVIRONMENT_NAMES } from "../constants.js";
import { getHandles, resetHandles } from "../../assets/utils/assets.js";
import { pathToDirectoryURL } from "./common.js";
import { getOrCreateSSRAssets } from "./internal.js";
/**
* Vite plugin that tracks emitted assets.
* This plugin coordinates with emitClientAsset() to track which assets should
* be moved to the client directory. The resolved filenames are stored in
* BuildInternals.ssrAssetsPerEnvironment during generateBundle.
*/
export function vitePluginSSRAssets(internals) {
return {
name: "astro:ssr-assets",
applyToEnvironment(environment) {
return environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.client || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || environment.name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender;
},
configEnvironment(name) {
// Enable manifest generation for SSR and prerender environments
// so we can read CSS files from the manifest
if (name === ASTRO_VITE_ENVIRONMENT_NAMES.ssr || name === ASTRO_VITE_ENVIRONMENT_NAMES.prerender) {
return { build: { manifest: true } };
}
},
buildStart() {
// Reset tracking for this environment at the start of each build
resetHandles(this.environment);
},
generateBundle() {
const env = this.environment;
const handles = getHandles(env);
const filenames = getOrCreateSSRAssets(internals, env.name);
// Resolve handles to filenames and store in internals
if (handles) {
for (const handle of handles) {
try {
const filename = this.getFileName(handle);
filenames.add(filename);
} catch {}
}
}
},
writeBundle: {
sequential: true,
order: "post",
async handler() {
const env = this.environment;
// Add CSS and assets from manifest (these are always client assets)
// Must be done in writeBundle because the manifest is written during the bundle write phase
const manifestDir = pathToDirectoryURL(env.config.build.outDir);
const manifest = loadViteManifest(manifestDir);
if (manifest) {
const manifestAssets = collectAssetsFromManifest(manifest);
if (manifestAssets.size > 0) {
// Append manifest assets to internals.ssrAssetsPerEnvironment for this environment
const filenames = getOrCreateSSRAssets(internals, env.name);
for (const asset of manifestAssets) {
filenames.add(asset);
}
}
}
}
}
};
}
/**
* Loads a Vite manifest from a directory if it exists.
*/
function loadViteManifest(directory) {
const manifestPath = new URL(".vite/manifest.json", directory);
if (!fs.existsSync(manifestPath)) {
return null;
}
const contents = fs.readFileSync(manifestPath, "utf-8");
return JSON.parse(contents);
}
/**
* Collects client asset filenames from a Vite manifest.
* This includes CSS files and other assets (fonts, images) that need to be
* moved to the client directory.
*/
function collectAssetsFromManifest(manifest) {
const assets = new Set();
for (const chunk of Object.values(manifest)) {
// Add CSS files listed in the css array
if (chunk.css) {
for (const css of chunk.css) {
assets.add(css);
}
}
// Add assets (fonts, images referenced from CSS, etc.)
if (chunk.assets) {
for (const asset of chunk.assets) {
assets.add(asset);
}
}
// Add CSS files that are the primary output of a chunk
// This happens when a JS module only imports CSS and has no JS output
if (chunk.file.endsWith(".css")) {
assets.add(chunk.file);
}
}
return assets;
}
/**
* Deletes the .vite folder from a directory if it exists.
*/
export async function removeViteManifestDirectories(directories) {
const viteFolders = new Set(Array.from(directories, (directory) => new URL(".vite/", directory).href));
await Promise.all(Array.from(viteFolders, (viteFolder) => fs.promises.rm(new URL(viteFolder), {
recursive: true,
force: true
})));
}