From f722d4ae47f90f7b5119d74325083b267aa41f65 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Thu, 19 Mar 2026 05:27:31 +0100 Subject: [PATCH] Update dist --- dist/commands/build/index.js | 4 +- dist/commands/dev/index.js | 4 +- dist/commands/start/index.js | 4 +- dist/functions/bundler/all-pages-bundler.js | 7 +- dist/functions/init.js | 1 + dist/functions/server/rebuild-bundler.js | 3 +- dist/functions/server/start-server.js | 7 +- dist/functions/server/watcher.js | 5 +- .../server/web-pages/grab-file-path-module.js | 46 +++++++++++++ .../grab-page-bundled-react-component.js | 34 +++++++++ .../server/web-pages/grab-page-component.js | 47 +++++++++---- .../web-pages/grab-tsx-string-module.js | 55 +++++++++++++++ dist/presets/server-error.js | 8 ++- dist/utils/exit-with-error.js | 3 +- dist/utils/grab-app-names.js | 1 + dist/utils/grab-dir-names.js | 2 + dist/utils/log.js | 20 ++++++ dist/utils/register-dev-plugin.js | 69 +++++++++++++++++++ package.json | 2 +- 19 files changed, 296 insertions(+), 26 deletions(-) create mode 100644 dist/functions/server/web-pages/grab-file-path-module.js create mode 100644 dist/functions/server/web-pages/grab-page-bundled-react-component.js create mode 100644 dist/functions/server/web-pages/grab-tsx-string-module.js create mode 100644 dist/utils/log.js create mode 100644 dist/utils/register-dev-plugin.js diff --git a/dist/commands/build/index.js b/dist/commands/build/index.js index 6b2ffac..4bd5d74 100644 --- a/dist/commands/build/index.js +++ b/dist/commands/build/index.js @@ -2,11 +2,13 @@ import { Command } from "commander"; import grabConfig from "../../functions/grab-config"; import init from "../../functions/init"; import allPagesBundler from "../../functions/bundler/all-pages-bundler"; +import { log } from "../../utils/log"; export default function () { return new Command("build") .description("Build Project") .action(async () => { - console.log(`Building Project ...`); + log.banner(); + log.build("Building Project ..."); process.env.NODE_ENV = "production"; await init(); const config = (await grabConfig()) || {}; diff --git a/dist/commands/dev/index.js b/dist/commands/dev/index.js index d7375ad..849c67d 100644 --- a/dist/commands/dev/index.js +++ b/dist/commands/dev/index.js @@ -2,11 +2,13 @@ import { Command } from "commander"; import grabConfig from "../../functions/grab-config"; import startServer from "../../functions/server/start-server"; import init from "../../functions/init"; +import { log } from "../../utils/log"; export default function () { return new Command("dev") .description("Run development server") .action(async () => { - console.log(`Running development server ...`); + log.banner(); + log.info("Running development server ..."); await init(); const config = (await grabConfig()) || {}; global.CONFIG = { diff --git a/dist/commands/start/index.js b/dist/commands/start/index.js index f75d9c7..8653aa7 100644 --- a/dist/commands/start/index.js +++ b/dist/commands/start/index.js @@ -2,11 +2,13 @@ import { Command } from "commander"; import grabConfig from "../../functions/grab-config"; import startServer from "../../functions/server/start-server"; import init from "../../functions/init"; +import { log } from "../../utils/log"; export default function () { return new Command("start") .description("Start production server") .action(async () => { - console.log(`Starting production server ...`); + log.banner(); + log.info("Starting production server ..."); await init(); const config = await grabConfig(); global.CONFIG = { ...config }; diff --git a/dist/functions/bundler/all-pages-bundler.js b/dist/functions/bundler/all-pages-bundler.js index c9698a7..9df48a7 100644 --- a/dist/functions/bundler/all-pages-bundler.js +++ b/dist/functions/bundler/all-pages-bundler.js @@ -10,6 +10,7 @@ import AppNames from "../../utils/grab-app-names"; import isDevelopment from "../../utils/is-development"; import { execSync } from "child_process"; import grabConstants from "../../utils/grab-constants"; +import { log } from "../../utils/log"; const { HYDRATION_DST_DIR, PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames(); const tailwindPlugin = { name: "tailwindcss", @@ -69,8 +70,9 @@ export default async function allPagesBundler(params) { const artifactTracker = { name: "artifact-tracker", setup(build) { + let buildStart = 0; build.onStart(() => { - console.time("build"); + buildStart = performance.now(); }); build.onEnd((result) => { if (result.errors.length > 0) @@ -105,7 +107,8 @@ export default async function allPagesBundler(params) { params?.post_build_fn?.({ artifacts: final_artifacts }); writeFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, JSON.stringify(artifacts)); } - console.timeEnd("build"); + const elapsed = (performance.now() - buildStart).toFixed(0); + log.success(`Built in ${elapsed}ms`); if (params?.exit_after_first_build) { process.exit(); } diff --git a/dist/functions/init.js b/dist/functions/init.js index 28cfea2..2db331a 100644 --- a/dist/functions/init.js +++ b/dist/functions/init.js @@ -4,6 +4,7 @@ import { execSync } from "child_process"; export default async function () { const dirNames = grabDirNames(); execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`); + execSync(`rm -rf ${dirNames.BUNX_CWD_MODULE_CACHE_DIR}`); const keys = Object.keys(dirNames); for (let i = 0; i < keys.length; i++) { const key = keys[i]; diff --git a/dist/functions/server/rebuild-bundler.js b/dist/functions/server/rebuild-bundler.js index 8057be8..7dab958 100644 --- a/dist/functions/server/rebuild-bundler.js +++ b/dist/functions/server/rebuild-bundler.js @@ -1,5 +1,6 @@ import allPagesBundler from "../bundler/all-pages-bundler"; import serverPostBuildFn from "./server-post-build-fn"; +import { log } from "../../utils/log"; export default async function rebuildBundler() { try { global.ROUTER.reload(); @@ -11,6 +12,6 @@ export default async function rebuildBundler() { }); } catch (error) { - console.error(error); + log.error(error); } } diff --git a/dist/functions/server/start-server.js b/dist/functions/server/start-server.js index be91117..d331752 100644 --- a/dist/functions/server/start-server.js +++ b/dist/functions/server/start-server.js @@ -1,5 +1,6 @@ import _ from "lodash"; import AppNames from "../../utils/grab-app-names"; +import { log } from "../../utils/log"; import allPagesBundler from "../bundler/all-pages-bundler"; import serverParamsGen from "./server-params-gen"; import watcher from "./watcher"; @@ -22,7 +23,7 @@ export default async function startServer(params) { else { const artifacts = EJSON.parse(readFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, "utf-8")); if (!artifacts?.[0]) { - console.error(`Please build first.`); + log.error("Please build first."); process.exit(1); } global.BUNDLER_CTX_MAP = artifacts; @@ -33,7 +34,7 @@ export default async function startServer(params) { const MAX_BUNDLE_READY_RETRIES = 10; while (!global.IS_FIRST_BUNDLE_READY) { if (bundle_ready_retries > MAX_BUNDLE_READY_RETRIES) { - console.error(`Couldn't grab first bundle for dev environment`); + log.error("Couldn't grab first bundle for dev environment"); process.exit(1); } bundle_ready_retries++; @@ -41,6 +42,6 @@ export default async function startServer(params) { } const server = Bun.serve(serverParams); global.SERVER = server; - console.log(`${name} Server Running on http://localhost:${server.port} ...`); + log.server(`http://localhost:${server.port}`); return server; } diff --git a/dist/functions/server/watcher.js b/dist/functions/server/watcher.js index 5fa1478..58172d8 100644 --- a/dist/functions/server/watcher.js +++ b/dist/functions/server/watcher.js @@ -2,6 +2,7 @@ import { watch, existsSync } from "fs"; import path from "path"; import grabDirNames from "../../utils/grab-dir-names"; import rebuildBundler from "./rebuild-bundler"; +import { log } from "../../utils/log"; const { SRC_DIR } = grabDirNames(); export default function watcher() { watch(SRC_DIR, { @@ -18,11 +19,11 @@ export default function watcher() { const action = existsSync(fullPath) ? "created" : "deleted"; try { global.RECOMPILING = true; - console.log(`Page ${action}: ${filename}. Rebuilding ...`); + log.watch(`Page ${action}: ${filename}. Rebuilding ...`); await rebuildBundler(); } catch (error) { - console.error(error); + log.error(error); } finally { global.RECOMPILING = false; diff --git a/dist/functions/server/web-pages/grab-file-path-module.js b/dist/functions/server/web-pages/grab-file-path-module.js new file mode 100644 index 0000000..557a60c --- /dev/null +++ b/dist/functions/server/web-pages/grab-file-path-module.js @@ -0,0 +1,46 @@ +import isDevelopment from "../../../utils/is-development"; +import * as esbuild from "esbuild"; +import postcss from "postcss"; +import tailwindcss from "@tailwindcss/postcss"; +import { readFile } from "fs/promises"; +import grabDirNames from "../../../utils/grab-dir-names"; +import path from "path"; +const tailwindPlugin = { + name: "tailwindcss", + setup(build) { + build.onLoad({ filter: /\.css$/ }, async (args) => { + const source = await readFile(args.path, "utf-8"); + const result = await postcss([tailwindcss()]).process(source, { + from: args.path, + }); + return { + contents: result.css, + loader: "css", + }; + }); + }, +}; +export default async function grabFilePathModule({ file_path, }) { + const dev = isDevelopment(); + const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); + const target_cache_file_path = path.join(BUNX_CWD_MODULE_CACHE_DIR, `${path.basename(file_path)}.js`); + await esbuild.build({ + entryPoints: [file_path], + bundle: true, + format: "esm", + target: "es2020", + platform: "node", + external: ["react", "react-dom"], + minify: true, + define: { + "process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"), + }, + metafile: true, + plugins: [tailwindPlugin], + jsx: "automatic", + outfile: target_cache_file_path, + }); + Loader.registry.delete(target_cache_file_path); + const module = await import(`${target_cache_file_path}?t=${Date.now()}`); + return module; +} diff --git a/dist/functions/server/web-pages/grab-page-bundled-react-component.js b/dist/functions/server/web-pages/grab-page-bundled-react-component.js new file mode 100644 index 0000000..8d53a0e --- /dev/null +++ b/dist/functions/server/web-pages/grab-page-bundled-react-component.js @@ -0,0 +1,34 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import EJSON from "../../../utils/ejson"; +import grabTsxStringModule from "./grab-tsx-string-module"; +export default async function grabPageBundledReactComponent({ file_path, root_file, server_res, }) { + try { + let tsx = ``; + const server_res_json = EJSON.stringify(server_res || {})?.replace(/"/g, '\\"'); + if (root_file) { + tsx += `import Root from "${root_file}"\n`; + } + tsx += `import Page from "${file_path}"\n`; + tsx += `export default function Main() {\n\n`; + tsx += `const props = JSON.parse("${server_res_json}")\n\n`; + tsx += ` return (\n`; + if (root_file) { + tsx += ` \n`; + } + else { + tsx += ` \n`; + } + tsx += ` )\n`; + tsx += `}\n`; + const mod = await grabTsxStringModule({ tsx, file_path }); + const Main = mod.default; + const component = _jsx(Main, {}); + return { + component, + server_res, + }; + } + catch (error) { + return undefined; + } +} diff --git a/dist/functions/server/web-pages/grab-page-component.js b/dist/functions/server/web-pages/grab-page-component.js index 7b770ef..9a29a83 100644 --- a/dist/functions/server/web-pages/grab-page-component.js +++ b/dist/functions/server/web-pages/grab-page-component.js @@ -1,10 +1,10 @@ -import { jsx as _jsx } from "react/jsx-runtime"; import grabDirNames from "../../../utils/grab-dir-names"; import grabRouteParams from "../../../utils/grab-route-params"; import path from "path"; import AppNames from "../../../utils/grab-app-names"; import { existsSync } from "fs"; import grabPageErrorComponent from "./grab-page-error-component"; +import grabPageBundledReactComponent from "./grab-page-bundled-react-component"; class NotFoundError extends Error { } export default async function grabPageComponent({ req, file_path: passed_file_path, }) { @@ -34,7 +34,6 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa console.error(errMsg); throw new Error(errMsg); } - // const pageName = grabPageName({ path: file_path }); const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`; const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`; const root_pages_component_js_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.js`; @@ -49,14 +48,7 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa ? root_pages_component_js_file : undefined; const now = Date.now(); - const root_module = root_file - ? await import(`${root_file}?t=${now}`) - : undefined; - const RootComponent = root_module?.default; - // const component_file_path = root_module - // ? `${file_path}` - // : `${file_path}?t=${global.LAST_BUILD_TIME ?? 0}`; - const module = await import(`${file_path}?t=${now}`); + const module = await import(file_path); const serverRes = await (async () => { try { if (routeParams) { @@ -86,9 +78,15 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa ? module.meta : undefined : undefined; - const Component = module.default; const Head = module.Head; - const component = RootComponent ? (_jsx(RootComponent, { ...serverRes, children: _jsx(Component, { ...serverRes }) })) : (_jsx(Component, { ...serverRes })); + const { component } = (await grabPageBundledReactComponent({ + file_path, + root_file, + server_res: serverRes, + })) || {}; + if (!component) { + throw new Error(`Couldn't grab page component`); + } return { component, serverRes, @@ -107,3 +105,28 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa }); } } +// let root_module: any; +// if (root_file) { +// if (isDevelopment()) { +// root_module = await grabFilePathModule({ +// file_path: root_file, +// }); +// } else { +// root_module = root_file ? await import(root_file) : undefined; +// } +// } +// const RootComponent = root_module?.default as FC | undefined; +// let module: BunextPageModule; +// if (isDevelopment()) { +// module = await grabFilePathModule({ file_path }); +// } else { +// module = await import(file_path); +// } +// const Component = main_module.default as FC; +// const component = RootComponent ? ( +// +// +// +// ) : ( +// +// ); diff --git a/dist/functions/server/web-pages/grab-tsx-string-module.js b/dist/functions/server/web-pages/grab-tsx-string-module.js new file mode 100644 index 0000000..5b4b30b --- /dev/null +++ b/dist/functions/server/web-pages/grab-tsx-string-module.js @@ -0,0 +1,55 @@ +import isDevelopment from "../../../utils/is-development"; +import * as esbuild from "esbuild"; +import postcss from "postcss"; +import tailwindcss from "@tailwindcss/postcss"; +import { readFile } from "fs/promises"; +import grabDirNames from "../../../utils/grab-dir-names"; +import path from "path"; +import { execSync } from "child_process"; +const tailwindPlugin = { + name: "tailwindcss", + setup(build) { + build.onLoad({ filter: /\.css$/ }, async (args) => { + const source = await readFile(args.path, "utf-8"); + const result = await postcss([tailwindcss()]).process(source, { + from: args.path, + }); + return { + contents: result.css, + loader: "css", + }; + }); + }, +}; +export default async function grabTsxStringModule({ tsx, file_path, }) { + const dev = isDevelopment(); + const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames(); + const trimmed_file_path = file_path + .replace(/.*\/src\/pages\//, "") + .replace(/\.tsx$/, ""); + const out_file_path = path.join(BUNX_CWD_MODULE_CACHE_DIR, `${trimmed_file_path}.js`); + await esbuild.build({ + stdin: { + contents: tsx, + resolveDir: process.cwd(), + loader: "tsx", + }, + bundle: true, + format: "esm", + target: "es2020", + platform: "node", + external: ["react", "react-dom"], + minify: true, + define: { + "process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"), + }, + metafile: true, + plugins: [tailwindPlugin], + jsx: "automatic", + write: true, + outfile: out_file_path, + }); + Loader.registry.delete(out_file_path); + const module = await import(`${out_file_path}?t=${Date.now()}`); + return module; +} diff --git a/dist/presets/server-error.js b/dist/presets/server-error.js index ece79b0..dc5ef2a 100644 --- a/dist/presets/server-error.js +++ b/dist/presets/server-error.js @@ -9,5 +9,11 @@ export default function DefaultServerErrorPage({ children, }) { justifyContent: "center", flexDirection: "column", gap: "20px", - }, children: [_jsx("h1", { children: "500 Internal Server Error" }), _jsx("span", { children: children })] })); + }, children: [_jsx("h1", { children: "500 Internal Server Error" }), _jsx("div", { style: { + maxWidth: "800px", + overflowWrap: "break-word", + wordBreak: "break-all", + maxHeight: "80vh", + overflowY: "auto", + }, children: children })] })); } diff --git a/dist/utils/exit-with-error.js b/dist/utils/exit-with-error.js index 221bf53..f8fd7b3 100644 --- a/dist/utils/exit-with-error.js +++ b/dist/utils/exit-with-error.js @@ -1,4 +1,5 @@ +import { log } from "./log"; export default function exitWithError(msg, code) { - console.error(msg); + log.error(msg); process.exit(code || 1); } diff --git a/dist/utils/grab-app-names.js b/dist/utils/grab-app-names.js index f6f5acb..ea7580d 100644 --- a/dist/utils/grab-app-names.js +++ b/dist/utils/grab-app-names.js @@ -2,6 +2,7 @@ const AppNames = { defaultPort: 7000, defaultAssetPrefix: "_bunext/static", name: "Bunext", + version: "1.0.1", defaultDistDir: ".bunext", RootPagesComponentName: "__root", }; diff --git a/dist/utils/grab-dir-names.js b/dist/utils/grab-dir-names.js index f19dd28..37c354e 100644 --- a/dist/utils/grab-dir-names.js +++ b/dist/utils/grab-dir-names.js @@ -11,6 +11,7 @@ export default function grabDirNames() { const HYDRATION_DST_DIR_MAP_JSON_FILE = path.join(HYDRATION_DST_DIR, "map.json"); const CONFIG_FILE = path.join(ROOT_DIR, "bunext.config.ts"); const BUNX_CWD_DIR = path.resolve(ROOT_DIR, ".bunext"); + const BUNX_CWD_MODULE_CACHE_DIR = path.resolve(BUNX_CWD_DIR, "module-cache"); const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp"); const BUNX_HYDRATION_SRC_DIR = path.resolve(BUNX_CWD_DIR, "client", "hydration-src"); const BUNX_ROOT_DIR = path.resolve(__dirname, "../../"); @@ -39,5 +40,6 @@ export default function grabDirNames() { BUNX_ROOT_404_FILE_NAME, HYDRATION_DST_DIR_MAP_JSON_FILE, BUNEXT_CACHE_DIR, + BUNX_CWD_MODULE_CACHE_DIR, }; } diff --git a/dist/utils/log.js b/dist/utils/log.js new file mode 100644 index 0000000..2d53a26 --- /dev/null +++ b/dist/utils/log.js @@ -0,0 +1,20 @@ +import chalk from "chalk"; +import AppNames from "./grab-app-names"; +const prefix = { + info: chalk.cyan.bold("ℹ"), + success: chalk.green.bold("✓"), + error: chalk.red.bold("✗"), + warn: chalk.yellow.bold("⚠"), + build: chalk.magenta.bold("⚙"), + watch: chalk.blue.bold("◉"), +}; +export const log = { + info: (msg) => console.log(`${prefix.info} ${chalk.white(msg)}`), + success: (msg) => console.log(`${prefix.success} ${chalk.green(msg)}`), + error: (msg) => console.error(`${prefix.error} ${chalk.red(String(msg))}`), + warn: (msg) => console.warn(`${prefix.warn} ${chalk.yellow(msg)}`), + build: (msg) => console.log(`${prefix.build} ${chalk.magenta(msg)}`), + watch: (msg) => console.log(`${prefix.watch} ${chalk.blue(msg)}`), + server: (url) => console.log(`${prefix.success} ${chalk.white("Server running on")} ${chalk.cyan.underline(url)}`), + banner: () => console.log(`\n ${chalk.cyan.bold(AppNames.name)} ${chalk.gray(`v${AppNames.version}`)}\n`), +}; diff --git a/dist/utils/register-dev-plugin.js b/dist/utils/register-dev-plugin.js new file mode 100644 index 0000000..d738286 --- /dev/null +++ b/dist/utils/register-dev-plugin.js @@ -0,0 +1,69 @@ +import { resolve, dirname, extname } from "path"; +import { existsSync } from "fs"; +const SOURCE_EXTENSIONS = [".tsx", ".ts", ".jsx", ".js"]; +function getLoader(filePath) { + const ext = extname(filePath).slice(1); + return SOURCE_EXTENSIONS.map((e) => e.slice(1)).includes(ext) ? ext : "js"; +} +function tryResolveSync(absPath) { + if (existsSync(absPath)) + return absPath; + for (const ext of SOURCE_EXTENSIONS) { + const p = absPath + ext; + if (existsSync(p)) + return p; + } + for (const ext of SOURCE_EXTENSIONS) { + const p = resolve(absPath, "index" + ext); + if (existsSync(p)) + return p; + } + return null; +} +export default function registerDevPlugin() { + Bun.plugin({ + name: "bunext-dev-hmr", + setup(build) { + // Intercept absolute-path imports that already carry ?t= (our dynamic imports) + build.onResolve({ filter: /\?t=\d+$/ }, (args) => { + if (args.path.includes("node_modules")) + return undefined; + const cleanPath = args.path.replace(/\?t=\d+$/, ""); + const resolved = tryResolveSync(cleanPath); + if (!resolved) + return undefined; + if (!SOURCE_EXTENSIONS.some((e) => resolved.endsWith(e))) + return undefined; + return { + path: `${resolved}?t=${global.LAST_BUILD_TIME ?? 0}`, + namespace: "bunext-dev", + }; + }); + // Intercept relative imports from within bunext-dev modules + build.onResolve({ filter: /^\./ }, (args) => { + if (!/\?t=\d+/.test(args.importer)) + return undefined; + // Strip "namespace:" prefix (e.g. "bunext-dev:") Bun prepends to importer + const cleanImporter = args.importer + .replace(/^[^/]+:(?=\/)/, "") + .replace(/\?t=\d+$/, ""); + const base = resolve(dirname(cleanImporter), args.path); + const resolved = tryResolveSync(base); + if (!resolved) + return undefined; + if (!SOURCE_EXTENSIONS.some((e) => resolved.endsWith(e))) + return undefined; + return { + path: `${resolved}?t=${global.LAST_BUILD_TIME ?? 0}`, + namespace: "bunext-dev", + }; + }); + // Load files in the bunext-dev namespace from disk (async is fine in onLoad) + build.onLoad({ filter: /.*/, namespace: "bunext-dev" }, async (args) => { + const realPath = args.path.replace(/\?t=\d+$/, ""); + const source = await Bun.file(realPath).text(); + return { contents: source, loader: getLoader(realPath) }; + }); + }, + }); +} diff --git a/package.json b/package.json index 91a6c52..38e28b2 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@moduletrace/bunext", "module": "index.ts", "type": "module", - "version": "1.0.3", + "version": "1.0.4", "bin": { "bunext": "dist/index.js" },