import {} from "esbuild"; import { log } from "../../../utils/log"; import grabArtifactsFromBundledResults from "../grab-artifacts-from-bundled-result"; import _ from "lodash"; import enqueueSSRRebuild from "../pages-ssr-context-bundler"; import grabDirNames from "../../../utils/grab-dir-names"; import { logBuildErrorsOnce, resetBuildErrorLog, } from "../../../utils/build-error-log"; import { cpSync, existsSync, mkdirSync, rmSync } from "fs"; import fullRebuild from "../../server/full-rebuild"; import path from "path"; import cleanupLogsDirs from "../../cleanup-logs-dir"; import grabRelativePath from "../../../utils/grab-relative-path"; import cleanupArtifacts from "../../server/cleanup-artifacts"; const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames(); let build_start = 0; export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, build_only, }) { const artifactTracker = { name: "artifact-tracker", setup(build) { build.onStart(async () => { build_start = performance.now(); }); build.onEnd(async (result) => { if (result.errors.length > 0) { global.BUNEXT_BUILD_FAILED = true; global.BUNEXT_RECOMPILING = false; global.BUNEXT_IS_SERVER_COMPONENT = false; // 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 { controller?.controller?.enqueue(`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`); } catch { global.BUNEXT_HMR_CONTROLLERS.splice(i, 1); } } return; } const artifacts = grabArtifactsFromBundledResults({ result, entryToPage, }); // Pages whose entry output hash changed since the previous // build. With `splitting`, a change to any shared chunk also // re-hashes every page entry that imports it, so this set // covers all pages whose client graph actually changed. const changed_pages = []; if (artifacts?.[0] && artifacts.length > 0) { for (let i = 0; i < artifacts.length; i++) { const artifact = artifacts[i]; if (artifact?.local_path && global.BUNEXT_BUNDLER_CTX_MAP) { const relative_path = grabRelativePath(artifact.local_path); const prior = global.BUNEXT_BUNDLER_CTX_MAP[relative_path]; if (!prior?.path || prior.path !== artifact.path || prior.css_path !== artifact.css_path) { changed_pages.push(relative_path); } global.BUNEXT_BUNDLER_CTX_MAP[relative_path] = _.merge(prior, { ...artifact, local_path: relative_path, }); } } } 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_BUNDLER_CTX_DISPOSED = false; const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE); // SSR must finish before HMR so server props are fresh. // The rebuild is single-flighted: concurrent hydration // rebuilds join the in-flight SSR rebuild instead of // starting overlapping ones. if (build_only) { try { await enqueueSSRRebuild(); } catch (error) { log.error(`SSR Bundler Error: ${error}`); } } else if (does_error_file_exist) { mkdirSync(BUNX_ERROR_LOGS_DIR, { recursive: true }); cpSync(BUNX_BUNDLER_ERROR_EXIT_FILE, path.join(BUNX_ERROR_LOGS_DIR, `${Date.now()}.log`)); rmSync(BUNX_BUNDLER_ERROR_EXIT_FILE, { force: true }); cleanupLogsDirs(); await fullRebuild(); } else { try { await enqueueSSRRebuild(); } catch (error) { log.error(`SSR Bundler Error: ${error}`); } if (artifacts?.[0] && artifacts.length > 0) { try { await post_build_fn?.({ artifacts, changed_pages, }); } catch (error) { log.error(`Post-build Error: ${error}`); } } } // Prune orphaned entry outputs from previous rebuilds. await cleanupArtifacts({ paradigm: "hydration" }); global.BUNEXT_RECOMPILING = false; global.BUNEXT_IS_SERVER_COMPONENT = false; }); }, }; return artifactTracker; }