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
@@ -2,6 +2,7 @@ import type { BundlerCTXMap } from "../../types";
type Params = {
post_build_fn?: (params: {
artifacts: BundlerCTXMap[];
changed_pages?: string[];
}) => Promise<void> | void;
build_only?: boolean;
start?: boolean;
+71 -45
View File
@@ -9,9 +9,23 @@ import virtualFilesPlugin from "./plugins/virtual-files-plugin";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
import { logBuildErrorsOnce, } from "../../utils/build-error-log";
import { log } from "../../utils/log";
import grabRootFilePath from "../server/web-pages/grab-root-file-path";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
// Snapshot of the entry key set + root file presence of the last build.
// When it differs from the freshly computed set, the esbuild context must
// be recreated (entry points are fixed at context creation).
let last_entry_keys = undefined;
let last_root_file_path = undefined;
function arraysEqual(a, b) {
if (a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i])
return false;
}
return true;
}
export default async function allPagesESBuildContextBundler(params) {
const _t0 = Date.now();
try {
const pages = grabAllPages({ exclude_api: true });
global.BUNEXT_PAGE_FILES = pages;
@@ -25,53 +39,65 @@ export default async function allPagesESBuildContextBundler(params) {
continue;
}
const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`);
// await Bun.write(entryFile, txt, { createPath: true });
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,
bundle: true,
minify: !dev,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
forceExternalReact(),
tailwindEsbuildPlugin,
virtualFilesPlugin({
entryToPage,
}),
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
build_only: params?.build_only || params?.start,
}),
],
jsx: "automatic",
splitting: true,
treeShaking: true,
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
"react/jsx-dev-runtime",
...(global.BUNEXT_CONFIG.page_compiler_excludes || []),
],
// esbuild's own error logging prints per attempt; the
// artifact-tracker plugin reports errors (deduped, once) itself.
logLevel: "silent",
});
const entry_keys = [...entryToPage.keys()].sort();
const { root_file_path } = grabRootFilePath();
const structure_changed = !global.BUNEXT_BUNDLER_CTX ||
global.BUNEXT_BUNDLER_CTX_DISPOSED ||
!last_entry_keys ||
!arraysEqual(last_entry_keys, entry_keys) ||
last_root_file_path !== root_file_path;
if (structure_changed) {
try {
await global.BUNEXT_BUNDLER_CTX?.dispose();
}
catch (error) { }
global.BUNEXT_BUNDLER_CTX = undefined;
global.BUNEXT_BUNDLER_CTX = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
forceExternalReact(),
tailwindEsbuildPlugin,
virtualFilesPlugin({
entryToPage,
}),
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
build_only: params?.build_only || params?.start,
}),
],
jsx: "automatic",
splitting: true,
treeShaking: true,
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
"react/jsx-dev-runtime",
...(global.BUNEXT_CONFIG.page_compiler_excludes || []),
],
// esbuild's own error logging prints per attempt; the
// artifact-tracker plugin reports errors (deduped, once) itself.
logLevel: "silent",
});
last_entry_keys = entry_keys;
last_root_file_path = root_file_path;
}
await global.BUNEXT_BUNDLER_CTX.rebuild();
}
catch (error) {
-7
View File
@@ -1,7 +0,0 @@
type Params = {
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
};
export default function pagesSSRBundler(params?: Params): Promise<void>;
export {};
-101
View File
@@ -1,101 +0,0 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabPageReactComponentString from "../server/web-pages/grab-page-react-component-string";
import grabRootFilePath from "../server/web-pages/grab-root-file-path";
import ssrVirtualFilesPlugin from "./plugins/ssr-virtual-files-plugin";
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({
include_server: true,
});
const dev = isDevelopment();
const config = global.BUNEXT_CONFIG;
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-pages.json"), JSON.stringify(pages, null, 4));
}
catch (error) { }
const entryToPage = new Map();
const { root_file_path } = grabRootFilePath();
for (const page of pages) {
if (page.local_path.match(/\/pages\/api\//) ||
page.local_path.match(/\.server\.tsx?$/)) {
const ts = await Bun.file(page.local_path).text();
if (ts.match(/(export default)|(export \w+ handler)|(export \w+ server)/)) {
entryToPage.set(page.local_path, { ...page, tsx: ts });
}
continue;
}
const tsx = grabPageReactComponentString({
file_path: page.local_path,
root_file_path,
});
if (!tsx)
continue;
if (!tsx.match(/export default/))
continue;
entryToPage.set(page.local_path, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()].map((e) => `ssr-virtual:${e}`);
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-entry-to-page.json"), JSON.stringify(Object(entryToPage), null, 4));
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-entrypoints.json"), JSON.stringify(entryPoints, null, 4));
}
catch (error) { }
try {
await esbuild.build({
entryPoints,
outdir: BUNX_CWD_MODULE_CACHE_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "esnext",
platform: "node",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
ssrVirtualFilesPlugin({
entryToPage,
}),
ssrCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
external: [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"bun:*",
"bun",
"sqlite-vec",
"better-sqlite3",
...(config.ssr_compiler_excludes || []),
],
splitting: true,
logLevel: "silent",
});
}
catch (error) {
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
global.BUNEXT_BUILD_FAILED = true;
logBuildErrorsOnce([
{
text: `SSR Bundler Error: ${error?.message || String(error)}`,
},
], (line) => log.error(line));
}
}
+16 -5
View File
@@ -1,7 +1,18 @@
type Params = {
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
type RebuildParams = {
/**
* Force a context recreation even when the entry set is unchanged.
* Used by `fullRebuild` after it disposed the contexts.
*/
force?: boolean;
};
export default function pagesSSRContextBundler(params?: Params): Promise<void>;
/**
* Single-flight SSR rebuild. Concurrent callers (each successful hydration
* rebuild triggers this, plus watcher/structural paths) join the in-flight
* rebuild instead of spawning overlapping esbuild builds.
*
* A fresh content change that lands while a rebuild is in flight is picked
* up by the next hydration rebuild cycle (each watcher event re-runs the
* hydration context rebuild, which re-enters this queue).
*/
export default function enqueueSSRRebuild(params?: RebuildParams): Promise<void>;
export {};
+157 -44
View File
@@ -7,20 +7,49 @@ import grabPageReactComponentString from "../server/web-pages/grab-page-react-co
import grabRootFilePath from "../server/web-pages/grab-root-file-path";
import ssrVirtualFilesPlugin from "./plugins/ssr-virtual-files-plugin";
import ssrCTXArtifactTracker from "./plugins/ssr-ctx-artifact-tracker";
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
export default async function pagesSSRContextBundler(params) {
const pages = grabAllPages();
const dev = isDevelopment();
if (global.BUNEXT_SSR_BUNDLER_CTX) {
await global.BUNEXT_SSR_BUNDLER_CTX.dispose();
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
import cleanupArtifacts from "../server/cleanup-artifacts";
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();
// Current virtual entry contents. The SSR virtual plugin resolves its
// onLoad contents lazily from this map on every rebuild, so edits to
// `.server.ts`/api files (whose entries mirror their raw file content) are
// picked up without recreating the esbuild context.
let current_entry_map = new Map();
// Entry key set + root file presence of the last *successful* build. When
// this differs from the freshly computed set, the context must be
// recreated (esbuild cannot add/remove entry points on a live context).
let last_entry_keys = undefined;
let last_root_file_path = undefined;
let rebuild_in_flight;
function arraysEqual(a, b) {
if (a.length !== b.length)
return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i])
return false;
}
const entryToPage = new Map();
return true;
}
async function computeEntryToPage() {
const pages = grabAllPages({
include_server: true,
});
const { root_file_path } = grabRootFilePath();
const entryToPage = new Map();
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-pages.json"), JSON.stringify(pages, null, 4));
}
catch (error) { }
for (const page of pages) {
if (page.local_path.match(/\/pages\/api\//)) {
if (page.local_path.match(/\/pages\/api\//) ||
page.local_path.match(/\.server\.tsx?$/)) {
const ts = await Bun.file(page.local_path).text();
entryToPage.set(page.local_path, { ...page, tsx: ts });
if (ts.match(/(export default)|(export \w+ handler)|(export \w+ server)/)) {
entryToPage.set(page.local_path, { ...page, tsx: ts });
}
continue;
}
const tsx = grabPageReactComponentString({
@@ -29,41 +58,125 @@ export default async function pagesSSRContextBundler(params) {
});
if (!tsx)
continue;
if (!tsx.match(/export default/))
continue;
entryToPage.set(page.local_path, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()].map((e) => `ssr-virtual:${e}`);
global.BUNEXT_SSR_BUNDLER_CTX = await esbuild.context({
entryPoints,
outdir: BUNX_CWD_MODULE_CACHE_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "esnext",
platform: "node",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
ssrVirtualFilesPlugin({
entryToPage,
}),
ssrCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
external: [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"bun:*",
],
// logLevel: "silent",
return { entryToPage, root_file_path };
}
/**
* Single-flight SSR rebuild. Concurrent callers (each successful hydration
* rebuild triggers this, plus watcher/structural paths) join the in-flight
* rebuild instead of spawning overlapping esbuild builds.
*
* A fresh content change that lands while a rebuild is in flight is picked
* up by the next hydration rebuild cycle (each watcher event re-runs the
* hydration context rebuild, which re-enters this queue).
*/
export default async function enqueueSSRRebuild(params) {
if (rebuild_in_flight) {
return rebuild_in_flight;
}
const run = doSSRRebuild(params).finally(() => {
if (rebuild_in_flight === run) {
rebuild_in_flight = undefined;
}
});
await global.BUNEXT_SSR_BUNDLER_CTX.rebuild();
rebuild_in_flight = run;
return run;
}
async function doSSRRebuild(params) {
const force = Boolean(params?.force);
const dev = isDevelopment();
const config = global.BUNEXT_CONFIG;
try {
const { entryToPage, root_file_path } = await computeEntryToPage();
const entry_keys = [...entryToPage.keys()].sort();
if (entry_keys.length === 0) {
current_entry_map = entryToPage;
last_entry_keys = entry_keys;
last_root_file_path = root_file_path;
return;
}
// Refresh the virtual contents BEFORE rebuilding so the plugin's
// onLoad sees the latest `.server.ts`/api contents.
current_entry_map = entryToPage;
const entryPoints = [...entryToPage.keys()].map((e) => `ssr-virtual:${e}`);
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-entry-to-page.json"), JSON.stringify(Object(entryToPage), null, 4));
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-entrypoints.json"), JSON.stringify(entryPoints, null, 4));
}
catch (error) { }
const structure_changed = !global.BUNEXT_SSR_BUNDLER_CTX ||
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED ||
force ||
!last_entry_keys ||
!arraysEqual(last_entry_keys, entry_keys) ||
last_root_file_path !== root_file_path;
if (structure_changed) {
try {
await global.BUNEXT_SSR_BUNDLER_CTX?.dispose();
}
catch (error) { }
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
global.BUNEXT_SSR_BUNDLER_CTX = await esbuild.context({
entryPoints,
outdir: BUNX_CWD_MODULE_CACHE_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "esnext",
platform: "node",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
ssrVirtualFilesPlugin({
getEntryToPage: () => current_entry_map,
}),
ssrCTXArtifactTracker({
entryToPage,
}),
],
jsx: "automatic",
external: [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"bun:*",
"bun",
"sqlite-vec",
"better-sqlite3",
...(config.ssr_compiler_excludes || []),
],
splitting: true,
logLevel: "silent",
});
}
await global.BUNEXT_SSR_BUNDLER_CTX.rebuild();
// Only record the entry set after a successful build so a failed
// build retries with a fresh context next time.
last_entry_keys = entry_keys;
last_root_file_path = root_file_path;
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = false;
global.BUNEXT_BUILD_FAILED = false;
await cleanupArtifacts({ paradigm: "ssr" });
}
catch (error) {
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
global.BUNEXT_BUILD_FAILED = true;
// esbuild errors are reported (deduped) by the ssr artifact tracker's
// onEnd; only re-log unexpected failures.
if (!String(error?.message || "").startsWith("Build failed with")) {
logBuildErrorsOnce([
{
text: `SSR Bundler Error: ${error?.message || String(error)}`,
},
], (line) => log.error(line));
}
}
}
@@ -6,6 +6,7 @@ type Params = {
}>;
post_build_fn?: (params: {
artifacts: any[];
changed_pages?: string[];
}) => Promise<void> | void;
build_only?: boolean;
};
@@ -1,31 +1,24 @@
import {} from "esbuild";
import { log } from "../../../utils/log";
import grabArtifactsFromBundledResults from "../grab-artifacts-from-bundled-result";
import buildOnstartErrorHandler from "../build-on-start-error-handler";
import _ from "lodash";
import pagesSSRBundler from "../pages-ssr-bundler";
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, writeFileSync } from "fs";
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";
const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR, ROOT_DIR } = grabDirNames();
import cleanupArtifacts from "../../server/cleanup-artifacts";
const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames();
let build_start = 0;
const MAX_BUILD_STARTS = 2;
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, build_only, }) {
const artifactTracker = {
name: "artifact-tracker",
setup(build) {
build.onStart(async () => {
global.BUNEXT_MAIN_CTX_BUILD_STARTS++;
build_start = performance.now();
const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
if (global.BUNEXT_MAIN_CTX_BUILD_STARTS >= MAX_BUILD_STARTS &&
!does_error_file_exist) {
await buildOnstartErrorHandler();
}
});
build.onEnd(async (result) => {
if (result.errors.length > 0) {
@@ -50,14 +43,28 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
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(global.BUNEXT_BUNDLER_CTX_MAP[relative_path], { ...artifact, local_path: relative_path });
_.merge(prior, {
...artifact,
local_path: relative_path,
});
}
}
}
@@ -70,13 +77,15 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
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);
// SSR must finish before HMR so server props are fresh
// 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 pagesSSRBundler();
await enqueueSSRRebuild();
}
catch (error) {
log.error(`SSR Bundler Error: ${error}`);
@@ -91,20 +100,25 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
}
else {
try {
await pagesSSRBundler();
await enqueueSSRRebuild();
}
catch (error) {
log.error(`SSR Bundler Error: ${error}`);
}
if (artifacts?.[0] && artifacts.length > 0) {
try {
await post_build_fn?.({ artifacts });
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;
});
@@ -4,9 +4,6 @@ type Params = {
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
};
export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }: Params): Plugin;
export default function ssrCTXArtifactTracker({ entryToPage }: Params): Plugin;
export {};
+3 -20
View File
@@ -7,32 +7,18 @@ 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;
const { BUNX_TMP_DIR, ROOT_DIR } = grabDirNames();
export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
export default function ssrCTXArtifactTracker({ entryToPage }) {
const artifactTracker = {
name: "ssr-artifact-tracker",
setup(build) {
build.onStart(async () => {
build_starts++;
build_start = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
await global.BUNEXT_SSR_BUNDLER_CTX?.dispose();
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
}
});
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();
}
catch { }
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
build_starts = 0;
logBuildErrorsOnce(result.errors, (line) => log.error(`SSR Build error: ${line}`));
return;
}
@@ -53,16 +39,13 @@ export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
};
}
}
// post_build_fn?.({ artifacts });
// const elapsed = (performance.now() - build_start).toFixed(
// 0,
// );
// log.success(`SSR [Built] in ${elapsed}ms`);
}
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ctx-map.json"), JSON.stringify(global.BUNEXT_SSR_BUNDLER_CTX_MAP, null, 4));
}
catch (error) { }
const elapsed = (performance.now() - build_start).toFixed(0);
log.success(`SSR [Built] in ${elapsed}ms`);
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = false;
global.BUNEXT_BUILD_FAILED = false;
resetBuildErrorLog();
@@ -1,9 +1,16 @@
import type { Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
type Params = {
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
type SSRVirtualEntry = PageFiles & {
tsx: string;
};
export default function ssrVirtualFilesPlugin({ entryToPage }: Params): Plugin;
type Params = {
/**
* Lazily-resolved current virtual entry contents. The map is refreshed
* before every rebuild so edits to `.server.ts`/api files (whose virtual
* entries mirror their raw file content) are picked up by a plain
* `ctx.rebuild()` without recreating the esbuild context.
*/
getEntryToPage: () => Map<string, SSRVirtualEntry>;
};
export default function ssrVirtualFilesPlugin({ getEntryToPage }: Params): Plugin;
export {};
+2 -3
View File
@@ -1,6 +1,5 @@
import path from "path";
import { log } from "../../../utils/log";
export default function ssrVirtualFilesPlugin({ entryToPage }) {
export default function ssrVirtualFilesPlugin({ getEntryToPage }) {
const virtualPlugin = {
name: "ssr-virtual-hydration",
setup(build) {
@@ -12,7 +11,7 @@ export default function ssrVirtualFilesPlugin({ entryToPage }) {
};
});
build.onLoad({ filter: /.*/, namespace: "ssr-virtual" }, (args) => {
const target = entryToPage.get(args.path);
const target = getEntryToPage().get(args.path);
if (!target?.tsx)
return null;
const contents = target.tsx;