1.4.0: not_found, server-only response options, compression
- 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]>
This commit is contained in:
+38
-9
@@ -10,11 +10,16 @@ import grabAllPages from "../../utils/grab-all-pages";
|
||||
import serverPostBuildFn from "./server-post-build-fn";
|
||||
import { log } from "../../utils/log";
|
||||
import { logBuildErrorsOnce } from "../../utils/build-error-log";
|
||||
const { ROOT_DIR, BUNX_CWD_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
|
||||
const { ROOT_DIR } = grabDirNames();
|
||||
// The watchdog writes this file and expects the watcher to report it. It
|
||||
// sits under `.bunext` (otherwise ignored), so only this path is watched.
|
||||
const WATCH_PROBE_DIR = path.join(BUNX_CWD_DIR, "watch-probe");
|
||||
const WATCH_PROBE_FILE = path.join(WATCH_PROBE_DIR, "probe");
|
||||
// sits under the dist directory (otherwise ignored), so only this path is
|
||||
// watched.
|
||||
function grabWatchPaths() {
|
||||
const { BUNX_CWD_DIR } = grabDirNames();
|
||||
const WATCH_PROBE_DIR = path.join(BUNX_CWD_DIR, "watch-probe");
|
||||
const WATCH_PROBE_FILE = path.join(WATCH_PROBE_DIR, "probe");
|
||||
return { BUNX_CWD_DIR, WATCH_PROBE_DIR, WATCH_PROBE_FILE };
|
||||
}
|
||||
// Dedupes near-simultaneous restarts (fullRebuild, watchdog).
|
||||
let last_watcher_restart = 0;
|
||||
// The current watcher finished its initial scan; probes written before
|
||||
@@ -62,6 +67,7 @@ function grabPagesSnapshot() {
|
||||
* project tree.
|
||||
*/
|
||||
export function watcherWatchdogTick() {
|
||||
const { WATCH_PROBE_DIR, WATCH_PROBE_FILE } = grabWatchPaths();
|
||||
if (!global.BUNEXT_PAGES_SRC_WATCHER || !watcher_ready)
|
||||
return;
|
||||
if (probe_pending) {
|
||||
@@ -76,7 +82,25 @@ export function watcherWatchdogTick() {
|
||||
catch (error) { }
|
||||
}
|
||||
function isWatchProbePath(p) {
|
||||
return (p === WATCH_PROBE_FILE || p === WATCH_PROBE_DIR || p === BUNX_CWD_DIR);
|
||||
const { WATCH_PROBE_DIR, WATCH_PROBE_FILE } = grabWatchPaths();
|
||||
return (p === WATCH_PROBE_FILE ||
|
||||
p === WATCH_PROBE_DIR ||
|
||||
isDistDirOrAncestor(p));
|
||||
}
|
||||
/**
|
||||
* The dist directory and the folders between it and the project root must
|
||||
* stay watched for the watcher to reach the probe, even when an ignore rule
|
||||
* matches them (dot folders, `dist`, a custom `dist_dir`).
|
||||
*/
|
||||
function isDistDirOrAncestor(p) {
|
||||
const { BUNX_CWD_DIR } = grabDirNames();
|
||||
if (p === ROOT_DIR)
|
||||
return false;
|
||||
return p === BUNX_CWD_DIR || BUNX_CWD_DIR.startsWith(p + path.sep);
|
||||
}
|
||||
function isInDistDir(p) {
|
||||
const { BUNX_CWD_DIR } = grabDirNames();
|
||||
return p === BUNX_CWD_DIR || p.startsWith(BUNX_CWD_DIR + path.sep);
|
||||
}
|
||||
const TARGET_FILES_MATCH = /\.(tsx?|jsx?|css)$/;
|
||||
// Rebuild coalescing: editors emit bursts of events per save (change +
|
||||
@@ -193,14 +217,16 @@ function isPathExcluded(filename, full_file_path) {
|
||||
// Heavy directories are filtered at the watcher level so inotify doesn't
|
||||
// install watches (and deliver events) for trees the bundler never reads.
|
||||
function buildIgnoredPatterns() {
|
||||
const ignored = [
|
||||
(p) => !isWatchProbePath(p) && /(^|[\/\\])\../.test(p),
|
||||
const builtin_patterns = [
|
||||
/(^|[\/\\])\../,
|
||||
/(^|[\/\\])node_modules([\/\\]|$)/,
|
||||
/(^|[\/\\])public([\/\\]|$)/,
|
||||
(p) => !isWatchProbePath(p) && /(^|[\/\\])\.bunext([\/\\]|$)/.test(p),
|
||||
/(^|[\/\\])\.git([\/\\]|$)/,
|
||||
/(^|[\/\\])dist([\/\\]|$)/,
|
||||
/bun\.lockb$/,
|
||||
];
|
||||
const ignored = [
|
||||
(p) => !isWatchProbePath(p) &&
|
||||
(isInDistDir(p) || builtin_patterns.some((re) => re.test(p))),
|
||||
(p) => p.endsWith(AppData["BunextTmpFileExt"]),
|
||||
];
|
||||
const user_patterns = global.BUNEXT_CONFIG?.exclude_watch_patterns;
|
||||
@@ -219,6 +245,7 @@ function buildIgnoredPatterns() {
|
||||
return ignored;
|
||||
}
|
||||
export default async function watcherEsbuildCTX(params) {
|
||||
const { WATCH_PROBE_DIR } = grabWatchPaths();
|
||||
try {
|
||||
mkdirSync(WATCH_PROBE_DIR, { recursive: true });
|
||||
}
|
||||
@@ -247,6 +274,8 @@ export default async function watcherEsbuildCTX(params) {
|
||||
global.BUNEXT_PAGES_SRC_WATCHER = pages_src_watcher;
|
||||
}
|
||||
function handleEvent(event, filePath) {
|
||||
const { WATCH_PROBE_FILE } = grabWatchPaths();
|
||||
const { BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
|
||||
if (isWatchProbePath(filePath)) {
|
||||
if (filePath === WATCH_PROBE_FILE) {
|
||||
probe_pending = false;
|
||||
|
||||
Reference in New Issue
Block a user