Error handling bugfix: pass #1

This commit is contained in:
2026-10-02 07:04:22 +01:00
parent 39a5e91eeb
commit d8ea4f8e02
53 changed files with 1179 additions and 410 deletions
+23 -7
View File
@@ -7,11 +7,12 @@ import grabClientHydrationScript from "./grab-client-hydration-script";
import path from "path";
import virtualFilesPlugin from "./plugins/virtual-files-plugin";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
import { existsSync } from "fs";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE, } = grabDirNames();
import { logBuildErrorsOnce, } from "../../utils/build-error-log";
import { log } from "../../utils/log";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
export default async function allPagesESBuildContextBundler(params) {
const _t0 = Date.now();
try {
const did_process_exit_because_of_bundler_error = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
const pages = grabAllPages({ exclude_api: true });
global.BUNEXT_PAGE_FILES = pages;
const dev = isDevelopment();
@@ -28,6 +29,9 @@ export default async function allPagesESBuildContextBundler(params) {
entryToPage.set(entryFile, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()].map((e) => `hydration-virtual:${e}`);
// This is a brand-new context: wipe the previous context's failure
// history so the "2 failed starts => dispose" guard starts fresh.
global.BUNEXT_MAIN_CTX_BUILD_STARTS = 0;
global.BUNEXT_BUNDLER_CTX = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
@@ -64,14 +68,26 @@ export default async function allPagesESBuildContextBundler(params) {
"react/jsx-dev-runtime",
...(global.BUNEXT_CONFIG.page_compiler_excludes || []),
],
logLevel: did_process_exit_because_of_bundler_error
? "silent"
: undefined,
// esbuild's own error logging prints per attempt; the
// artifact-tracker plugin reports errors (deduped, once) itself.
logLevel: "silent",
});
await global.BUNEXT_BUNDLER_CTX.rebuild();
}
catch (error) {
console.log(`ESBUILD Error =>`, error);
global.BUNEXT_BUILD_FAILED = true;
// When the build fails with esbuild errors, the artifact-tracker's
// onEnd already reported them (deduped). "Build failed with N errors"
// is that same failure rethrown by esbuild's API — re-logging it here
// would double every incident.
if (String(error?.message || "").startsWith("Build failed with")) {
return;
}
logBuildErrorsOnce([
{
text: `ESBUILD Error => ${error?.message || String(error)}`,
},
], (line) => log.error(line));
}
}
function forceExternalReact() {
+9 -10
View File
@@ -1,18 +1,17 @@
export default async function buildOnstartErrorHandler(params) {
// const error_msg = `Build Failed. Please check all your components and imports.`;
// log.error(error_msg);
if (global.BUNEXT_BUNDLER_CTX_DISPOSED) {
return;
}
// console.log(`Killing Bundler ...`);
// console.log(`global.BUNEXT_BUNDLER_CTX_DISPOSED`, global.BUNEXT_BUNDLER_CTX_DISPOSED);
global.BUNEXT_BUNDLER_CTX_DISPOSED = true;
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
await Promise.all([
global.BUNEXT_SSR_BUNDLER_CTX?.dispose(),
global.BUNEXT_BUNDLER_CTX?.dispose(),
]);
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
global.BUNEXT_BUNDLER_CTX = undefined;
// NOTE: must NOT dispose the currently-building context here. This
// handler runs from the artifact-tracker plugin's onStart, i.e. while the
// context's own build is in flight. esbuild's `dispose()` waits for the
// in-flight build to finish, so disposing self = deadlock, which left the
// dev server permanently stuck after a failed rebuild.
//
// Marking the context as disposed is enough: the next rebuild attempt
// (watcher / fullRebuild) sees BUNEXT_BUNDLER_CTX_DISPOSED and recreates a
// fresh context, whose cleanup disposes the old (now idle) one.
}
+8 -1
View File
@@ -10,6 +10,7 @@ import ssrCTXArtifactTracker from "./plugins/ssr-ctx-artifact-tracker";
import { writeFileSync } from "fs";
import path from "path";
import { log } from "../../utils/log";
import { logBuildErrorsOnce, } from "../../utils/build-error-log";
const { BUNX_CWD_MODULE_CACHE_DIR, BUNX_TMP_DIR } = grabDirNames();
export default async function pagesSSRBundler(params) {
const pages = grabAllPages({
@@ -85,10 +86,16 @@ export default async function pagesSSRBundler(params) {
...(config.ssr_compiler_excludes || []),
],
splitting: true,
logLevel: "silent",
});
}
catch (error) {
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
log.error(`SSR Bundler Error: ${error}`);
global.BUNEXT_BUILD_FAILED = true;
logBuildErrorsOnce([
{
text: `SSR Bundler Error: ${error?.message || String(error)}`,
},
], (line) => log.error(line));
}
}
@@ -5,6 +5,7 @@ import buildOnstartErrorHandler from "../build-on-start-error-handler";
import _ from "lodash";
import pagesSSRBundler from "../pages-ssr-bundler";
import grabDirNames from "../../../utils/grab-dir-names";
import { logBuildErrorsOnce, resetBuildErrorLog, } from "../../../utils/build-error-log";
import { cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "fs";
import fullRebuild from "../../server/full-rebuild";
import path from "path";
@@ -28,12 +29,12 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
});
build.onEnd(async (result) => {
if (result.errors.length > 0) {
global.BUNEXT_BUILD_FAILED = true;
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
log.error(`Build errors:`);
for (const err of result.errors) {
log.error(` ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`);
}
// Rebuild attempts repeat for every file edit / request
// while the error persists; only log the error once.
logBuildErrorsOnce(result.errors, (line) => log.error(` ${line}`));
for (let i = global.BUNEXT_HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.BUNEXT_HMR_CONTROLLERS[i];
try {
@@ -62,6 +63,13 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
}
const elapsed = (performance.now() - build_start).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
// A successful build clears the "last error" so a NEW incident
// with the same signature is reported again.
resetBuildErrorLog();
if (result.warnings.length > 0) {
logBuildErrorsOnce(result.warnings, (line) => log.warn(line));
}
global.BUNEXT_BUILD_FAILED = false;
global.BUNEXT_MAIN_CTX_BUILD_STARTS = 0;
global.BUNEXT_BUNDLER_CTX_DISPOSED = false;
const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
+6 -3
View File
@@ -4,6 +4,8 @@ import { writeFileSync } from "fs";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
import grabRelativePath from "../../../utils/grab-relative-path";
import { logBuildErrorsOnce, resetBuildErrorLog, } from "../../../utils/build-error-log";
import { log } from "../../../utils/log";
let build_start = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 2;
@@ -23,6 +25,7 @@ export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
});
build.onEnd(async (result) => {
if (result.errors.length > 0) {
global.BUNEXT_BUILD_FAILED = true;
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
try {
await global.BUNEXT_SSR_BUNDLER_CTX?.dispose();
@@ -30,9 +33,7 @@ export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
catch { }
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
build_starts = 0;
for (const err of result.errors) {
console.error(`SSR Build error: ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`);
}
logBuildErrorsOnce(result.errors, (line) => log.error(`SSR Build error: ${line}`));
return;
}
const artifacts = grabArtifactsFromBundledResults({
@@ -63,6 +64,8 @@ export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
}
catch (error) { }
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = false;
global.BUNEXT_BUILD_FAILED = false;
resetBuildErrorLog();
});
},
};