Switch watcher to chokidar

This commit is contained in:
Benjamin Toby 2026-04-20 16:12:24 +01:00
parent c06cb73181
commit e2b8b95a4b
20 changed files with 344 additions and 65 deletions

View File

@ -12,6 +12,7 @@
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.2",
"bun-plugin-tailwind": "^0.1.2", "bun-plugin-tailwind": "^0.1.2",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"chokidar": "^5.0.0",
"commander": "^14.0.2", "commander": "^14.0.2",
"esbuild": "^0.27.4", "esbuild": "^0.27.4",
"lightningcss-wasm": "^1.32.0", "lightningcss-wasm": "^1.32.0",
@ -26,6 +27,7 @@
}, },
"devDependencies": { "devDependencies": {
"@testing-library/dom": "^10.4.1", "@testing-library/dom": "^10.4.1",
"@types/chokidar": "^2.1.7",
"@types/lodash": "^4.17.24", "@types/lodash": "^4.17.24",
"@types/micromatch": "^4.0.10", "@types/micromatch": "^4.0.10",
"happy-dom": "^20.8.4", "happy-dom": "^20.8.4",
@ -165,6 +167,8 @@
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="], "@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
"@types/chokidar": ["@types/chokidar@2.1.7", "", { "dependencies": { "chokidar": "*" } }, "sha512-A7/MFHf6KF7peCzjEC1BBTF8jpmZTokb3vr/A0NxRGfwRLK3Ws+Hq6ugVn6cJIMfM6wkCak/aplWrxbTcu8oig=="],
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="], "@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
"@types/micromatch": ["@types/micromatch@4.0.10", "", { "dependencies": { "@types/braces": "*" } }, "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ=="], "@types/micromatch": ["@types/micromatch@4.0.10", "", { "dependencies": { "@types/braces": "*" } }, "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ=="],
@ -195,6 +199,8 @@
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], "chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
"cli-spinners": ["cli-spinners@3.3.0", "", {}, "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ=="], "cli-spinners": ["cli-spinners@3.3.0", "", {}, "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ=="],
@ -291,6 +297,8 @@
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], "react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],

View File

@ -1,6 +1,11 @@
export default async function buildOnstartErrorHandler(params) { export default async function buildOnstartErrorHandler(params) {
// const error_msg = `Build Failed. Please check all your components and imports.`; // const error_msg = `Build Failed. Please check all your components and imports.`;
// log.error(error_msg); // log.error(error_msg);
if (global.BUNDLER_CTX_DISPOSED) {
return;
}
console.log(`Killing Bundler ...`);
console.log(`global.BUNDLER_CTX_DISPOSED`, global.BUNDLER_CTX_DISPOSED);
global.BUNDLER_CTX_DISPOSED = true; global.BUNDLER_CTX_DISPOSED = true;
global.RECOMPILING = false; global.RECOMPILING = false;
global.IS_SERVER_COMPONENT = false; global.IS_SERVER_COMPONENT = false;

View File

@ -11,17 +11,16 @@ import path from "path";
import cleanupLogsDirs from "../../cleanup-logs-dir"; import cleanupLogsDirs from "../../cleanup-logs-dir";
const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames(); const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames();
let build_start = 0; let build_start = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 2; const MAX_BUILD_STARTS = 2;
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, }) { export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, }) {
const artifactTracker = { const artifactTracker = {
name: "artifact-tracker", name: "artifact-tracker",
setup(build) { setup(build) {
build.onStart(async () => { build.onStart(async () => {
build_starts++; global.MAIN_CTX_BUILD_STARTS++;
build_start = performance.now(); build_start = performance.now();
const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE); const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
if (build_starts >= MAX_BUILD_STARTS && if (global.MAIN_CTX_BUILD_STARTS >= MAX_BUILD_STARTS &&
!does_error_file_exist) { !does_error_file_exist) {
await buildOnstartErrorHandler(); await buildOnstartErrorHandler();
} }
@ -30,7 +29,6 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
if (result.errors.length > 0) { if (result.errors.length > 0) {
global.RECOMPILING = false; global.RECOMPILING = false;
global.IS_SERVER_COMPONENT = false; global.IS_SERVER_COMPONENT = false;
build_starts = 0;
log.error(`Build errors:`); log.error(`Build errors:`);
for (const err of result.errors) { for (const err of result.errors) {
log.error(` ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`); log.error(` ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`);
@ -64,7 +62,8 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
log.success(`[Built] in ${elapsed}ms`); log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false; global.RECOMPILING = false;
global.IS_SERVER_COMPONENT = false; global.IS_SERVER_COMPONENT = false;
build_starts = 0; global.MAIN_CTX_BUILD_STARTS = 0;
global.BUNDLER_CTX_DISPOSED = false;
const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE); const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
if (does_error_file_exist) { if (does_error_file_exist) {
mkdirSync(BUNX_ERROR_LOGS_DIR, { recursive: true }); mkdirSync(BUNX_ERROR_LOGS_DIR, { recursive: true });

View File

@ -20,9 +20,10 @@ export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
global.SSR_BUNDLER_CTX = undefined; global.SSR_BUNDLER_CTX = undefined;
} }
}); });
build.onEnd((result) => { build.onEnd(async (result) => {
if (result.errors.length > 0) { if (result.errors.length > 0) {
global.SSR_BUNDLER_CTX_DISPOSED = false; global.SSR_BUNDLER_CTX_DISPOSED = true;
await global.SSR_BUNDLER_CTX?.dispose();
build_starts = 0; build_starts = 0;
console.log("SSR Build errors:", result.errors); console.log("SSR Build errors:", result.errors);
return; return;

View File

@ -1,5 +1,4 @@
import path from "path"; import path from "path";
import { log } from "../../../utils/log";
export default function virtualFilesPlugin({ entryToPage }) { export default function virtualFilesPlugin({ entryToPage }) {
const virtualPlugin = { const virtualPlugin = {
name: "virtual-hydration", name: "virtual-hydration",

View File

@ -1,9 +1,9 @@
import type { BundlerCTXMap, BunextConfig, GlobalHMRControllerObject, PageFiles } from "../types"; import type { BundlerCTXMap, BunextConfig, GlobalHMRControllerObject, PageFiles } from "../types";
import type { FileSystemRouter, Server } from "bun"; import type { FileSystemRouter, Server } from "bun";
import { type DirNames } from "../utils/grab-dir-names"; import { type DirNames } from "../utils/grab-dir-names";
import { type FSWatcher } from "fs";
import type { BuildContext } from "esbuild"; import type { BuildContext } from "esbuild";
import grabConstants from "../utils/grab-constants"; import grabConstants from "../utils/grab-constants";
import type { FSWatcher } from "chokidar";
/** /**
* # Declare Global Variables * # Declare Global Variables
*/ */
@ -45,5 +45,6 @@ declare global {
var REBUILD_RETRIES: number; var REBUILD_RETRIES: number;
var IS_404_PAGE: boolean; var IS_404_PAGE: boolean;
var CONSTANTS: ReturnType<typeof grabConstants>; var CONSTANTS: ReturnType<typeof grabConstants>;
var MAIN_CTX_BUILD_STARTS: number;
} }
export default function bunextInit(): Promise<void>; export default function bunextInit(): Promise<void>;

View File

@ -1,14 +1,13 @@
import grabDirNames, {} from "../utils/grab-dir-names"; import grabDirNames, {} from "../utils/grab-dir-names";
import {} from "fs";
import init from "./init"; import init from "./init";
import isDevelopment from "../utils/is-development"; import isDevelopment from "../utils/is-development";
import { log } from "../utils/log"; import { log } from "../utils/log";
import cron from "./server/cron"; import cron from "./server/cron";
import watcherEsbuildCTX from "./server/watcher-esbuild-ctx";
import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler"; import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server/server-post-build-fn"; import serverPostBuildFn from "./server/server-post-build-fn";
import reactModulesBundler from "./bundler/react-modules-bundler"; import reactModulesBundler from "./bundler/react-modules-bundler";
import grabConstants from "../utils/grab-constants"; import grabConstants from "../utils/grab-constants";
import chokadirWatcherEsbuildCTX from "./server/chokidar-watcher-esbuild-ctx";
const dirNames = grabDirNames(); const dirNames = grabDirNames();
const { PAGES_DIR } = dirNames; const { PAGES_DIR } = dirNames;
export default async function bunextInit() { export default async function bunextInit() {
@ -23,6 +22,7 @@ export default async function bunextInit() {
global.DIR_NAMES = dirNames; global.DIR_NAMES = dirNames;
global.REACT_IMPORTS_MAP = { imports: {} }; global.REACT_IMPORTS_MAP = { imports: {} };
global.REACT_DOM_MODULE_CACHE = new Map(); global.REACT_DOM_MODULE_CACHE = new Map();
global.MAIN_CTX_BUILD_STARTS = 0;
await init(); await init();
log.banner(); log.banner();
global.CONSTANTS = grabConstants(); global.CONSTANTS = grabConstants();
@ -40,7 +40,7 @@ export default async function bunextInit() {
serverPostBuildFn(); serverPostBuildFn();
}, },
}); });
watcherEsbuildCTX(); chokadirWatcherEsbuildCTX();
} }
else { else {
log.build(`Building Modules ...`); log.build(`Building Modules ...`);

View File

@ -0,0 +1 @@
export default function chokadirWatcherEsbuildCTX(): Promise<void>;

View File

@ -0,0 +1,115 @@
import chokidar from "chokidar";
import path from "path";
import { existsSync, statSync } from "fs";
import grabDirNames from "../../utils/grab-dir-names";
import fullRebuild from "./full-rebuild";
import { AppData } from "../../data/app-data";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
import pagesSSRBundler from "../bundler/pages-ssr-bundler";
const { ROOT_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
export default async function chokadirWatcherEsbuildCTX() {
// Define ignored patterns directly in Chokidar for better performance
const watcher = chokidar.watch(ROOT_DIR, {
ignored: [
/(^|[\/\\])\../, // ignore dotfiles
/node_modules/,
/public/,
/\.bunext/,
/\.git/,
/dist/,
/bun\.lockb/,
(path) => path.endsWith(AppData["BunextTmpFileExt"]),
],
persistent: true,
ignoreInitial: true,
depth: 99,
});
const handleEvent = async (event, filePath) => {
const filename = path.relative(ROOT_DIR, filePath);
if (existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE)) {
await fullRebuild();
return;
}
if (global.BUNDLER_CTX_DISPOSED) {
await fullRebuild({ msg: `Restarting Bundler ...` });
}
if (global.SSR_BUNDLER_CTX_DISPOSED) {
pagesSSRBundler();
}
if (filename.match(/\/styles$/) || filename === "styles") {
global.RECOMPILING = true;
await Bun.sleep(1000);
await fullRebuild({
msg: `Detected new \`styles\` directory. Rebuilding ...`,
});
return;
}
if (filename.match(/bunext.config\.ts/)) {
await fullRebuild({
msg: `bunext.config.ts file changed. Rebuilding server ...`,
});
return;
}
const target_files_match = /\.(tsx?|jsx?|css)$/;
if (event === "change") {
if (filename.match(target_files_match)) {
if (global.RECOMPILING)
return;
global.RECOMPILING = true;
if (filename.match(/.*\.server\.tsx?/)) {
global.IS_SERVER_COMPONENT = true;
}
if (global.BUNDLER_CTX) {
await global.BUNDLER_CTX.rebuild();
}
// HMR for error pages
if (filename.match(/(404|500)\.tsx?/)) {
global.HMR_CONTROLLERS.forEach((controller) => {
controller?.controller?.enqueue(`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`);
});
}
}
return;
}
// Handle Structural Changes (Add/Delete)
if (["add", "unlink", "addDir", "unlinkDir"].includes(event)) {
const is_file_of_interest = !!filename.match(target_files_match) || event.includes("Dir");
if (!is_file_of_interest)
return;
// Validation logic
if (!filename.match(/^src\/pages\/|\.css$/) ||
checkExcludedPatterns({ path: filename }) ||
filename.includes(" ")) {
// With chokidar, you rarely need to "reload" the whole watcher.
// But we keep the logic for consistency.
return reloadWatcher();
}
if (global.RECOMPILING)
return;
const action = event.startsWith("add") ? "created" : "deleted";
const type = filename.match(/\.css$/)
? "Stylesheet"
: event.includes("Dir")
? "Directory"
: filename.match(/\/pages\/api\//)
? "API Route"
: "Page";
await fullRebuild({
msg: `${type} ${action}: ${filename}. Rebuilding ...`,
});
}
};
watcher
.on("add", (path) => handleEvent("add", path))
.on("change", (path) => handleEvent("change", path))
.on("unlink", (path) => handleEvent("unlink", path))
.on("addDir", (path) => handleEvent("addDir", path))
.on("unlinkDir", (path) => handleEvent("unlinkDir", path));
global.PAGES_SRC_WATCHER = watcher;
}
function reloadWatcher() {
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
chokadirWatcherEsbuildCTX();
}
}

View File

@ -1,8 +1,7 @@
import { log } from "../../utils/log"; import { log } from "../../utils/log";
import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler"; import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler";
import pagesSSRBundler from "../bundler/pages-ssr-bundler"; import chokadirWatcherEsbuildCTX from "./chokidar-watcher-esbuild-ctx";
import serverPostBuildFn from "./server-post-build-fn"; import serverPostBuildFn from "./server-post-build-fn";
import watcherEsbuildCTX from "./watcher-esbuild-ctx";
export default async function fullRebuild(params) { export default async function fullRebuild(params) {
try { try {
const { msg } = params || {}; const { msg } = params || {};
@ -11,11 +10,14 @@ export default async function fullRebuild(params) {
log.watch(msg); log.watch(msg);
} }
global.ROUTER.reload(); global.ROUTER.reload();
await global.BUNDLER_CTX?.dispose(); try {
global.BUNDLER_CTX = undefined; await global.BUNDLER_CTX?.dispose();
await global.SSR_BUNDLER_CTX?.dispose(); global.BUNDLER_CTX = undefined;
global.SSR_BUNDLER_CTX = undefined; await global.SSR_BUNDLER_CTX?.dispose();
await pagesSSRBundler(); global.SSR_BUNDLER_CTX = undefined;
}
catch (error) { }
// await pagesSSRBundler();
allPagesESBuildContextBundler({ allPagesESBuildContextBundler({
post_build_fn: () => { post_build_fn: () => {
serverPostBuildFn(); serverPostBuildFn();
@ -29,6 +31,6 @@ export default async function fullRebuild(params) {
} }
if (global.PAGES_SRC_WATCHER) { if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close(); global.PAGES_SRC_WATCHER.close();
watcherEsbuildCTX(); chokadirWatcherEsbuildCTX();
} }
} }

View File

@ -22,7 +22,6 @@ export default async function watcherEsbuildCTX() {
} }
if (global.BUNDLER_CTX_DISPOSED) { if (global.BUNDLER_CTX_DISPOSED) {
await fullRebuild({ msg: `Restarting Bundler ...` }); await fullRebuild({ msg: `Restarting Bundler ...` });
global.BUNDLER_CTX_DISPOSED = false;
} }
if (global.SSR_BUNDLER_CTX_DISPOSED) { if (global.SSR_BUNDLER_CTX_DISPOSED) {
pagesSSRBundler(); pagesSSRBundler();
@ -63,12 +62,7 @@ export default async function watcherEsbuildCTX() {
global.IS_SERVER_COMPONENT = true; global.IS_SERVER_COMPONENT = true;
} }
if (global.BUNDLER_CTX) { if (global.BUNDLER_CTX) {
try { await global.BUNDLER_CTX.rebuild();
await global.BUNDLER_CTX.rebuild();
}
catch (error) {
console.log(`ESBUILD Rebuild Error =>`, error);
}
} }
if (filename.match(/(404|500)\.tsx?/)) { if (filename.match(/(404|500)\.tsx?/)) {
for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) { for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) {
@ -104,11 +98,11 @@ export default async function watcherEsbuildCTX() {
msg: `${type} ${action}: ${filename}. Rebuilding ...`, msg: `${type} ${action}: ${filename}. Rebuilding ...`,
}); });
}); });
global.PAGES_SRC_WATCHER = pages_src_watcher; // global.PAGES_SRC_WATCHER = pages_src_watcher;
} }
function reloadWatcher() { function reloadWatcher() {
if (global.PAGES_SRC_WATCHER) { // if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close(); // global.PAGES_SRC_WATCHER.close();
watcherEsbuildCTX(); // watcherEsbuildCTX();
} // }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@moduletrace/bunext", "name": "@moduletrace/bunext",
"version": "1.0.91", "version": "1.0.92",
"main": "dist/index.js", "main": "dist/index.js",
"module": "index.ts", "module": "index.ts",
"dependencies": { "dependencies": {
@ -11,6 +11,7 @@
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.2",
"bun-plugin-tailwind": "^0.1.2", "bun-plugin-tailwind": "^0.1.2",
"chalk": "^5.6.2", "chalk": "^5.6.2",
"chokidar": "^5.0.0",
"commander": "^14.0.2", "commander": "^14.0.2",
"esbuild": "^0.27.4", "esbuild": "^0.27.4",
"lightningcss-wasm": "^1.32.0", "lightningcss-wasm": "^1.32.0",
@ -25,6 +26,7 @@
}, },
"devDependencies": { "devDependencies": {
"@testing-library/dom": "^10.4.1", "@testing-library/dom": "^10.4.1",
"@types/chokidar": "^2.1.7",
"@types/lodash": "^4.17.24", "@types/lodash": "^4.17.24",
"@types/micromatch": "^4.0.10", "@types/micromatch": "^4.0.10",
"happy-dom": "^20.8.4" "happy-dom": "^20.8.4"

View File

@ -4,6 +4,13 @@ export default async function buildOnstartErrorHandler(params?: Params) {
// const error_msg = `Build Failed. Please check all your components and imports.`; // const error_msg = `Build Failed. Please check all your components and imports.`;
// log.error(error_msg); // log.error(error_msg);
if (global.BUNDLER_CTX_DISPOSED) {
return;
}
console.log(`Killing Bundler ...`);
console.log(`global.BUNDLER_CTX_DISPOSED`, global.BUNDLER_CTX_DISPOSED);
global.BUNDLER_CTX_DISPOSED = true; global.BUNDLER_CTX_DISPOSED = true;
global.RECOMPILING = false; global.RECOMPILING = false;

View File

@ -14,7 +14,6 @@ import cleanupLogsDirs from "../../cleanup-logs-dir";
const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames(); const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames();
let build_start = 0; let build_start = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 2; const MAX_BUILD_STARTS = 2;
type Params = { type Params = {
@ -35,7 +34,7 @@ export default function esbuildCTXArtifactTracker({
name: "artifact-tracker", name: "artifact-tracker",
setup(build) { setup(build) {
build.onStart(async () => { build.onStart(async () => {
build_starts++; global.MAIN_CTX_BUILD_STARTS++;
build_start = performance.now(); build_start = performance.now();
const does_error_file_exist = existsSync( const does_error_file_exist = existsSync(
@ -43,7 +42,7 @@ export default function esbuildCTXArtifactTracker({
); );
if ( if (
build_starts >= MAX_BUILD_STARTS && global.MAIN_CTX_BUILD_STARTS >= MAX_BUILD_STARTS &&
!does_error_file_exist !does_error_file_exist
) { ) {
await buildOnstartErrorHandler(); await buildOnstartErrorHandler();
@ -54,14 +53,19 @@ export default function esbuildCTXArtifactTracker({
if (result.errors.length > 0) { if (result.errors.length > 0) {
global.RECOMPILING = false; global.RECOMPILING = false;
global.IS_SERVER_COMPONENT = false; global.IS_SERVER_COMPONENT = false;
build_starts = 0;
log.error(`Build errors:`); log.error(`Build errors:`);
for (const err of result.errors) { for (const err of result.errors) {
log.error(` ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`); log.error(
` ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`,
);
} }
for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) { for (
let i = global.HMR_CONTROLLERS.length - 1;
i >= 0;
i--
) {
const controller = global.HMR_CONTROLLERS[i]; const controller = global.HMR_CONTROLLERS[i];
try { try {
controller?.controller?.enqueue( controller?.controller?.enqueue(
@ -101,7 +105,8 @@ export default function esbuildCTXArtifactTracker({
global.RECOMPILING = false; global.RECOMPILING = false;
global.IS_SERVER_COMPONENT = false; global.IS_SERVER_COMPONENT = false;
build_starts = 0; global.MAIN_CTX_BUILD_STARTS = 0;
global.BUNDLER_CTX_DISPOSED = false;
const does_error_file_exist = existsSync( const does_error_file_exist = existsSync(
BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_BUNDLER_ERROR_EXIT_FILE,

View File

@ -38,9 +38,10 @@ export default function ssrCTXArtifactTracker({
} }
}); });
build.onEnd((result) => { build.onEnd(async (result) => {
if (result.errors.length > 0) { if (result.errors.length > 0) {
global.SSR_BUNDLER_CTX_DISPOSED = false; global.SSR_BUNDLER_CTX_DISPOSED = true;
await global.SSR_BUNDLER_CTX?.dispose();
build_starts = 0; build_starts = 0;
console.log("SSR Build errors:", result.errors); console.log("SSR Build errors:", result.errors);
return; return;

View File

@ -1,7 +1,6 @@
import type { Plugin } from "esbuild"; import type { Plugin } from "esbuild";
import path from "path"; import path from "path";
import type { PageFiles } from "../../../types"; import type { PageFiles } from "../../../types";
import { log } from "../../../utils/log";
type Params = { type Params = {
entryToPage: Map< entryToPage: Map<

View File

@ -6,17 +6,17 @@ import type {
} from "../types"; } from "../types";
import type { FileSystemRouter, Server } from "bun"; import type { FileSystemRouter, Server } from "bun";
import grabDirNames, { type DirNames } from "../utils/grab-dir-names"; import grabDirNames, { type DirNames } from "../utils/grab-dir-names";
import { type FSWatcher } from "fs";
import init from "./init"; import init from "./init";
import isDevelopment from "../utils/is-development"; import isDevelopment from "../utils/is-development";
import { log } from "../utils/log"; import { log } from "../utils/log";
import cron from "./server/cron"; import cron from "./server/cron";
import type { BuildContext } from "esbuild"; import type { BuildContext } from "esbuild";
import watcherEsbuildCTX from "./server/watcher-esbuild-ctx";
import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler"; import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server/server-post-build-fn"; import serverPostBuildFn from "./server/server-post-build-fn";
import reactModulesBundler from "./bundler/react-modules-bundler"; import reactModulesBundler from "./bundler/react-modules-bundler";
import grabConstants from "../utils/grab-constants"; import grabConstants from "../utils/grab-constants";
import type { FSWatcher } from "chokidar";
import chokadirWatcherEsbuildCTX from "./server/chokidar-watcher-esbuild-ctx";
/** /**
* # Declare Global Variables * # Declare Global Variables
@ -52,6 +52,7 @@ declare global {
var REBUILD_RETRIES: number; var REBUILD_RETRIES: number;
var IS_404_PAGE: boolean; var IS_404_PAGE: boolean;
var CONSTANTS: ReturnType<typeof grabConstants>; var CONSTANTS: ReturnType<typeof grabConstants>;
var MAIN_CTX_BUILD_STARTS: number;
} }
const dirNames = grabDirNames(); const dirNames = grabDirNames();
@ -69,6 +70,7 @@ export default async function bunextInit() {
global.DIR_NAMES = dirNames; global.DIR_NAMES = dirNames;
global.REACT_IMPORTS_MAP = { imports: {} }; global.REACT_IMPORTS_MAP = { imports: {} };
global.REACT_DOM_MODULE_CACHE = new Map<string, any>(); global.REACT_DOM_MODULE_CACHE = new Map<string, any>();
global.MAIN_CTX_BUILD_STARTS = 0;
await init(); await init();
log.banner(); log.banner();
@ -93,7 +95,7 @@ export default async function bunextInit() {
serverPostBuildFn(); serverPostBuildFn();
}, },
}); });
watcherEsbuildCTX(); chokadirWatcherEsbuildCTX();
} else { } else {
log.build(`Building Modules ...`); log.build(`Building Modules ...`);
await allPagesESBuildContextBundler(); await allPagesESBuildContextBundler();

View File

@ -0,0 +1,142 @@
import chokidar from "chokidar";
import path from "path";
import { existsSync, statSync } from "fs";
import grabDirNames from "../../utils/grab-dir-names";
import fullRebuild from "./full-rebuild";
import { AppData } from "../../data/app-data";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
import pagesSSRBundler from "../bundler/pages-ssr-bundler";
const { ROOT_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
export default async function chokadirWatcherEsbuildCTX() {
// Define ignored patterns directly in Chokidar for better performance
const watcher = chokidar.watch(ROOT_DIR, {
ignored: [
/(^|[\/\\])\../, // ignore dotfiles
/node_modules/,
/public/,
/\.bunext/,
/\.git/,
/dist/,
/bun\.lockb/,
(path: string) => path.endsWith(AppData["BunextTmpFileExt"]),
],
persistent: true,
ignoreInitial: true,
depth: 99,
});
const handleEvent = async (
event: "add" | "change" | "unlink" | "addDir" | "unlinkDir",
filePath: string,
) => {
const filename = path.relative(ROOT_DIR, filePath);
if (existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE)) {
await fullRebuild();
return;
}
if (global.BUNDLER_CTX_DISPOSED) {
await fullRebuild({ msg: `Restarting Bundler ...` });
}
if (global.SSR_BUNDLER_CTX_DISPOSED) {
pagesSSRBundler();
}
if (filename.match(/\/styles$/) || filename === "styles") {
global.RECOMPILING = true;
await Bun.sleep(1000);
await fullRebuild({
msg: `Detected new \`styles\` directory. Rebuilding ...`,
});
return;
}
if (filename.match(/bunext.config\.ts/)) {
await fullRebuild({
msg: `bunext.config.ts file changed. Rebuilding server ...`,
});
return;
}
const target_files_match = /\.(tsx?|jsx?|css)$/;
if (event === "change") {
if (filename.match(target_files_match)) {
if (global.RECOMPILING) return;
global.RECOMPILING = true;
if (filename.match(/.*\.server\.tsx?/)) {
global.IS_SERVER_COMPONENT = true;
}
if (global.BUNDLER_CTX) {
await global.BUNDLER_CTX.rebuild();
}
// HMR for error pages
if (filename.match(/(404|500)\.tsx?/)) {
global.HMR_CONTROLLERS.forEach((controller) => {
controller?.controller?.enqueue(
`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`,
);
});
}
}
return;
}
// Handle Structural Changes (Add/Delete)
if (["add", "unlink", "addDir", "unlinkDir"].includes(event)) {
const is_file_of_interest =
!!filename.match(target_files_match) || event.includes("Dir");
if (!is_file_of_interest) return;
// Validation logic
if (
!filename.match(/^src\/pages\/|\.css$/) ||
checkExcludedPatterns({ path: filename }) ||
filename.includes(" ")
) {
// With chokidar, you rarely need to "reload" the whole watcher.
// But we keep the logic for consistency.
return reloadWatcher();
}
if (global.RECOMPILING) return;
const action = event.startsWith("add") ? "created" : "deleted";
const type = filename.match(/\.css$/)
? "Stylesheet"
: event.includes("Dir")
? "Directory"
: filename.match(/\/pages\/api\//)
? "API Route"
: "Page";
await fullRebuild({
msg: `${type} ${action}: ${filename}. Rebuilding ...`,
});
}
};
watcher
.on("add", (path) => handleEvent("add", path))
.on("change", (path) => handleEvent("change", path))
.on("unlink", (path) => handleEvent("unlink", path))
.on("addDir", (path) => handleEvent("addDir", path))
.on("unlinkDir", (path) => handleEvent("unlinkDir", path));
global.PAGES_SRC_WATCHER = watcher;
}
function reloadWatcher() {
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
chokadirWatcherEsbuildCTX();
}
}

View File

@ -1,8 +1,7 @@
import { log } from "../../utils/log"; import { log } from "../../utils/log";
import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler"; import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler";
import pagesSSRBundler from "../bundler/pages-ssr-bundler"; import chokadirWatcherEsbuildCTX from "./chokidar-watcher-esbuild-ctx";
import serverPostBuildFn from "./server-post-build-fn"; import serverPostBuildFn from "./server-post-build-fn";
import watcherEsbuildCTX from "./watcher-esbuild-ctx";
export default async function fullRebuild(params?: { msg?: string }) { export default async function fullRebuild(params?: { msg?: string }) {
try { try {
@ -16,13 +15,15 @@ export default async function fullRebuild(params?: { msg?: string }) {
global.ROUTER.reload(); global.ROUTER.reload();
await global.BUNDLER_CTX?.dispose(); try {
global.BUNDLER_CTX = undefined; await global.BUNDLER_CTX?.dispose();
global.BUNDLER_CTX = undefined;
await global.SSR_BUNDLER_CTX?.dispose(); await global.SSR_BUNDLER_CTX?.dispose();
global.SSR_BUNDLER_CTX = undefined; global.SSR_BUNDLER_CTX = undefined;
} catch (error) {}
await pagesSSRBundler(); // await pagesSSRBundler();
allPagesESBuildContextBundler({ allPagesESBuildContextBundler({
post_build_fn: () => { post_build_fn: () => {
@ -37,6 +38,6 @@ export default async function fullRebuild(params?: { msg?: string }) {
if (global.PAGES_SRC_WATCHER) { if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close(); global.PAGES_SRC_WATCHER.close();
watcherEsbuildCTX(); chokadirWatcherEsbuildCTX();
} }
} }

View File

@ -29,7 +29,6 @@ export default async function watcherEsbuildCTX() {
if (global.BUNDLER_CTX_DISPOSED) { if (global.BUNDLER_CTX_DISPOSED) {
await fullRebuild({ msg: `Restarting Bundler ...` }); await fullRebuild({ msg: `Restarting Bundler ...` });
global.BUNDLER_CTX_DISPOSED = false;
} }
if (global.SSR_BUNDLER_CTX_DISPOSED) { if (global.SSR_BUNDLER_CTX_DISPOSED) {
@ -80,11 +79,7 @@ export default async function watcherEsbuildCTX() {
} }
if (global.BUNDLER_CTX) { if (global.BUNDLER_CTX) {
try { await global.BUNDLER_CTX.rebuild();
await global.BUNDLER_CTX.rebuild();
} catch (error) {
console.log(`ESBUILD Rebuild Error =>`, error);
}
} }
if (filename.match(/(404|500)\.tsx?/)) { if (filename.match(/(404|500)\.tsx?/)) {
@ -133,12 +128,12 @@ export default async function watcherEsbuildCTX() {
}, },
); );
global.PAGES_SRC_WATCHER = pages_src_watcher; // global.PAGES_SRC_WATCHER = pages_src_watcher;
} }
function reloadWatcher() { function reloadWatcher() {
if (global.PAGES_SRC_WATCHER) { // if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close(); // global.PAGES_SRC_WATCHER.close();
watcherEsbuildCTX(); // watcherEsbuildCTX();
} // }
} }