diff --git a/bun.lock b/bun.lock index 1ce009f..b9eb893 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,7 @@ "@types/react-dom": "^19.2.2", "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", + "chokidar": "^5.0.0", "commander": "^14.0.2", "esbuild": "^0.27.4", "lightningcss-wasm": "^1.32.0", @@ -26,6 +27,7 @@ }, "devDependencies": { "@testing-library/dom": "^10.4.1", + "@types/chokidar": "^2.1.7", "@types/lodash": "^4.17.24", "@types/micromatch": "^4.0.10", "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/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/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=="], + "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-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=="], + "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=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], diff --git a/dist/functions/bundler/build-on-start-error-handler.js b/dist/functions/bundler/build-on-start-error-handler.js index 4d9aeac..3efd5cb 100644 --- a/dist/functions/bundler/build-on-start-error-handler.js +++ b/dist/functions/bundler/build-on-start-error-handler.js @@ -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; diff --git a/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js b/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js index 426d63a..205640d 100644 --- a/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js +++ b/dist/functions/bundler/plugins/esbuild-ctx-artifact-tracker.js @@ -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 }); diff --git a/dist/functions/bundler/plugins/ssr-ctx-artifact-tracker.js b/dist/functions/bundler/plugins/ssr-ctx-artifact-tracker.js index 844b216..0ddbd31 100644 --- a/dist/functions/bundler/plugins/ssr-ctx-artifact-tracker.js +++ b/dist/functions/bundler/plugins/ssr-ctx-artifact-tracker.js @@ -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; diff --git a/dist/functions/bundler/plugins/virtual-files-plugin.js b/dist/functions/bundler/plugins/virtual-files-plugin.js index f9b159b..3fb8419 100644 --- a/dist/functions/bundler/plugins/virtual-files-plugin.js +++ b/dist/functions/bundler/plugins/virtual-files-plugin.js @@ -1,5 +1,4 @@ import path from "path"; -import { log } from "../../../utils/log"; export default function virtualFilesPlugin({ entryToPage }) { const virtualPlugin = { name: "virtual-hydration", diff --git a/dist/functions/bunext-init.d.ts b/dist/functions/bunext-init.d.ts index 9b5b11d..f5878b7 100644 --- a/dist/functions/bunext-init.d.ts +++ b/dist/functions/bunext-init.d.ts @@ -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; + var MAIN_CTX_BUILD_STARTS: number; } export default function bunextInit(): Promise; diff --git a/dist/functions/bunext-init.js b/dist/functions/bunext-init.js index 5c7ec3b..5aa8479 100644 --- a/dist/functions/bunext-init.js +++ b/dist/functions/bunext-init.js @@ -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 ...`); diff --git a/dist/functions/server/chokidar-watcher-esbuild-ctx.d.ts b/dist/functions/server/chokidar-watcher-esbuild-ctx.d.ts new file mode 100644 index 0000000..476a038 --- /dev/null +++ b/dist/functions/server/chokidar-watcher-esbuild-ctx.d.ts @@ -0,0 +1 @@ +export default function chokadirWatcherEsbuildCTX(): Promise; diff --git a/dist/functions/server/chokidar-watcher-esbuild-ctx.js b/dist/functions/server/chokidar-watcher-esbuild-ctx.js new file mode 100644 index 0000000..6e05e9a --- /dev/null +++ b/dist/functions/server/chokidar-watcher-esbuild-ctx.js @@ -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(); + } +} diff --git a/dist/functions/server/full-rebuild.js b/dist/functions/server/full-rebuild.js index 0ace751..28d676a 100644 --- a/dist/functions/server/full-rebuild.js +++ b/dist/functions/server/full-rebuild.js @@ -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(); } } diff --git a/dist/functions/server/watcher-esbuild-ctx.js b/dist/functions/server/watcher-esbuild-ctx.js index ccd80d8..699ac3c 100644 --- a/dist/functions/server/watcher-esbuild-ctx.js +++ b/dist/functions/server/watcher-esbuild-ctx.js @@ -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(); + // } } diff --git a/package.json b/package.json index d18c696..d8f249c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/bunext", - "version": "1.0.91", + "version": "1.0.92", "main": "dist/index.js", "module": "index.ts", "dependencies": { @@ -11,6 +11,7 @@ "@types/react-dom": "^19.2.2", "bun-plugin-tailwind": "^0.1.2", "chalk": "^5.6.2", + "chokidar": "^5.0.0", "commander": "^14.0.2", "esbuild": "^0.27.4", "lightningcss-wasm": "^1.32.0", @@ -25,6 +26,7 @@ }, "devDependencies": { "@testing-library/dom": "^10.4.1", + "@types/chokidar": "^2.1.7", "@types/lodash": "^4.17.24", "@types/micromatch": "^4.0.10", "happy-dom": "^20.8.4" diff --git a/src/functions/bundler/build-on-start-error-handler.ts b/src/functions/bundler/build-on-start-error-handler.ts index dc25906..eb58bc9 100644 --- a/src/functions/bundler/build-on-start-error-handler.ts +++ b/src/functions/bundler/build-on-start-error-handler.ts @@ -4,6 +4,13 @@ export default async function buildOnstartErrorHandler(params?: 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; diff --git a/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts b/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts index 8a083b3..111113e 100644 --- a/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts +++ b/src/functions/bundler/plugins/esbuild-ctx-artifact-tracker.ts @@ -14,7 +14,6 @@ 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; type Params = { @@ -35,7 +34,7 @@ export default function esbuildCTXArtifactTracker({ 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( @@ -43,7 +42,7 @@ export default function esbuildCTXArtifactTracker({ ); if ( - build_starts >= MAX_BUILD_STARTS && + global.MAIN_CTX_BUILD_STARTS >= MAX_BUILD_STARTS && !does_error_file_exist ) { await buildOnstartErrorHandler(); @@ -54,14 +53,19 @@ export default function esbuildCTXArtifactTracker({ 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})` : ""}`); + 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]; try { controller?.controller?.enqueue( @@ -101,7 +105,8 @@ export default function esbuildCTXArtifactTracker({ 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, diff --git a/src/functions/bundler/plugins/ssr-ctx-artifact-tracker.ts b/src/functions/bundler/plugins/ssr-ctx-artifact-tracker.ts index c0d892f..97d05b2 100644 --- a/src/functions/bundler/plugins/ssr-ctx-artifact-tracker.ts +++ b/src/functions/bundler/plugins/ssr-ctx-artifact-tracker.ts @@ -38,9 +38,10 @@ export default function ssrCTXArtifactTracker({ } }); - 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; diff --git a/src/functions/bundler/plugins/virtual-files-plugin.ts b/src/functions/bundler/plugins/virtual-files-plugin.ts index 81e99ea..7315085 100644 --- a/src/functions/bundler/plugins/virtual-files-plugin.ts +++ b/src/functions/bundler/plugins/virtual-files-plugin.ts @@ -1,7 +1,6 @@ import type { Plugin } from "esbuild"; import path from "path"; import type { PageFiles } from "../../../types"; -import { log } from "../../../utils/log"; type Params = { entryToPage: Map< diff --git a/src/functions/bunext-init.ts b/src/functions/bunext-init.ts index 0fe37c4..b6d0097 100644 --- a/src/functions/bunext-init.ts +++ b/src/functions/bunext-init.ts @@ -6,17 +6,17 @@ import type { } from "../types"; import type { FileSystemRouter, Server } from "bun"; import grabDirNames, { type DirNames } from "../utils/grab-dir-names"; -import { type FSWatcher } from "fs"; import init from "./init"; import isDevelopment from "../utils/is-development"; import { log } from "../utils/log"; import cron from "./server/cron"; import type { BuildContext } from "esbuild"; -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 type { FSWatcher } from "chokidar"; +import chokadirWatcherEsbuildCTX from "./server/chokidar-watcher-esbuild-ctx"; /** * # Declare Global Variables @@ -52,6 +52,7 @@ declare global { var REBUILD_RETRIES: number; var IS_404_PAGE: boolean; var CONSTANTS: ReturnType; + var MAIN_CTX_BUILD_STARTS: number; } const dirNames = grabDirNames(); @@ -69,6 +70,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(); @@ -93,7 +95,7 @@ export default async function bunextInit() { serverPostBuildFn(); }, }); - watcherEsbuildCTX(); + chokadirWatcherEsbuildCTX(); } else { log.build(`Building Modules ...`); await allPagesESBuildContextBundler(); diff --git a/src/functions/server/chokidar-watcher-esbuild-ctx.ts b/src/functions/server/chokidar-watcher-esbuild-ctx.ts new file mode 100644 index 0000000..da1be40 --- /dev/null +++ b/src/functions/server/chokidar-watcher-esbuild-ctx.ts @@ -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(); + } +} diff --git a/src/functions/server/full-rebuild.ts b/src/functions/server/full-rebuild.ts index 1d68578..39bdb02 100644 --- a/src/functions/server/full-rebuild.ts +++ b/src/functions/server/full-rebuild.ts @@ -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?: { msg?: string }) { try { @@ -16,13 +15,15 @@ export default async function fullRebuild(params?: { msg?: string }) { global.ROUTER.reload(); - await global.BUNDLER_CTX?.dispose(); - global.BUNDLER_CTX = undefined; + try { + await global.BUNDLER_CTX?.dispose(); + global.BUNDLER_CTX = undefined; - await global.SSR_BUNDLER_CTX?.dispose(); - global.SSR_BUNDLER_CTX = undefined; + await global.SSR_BUNDLER_CTX?.dispose(); + global.SSR_BUNDLER_CTX = undefined; + } catch (error) {} - await pagesSSRBundler(); + // await pagesSSRBundler(); allPagesESBuildContextBundler({ post_build_fn: () => { @@ -37,6 +38,6 @@ export default async function fullRebuild(params?: { msg?: string }) { if (global.PAGES_SRC_WATCHER) { global.PAGES_SRC_WATCHER.close(); - watcherEsbuildCTX(); + chokadirWatcherEsbuildCTX(); } } diff --git a/src/functions/server/watcher-esbuild-ctx.ts b/src/functions/server/watcher-esbuild-ctx.ts index 2862739..664121b 100644 --- a/src/functions/server/watcher-esbuild-ctx.ts +++ b/src/functions/server/watcher-esbuild-ctx.ts @@ -29,7 +29,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) { @@ -80,11 +79,7 @@ export default async function watcherEsbuildCTX() { } 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?/)) { @@ -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() { - if (global.PAGES_SRC_WATCHER) { - global.PAGES_SRC_WATCHER.close(); - watcherEsbuildCTX(); - } + // if (global.PAGES_SRC_WATCHER) { + // global.PAGES_SRC_WATCHER.close(); + // watcherEsbuildCTX(); + // } }