This commit is contained in:
2026-03-23 07:42:00 +01:00
parent 567fb4f746
commit 4b8b610e32
43 changed files with 635 additions and 169 deletions
+65 -23
View File
@@ -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");
});
});
@@ -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(() => {
@@ -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");
+4 -4
View File
@@ -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"),
);
});
@@ -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,
@@ -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;
+8 -1
View File
@@ -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 });
}