Switch watcher to chokidar

This commit is contained in:
2026-04-20 16:12:24 +01:00
parent c06cb73181
commit e2b8b95a4b
20 changed files with 344 additions and 65 deletions
@@ -1,6 +1,11 @@
export default async function buildOnstartErrorHandler(params) {
// const error_msg = `Build Failed. Please check all your components and imports.`;
// 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.RECOMPILING = false;
global.IS_SERVER_COMPONENT = false;
@@ -11,17 +11,16 @@ import path from "path";
import cleanupLogsDirs from "../../cleanup-logs-dir";
const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames();
let build_start = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 2;
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, }) {
const artifactTracker = {
name: "artifact-tracker",
setup(build) {
build.onStart(async () => {
build_starts++;
global.MAIN_CTX_BUILD_STARTS++;
build_start = performance.now();
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) {
await buildOnstartErrorHandler();
}
@@ -30,7 +29,6 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
if (result.errors.length > 0) {
global.RECOMPILING = false;
global.IS_SERVER_COMPONENT = false;
build_starts = 0;
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})` : ""}`);
@@ -64,7 +62,8 @@ export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn,
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = 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);
if (does_error_file_exist) {
mkdirSync(BUNX_ERROR_LOGS_DIR, { recursive: true });
+3 -2
View File
@@ -20,9 +20,10 @@ export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
global.SSR_BUNDLER_CTX = undefined;
}
});
build.onEnd((result) => {
build.onEnd(async (result) => {
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;
console.log("SSR Build errors:", result.errors);
return;
@@ -1,5 +1,4 @@
import path from "path";
import { log } from "../../../utils/log";
export default function virtualFilesPlugin({ entryToPage }) {
const virtualPlugin = {
name: "virtual-hydration",
+2 -1
View File
@@ -1,9 +1,9 @@
import type { BundlerCTXMap, BunextConfig, GlobalHMRControllerObject, PageFiles } from "../types";
import type { FileSystemRouter, Server } from "bun";
import { type DirNames } from "../utils/grab-dir-names";
import { type FSWatcher } from "fs";
import type { BuildContext } from "esbuild";
import grabConstants from "../utils/grab-constants";
import type { FSWatcher } from "chokidar";
/**
* # Declare Global Variables
*/
@@ -45,5 +45,6 @@ declare global {
var REBUILD_RETRIES: number;
var IS_404_PAGE: boolean;
var CONSTANTS: ReturnType<typeof grabConstants>;
var MAIN_CTX_BUILD_STARTS: number;
}
export default function bunextInit(): Promise<void>;
+3 -3
View File
@@ -1,14 +1,13 @@
import grabDirNames, {} from "../utils/grab-dir-names";
import {} from "fs";
import init from "./init";
import isDevelopment from "../utils/is-development";
import { log } from "../utils/log";
import cron from "./server/cron";
import watcherEsbuildCTX from "./server/watcher-esbuild-ctx";
import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server/server-post-build-fn";
import reactModulesBundler from "./bundler/react-modules-bundler";
import grabConstants from "../utils/grab-constants";
import chokadirWatcherEsbuildCTX from "./server/chokidar-watcher-esbuild-ctx";
const dirNames = grabDirNames();
const { PAGES_DIR } = dirNames;
export default async function bunextInit() {
@@ -23,6 +22,7 @@ export default async function bunextInit() {
global.DIR_NAMES = dirNames;
global.REACT_IMPORTS_MAP = { imports: {} };
global.REACT_DOM_MODULE_CACHE = new Map();
global.MAIN_CTX_BUILD_STARTS = 0;
await init();
log.banner();
global.CONSTANTS = grabConstants();
@@ -40,7 +40,7 @@ export default async function bunextInit() {
serverPostBuildFn();
},
});
watcherEsbuildCTX();
chokadirWatcherEsbuildCTX();
}
else {
log.build(`Building Modules ...`);
@@ -0,0 +1 @@
export default function chokadirWatcherEsbuildCTX(): Promise<void>;
+115
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();
}
}
+10 -8
View File
@@ -1,8 +1,7 @@
import { log } from "../../utils/log";
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 watcherEsbuildCTX from "./watcher-esbuild-ctx";
export default async function fullRebuild(params) {
try {
const { msg } = params || {};
@@ -11,11 +10,14 @@ export default async function fullRebuild(params) {
log.watch(msg);
}
global.ROUTER.reload();
await global.BUNDLER_CTX?.dispose();
global.BUNDLER_CTX = undefined;
await global.SSR_BUNDLER_CTX?.dispose();
global.SSR_BUNDLER_CTX = undefined;
await pagesSSRBundler();
try {
await global.BUNDLER_CTX?.dispose();
global.BUNDLER_CTX = undefined;
await global.SSR_BUNDLER_CTX?.dispose();
global.SSR_BUNDLER_CTX = undefined;
}
catch (error) { }
// await pagesSSRBundler();
allPagesESBuildContextBundler({
post_build_fn: () => {
serverPostBuildFn();
@@ -29,6 +31,6 @@ export default async function fullRebuild(params) {
}
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
watcherEsbuildCTX();
chokadirWatcherEsbuildCTX();
}
}
+6 -12
View File
@@ -22,7 +22,6 @@ export default async function watcherEsbuildCTX() {
}
if (global.BUNDLER_CTX_DISPOSED) {
await fullRebuild({ msg: `Restarting Bundler ...` });
global.BUNDLER_CTX_DISPOSED = false;
}
if (global.SSR_BUNDLER_CTX_DISPOSED) {
pagesSSRBundler();
@@ -63,12 +62,7 @@ export default async function watcherEsbuildCTX() {
global.IS_SERVER_COMPONENT = true;
}
if (global.BUNDLER_CTX) {
try {
await global.BUNDLER_CTX.rebuild();
}
catch (error) {
console.log(`ESBUILD Rebuild Error =>`, error);
}
await global.BUNDLER_CTX.rebuild();
}
if (filename.match(/(404|500)\.tsx?/)) {
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 ...`,
});
});
global.PAGES_SRC_WATCHER = pages_src_watcher;
// global.PAGES_SRC_WATCHER = pages_src_watcher;
}
function reloadWatcher() {
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
watcherEsbuildCTX();
}
// if (global.PAGES_SRC_WATCHER) {
// global.PAGES_SRC_WATCHER.close();
// watcherEsbuildCTX();
// }
}