Fix build lags

This commit is contained in:
2026-10-02 14:48:38 +01:00
parent 17a5c3df01
commit 8fa5b64b48
38 changed files with 1390 additions and 1238 deletions
+93 -33
View File
@@ -2,51 +2,111 @@ import { log } from "../../utils/log";
import path from "path";
import grabDirNames from "../../utils/grab-dir-names";
import { existsSync, readdirSync, statSync, unlinkSync } from "fs";
import type { BundlerCTXMap } from "../../types";
type Params = {
new_artifacts: BundlerCTXMap[];
paradigm: "hydration" | "ssr";
};
const { ROOT_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE_NAME } = grabDirNames();
const { ROOT_DIR, HYDRATION_DST_DIR, BUNX_CWD_MODULE_CACHE_DIR } =
grabDirNames();
// esbuild's content hashes are 8-char base64-ish (e.g. `CMSI7VSQ`). Entry
// outputs are `<dir>/<hash>.js` / `<dir>/<hash>.css`. Shared chunks are
// `chunk-*.js` and MUST be kept: any current entry may still reference an
// older chunk (esbuild reuses unchanged chunks across rebuilds), and
// verifying references would require parsing every entry's imports.
const ENTRY_HASH_FILE = /^[A-Za-z0-9]{8}\.(js|css)$/;
/**
* Deletes orphaned entry outputs left behind by previous rebuilds.
*
* After a rebuild the page/SSR artifact maps only reference the newest
* hashed entry files; every previously hashed entry for the same directory
* is provably unreferenced (generated HTML always uses the current map) and
* is removed. Chunks, non-hashed fallback module files and `map.json` are
* left untouched.
*/
export default async function cleanupArtifacts({ paradigm }: Params) {
const is_hydration = paradigm === "hydration";
const root_dir = is_hydration
? HYDRATION_DST_DIR
: BUNX_CWD_MODULE_CACHE_DIR;
const map = is_hydration
? global.BUNEXT_BUNDLER_CTX_MAP
: global.BUNEXT_SSR_BUNDLER_CTX_MAP;
if (!map || !existsSync(root_dir)) {
return;
}
export default async function cleanupArtifacts({ new_artifacts }: Params) {
try {
for (let i = 0; i < new_artifacts.length; i++) {
const new_artifact = new_artifacts[i];
const keep_by_dir = new Map<string, Set<string>>();
const artifact_public_dir = path.dirname(
path.join(ROOT_DIR, new_artifact.path),
);
for (const entry of Object.values(map)) {
if (!entry?.path) continue;
const dir_content = readdirSync(artifact_public_dir);
const full_path = path.join(ROOT_DIR, entry.path);
const dir_path = path.dirname(full_path);
let keep = keep_by_dir.get(dir_path);
if (!keep) {
keep = new Set<string>();
keep_by_dir.set(dir_path, keep);
}
for (let d = 0; d < dir_content.length; d++) {
const dir_or_file = dir_content[d];
const full_path = path.join(artifact_public_dir, dir_or_file);
keep.add(path.basename(full_path));
const file_or_path_stats = statSync(full_path);
if (
file_or_path_stats.isDirectory() ||
dir_or_file == HYDRATION_DST_DIR_MAP_JSON_FILE_NAME
) {
continue;
}
if (
new_artifact.path.includes(dir_or_file) ||
new_artifact.css_path?.includes(dir_or_file)
) {
continue;
}
if (existsSync(full_path)) {
unlinkSync(full_path);
}
if (entry.css_path) {
keep.add(path.basename(entry.css_path));
}
}
sweepDir(root_dir, keep_by_dir);
} catch (error: any) {
log.error(error);
log.error(`Cleanup Artifacts Error: ${error?.message || error}`);
}
}
function sweepDir(
dir_path: string,
keep_by_dir: Map<string, Set<string>>,
): void {
let entries: string[];
try {
entries = readdirSync(dir_path);
} catch (error) {
return;
}
for (const name of entries) {
const full_path = path.join(dir_path, name);
let is_dir = false;
try {
is_dir = statSync(full_path).isDirectory();
} catch (error) {
continue;
}
if (is_dir) {
sweepDir(full_path, keep_by_dir);
continue;
}
if (!name.match(ENTRY_HASH_FILE)) {
continue;
}
if (name.startsWith("chunk-")) {
continue;
}
const keep = keep_by_dir.get(path.dirname(full_path));
if (keep?.has(name)) {
continue;
}
try {
unlinkSync(full_path);
} catch (error) {}
}
}