diff --git a/README.md b/README.md
index 4322e86..64df12c 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# Bunext
-A server-rendering framework for React, built on [Bun](https://bun.sh). Bunext handles file-system routing, SSR, HMR, and client hydration — using ESBuild to bundle client assets and `Bun.serve` as the HTTP server.
+A server-rendering framework for React, built entirely on [Bun](https://bun.sh). Bunext handles file-system routing, SSR, HMR, and client hydration — using `Bun.build` to bundle client assets and `Bun.serve` as the HTTP server.
## Philosophy
@@ -8,7 +8,7 @@ Bunext is focused on **server-side rendering and processing**. Every page is ren
The goal is a framework that is:
-- Fast — Bun's runtime speed and ESBuild's bundling make the full dev loop snappy
+- Fast — Bun's runtime speed and Bun.build's bundling make the full dev loop snappy
- Transparent — the entire request pipeline is readable and debugable
- Standard — server functions and API handlers use native Web APIs (`Request`, `Response`, `URL`) with no custom wrappers
@@ -61,7 +61,8 @@ The goal is a framework that is:
- [Bun](https://bun.sh) v1.0 or later
- TypeScript 5.0+
-- React 19 and react-dom 19 (peer dependencies)
+
+> **React is managed by Bunext.** You do not need to install `react` or `react-dom` — Bunext enforces its own pinned React version and removes any user-installed copies at startup to prevent version conflicts. Installing this package is all you need.
---
@@ -152,7 +153,7 @@ bun run dev
| Command | Description |
| -------------- | ---------------------------------------------------------------------- |
| `bunext dev` | Start the development server with HMR and file watching. |
-| `bunext build` | Bundle all pages for production. Outputs artifacts to `public/pages/`. |
+| `bunext build` | Bundle all pages for production. Outputs artifacts to `.bunext/public/pages/`. |
| `bunext start` | Start the production server using pre-built artifacts. |
### Running the CLI
@@ -186,7 +187,7 @@ bunext build
bunext start
```
-> **Note:** `bunext start` will exit with an error if `public/pages/map.json` does not exist. Always run `bunext build` (or `bun run build`) before `bunext start`.
+> **Note:** `bunext start` will exit with an error if `.bunext/public/pages/map.json` does not exist. Always run `bunext build` (or `bun run build`) before `bunext start`.
---
@@ -208,9 +209,10 @@ my-app/
│ │ └── [slug].tsx # Route: /blog/:slug (dynamic)
│ └── api/
│ └── users.ts # API route: /api/users
-├── public/ # Static files and bundler output
-│ └── __bunext/
-│ ├── pages/ # Generated by bundler (do not edit manually)
+├── public/ # Static files served at /public/*
+├── .bunext/ # Internal build artifacts (do not edit manually)
+│ └── public/
+│ ├── pages/ # Generated by bundler
│ │ └── map.json # Artifact map used by production server
│ └── cache/ # File-based HTML cache (production only)
├── bunext.config.ts # Optional configuration
@@ -605,7 +607,7 @@ public/
Bunext includes a file-based HTML cache for production. Caching is **disabled in development** — every request renders fresh. In production, a cron job runs every 30 seconds to delete expired cache entries.
-Cache files are stored in `public/__bunext/cache/`. Each cached page produces two files:
+Cache files are stored in `.bunext/public/cache/`. Each cached page produces two files:
| File | Contents |
| ----------------- | ---------------------------------------------- |
@@ -670,14 +672,14 @@ Expiry resolution order (first truthy value wins):
2. `defaultCacheExpiry` in `bunext.config.ts` (global default, in seconds)
3. Built-in default: **3600 seconds (1 hour)**
-The cron job checks all cache entries every 30 seconds and deletes any whose age exceeds their expiry. Static bundled assets (JS/CSS in `public/__bunext/`) receive a separate HTTP `Cache-Control: public, max-age=604800` header (7 days) via the browser cache — this is independent of the page HTML cache.
+The cron job checks all cache entries every 30 seconds and deletes any whose age exceeds their expiry. Static bundled assets (JS/CSS in `.bunext/public/`) receive a separate HTTP `Cache-Control: public, max-age=604800` header (7 days) via the browser cache — this is independent of the page HTML cache.
### Cache Behavior and Limitations
- **Production only.** Caching never activates in development (`bunext dev`).
- **Cold start required.** The cache is populated on the first request; there is no pre-warming step.
- **Immutable within the expiry window.** Once a page is cached, `writeCache` skips all subsequent write attempts for that key until the cron job deletes the expired entry. There is no manual invalidation API.
-- **Cache is not cleared on rebuild.** Deploying a new build does not automatically flush `public/__bunext/cache/`. Stale HTML files referencing old JS bundles can be served until they expire. Clear the cache directory as part of your deploy process if needed.
+- **Cache is not cleared on rebuild.** Deploying a new build does not automatically flush `.bunext/public/cache/`. Stale HTML files referencing old JS bundles can be served until they expire. Clear the cache directory as part of your deploy process if needed.
- **No key collision.** Cache keys are generated via `encodeURIComponent()` on the URL path. `/foo/bar` encodes to `%2Ffoo%2Fbar` and `/foo-bar` to `%2Ffoo-bar` — distinct filenames with no collision risk.
---
@@ -880,7 +882,7 @@ Running `bunext dev`:
1. Loads `bunext.config.ts` and sets `development: true`.
2. Initializes directories (`.bunext/`, `public/pages/`).
3. Creates a `Bun.FileSystemRouter` pointed at `src/pages/`.
-4. Starts the ESBuild bundler in **watch mode** — it will automatically rebuild when file content changes.
+4. Starts `Bun.build` in **watch mode** — it will automatically rebuild when file content changes.
5. Starts a file-system watcher on `src/` — when a file is created or deleted (a "rename" event), it triggers a full bundler rebuild to update the entry points.
6. Waits for the first successful bundle.
7. Starts `Bun.serve()`.
@@ -890,26 +892,26 @@ Running `bunext dev`:
Running `bunext build`:
1. Sets `NODE_ENV=production`.
-2. Runs ESBuild once (not in watch mode) with minification enabled.
-3. Writes all bundled artifacts to `public/pages/` and the artifact map to `public/pages/map.json`.
+2. Runs `Bun.build` once with minification enabled.
+3. Writes all bundled artifacts to `.bunext/public/pages/` and the artifact map to `.bunext/public/pages/map.json`.
4. Exits.
### Production Server
Running `bunext start`:
-1. Reads `public/pages/map.json` to load the pre-built artifact map.
+1. Reads `.bunext/public/pages/map.json` to load the pre-built artifact map.
2. Starts `Bun.serve()` without any bundler or file watcher.
### Bundler
-The bundler (`allPagesBundler`) uses ESBuild with three custom plugins:
+The bundler uses `Bun.build` with the `bun-plugin-tailwind` plugin. For each page, a client hydration entry point is generated and written as a real temporary file under `.bunext/hydration-src/`. Each entry imports the page component and calls `hydrateRoot()` against the server-rendered DOM node. If `src/pages/__root.tsx` exists, the page is wrapped in the root layout.
-- **`tailwindcss` plugin** — Processes any `.css` files through PostCSS + Tailwind CSS before bundling.
-- **`virtual-entrypoints` plugin** — Generates an in-memory client hydration entry point for each page. Each entry imports the page component and calls `hydrateRoot()` against the server-rendered DOM node. If `src/pages/__root.tsx` exists, the page is wrapped in the root layout.
-- **`artifact-tracker` plugin** — After each build, collects all output file paths, content hashes, and source entrypoints into a `BundlerCTXMap[]`. This map is stored in `global.BUNDLER_CTX_MAP` and written to `public/pages/map.json`.
+React is loaded externally — `react`, `react-dom`, `react-dom/client`, and `react/jsx-runtime` are all marked as external in the `Bun.build` config. The correct React version is resolved from the framework's own `node_modules` at startup and injected into every HTML page via a `\n`;
+ const serializedProps = (EJSON.stringify(pageProps || {}) || "{}").replace(/<\//g, "<\\/");
+ html += ` \n`;
if (bundledMap?.path) {
+ const dev = isDevelopment();
+ const devSuffix = dev ? "?dev" : "";
+ const importMap = JSON.stringify({
+ imports: {
+ react: `https://esm.sh/react@${_reactVersion}${devSuffix}`,
+ "react-dom": `https://esm.sh/react-dom@${_reactVersion}${devSuffix}`,
+ "react-dom/client": `https://esm.sh/react-dom@${_reactVersion}/client${devSuffix}`,
+ "react/jsx-runtime": `https://esm.sh/react@${_reactVersion}/jsx-runtime${devSuffix}`,
+ "react/jsx-dev-runtime": `https://esm.sh/react@${_reactVersion}/jsx-dev-runtime${devSuffix}`,
+ },
+ });
+ html += ` \n`;
html += ` \n`;
}
if (isDevelopment()) {
diff --git a/dist/functions/server/web-pages/grab-page-component.js b/dist/functions/server/web-pages/grab-page-component.js
index ba3d5ea..57089fd 100644
--- a/dist/functions/server/web-pages/grab-page-component.js
+++ b/dist/functions/server/web-pages/grab-page-component.js
@@ -33,7 +33,7 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
// log.error(errMsg);
throw new Error(errMsg);
}
- const bundledMap = global.BUNDLER_CTX_MAP[file_path];
+ const bundledMap = global.BUNDLER_CTX_MAP?.[file_path];
if (!bundledMap?.path) {
const errMsg = `No Bundled File Path for this request path!`;
log.error(errMsg);
diff --git a/dist/functions/server/web-pages/grab-page-error-component.js b/dist/functions/server/web-pages/grab-page-error-component.js
index 2881544..e9b5abf 100644
--- a/dist/functions/server/web-pages/grab-page-error-component.js
+++ b/dist/functions/server/web-pages/grab-page-error-component.js
@@ -11,8 +11,8 @@ export default async function grabPageErrorComponent({ error, routeParams, is404
const match = router.match(errorRoute);
const filePath = match?.filePath || presetComponent;
const bundledMap = match?.filePath
- ? global.BUNDLER_CTX_MAP[match.filePath]
- : {};
+ ? global.BUNDLER_CTX_MAP?.[match.filePath]
+ : undefined;
const module = await import(filePath);
const Component = module.default;
const component = _jsx(Component, { children: _jsx("span", { children: error.message }) });
@@ -41,7 +41,7 @@ export default async function grabPageErrorComponent({ error, routeParams, is404
component: _jsx(DefaultNotFound, {}),
routeParams,
module: { default: DefaultNotFound },
- bundledMap: {},
+ bundledMap: undefined,
serverRes: {
responseOptions: {
status: is404 ? 404 : 500,
diff --git a/dist/functions/server/web-pages/grab-web-meta-html.js b/dist/functions/server/web-pages/grab-web-meta-html.js
index 0ee566f..e49986c 100644
--- a/dist/functions/server/web-pages/grab-web-meta-html.js
+++ b/dist/functions/server/web-pages/grab-web-meta-html.js
@@ -1,60 +1,61 @@
+import { escape } from "lodash";
export default function grabWebMetaHTML({ meta }) {
let html = ``;
if (meta.title) {
- html += `
${meta.title}\n`;
+ html += ` ${escape(meta.title)}\n`;
}
if (meta.description) {
- html += ` \n`;
+ html += ` \n`;
}
if (meta.keywords) {
const keywords = Array.isArray(meta.keywords)
? meta.keywords.join(", ")
: meta.keywords;
- html += ` \n`;
+ html += ` \n`;
}
if (meta.author) {
- html += ` \n`;
+ html += ` \n`;
}
if (meta.robots) {
- html += ` \n`;
+ html += ` \n`;
}
if (meta.canonical) {
- html += ` \n`;
+ html += ` \n`;
}
if (meta.themeColor) {
- html += ` \n`;
+ html += ` \n`;
}
if (meta.og) {
const { og } = meta;
if (og.title)
- html += ` \n`;
+ html += ` \n`;
if (og.description)
- html += ` \n`;
+ html += ` \n`;
if (og.image)
- html += ` \n`;
+ html += ` \n`;
if (og.url)
- html += ` \n`;
+ html += ` \n`;
if (og.type)
- html += ` \n`;
+ html += ` \n`;
if (og.siteName)
- html += ` \n`;
+ html += ` \n`;
if (og.locale)
- html += ` \n`;
+ html += ` \n`;
}
if (meta.twitter) {
const { twitter } = meta;
if (twitter.card)
- html += ` \n`;
+ html += ` \n`;
if (twitter.title)
- html += ` \n`;
+ html += ` \n`;
if (twitter.description)
- html += ` \n`;
+ html += ` \n`;
if (twitter.image)
- html += ` \n`;
+ html += ` \n`;
if (twitter.site)
- html += ` \n`;
+ html += ` \n`;
if (twitter.creator)
- html += ` \n`;
+ html += ` \n`;
}
return html;
}
diff --git a/dist/utils/grab-all-pages.js b/dist/utils/grab-all-pages.js
index 2425de1..cbbd83b 100644
--- a/dist/utils/grab-all-pages.js
+++ b/dist/utils/grab-all-pages.js
@@ -48,7 +48,13 @@ function grabPageDirRecursively({ page_dir }) {
}
}
}
- return pages_files;
+ return pages_files.sort((a, b) => {
+ if (a.url_path === "/index")
+ return -1;
+ if (b.url_path === "/index")
+ return 1;
+ return 0;
+ });
}
function grabPageFileObject({ file_path, }) {
let url_path = file_path
diff --git a/dist/utils/grab-dir-names.d.ts b/dist/utils/grab-dir-names.d.ts
index 09ac9df..86029c6 100644
--- a/dist/utils/grab-dir-names.d.ts
+++ b/dist/utils/grab-dir-names.d.ts
@@ -20,4 +20,5 @@ export default function grabDirNames(): {
BUNEXT_CACHE_DIR: string;
BUNX_CWD_MODULE_CACHE_DIR: string;
BUNX_CWD_PAGES_REWRITE_DIR: string;
+ HYDRATION_DST_DIR_MAP_JSON_FILE_NAME: string;
};
diff --git a/dist/utils/grab-dir-names.js b/dist/utils/grab-dir-names.js
index 28cf1b6..3f02d0f 100644
--- a/dist/utils/grab-dir-names.js
+++ b/dist/utils/grab-dir-names.js
@@ -5,16 +5,17 @@ export default function grabDirNames() {
const PAGES_DIR = path.join(SRC_DIR, "pages");
const API_DIR = path.join(PAGES_DIR, "api");
const PUBLIC_DIR = path.join(ROOT_DIR, "public");
- const BUNEXT_PUBLIC_DIR = path.join(PUBLIC_DIR, "__bunext");
- const HYDRATION_DST_DIR = path.join(BUNEXT_PUBLIC_DIR, "pages");
- const BUNEXT_CACHE_DIR = path.join(BUNEXT_PUBLIC_DIR, "cache");
- 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_CWD_PAGES_REWRITE_DIR = path.resolve(BUNX_CWD_DIR, "pages");
const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp");
const BUNX_HYDRATION_SRC_DIR = path.resolve(BUNX_CWD_DIR, "client", "hydration-src");
+ const BUNEXT_PUBLIC_DIR = path.join(BUNX_CWD_DIR, "public");
+ const HYDRATION_DST_DIR = path.join(BUNEXT_PUBLIC_DIR, "pages");
+ const BUNEXT_CACHE_DIR = path.join(BUNEXT_PUBLIC_DIR, "cache");
+ const HYDRATION_DST_DIR_MAP_JSON_FILE_NAME = "map.json";
+ const HYDRATION_DST_DIR_MAP_JSON_FILE = path.join(HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE_NAME);
const BUNX_ROOT_DIR = path.resolve(__dirname, "../../");
const BUNX_ROOT_SRC_DIR = path.join(BUNX_ROOT_DIR, "src");
const BUNX_ROOT_PRESETS_DIR = path.join(BUNX_ROOT_SRC_DIR, "presets");
@@ -44,5 +45,6 @@ export default function grabDirNames() {
BUNEXT_CACHE_DIR,
BUNX_CWD_MODULE_CACHE_DIR,
BUNX_CWD_PAGES_REWRITE_DIR,
+ HYDRATION_DST_DIR_MAP_JSON_FILE_NAME,
};
}
diff --git a/src/__tests__/e2e/e2e.test.ts b/src/__tests__/e2e/e2e.test.ts
index c8f0969..ef90181 100644
--- a/src/__tests__/e2e/e2e.test.ts
+++ b/src/__tests__/e2e/e2e.test.ts
@@ -1,21 +1,60 @@
import { describe, expect, test, beforeAll, afterAll } from "bun:test";
import startServer from "../../../src/functions/server/start-server";
-import bunextInit from "../../../src/functions/bunext-init";
+import rewritePagesModule from "../../../src/utils/rewrite-pages-module";
+import pagePathTransform from "../../../src/utils/page-path-transform";
import path from "path";
import fs from "fs";
+// Fixture lives under test/ so the framework's directory guard allows it
+const fixtureDir = path.resolve(__dirname, "../../../test/e2e-fixture");
+const fixturePagesDir = path.join(fixtureDir, "src", "pages");
+const fixtureIndexPage = path.join(fixturePagesDir, "index.tsx");
+
+// The rewritten page path (inside .bunext/pages, stripped of server logic)
+const rewrittenIndexPage = pagePathTransform({ page_path: fixtureIndexPage });
+
let originalCwd = process.cwd();
+let originalPort: string | undefined;
describe("E2E Integration", () => {
let server: any;
-
+
beforeAll(async () => {
- // Change to the fixture directory to simulate actual user repo
- const fixtureDir = path.resolve(__dirname, "../__fixtures__/app");
+ originalPort = process.env.PORT;
+ // Use port 0 so Bun.serve picks a random available port
+ process.env.PORT = "0";
+
process.chdir(fixtureDir);
-
- // Mock grabAppPort to assign dynamically to avoid port conflicts
+
global.CONFIG = { development: true };
+ global.HMR_CONTROLLERS = [];
+ global.BUNDLER_REBUILDS = 0;
+ global.PAGE_FILES = [];
+
+ // Set up router pointing at the fixture's pages directory
+ global.ROUTER = new Bun.FileSystemRouter({
+ style: "nextjs",
+ dir: fixturePagesDir,
+ });
+
+ // Rewrite the fixture page (strips server logic) into .bunext/pages
+ // so that grab-page-react-component-string can resolve the import
+ await rewritePagesModule({ page_file_path: fixtureIndexPage });
+
+ // Pre-populate the bundler context map so grab-page-component can
+ // look up the compiled path. The `path` value only needs to be
+ // present for the guard check; SSR does not require the file to exist.
+ global.BUNDLER_CTX_MAP = {
+ [fixtureIndexPage]: {
+ path: ".bunext/public/pages/index.js",
+ hash: "index",
+ type: "text/javascript",
+ entrypoint: fixtureIndexPage,
+ local_path: fixtureIndexPage,
+ url_path: "/",
+ file_name: "index",
+ },
+ };
});
afterAll(async () => {
@@ -23,32 +62,35 @@ describe("E2E Integration", () => {
server.stop(true);
}
process.chdir(originalCwd);
-
- // Ensure to remove the dummy generated .bunext folder
- const dotBunext = path.resolve(__dirname, "../__fixtures__/app/.bunext");
+
+ // Restore PORT env variable
+ if (originalPort !== undefined) {
+ process.env.PORT = originalPort;
+ } else {
+ delete process.env.PORT;
+ }
+
+ // Remove the rewritten page created during setup
+ const rewrittenDir = path.dirname(rewrittenIndexPage);
+ if (fs.existsSync(rewrittenDir)) {
+ fs.rmSync(rewrittenDir, { recursive: true, force: true });
+ }
+
+ // Remove any generated .bunext artifacts from the fixture
+ const dotBunext = path.join(fixtureDir, ".bunext");
if (fs.existsSync(dotBunext)) {
fs.rmSync(dotBunext, { recursive: true, force: true });
}
- const pubBunext = path.resolve(__dirname, "../__fixtures__/app/public/__bunext");
- if (fs.existsSync(pubBunext)) {
- fs.rmSync(pubBunext, { recursive: true, force: true });
- }
});
test("boots up the server and correctly routes to index.tsx page", async () => {
- // Mock to randomize port
- // Note: Bun test runs modules in isolation but startServer imports grab-app-port
- // If we can't easily mock we can set PORT env
- process.env.PORT = "0"; // Let Bun.serve pick port
-
- await bunextInit();
server = await startServer();
expect(server).toBeDefined();
-
- // Fetch the index page
+ expect(server.port).toBeGreaterThan(0);
+
const response = await fetch(`http://localhost:${server.port}/`);
expect(response.status).toBe(200);
-
+
const html = await response.text();
expect(html).toContain("Hello E2E");
});
@@ -57,7 +99,7 @@ describe("E2E Integration", () => {
const response = await fetch(`http://localhost:${server.port}/unknown-foo-bar123`);
expect(response.status).toBe(404);
const text = await response.text();
- // Assume default 404 preset component is rendered
+ // Default 404 component is rendered
expect(text).toContain("404");
});
});
diff --git a/src/__tests__/functions/server/handle-hmr.test.ts b/src/__tests__/functions/server/handle-hmr.test.ts
index a2ec590..9026b37 100644
--- a/src/__tests__/functions/server/handle-hmr.test.ts
+++ b/src/__tests__/functions/server/handle-hmr.test.ts
@@ -10,9 +10,9 @@ describe("handle-hmr", () => {
}
} as any;
global.HMR_CONTROLLERS = [];
- global.BUNDLER_CTX_MAP = [
- { local_path: "/test-file" } as any
- ];
+ global.BUNDLER_CTX_MAP = {
+ "/test-file": { local_path: "/test-file" } as any,
+ };
});
afterEach(() => {
diff --git a/src/__tests__/functions/server/handle-routes.test.ts b/src/__tests__/functions/server/handle-routes.test.ts
index c813c23..e76af50 100644
--- a/src/__tests__/functions/server/handle-routes.test.ts
+++ b/src/__tests__/functions/server/handle-routes.test.ts
@@ -40,11 +40,11 @@ describe("handle-routes", () => {
mock.restore();
});
- test("returns 401 for unknown route", async () => {
+ test("returns 404 for unknown route", async () => {
const req = new Request("http://localhost/api/unknown");
const res = await handleRoutes({ req });
-
- expect(res.status).toBe(401);
+
+ expect(res.status).toBe(404);
const json = await res.json();
expect(json.success).toBe(false);
expect(json.msg).toContain("not found");
diff --git a/src/__tests__/utils/grab-dir-names.test.ts b/src/__tests__/utils/grab-dir-names.test.ts
index 2e18387..3e48e4d 100644
--- a/src/__tests__/utils/grab-dir-names.test.ts
+++ b/src/__tests__/utils/grab-dir-names.test.ts
@@ -14,17 +14,17 @@ describe("grabDirNames", () => {
expect(dirs.PUBLIC_DIR).toBe(path.join(cwd, "public"));
});
- it("nests HYDRATION_DST_DIR under public/__bunext/pages", () => {
+ it("nests HYDRATION_DST_DIR under .bunext/public/pages", () => {
const dirs = grabDirNames();
expect(dirs.HYDRATION_DST_DIR).toBe(
- path.join(dirs.PUBLIC_DIR, "__bunext", "pages"),
+ path.join(dirs.BUNX_CWD_DIR, "public", "pages"),
);
});
- it("nests BUNEXT_CACHE_DIR under public/__bunext/cache", () => {
+ it("nests BUNEXT_CACHE_DIR under .bunext/public/cache", () => {
const dirs = grabDirNames();
expect(dirs.BUNEXT_CACHE_DIR).toBe(
- path.join(dirs.PUBLIC_DIR, "__bunext", "cache"),
+ path.join(dirs.BUNX_CWD_DIR, "public", "cache"),
);
});
diff --git a/src/functions/bundler/all-pages-bun-bundler.ts b/src/functions/bundler/all-pages-bun-bundler.ts
index 3078b18..3725b67 100644
--- a/src/functions/bundler/all-pages-bun-bundler.ts
+++ b/src/functions/bundler/all-pages-bun-bundler.ts
@@ -71,6 +71,7 @@ export default async function allPagesBunBundler(params?: Params) {
chunk: "chunks/[hash].[ext]",
},
plugins: [tailwindcss],
+ // plugins: [tailwindcss, BunSkipNonBrowserPlugin],
splitting: true,
target,
metafile: true,
diff --git a/src/functions/bundler/plugins/bun-skip-browser-plugin.ts b/src/functions/bundler/plugins/bun-skip-browser-plugin.ts
new file mode 100644
index 0000000..23db2e7
--- /dev/null
+++ b/src/functions/bundler/plugins/bun-skip-browser-plugin.ts
@@ -0,0 +1,35 @@
+const BunSkipNonBrowserPlugin: Bun.BunPlugin = {
+ name: "skip-non-browser",
+ setup(build) {
+ build.onResolve({ filter: /^(bun:|node:)/ }, (args) => {
+ return { path: args.path, external: true };
+ });
+
+ build.onResolve({ filter: /^[^./]/ }, (args) => {
+ // If it's a built-in like 'fs' or 'path', skip it immediately
+ const excludes = [
+ "fs",
+ "path",
+ "os",
+ "crypto",
+ "net",
+ "events",
+ "util",
+ ];
+
+ if (excludes.includes(args.path) || args.path.startsWith("node:")) {
+ return { path: args.path, external: true };
+ }
+
+ try {
+ Bun.resolveSync(args.path, args.importer || process.cwd());
+ return null;
+ } catch (e) {
+ console.warn(`[Skip] Mark as external: ${args.path}`);
+ return { path: args.path, external: true };
+ }
+ });
+ },
+};
+
+export default BunSkipNonBrowserPlugin;
diff --git a/src/functions/init.ts b/src/functions/init.ts
index 6cb51f4..6a18c36 100644
--- a/src/functions/init.ts
+++ b/src/functions/init.ts
@@ -3,6 +3,7 @@ import grabDirNames from "../utils/grab-dir-names";
import path from "path";
import grabConfig from "./grab-config";
import type { BunextConfig } from "../types";
+import { log } from "../utils/log";
export default async function () {
const dirNames = grabDirNames();
@@ -29,7 +30,13 @@ export default async function () {
"react-dom",
);
- if (dirNames.BUNX_ROOT_DIR !== dirNames.ROOT_DIR) {
+ if (
+ dirNames.ROOT_DIR.startsWith(dirNames.BUNX_ROOT_DIR) &&
+ !dirNames.ROOT_DIR.includes(`${dirNames.BUNX_ROOT_DIR}/test/`)
+ ) {
+ log.error(`Can't Run From this Directory => ${dirNames.ROOT_DIR}`);
+ process.exit(1);
+ } else {
rmSync(react_package_dir, { recursive: true });
rmSync(react_dom_package_dir, { recursive: true });
}