- Server functions can return not_found: true to render the 404 page with status 404. - Page components and window.__PAGE_PROPS__ (and HMR updates) only get props, query, url and error. response_options, redirect, cache settings and res_transform stay on the server, so cookies and private headers no longer end up in the HTML. BunextPageProps is narrowed to match (breaking for code that read server-only fields from component props). - response_options headers go through a Headers object, so repeated headers such as several Set-Cookie survive. - Redirects: status_code wins over permanent; response_options headers and res_transform apply to redirects too. - Gzip for compressible text responses; compress: false to turn it off. - Files the router matches but never bundles (.server.ts, __root, excluded paths and directories) return 404 instead of looping through dev rebuild retries. - Missing public files return 404 rather than 403, so dotted page paths like /users/john.doe fall through to pages again. - importFresh detects rewrites within the filesystem's mtime granularity. - README: self-hosted React (not esm.sh), snake_case option names, static file serving, cache headers, the new options. Co-Authored-By: Claude Opus 5.5 <[email protected]>
93 lines
3.1 KiB
JavaScript
93 lines
3.1 KiB
JavaScript
import { log } from "../../utils/log";
|
|
import path from "path";
|
|
import grabDirNames from "../../utils/grab-dir-names";
|
|
import { existsSync, readdirSync, statSync, unlinkSync } from "fs";
|
|
const { ROOT_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 }) {
|
|
const { HYDRATION_DST_DIR, BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
|
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;
|
|
}
|
|
try {
|
|
const keep_by_dir = new Map();
|
|
for (const entry of Object.values(map)) {
|
|
if (!entry?.path)
|
|
continue;
|
|
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();
|
|
keep_by_dir.set(dir_path, keep);
|
|
}
|
|
keep.add(path.basename(full_path));
|
|
if (entry.css_path) {
|
|
keep.add(path.basename(entry.css_path));
|
|
}
|
|
}
|
|
sweepDir(root_dir, keep_by_dir);
|
|
}
|
|
catch (error) {
|
|
log.error(`Cleanup Artifacts Error: ${error?.message || error}`);
|
|
}
|
|
}
|
|
function sweepDir(dir_path, keep_by_dir) {
|
|
let entries;
|
|
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) { }
|
|
}
|
|
}
|