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"
},