Update excluded pages paths logic. Add HMR retries on errors.

This commit is contained in:
Benjamin Toby 2026-04-12 05:47:12 +01:00
parent e0c2ab5872
commit 532d0d6b56
29 changed files with 168 additions and 65 deletions

View File

@ -5,4 +5,5 @@ export declare const AppData: {
readonly ClientHMRPath: "__bunext_client_hmr__"; readonly ClientHMRPath: "__bunext_client_hmr__";
readonly BunextClientHydrationScriptID: "bunext-client-hydration-script"; readonly BunextClientHydrationScriptID: "bunext-client-hydration-script";
readonly BunextTmpFileExt: ".bunext_tmp.tsx"; readonly BunextTmpFileExt: ".bunext_tmp.tsx";
readonly BunextHMRRetryRoute: "/.bunext/hmr-retry";
}; };

View File

@ -5,4 +5,5 @@ export const AppData = {
ClientHMRPath: "__bunext_client_hmr__", ClientHMRPath: "__bunext_client_hmr__",
BunextClientHydrationScriptID: "bunext-client-hydration-script", BunextClientHydrationScriptID: "bunext-client-hydration-script",
BunextTmpFileExt: ".bunext_tmp.tsx", BunextTmpFileExt: ".bunext_tmp.tsx",
BunextHMRRetryRoute: "/.bunext/hmr-retry",
}; };

View File

@ -3,6 +3,7 @@ import type { FileSystemRouter, Server } from "bun";
import grabDirNames from "../utils/grab-dir-names"; import grabDirNames from "../utils/grab-dir-names";
import { type FSWatcher } from "fs"; import { type FSWatcher } from "fs";
import type { BuildContext } from "esbuild"; import type { BuildContext } from "esbuild";
import grabConstants from "../utils/grab-constants";
/** /**
* # Declare Global Variables * # Declare Global Variables
*/ */
@ -46,5 +47,6 @@ declare global {
var BUNDLER_CTX_DISPOSED: boolean | undefined; var BUNDLER_CTX_DISPOSED: boolean | undefined;
var REBUILD_RETRIES: number; var REBUILD_RETRIES: number;
var IS_404_PAGE: boolean; var IS_404_PAGE: boolean;
var CONSTANTS: ReturnType<typeof grabConstants>;
} }
export default function bunextInit(): Promise<void>; export default function bunextInit(): Promise<void>;

View File

@ -8,6 +8,7 @@ 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";
const dirNames = grabDirNames(); const dirNames = grabDirNames();
const { PAGES_DIR } = dirNames; const { PAGES_DIR } = dirNames;
export default async function bunextInit() { export default async function bunextInit() {
@ -24,6 +25,7 @@ export default async function bunextInit() {
global.REACT_DOM_MODULE_CACHE = new Map(); global.REACT_DOM_MODULE_CACHE = new Map();
log.banner(); log.banner();
await init(); await init();
global.CONSTANTS = grabConstants();
await reactModulesBundler(); await reactModulesBundler();
const router = new Bun.FileSystemRouter({ const router = new Bun.FileSystemRouter({
style: "nextjs", style: "nextjs",
@ -36,17 +38,11 @@ export default async function bunextInit() {
await allPagesESBuildContextBundler({ await allPagesESBuildContextBundler({
post_build_fn: serverPostBuildFn, post_build_fn: serverPostBuildFn,
}); });
// initPages({
// log_time: true,
// });
watcherEsbuildCTX(); watcherEsbuildCTX();
} }
else { else {
log.build(`Building Modules ...`); log.build(`Building Modules ...`);
await allPagesESBuildContextBundler(); await allPagesESBuildContextBundler();
// initPages({
// log_time: true,
// });
cron(); cron();
} }
} }

View File

@ -10,7 +10,7 @@ export default async function trimCacheKey({ key, }) {
key, key,
}); });
const config = global.CONFIG; const config = global.CONFIG;
const default_expiry_time_seconds = config.defaultCacheExpiry || const default_expiry_time_seconds = config.default_cache_expiry ||
AppData["DefaultCacheExpiryTimeSeconds"]; AppData["DefaultCacheExpiryTimeSeconds"];
const default_expiry_time_milliseconds = default_expiry_time_seconds * 1000; const default_expiry_time_milliseconds = default_expiry_time_seconds * 1000;
const cache_content_path = path.join(BUNEXT_CACHE_DIR, cache_name); const cache_content_path = path.join(BUNEXT_CACHE_DIR, cache_name);

View File

@ -1,20 +1,24 @@
import handleWebPages from "./web-pages/handle-web-pages"; import handleWebPages from "./web-pages/handle-web-pages";
import handleRoutes from "./handle-routes"; import handleRoutes from "./handle-routes";
import isDevelopment from "../../utils/is-development"; import isDevelopment from "../../utils/is-development";
import grabConstants from "../../utils/grab-constants";
import handleHmr from "./handle-hmr"; import handleHmr from "./handle-hmr";
import handlePublic from "./handle-public"; import handlePublic from "./handle-public";
import handleFiles from "./handle-files"; import handleFiles from "./handle-files";
import handleBunextPublicAssets from "./handle-bunext-public-assets"; import handleBunextPublicAssets from "./handle-bunext-public-assets";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
import { AppData } from "../../data/app-data";
import fullRebuild from "./full-rebuild";
export default async function bunextRequestHandler({ req: initial_req, server, }) { export default async function bunextRequestHandler({ req: initial_req, server, }) {
const is_dev = isDevelopment(); const is_dev = isDevelopment();
let req = initial_req.clone(); let req = initial_req.clone();
try { try {
const url = new URL(req.url); const url = new URL(req.url);
const { config } = grabConstants(); if (checkExcludedPatterns({ path: url.pathname })) {
return Response.json({ success: false, msg: `Invalid Path` });
}
let response = undefined; let response = undefined;
if (config?.middleware) { if (global.CONSTANTS.config?.middleware) {
const middleware_res = await config.middleware({ const middleware_res = await global.CONSTANTS.config.middleware({
req: initial_req, req: initial_req,
url, url,
}); });
@ -25,10 +29,10 @@ export default async function bunextRequestHandler({ req: initial_req, server, }
req = middleware_res; req = middleware_res;
} }
} }
// const server_upgrade = server.upgrade(req); if (is_dev && url.pathname == AppData["BunextHMRRetryRoute"]) {
// if (server_upgrade) { await fullRebuild({ msg: `HMR Retry Rebuild ...` });
// return undefined; return new Response("Modules Rebuilt");
// } }
if (url.pathname === "/__hmr" && is_dev) { if (url.pathname === "/__hmr" && is_dev) {
response = await handleHmr({ req }); response = await handleHmr({ req });
} }

View File

@ -15,6 +15,6 @@ export default async function () {
idleTimeout: development ? 0 : undefined, idleTimeout: development ? 0 : undefined,
development, development,
websocket: config?.websocket, websocket: config?.websocket,
..._.omit(config?.serverOptions || {}, ["fetch"]), ..._.omit(config?.server_options || {}, ["fetch"]),
}; };
} }

View File

@ -1,11 +1,9 @@
import { watch, existsSync, statSync } from "fs"; import { watch, existsSync, statSync } from "fs";
import path from "path"; import path from "path";
import grabDirNames from "../../utils/grab-dir-names"; import grabDirNames from "../../utils/grab-dir-names";
import { log } from "../../utils/log";
import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server-post-build-fn";
import fullRebuild from "./full-rebuild"; import fullRebuild from "./full-rebuild";
import { AppData } from "../../data/app-data"; import { AppData } from "../../data/app-data";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
const { ROOT_DIR } = grabDirNames(); const { ROOT_DIR } = grabDirNames();
export default async function watcherEsbuildCTX() { export default async function watcherEsbuildCTX() {
const pages_src_watcher = watch(ROOT_DIR, { const pages_src_watcher = watch(ROOT_DIR, {
@ -80,7 +78,7 @@ export default async function watcherEsbuildCTX() {
} }
if (!filename.match(/^src\/pages\/|\.css$/)) if (!filename.match(/^src\/pages\/|\.css$/))
return reloadWatcher(); return reloadWatcher();
if (filename.match(/\/(--|\()/)) if (checkExcludedPatterns({ path: filename }))
return reloadWatcher(); return reloadWatcher();
if (filename.match(/ /)) if (filename.match(/ /))
return reloadWatcher(); return reloadWatcher();

View File

@ -12,6 +12,9 @@ export default async function (params) {
const supress_condition = errors_to_supress const supress_condition = errors_to_supress
.map((e) => `args[0].includes("${e}")`) .map((e) => `args[0].includes("${e}")`)
.join(" || "); .join(" || ");
script += `let retries = 0;\n`;
script += `let retries_exhausted = false;\n`;
script += `const MAX_RETRIES = 1;\n`;
script += `const _ce = console.error.bind(console);\n`; script += `const _ce = console.error.bind(console);\n`;
script += `console.error = (...args) => {\n`; script += `console.error = (...args) => {\n`;
script += ` if (typeof args[0] === "string" && (${supress_condition})) return;\n`; script += ` if (typeof args[0] === "string" && (${supress_condition})) return;\n`;
@ -23,8 +26,15 @@ export default async function (params) {
script += ` const overlay = document.createElement("div");\n`; script += ` const overlay = document.createElement("div");\n`;
script += ` overlay.id = "__bunext_error_overlay";\n`; script += ` overlay.id = "__bunext_error_overlay";\n`;
script += ` overlay.style.cssText = "position:fixed;inset:0;z-index:99999;background:#1a1a1a;color:#ff6b6b;font-family:monospace;font-size:14px;padding:24px;overflow:auto;";\n`; script += ` overlay.style.cssText = "position:fixed;inset:0;z-index:99999;background:#1a1a1a;color:#ff6b6b;font-family:monospace;font-size:14px;padding:24px;overflow:auto;";\n`;
script += ` overlay.innerHTML = \`<div style="max-width:900px;margin:0 auto"><div style="font-size:18px;font-weight:bold;margin-bottom:12px;color:#ff4444">Runtime Error</div><div style="color:#fff;margin-bottom:16px">\${message}</div>\${source ? \`<div style="color:#888;margin-bottom:16px">\${source}</div>\` : ""}\${stack ? \`<pre style="background:#111;padding:16px;border-radius:6px;overflow:auto;color:#ffa07a;white-space:pre-wrap">\${stack}</pre>\` : ""}<button onclick="this.closest('#__bunext_error_overlay').remove()" style="margin-top:16px;padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer">Dismiss</button></div>\`;\n`; script += ` overlay.innerHTML = \`<div style="max-width:900px;margin:auto"><div style="font-size:18px;font-weight:bold;margin-bottom:12px;color:#ff4444">Runtime Error</div><div style="color:#fff;margin-bottom:16px">\${message}</div>\${source ? \`<div style="color:#888;margin-bottom:16px">\${source}</div>\` : ""}\${stack ? \`<pre style="background:#111;padding:16px;border-radius:6px;overflow:auto;color:#ffa07a;white-space:pre-wrap">\${stack}</pre>\` : ""}<button onclick="this.closest('#__bunext_error_overlay').remove()" style="margin-top:16px;padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer">Dismiss</button></div>\`;\n`;
script += ` document.body.appendChild(overlay);\n`; script += ` document.body.appendChild(overlay);\n`;
script += ` if (retries < MAX_RETRIES) {\n`;
script += ` retries++\n`;
script += ` console.log(\`Retrying \${retries} ...\`)\n`;
script += ` fetch("${AppData["BunextHMRRetryRoute"]}")\n`;
script += ` } else {\n`;
script += ` retries_exhausted = true\n`;
script += ` }\n`;
script += `}\n\n`; script += `}\n\n`;
script += `function __bunext_should_suppress_runtime_error(message) {\n`; script += `function __bunext_should_suppress_runtime_error(message) {\n`;
script += ` return false;\n`; script += ` return false;\n`;
@ -53,7 +63,14 @@ export default async function (params) {
script += `hmr.addEventListener("update", async (event) => {\n`; script += `hmr.addEventListener("update", async (event) => {\n`;
script += ` if (event?.data) {\n`; script += ` if (event?.data) {\n`;
script += ` try {\n`; script += ` try {\n`;
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`; script += ` if (retries_exhausted) {\n`;
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
script += ` retries = 0;\n`;
script += ` retries_exhausted = false;\n`;
script += ` }\n`;
// script += ` if (retries >= MAX_RETRIES && document.getElementById("__bunext_error_overlay")) {\n`;
// script += ` retries = 0;\n`;
// script += ` }\n`;
script += ` const data = JSON.parse(event.data);\n`; script += ` const data = JSON.parse(event.data);\n`;
// script += ` console.log("data", data);\n`; // script += ` console.log("data", data);\n`;
script += ` if (data.reload) {\n`; script += ` if (data.reload) {\n`;
@ -98,7 +115,9 @@ export default async function (params) {
// script += ` window.location.reload();\n`; // script += ` window.location.reload();\n`;
// script += ` }\n`; // script += ` }\n`;
// script += ` console.log("newScript", newScript);\n`; // script += ` console.log("newScript", newScript);\n`;
// script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
script += ` document.head.appendChild(newScript);\n\n`; script += ` document.head.appendChild(newScript);\n\n`;
// script += ` retries = 0;\n\n`;
script += ` } catch (err) {\n`; script += ` } catch (err) {\n`;
script += ` console.error("HMR update failed, falling back to reload:", err.message);\n`; script += ` console.error("HMR update failed, falling back to reload:", err.message);\n`;
script += ` window.location.reload();\n`; script += ` window.location.reload();\n`;

15
dist/types/index.d.ts vendored
View File

@ -36,18 +36,23 @@ export type PageModule = {
staticParams: StaticParams; staticParams: StaticParams;
}; };
export type BunextConfig = { export type BunextConfig = {
distDir?: string; dist_dir?: string;
assetsPrefix?: string; assets_prefix?: string;
origin?: string; origin?: string;
globalVars?: { global_vars?: {
[k: string]: any; [k: string]: any;
}; };
port?: number; port?: number;
development?: boolean; development?: boolean;
middleware?: (params: BunextConfigMiddlewareParams) => Promise<Response | Request | undefined> | Response | Request | undefined; middleware?: (params: BunextConfigMiddlewareParams) => Promise<Response | Request | undefined> | Response | Request | undefined;
defaultCacheExpiry?: number; default_cache_expiry?: number;
websocket?: WebSocketHandler<any>; websocket?: WebSocketHandler<any>;
serverOptions?: Omit<Bun.Serve.Options<any>, "fetch" | "routes">; server_options?: Omit<Bun.Serve.Options<any>, "fetch" | "routes">;
/**
* These are patterns in the pages to exclude
* from the router
*/
pages_exclude_patterns?: RegExp[];
}; };
export type BunextConfigMiddlewareParams = { export type BunextConfigMiddlewareParams = {
req: Request; req: Request;

View File

@ -0,0 +1,5 @@
type Params = {
path: string;
};
export default function ({ path }: Params): boolean;
export {};

8
dist/utils/check-excluded-patterns.js vendored Normal file
View File

@ -0,0 +1,8 @@
export default function ({ path }) {
for (let i = 0; i < global.CONSTANTS.RouteIgnorePatterns.length; i++) {
const regex = global.CONSTANTS.RouteIgnorePatterns[i];
if (path.match(regex))
return true;
}
return false;
}

View File

@ -3,6 +3,7 @@ import grabDirNames from "./grab-dir-names";
import path from "path"; import path from "path";
import AppNames from "./grab-app-names"; import AppNames from "./grab-app-names";
import pagePathTransform from "./page-path-transform"; import pagePathTransform from "./page-path-transform";
import checkExcludedPatterns from "./check-excluded-patterns";
export default function grabAllPages(params) { export default function grabAllPages(params) {
const { PAGES_DIR } = grabDirNames(); const { PAGES_DIR } = grabDirNames();
const pages = grabPageDirRecursively({ page_dir: PAGES_DIR }); const pages = grabPageDirRecursively({ page_dir: PAGES_DIR });
@ -31,7 +32,10 @@ function grabPageDirRecursively({ page_dir }) {
if (page.match(new RegExp(`${AppNames["RootPagesComponentName"]}`))) { if (page.match(new RegExp(`${AppNames["RootPagesComponentName"]}`))) {
continue; continue;
} }
if (page.match(/\(|\)|--|\/api\//)) { if (checkExcludedPatterns({ path: page })) {
continue;
}
if (page.match(/\/api\//)) {
continue; continue;
} }
if (page_name.split(".").length > 2) { if (page_name.split(".").length > 2) {
@ -39,7 +43,7 @@ function grabPageDirRecursively({ page_dir }) {
} }
const page_stat = statSync(full_page_path); const page_stat = statSync(full_page_path);
if (page_stat.isDirectory()) { if (page_stat.isDirectory()) {
if (page.match(/\(|\)/)) if (checkExcludedPatterns({ path: page }))
continue; continue;
const new_page_files = grabPageDirRecursively({ const new_page_files = grabPageDirRecursively({
page_dir: full_page_path, page_dir: full_page_path,

View File

@ -1,7 +1,7 @@
import AppNames from "./grab-app-names"; import AppNames from "./grab-app-names";
export default function grabAssetsPrefix() { export default function grabAssetsPrefix() {
if (global.CONFIG.assetsPrefix) { if (global.CONFIG.assets_prefix) {
return global.CONFIG.assetsPrefix; return global.CONFIG.assets_prefix;
} }
const { defaultAssetPrefix } = AppNames; const { defaultAssetPrefix } = AppNames;
return defaultAssetPrefix; return defaultAssetPrefix;

View File

@ -6,4 +6,5 @@ export default function grabConstants(): {
readonly ClientRootComponentWindowName: "BUNEXT_ROOT"; readonly ClientRootComponentWindowName: "BUNEXT_ROOT";
readonly MaxBundlerRebuilds: 5; readonly MaxBundlerRebuilds: 5;
readonly config: import("../types").BunextConfig; readonly config: import("../types").BunextConfig;
readonly RouteIgnorePatterns: RegExp[];
}; };

View File

@ -6,6 +6,10 @@ export default function grabConstants() {
const ClientRootComponentWindowName = "BUNEXT_ROOT"; const ClientRootComponentWindowName = "BUNEXT_ROOT";
const ServerDefaultRequestBodyLimitBytes = MB_IN_BYTES * 10; const ServerDefaultRequestBodyLimitBytes = MB_IN_BYTES * 10;
const MaxBundlerRebuilds = 5; const MaxBundlerRebuilds = 5;
const RouteIgnorePatterns = [/\/\(/];
if (config.pages_exclude_patterns) {
RouteIgnorePatterns.push(...config.pages_exclude_patterns);
}
return { return {
ClientRootElementIDName, ClientRootElementIDName,
ClientWindowPagePropsName, ClientWindowPagePropsName,
@ -14,5 +18,6 @@ export default function grabConstants() {
ClientRootComponentWindowName, ClientRootComponentWindowName,
MaxBundlerRebuilds, MaxBundlerRebuilds,
config, config,
RouteIgnorePatterns,
}; };
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@moduletrace/bunext", "name": "@moduletrace/bunext",
"version": "1.0.74", "version": "1.0.75",
"main": "dist/index.js", "main": "dist/index.js",
"module": "index.ts", "module": "index.ts",
"dependencies": { "dependencies": {

View File

@ -5,4 +5,5 @@ export const AppData = {
ClientHMRPath: "__bunext_client_hmr__", ClientHMRPath: "__bunext_client_hmr__",
BunextClientHydrationScriptID: "bunext-client-hydration-script", BunextClientHydrationScriptID: "bunext-client-hydration-script",
BunextTmpFileExt: ".bunext_tmp.tsx", BunextTmpFileExt: ".bunext_tmp.tsx",
BunextHMRRetryRoute: "/.bunext/hmr-retry",
} as const; } as const;

View File

@ -16,7 +16,7 @@ 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 initPages from "./bundler/init-pages"; import grabConstants from "../utils/grab-constants";
/** /**
* # Declare Global Variables * # Declare Global Variables
@ -50,6 +50,7 @@ declare global {
var BUNDLER_CTX_DISPOSED: boolean | undefined; var BUNDLER_CTX_DISPOSED: boolean | undefined;
var REBUILD_RETRIES: number; var REBUILD_RETRIES: number;
var IS_404_PAGE: boolean; var IS_404_PAGE: boolean;
var CONSTANTS: ReturnType<typeof grabConstants>;
} }
const dirNames = grabDirNames(); const dirNames = grabDirNames();
@ -71,6 +72,9 @@ export default async function bunextInit() {
log.banner(); log.banner();
await init(); await init();
global.CONSTANTS = grabConstants();
await reactModulesBundler(); await reactModulesBundler();
const router = new Bun.FileSystemRouter({ const router = new Bun.FileSystemRouter({
@ -87,16 +91,10 @@ export default async function bunextInit() {
await allPagesESBuildContextBundler({ await allPagesESBuildContextBundler({
post_build_fn: serverPostBuildFn, post_build_fn: serverPostBuildFn,
}); });
// initPages({
// log_time: true,
// });
watcherEsbuildCTX(); watcherEsbuildCTX();
} else { } else {
log.build(`Building Modules ...`); log.build(`Building Modules ...`);
await allPagesESBuildContextBundler(); await allPagesESBuildContextBundler();
// initPages({
// log_time: true,
// });
cron(); cron();
} }
} }

View File

@ -21,7 +21,7 @@ export default async function trimCacheKey({
const config = global.CONFIG; const config = global.CONFIG;
const default_expiry_time_seconds = const default_expiry_time_seconds =
config.defaultCacheExpiry || config.default_cache_expiry ||
AppData["DefaultCacheExpiryTimeSeconds"]; AppData["DefaultCacheExpiryTimeSeconds"];
const default_expiry_time_milliseconds = const default_expiry_time_milliseconds =

View File

@ -1,11 +1,13 @@
import handleWebPages from "./web-pages/handle-web-pages"; import handleWebPages from "./web-pages/handle-web-pages";
import handleRoutes from "./handle-routes"; import handleRoutes from "./handle-routes";
import isDevelopment from "../../utils/is-development"; import isDevelopment from "../../utils/is-development";
import grabConstants from "../../utils/grab-constants";
import handleHmr from "./handle-hmr"; import handleHmr from "./handle-hmr";
import handlePublic from "./handle-public"; import handlePublic from "./handle-public";
import handleFiles from "./handle-files"; import handleFiles from "./handle-files";
import handleBunextPublicAssets from "./handle-bunext-public-assets"; import handleBunextPublicAssets from "./handle-bunext-public-assets";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
import { AppData } from "../../data/app-data";
import fullRebuild from "./full-rebuild";
type Params = { type Params = {
req: Request; req: Request;
server: Bun.Server<any>; server: Bun.Server<any>;
@ -21,12 +23,14 @@ export default async function bunextRequestHandler({
try { try {
const url = new URL(req.url); const url = new URL(req.url);
const { config } = grabConstants(); if (checkExcludedPatterns({ path: url.pathname })) {
return Response.json({ success: false, msg: `Invalid Path` });
}
let response: Response | undefined = undefined; let response: Response | undefined = undefined;
if (config?.middleware) { if (global.CONSTANTS.config?.middleware) {
const middleware_res = await config.middleware({ const middleware_res = await global.CONSTANTS.config.middleware({
req: initial_req, req: initial_req,
url, url,
}); });
@ -40,11 +44,10 @@ export default async function bunextRequestHandler({
} }
} }
// const server_upgrade = server.upgrade(req); if (is_dev && url.pathname == AppData["BunextHMRRetryRoute"]) {
await fullRebuild({ msg: `HMR Retry Rebuild ...` });
// if (server_upgrade) { return new Response("Modules Rebuilt");
// return undefined; }
// }
if (url.pathname === "/__hmr" && is_dev) { if (url.pathname === "/__hmr" && is_dev) {
response = await handleHmr({ req }); response = await handleHmr({ req });

View File

@ -18,6 +18,6 @@ export default async function (): Promise<Bun.Serve.Options<any>> {
idleTimeout: development ? 0 : undefined, idleTimeout: development ? 0 : undefined,
development, development,
websocket: config?.websocket, websocket: config?.websocket,
..._.omit(config?.serverOptions || {}, ["fetch"]), ..._.omit(config?.server_options || {}, ["fetch"]),
} as Bun.Serve.Options<any>; } as Bun.Serve.Options<any>;
} }

View File

@ -1,11 +1,9 @@
import { watch, existsSync, statSync } from "fs"; import { watch, existsSync, statSync } from "fs";
import path from "path"; import path from "path";
import grabDirNames from "../../utils/grab-dir-names"; import grabDirNames from "../../utils/grab-dir-names";
import { log } from "../../utils/log";
import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server-post-build-fn";
import fullRebuild from "./full-rebuild"; import fullRebuild from "./full-rebuild";
import { AppData } from "../../data/app-data"; import { AppData } from "../../data/app-data";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
const { ROOT_DIR } = grabDirNames(); const { ROOT_DIR } = grabDirNames();
@ -102,7 +100,8 @@ export default async function watcherEsbuildCTX() {
} }
if (!filename.match(/^src\/pages\/|\.css$/)) return reloadWatcher(); if (!filename.match(/^src\/pages\/|\.css$/)) return reloadWatcher();
if (filename.match(/\/(--|\()/)) return reloadWatcher(); if (checkExcludedPatterns({ path: filename }))
return reloadWatcher();
if (filename.match(/ /)) return reloadWatcher(); if (filename.match(/ /)) return reloadWatcher();
if (global.RECOMPILING) return; if (global.RECOMPILING) return;

View File

@ -23,6 +23,10 @@ export default async function (params?: Params) {
.map((e) => `args[0].includes("${e}")`) .map((e) => `args[0].includes("${e}")`)
.join(" || "); .join(" || ");
script += `let retries = 0;\n`;
script += `let retries_exhausted = false;\n`;
script += `const MAX_RETRIES = 1;\n`;
script += `const _ce = console.error.bind(console);\n`; script += `const _ce = console.error.bind(console);\n`;
script += `console.error = (...args) => {\n`; script += `console.error = (...args) => {\n`;
script += ` if (typeof args[0] === "string" && (${supress_condition})) return;\n`; script += ` if (typeof args[0] === "string" && (${supress_condition})) return;\n`;
@ -35,8 +39,16 @@ export default async function (params?: Params) {
script += ` const overlay = document.createElement("div");\n`; script += ` const overlay = document.createElement("div");\n`;
script += ` overlay.id = "__bunext_error_overlay";\n`; script += ` overlay.id = "__bunext_error_overlay";\n`;
script += ` overlay.style.cssText = "position:fixed;inset:0;z-index:99999;background:#1a1a1a;color:#ff6b6b;font-family:monospace;font-size:14px;padding:24px;overflow:auto;";\n`; script += ` overlay.style.cssText = "position:fixed;inset:0;z-index:99999;background:#1a1a1a;color:#ff6b6b;font-family:monospace;font-size:14px;padding:24px;overflow:auto;";\n`;
script += ` overlay.innerHTML = \`<div style="max-width:900px;margin:0 auto"><div style="font-size:18px;font-weight:bold;margin-bottom:12px;color:#ff4444">Runtime Error</div><div style="color:#fff;margin-bottom:16px">\${message}</div>\${source ? \`<div style="color:#888;margin-bottom:16px">\${source}</div>\` : ""}\${stack ? \`<pre style="background:#111;padding:16px;border-radius:6px;overflow:auto;color:#ffa07a;white-space:pre-wrap">\${stack}</pre>\` : ""}<button onclick="this.closest('#__bunext_error_overlay').remove()" style="margin-top:16px;padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer">Dismiss</button></div>\`;\n`; script += ` overlay.innerHTML = \`<div style="max-width:900px;margin:auto"><div style="font-size:18px;font-weight:bold;margin-bottom:12px;color:#ff4444">Runtime Error</div><div style="color:#fff;margin-bottom:16px">\${message}</div>\${source ? \`<div style="color:#888;margin-bottom:16px">\${source}</div>\` : ""}\${stack ? \`<pre style="background:#111;padding:16px;border-radius:6px;overflow:auto;color:#ffa07a;white-space:pre-wrap">\${stack}</pre>\` : ""}<button onclick="this.closest('#__bunext_error_overlay').remove()" style="margin-top:16px;padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer">Dismiss</button></div>\`;\n`;
script += ` document.body.appendChild(overlay);\n`; script += ` document.body.appendChild(overlay);\n`;
script += ` if (retries < MAX_RETRIES) {\n`;
script += ` retries++\n`;
script += ` console.log(\`Retrying \${retries} ...\`)\n`;
script += ` fetch("${AppData["BunextHMRRetryRoute"]}")\n`;
script += ` } else {\n`;
script += ` retries_exhausted = true\n`;
script += ` }\n`;
script += `}\n\n`; script += `}\n\n`;
script += `function __bunext_should_suppress_runtime_error(message) {\n`; script += `function __bunext_should_suppress_runtime_error(message) {\n`;
script += ` return false;\n`; script += ` return false;\n`;
@ -66,7 +78,16 @@ export default async function (params?: Params) {
script += `hmr.addEventListener("update", async (event) => {\n`; script += `hmr.addEventListener("update", async (event) => {\n`;
script += ` if (event?.data) {\n`; script += ` if (event?.data) {\n`;
script += ` try {\n`; script += ` try {\n`;
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`; script += ` if (retries_exhausted) {\n`;
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
script += ` retries = 0;\n`;
script += ` retries_exhausted = false;\n`;
script += ` }\n`;
// script += ` if (retries >= MAX_RETRIES && document.getElementById("__bunext_error_overlay")) {\n`;
// script += ` retries = 0;\n`;
// script += ` }\n`;
script += ` const data = JSON.parse(event.data);\n`; script += ` const data = JSON.parse(event.data);\n`;
// script += ` console.log("data", data);\n`; // script += ` console.log("data", data);\n`;
@ -118,7 +139,9 @@ export default async function (params?: Params) {
// script += ` window.location.reload();\n`; // script += ` window.location.reload();\n`;
// script += ` }\n`; // script += ` }\n`;
// script += ` console.log("newScript", newScript);\n`; // script += ` console.log("newScript", newScript);\n`;
// script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
script += ` document.head.appendChild(newScript);\n\n`; script += ` document.head.appendChild(newScript);\n\n`;
// script += ` retries = 0;\n\n`;
script += ` } catch (err) {\n`; script += ` } catch (err) {\n`;
script += ` console.error("HMR update failed, falling back to reload:", err.message);\n`; script += ` console.error("HMR update failed, falling back to reload:", err.message);\n`;

View File

@ -49,10 +49,10 @@ export type PageModule = {
}; };
export type BunextConfig = { export type BunextConfig = {
distDir?: string; dist_dir?: string;
assetsPrefix?: string; assets_prefix?: string;
origin?: string; origin?: string;
globalVars?: { [k: string]: any }; global_vars?: { [k: string]: any };
port?: number; port?: number;
development?: boolean; development?: boolean;
middleware?: ( middleware?: (
@ -62,9 +62,14 @@ export type BunextConfig = {
| Response | Response
| Request | Request
| undefined; | undefined;
defaultCacheExpiry?: number; default_cache_expiry?: number;
websocket?: WebSocketHandler<any>; websocket?: WebSocketHandler<any>;
serverOptions?: Omit<Bun.Serve.Options<any>, "fetch" | "routes">; server_options?: Omit<Bun.Serve.Options<any>, "fetch" | "routes">;
/**
* These are patterns in the pages to exclude
* from the router
*/
pages_exclude_patterns?: RegExp[];
}; };
export type BunextConfigMiddlewareParams = { export type BunextConfigMiddlewareParams = {

View File

@ -0,0 +1,14 @@
import type { APIResponseObject } from "../types";
type Params = {
path: string;
};
export default function ({ path }: Params): boolean {
for (let i = 0; i < global.CONSTANTS.RouteIgnorePatterns.length; i++) {
const regex = global.CONSTANTS.RouteIgnorePatterns[i];
if (path.match(regex)) return true;
}
return false;
}

View File

@ -4,6 +4,7 @@ import path from "path";
import type { PageFiles } from "../types"; import type { PageFiles } from "../types";
import AppNames from "./grab-app-names"; import AppNames from "./grab-app-names";
import pagePathTransform from "./page-path-transform"; import pagePathTransform from "./page-path-transform";
import checkExcludedPatterns from "./check-excluded-patterns";
type Params = { type Params = {
exclude_api?: boolean; exclude_api?: boolean;
@ -49,7 +50,11 @@ function grabPageDirRecursively({ page_dir }: { page_dir: string }) {
continue; continue;
} }
if (page.match(/\(|\)|--|\/api\//)) { if (checkExcludedPatterns({ path: page })) {
continue;
}
if (page.match(/\/api\//)) {
continue; continue;
} }
@ -60,7 +65,7 @@ function grabPageDirRecursively({ page_dir }: { page_dir: string }) {
const page_stat = statSync(full_page_path); const page_stat = statSync(full_page_path);
if (page_stat.isDirectory()) { if (page_stat.isDirectory()) {
if (page.match(/\(|\)/)) continue; if (checkExcludedPatterns({ path: page })) continue;
const new_page_files = grabPageDirRecursively({ const new_page_files = grabPageDirRecursively({
page_dir: full_page_path, page_dir: full_page_path,
}); });

View File

@ -1,8 +1,8 @@
import AppNames from "./grab-app-names"; import AppNames from "./grab-app-names";
export default function grabAssetsPrefix() { export default function grabAssetsPrefix() {
if (global.CONFIG.assetsPrefix) { if (global.CONFIG.assets_prefix) {
return global.CONFIG.assetsPrefix; return global.CONFIG.assets_prefix;
} }
const { defaultAssetPrefix } = AppNames; const { defaultAssetPrefix } = AppNames;

View File

@ -9,6 +9,11 @@ export default function grabConstants() {
const ServerDefaultRequestBodyLimitBytes = MB_IN_BYTES * 10; const ServerDefaultRequestBodyLimitBytes = MB_IN_BYTES * 10;
const MaxBundlerRebuilds = 5; const MaxBundlerRebuilds = 5;
const RouteIgnorePatterns = [/\/\(/];
if (config.pages_exclude_patterns) {
RouteIgnorePatterns.push(...config.pages_exclude_patterns);
}
return { return {
ClientRootElementIDName, ClientRootElementIDName,
@ -18,5 +23,6 @@ export default function grabConstants() {
ClientRootComponentWindowName, ClientRootComponentWindowName,
MaxBundlerRebuilds, MaxBundlerRebuilds,
config, config,
RouteIgnorePatterns,
} as const; } as const;
} }