Compare commits

...
74 Commits
Author SHA1 Message Date
tben 1bf1b651db Update dev server 2026-08-14 13:12:35 +01:00
tben 247a64c873 Update dev server 2026-08-14 12:59:20 +01:00
tben f590deb11b Update dev server 2026-08-14 09:58:12 +01:00
tben 3426c7b53b Update dev server 2026-08-14 09:57:22 +01:00
tben afd1af1827 Update dev server 2026-08-14 09:55:57 +01:00
tben 22d3dedab2 Update page server types 2026-08-14 06:49:12 +01:00
tben 8d12329f01 Add perpetual watcher reload in dev 2026-08-13 05:39:14 +01:00
tben 78e86b3999 Rename global variables 2026-08-03 12:01:40 +01:00
tben 87948340b0 Add public envs 2026-08-03 08:42:33 +01:00
tben 86ea86e7bd Updates 2026-08-01 05:25:47 +01:00
tben 596b9de047 Add request cookie to HMR reload pipeline 2026-07-31 07:21:52 +01:00
tben 9da1e16318 Bugfix: Fix dev function 2026-07-30 14:50:54 +01:00
tben 823c5bb1ca Remove bun imports from esbuild bundler 2026-07-30 11:52:08 +01:00
tben 88ead3b3d6 Remove all Loader.registry.delete lines 2026-07-29 14:05:12 +01:00
tben 45509deff8 Dev Server Refresh Bugfix. Loop happens after a file route is refactored to a folder route with index.ts 2026-07-29 13:55:23 +01:00
tben a19863b3e9 Updates 2026-07-20 20:54:59 +01:00
tben a9cd51d71c Bugfix: update stale HMR after fixed errors 2026-07-20 20:54:39 +01:00
tben e3a0f5fbeb Error handling bugfix in dev server 2026-07-20 20:41:30 +01:00
tben 1d0ac4aa80 Update start logic 2026-07-01 19:23:05 +01:00
tben 817beacc7a Update build step 2026-07-01 19:18:11 +01:00
tben 61a9d8d612 Bugfix in spawn file 2026-06-23 13:07:28 +01:00
tben cd9ac833dc Switch back to fs watcher 2026-04-21 16:25:57 +01:00
tben e2b8b95a4b Switch watcher to chokidar 2026-04-20 16:12:24 +01:00
tben c06cb73181 Update tests folder ingore pattern 2026-04-20 06:18:32 +01:00
tben f3bb972a20 Ignore test files/folders in bundling 2026-04-20 06:15:55 +01:00
tben 40a987b983 Update banner log 2026-04-19 18:57:55 +01:00
tben ceeb6fbdaf Security Fixes 2026-04-19 16:17:45 +01:00
tben 4f5445e3df Security fixes pass #2 2026-04-19 16:17:19 +01:00
tben b702e26bf6 Security fixes pass #2 2026-04-19 16:00:59 +01:00
tben 3b26292124 Security fixes pass #1 2026-04-19 14:59:20 +01:00
tben cb5126c947 Refactor error handling 2026-04-19 11:12:11 +01:00
tben f018b228a8 Update tests 2026-04-19 10:23:08 +01:00
tben 95fcee36b2 Fix server component stale caching. Add server components to SSR bundler 2026-04-19 09:04:55 +01:00
tben 41e28d7a3e Updates 2026-04-18 18:06:21 +01:00
tben b2e92e5792 Updates 2026-04-18 11:17:13 +01:00
tben 9a427412f3 Refactor start and dev commands. Start in child processes. Handle errors better 2026-04-17 08:47:36 +01:00
tben a5f25d522e Updates 2026-04-15 16:09:07 +01:00
tben 938411653d Updates 2026-04-14 20:32:36 +01:00
tben b597e1420e Updates 2026-04-14 20:24:08 +01:00
tben d6f0a7962e Updates 2026-04-14 15:50:21 +01:00
tben f0aae8a8fa Refactor SSR strategy. Redirect before bundling any page component. Additional bugfixes for dev environment 2026-04-12 16:20:16 +01:00
tben 84d490b189 Bugfix. Fix SSR pages filters. Remove dedicated API bundler, use SSR bundler for pages and api routes. 2026-04-12 06:48:10 +01:00
tben 532d0d6b56 Update excluded pages paths logic. Add HMR retries on errors. 2026-04-12 05:47:12 +01:00
tben e0c2ab5872 Update pages server function(s) 2026-04-11 16:29:53 +01:00
tben 35f7a6fc85 Persist Request accorss hydration refreshes 2026-04-11 12:52:56 +01:00
tben 814a289460 Fix API bundler. Use new esbuild context builder for it. 2026-04-11 12:06:53 +01:00
tben c4f7cf9164 Fix api route caching issue 2026-04-11 10:00:24 +01:00
tben af8c207ac1 Add retries to page component function error. Reload for all 404 pages 2026-04-11 08:48:01 +01:00
tben 5a0972beb8 Update API routes function pass #3 2026-04-10 11:54:17 +01:00
tben 257adfec39 Update API routes function pass #2 2026-04-10 11:48:30 +01:00
tben 349b99bacf Fix types error 2026-04-10 11:42:22 +01:00
tben 972f6945c2 Fix types error 2026-04-10 11:39:50 +01:00
tben 6477f446d1 Update API routes function 2026-04-10 11:34:22 +01:00
tben 7fb1784b95 Bugfix: Refactor SSR bundler. Fix stale cache SSR modules. 2026-04-09 23:50:58 +01:00
tben 40fc7778a8 Bugfix: SSR bundler errors fixed. Caching still faulty 2026-04-09 22:33:49 +01:00
tben f3b087a1f3 Handle errors better 2026-04-09 18:01:29 +01:00
tben eb0721f94b Updates 2026-04-09 07:47:38 +01:00
tben ab6fc3be26 Hydration bugfix 2026-04-08 08:08:27 +01:00
tben 6b7d29bc53 Bugfix: hydration mismatch 2026-04-08 07:46:09 +01:00
tben a84ac10b24 Bugfix. Update watcher to reload on excluded file/directory addition and deletion. 2026-04-08 06:51:44 +01:00
tben 4b3a4dbc77 Bugfix. Extract react imports from main bundler to vendor static files 2026-04-08 05:39:11 +01:00
tben c19b7c9607 Fix react and react-dom peer dependencies mismatch #4 2026-04-05 18:25:14 +01:00
tben 57957b89a4 Update README.md 2026-04-05 18:13:52 +01:00
tben d441f9982e Fix react and react-dom peer dependencies mismatch #3 2026-04-05 17:58:52 +01:00
tben f49a17b24e Fix react and react-dom peer dependencies #2 2026-04-05 17:13:25 +01:00
tben fa8a108686 Fix react and react-dom peer dependencies 2026-04-05 17:09:22 +01:00
tben 8d5b2a5c07 Update dev debug logs 2026-04-05 06:34:28 +01:00
tben 333c84281d Update watcher to rebuild on api dir change 2026-04-05 06:09:22 +01:00
tben 5166ba037f Component Bundler Bugfix 2026-04-04 20:30:40 +01:00
tben 1037bbb546 Component Bundler Bugfix debug 2026-04-04 20:05:41 +01:00
tben f34f8baa99 Update watcher and component gen functions 2026-04-04 20:02:36 +01:00
tben 48c25d2fb4 Update init function 2026-04-04 19:35:14 +01:00
tben 7dbd1f7e12 Remove esm.sh externals 2026-03-31 06:57:44 +01:00
tben 68d88bce4a Try local react imports 2026-03-31 06:22:26 +01:00
234 changed files with 6537 additions and 2706 deletions
+3 -1
View File
@@ -180,4 +180,6 @@ __fixtures__
/public
/.data
/.dump
/.vscode
/.vscode
/source.md
SECURITY.md
+37 -7
View File
@@ -150,11 +150,11 @@ bun run dev
## CLI Commands
| Command | Description |
| -------------- | ---------------------------------------------------------------------- |
| `bunext dev` | Start the development server with HMR and file watching. |
| Command | Description |
| -------------- | ------------------------------------------------------------------------------ |
| `bunext dev` | Start the development server with HMR and file watching. |
| `bunext build` | Bundle all pages for production. Outputs artifacts to `.bunext/public/pages/`. |
| `bunext start` | Start the production server using pre-built artifacts. |
| `bunext start` | Start the production server using pre-built artifacts. |
### Running the CLI
@@ -742,6 +742,9 @@ const config: BunextConfig = {
globalVars: {
MY_API_URL: "https://api.example.com",
},
public_envs: {
BUNEXT_PUBLIC_APP_NAME: "My App",
},
development: false, // forced by the CLI; set manually if needed
};
@@ -755,6 +758,7 @@ export default config;
| `distDir` | `string` | `.bunext` | Internal artifact directory |
| `assetsPrefix` | `string` | `_bunext/static` | URL prefix for static assets |
| `globalVars` | `{ [k: string]: any }` | — | Variables injected globally at build time |
| `public_envs` | `Record<string, string>` | — | Public env vars exposed to the client via `window.process.env` (see [Environment Variables](#environment-variables)) |
| `development` | `boolean` | — | Overridden to `true` by `bunext dev` automatically |
| `defaultCacheExpiry` | `number` | `3600` | Global page cache expiry in seconds |
| `middleware` | `(params: BunextConfigMiddlewareParams) => Response \| undefined \| Promise<...>` | — | Global middleware — see [Middleware](#middleware) |
@@ -909,9 +913,35 @@ bun run server.ts
## Environment Variables
| Variable | Description |
| -------- | ------------------------------------------------------- |
| `PORT` | Override the server port (takes precedence over config) |
| Variable | Description |
| ------------------ | ------------------------------------------------------------------------------------------------ |
| `PORT` | Override the server port (takes precedence over config) |
| `BUNEXT_PUBLIC_*` | Any env var prefixed with `BUNEXT_PUBLIC_` is exposed to the client via `window.process.env` |
### Public Environment Variables
Variables prefixed with `BUNEXT_PUBLIC_` are automatically injected into every page as `window.process.env`. You can also define public envs in config via `public_envs` (config values override env vars of the same name):
```bash
# .env
BUNEXT_PUBLIC_API_URL=https://api.example.com
```
```ts
// bunext.config.ts
const config: BunextConfig = {
public_envs: {
BUNEXT_PUBLIC_APP_NAME: "My App",
},
};
```
```tsx
// Client component — available after hydration
const apiUrl = window.process.env.BUNEXT_PUBLIC_API_URL;
```
`window.process.env` always includes `NODE_ENV` (`"development"` or `"production"`).
---
+13 -5
View File
@@ -12,6 +12,7 @@
"@types/react-dom": "^19.2.2",
"bun-plugin-tailwind": "^0.1.2",
"chalk": "^5.6.2",
"chokidar": "^5.0.0",
"commander": "^14.0.2",
"esbuild": "^0.27.4",
"lightningcss-wasm": "^1.32.0",
@@ -19,13 +20,14 @@
"micromatch": "^4.0.8",
"ora": "^9.0.0",
"postcss": "^8.5.8",
"react": "19.0.0",
"react-dom": "19.0.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"tailwindcss": "^4.2.2",
"typescript": "^5.0.0",
},
"devDependencies": {
"@testing-library/dom": "^10.4.1",
"@types/chokidar": "^2.1.7",
"@types/lodash": "^4.17.24",
"@types/micromatch": "^4.0.10",
"happy-dom": "^20.8.4",
@@ -165,6 +167,8 @@
"@types/bun": ["@types/bun@1.3.11", "", { "dependencies": { "bun-types": "1.3.11" } }, "sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg=="],
"@types/chokidar": ["@types/chokidar@2.1.7", "", { "dependencies": { "chokidar": "*" } }, "sha512-A7/MFHf6KF7peCzjEC1BBTF8jpmZTokb3vr/A0NxRGfwRLK3Ws+Hq6ugVn6cJIMfM6wkCak/aplWrxbTcu8oig=="],
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
"@types/micromatch": ["@types/micromatch@4.0.10", "", { "dependencies": { "@types/braces": "*" } }, "sha512-5jOhFDElqr4DKTrTEbnW8DZ4Hz5LRUEmyrGpCMrD/NphYv3nUnaF08xmSLx1rGGnyEs/kFnhiw6dCgcDqMr5PQ=="],
@@ -195,6 +199,8 @@
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="],
"cli-spinners": ["cli-spinners@3.3.0", "", {}, "sha512-/+40ljC3ONVnYIttjMWrlL51nItDAbBrq2upN8BPyvGU/2n5Oxw3tbNwORCaNuNqLJnxGqOfjUuhsv7l5Q4IsQ=="],
@@ -285,15 +291,17 @@
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
"react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
"react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"react-dom": ["react-dom@19.0.0", "", { "dependencies": { "scheduler": "^0.25.0" }, "peerDependencies": { "react": "^19.0.0" } }, "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ=="],
"react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="],
"scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
+4 -13
View File
@@ -1,12 +1,8 @@
import { Command } from "commander";
import { log } from "../../utils/log";
import init from "../../functions/init";
// import rewritePagesModule from "../../utils/rewrite-pages-module";
import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler";
import grabDirNames from "../../utils/grab-dir-names";
import { rmSync } from "fs";
import allPagesBundler from "../../functions/bundler/all-pages-bundler";
import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler";
import bunextInit from "../../functions/bunext-init";
const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames();
export default function () {
return new Command("build")
@@ -17,14 +13,9 @@ export default function () {
rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
}
catch (error) { }
global.SKIPPED_BROWSER_MODULES = new Set();
// await rewritePagesModule();
await init();
log.banner();
log.build("Building Project ...");
// await allPagesBunBundler();
// await allPagesBundler();
await allPagesESBuildContextBundler();
global.BUNEXT_SKIPPED_BROWSER_MODULES = new Set();
await bunextInit({ build_only: true });
log.success("Modules Built Successfully!");
process.exit();
});
}
+1
View File
@@ -0,0 +1 @@
export {};
+25
View File
@@ -0,0 +1,25 @@
import startServer from "../../functions/server/start-server";
import { log } from "../../utils/log";
import bunextInit from "../../functions/bunext-init";
import grabDirNames from "../../utils/grab-dir-names";
import { rmSync } from "fs";
const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames();
process.on("uncaughtException", (error) => {
log.error(`Uncaught exception: ${error}`);
});
process.on("unhandledRejection", (reason) => {
log.error(`Unhandled rejection: ${reason}`);
});
log.info("Running development server ...");
try {
rmSync(HYDRATION_DST_DIR, { recursive: true });
rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
}
catch (error) { }
try {
await bunextInit();
await startServer();
}
catch (error) {
log.error(`Failed to start development server: ${error}`);
}
+53 -21
View File
@@ -1,29 +1,61 @@
import { Command } from "commander";
import startServer from "../../functions/server/start-server";
import { log } from "../../utils/log";
import bunextInit from "../../functions/bunext-init";
import path from "path";
import grabDirNames from "../../utils/grab-dir-names";
import { rmSync } from "fs";
import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler";
import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "../../functions/server/server-post-build-fn";
const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames();
import writeErrorFile from "../../functions/write-error-file";
import { existsSync } from "fs";
let retries = 0;
let timeout;
const MAX_RETRIES = 5;
export default function () {
return new Command("dev")
.description("Run development server")
.action(async () => {
process.env.NODE_ENV = "development";
log.info("Running development server ...");
try {
rmSync(HYDRATION_DST_DIR, { recursive: true });
rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
}
catch (error) { }
await bunextInit();
// await allPagesBunBundler();
await allPagesESBuildContextBundler({
post_build_fn: serverPostBuildFn,
});
await startServer();
await dev();
});
}
async function dev() {
clearTimeout(timeout);
if (retries >= MAX_RETRIES) {
console.error(`Dev server crashed ${MAX_RETRIES} times. Exiting.`);
process.exit(1);
}
const dev_spawn_file = path.resolve(__dirname, "dev-spawn.ts");
const dev_spawn_js_file = path.resolve(__dirname, "dev-spawn.js");
const final_spawn_file = existsSync(dev_spawn_js_file)
? dev_spawn_js_file
: dev_spawn_file;
const spawn_options = {
cmd: ["bun", final_spawn_file],
stdio: ["inherit", "inherit", "inherit"],
async onExit(subprocess, exitCode, signalCode, error) {
writeErrorFile({ exitCode, error });
},
env: {
...process.env,
NODE_ENV: "development",
},
};
let dev_process;
try {
dev_process = Bun.spawn(spawn_options);
}
catch (error) {
console.error(`Failed to start dev process:`, error);
retries++;
timeout = setTimeout(() => {
retries = 0;
}, 10000);
return await dev();
}
const exited = await dev_process.exited;
if (exited) {
retries++;
timeout = setTimeout(() => {
retries = 0;
}, 10000);
return await dev();
}
timeout = setTimeout(() => {
retries = 0;
}, 10000);
}
+1 -3
View File
@@ -4,21 +4,19 @@ import start from "./start";
import dev from "./dev";
import build from "./build";
import { log } from "../utils/log";
import rewritePages from "./rewrite-pages";
/**
* # Describe Program
*/
program
.name(`bunext`)
.description(`A React Next JS replacement built with bun JS`)
.version(`1.0.0`);
.version(`1.0.43`);
/**
* # Declare Commands
*/
program.addCommand(dev());
program.addCommand(start());
program.addCommand(build());
program.addCommand(rewritePages());
/**
* # Handle Unavailable Commands
*/
-2
View File
@@ -1,2 +0,0 @@
import { Command } from "commander";
export default function (): Command;
-16
View File
@@ -1,16 +0,0 @@
import { Command } from "commander";
import { log } from "../../utils/log";
import init from "../../functions/init";
import rewritePagesModule from "../../utils/rewrite-pages-module";
export default function () {
return new Command("rewrite-pages")
.description("Rewrite pages from src to .bunext dir")
.action(async () => {
process.env.NODE_ENV = "production";
process.env.BUILD = "true";
await init();
log.banner();
log.build("Rewriting Pages ...");
await rewritePagesModule();
});
}
+53 -11
View File
@@ -1,18 +1,60 @@
import { Command } from "commander";
import startServer from "../../functions/server/start-server";
import { log } from "../../utils/log";
import bunextInit from "../../functions/bunext-init";
import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler";
import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler";
import path from "path";
import writeErrorFile from "../../functions/write-error-file";
import { existsSync } from "fs";
let retries = 0;
let timeout;
const MAX_RETRIES = 5;
export default function () {
return new Command("start")
.description("Start production server")
.action(async () => {
process.env.NODE_ENV = "production";
log.info("Starting production server ...");
await bunextInit();
// await allPagesBunBundler();
await allPagesESBuildContextBundler();
await startServer();
await start();
});
}
async function start() {
clearTimeout(timeout);
if (retries >= MAX_RETRIES) {
console.error(`Production server crashed ${MAX_RETRIES} times. Exiting.`);
process.exit(1);
}
const prod_spawn_file = path.resolve(__dirname, "prod-spawn.ts");
const prod_spawn_js_file = path.resolve(__dirname, "prod-spawn.js");
const final_spawn_file = existsSync(prod_spawn_js_file)
? prod_spawn_js_file
: prod_spawn_file;
const spawn_options = {
cmd: ["bun", final_spawn_file],
stdio: ["inherit", "inherit", "inherit"],
onExit(subprocess, exitCode, signalCode, error) {
writeErrorFile({ exitCode, error });
},
env: {
...process.env,
NODE_ENV: "production",
},
};
let dev_process;
try {
dev_process = Bun.spawn(spawn_options);
}
catch (error) {
console.error(`Failed to start production process:`, error);
retries++;
timeout = setTimeout(() => {
retries = 0;
}, 10000);
return await start();
}
const exited = await dev_process.exited;
if (exited) {
retries++;
timeout = setTimeout(() => {
retries = 0;
}, 10000);
return await start();
}
timeout = setTimeout(() => {
retries = 0;
}, 10000);
}
+1
View File
@@ -0,0 +1 @@
export {};
+6
View File
@@ -0,0 +1,6 @@
import bunextInit from "../../functions/bunext-init";
import startServer from "../../functions/server/start-server";
import { log } from "../../utils/log";
log.info("Starting production server ...");
await bunextInit();
await startServer();
+3
View File
@@ -4,4 +4,7 @@ export declare const AppData: {
readonly BunextStaticFilesCacheExpiry: number;
readonly ClientHMRPath: "__bunext_client_hmr__";
readonly BunextClientHydrationScriptID: "bunext-client-hydration-script";
readonly BunextTmpFileExt: ".bunext_tmp.tsx";
readonly BunextHMRRetryRoute: "/.bunext/hmr-retry";
readonly DefaultMaxLogs: 50;
};
+3
View File
@@ -4,4 +4,7 @@ export const AppData = {
BunextStaticFilesCacheExpiry: 60 * 60 * 24 * 7,
ClientHMRPath: "__bunext_client_hmr__",
BunextClientHydrationScriptID: "bunext-client-hydration-script",
BunextTmpFileExt: ".bunext_tmp.tsx",
BunextHMRRetryRoute: "/.bunext/hmr-retry",
DefaultMaxLogs: 50,
};
-7
View File
@@ -1,7 +0,0 @@
import type { BundlerCTXMap } from "../../types";
type Params = {
target?: "bun" | "browser";
page_file_paths?: string[];
};
export default function allPagesBunBundler(params?: Params): Promise<BundlerCTXMap[] | undefined>;
export {};
-106
View File
@@ -1,106 +0,0 @@
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import { log } from "../../utils/log";
import tailwindcss from "bun-plugin-tailwind";
import path from "path";
import grabClientHydrationScript from "./grab-client-hydration-script";
import { mkdirSync, rmSync } from "fs";
import recordArtifacts from "./record-artifacts";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
export default async function allPagesBunBundler(params) {
const { target = "browser", page_file_paths } = params || {};
const pages = grabAllPages({ exclude_api: true });
const target_pages = page_file_paths?.[0]
? pages.filter((p) => page_file_paths.includes(p.local_path))
: pages;
if (!page_file_paths) {
global.PAGE_FILES = pages;
try {
rmSync(BUNX_HYDRATION_SRC_DIR, { recursive: true });
}
catch { }
}
mkdirSync(BUNX_HYDRATION_SRC_DIR, { recursive: true });
const dev = isDevelopment();
const entryToPage = new Map();
for (const page of target_pages) {
const txt = await grabClientHydrationScript({
page_local_path: page.local_path,
});
if (!txt)
continue;
const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`);
await Bun.write(entryFile, txt, { createPath: true });
entryToPage.set(path.resolve(entryFile), page);
}
if (entryToPage.size === 0)
return;
const buildStart = performance.now();
const define = {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
};
const result = await Bun.build({
entrypoints: [...entryToPage.keys()],
outdir: HYDRATION_DST_DIR,
root: BUNX_HYDRATION_SRC_DIR,
minify: !dev,
format: "esm",
define,
naming: {
entry: "[dir]/[hash].[ext]",
chunk: "chunks/[hash].[ext]",
},
plugins: [tailwindcss],
// plugins: [tailwindcss, BunSkipNonBrowserPlugin],
splitting: true,
target,
metafile: true,
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
],
});
if (!result.success) {
for (const entry of result.logs) {
log.error(`[Build] ${entry.message}`);
}
return;
}
const artifacts = [];
for (const [outputPath, outputInfo] of Object.entries(result.metafile.outputs)) {
const entryPoint = outputInfo.entryPoint;
const cssBundle = outputInfo.cssBundle;
if (!entryPoint)
continue;
if (outputPath.match(/\.css$/))
continue;
const page = entryToPage.get(path.resolve(entryPoint));
if (!page)
continue;
artifacts.push({
path: path.join(".bunext/public/pages", outputPath),
hash: path.basename(outputPath, path.extname(outputPath)),
type: outputPath.endsWith(".css") ? "text/css" : "text/javascript",
entrypoint: entryPoint,
css_path: cssBundle
? path.join(".bunext/public/pages", cssBundle)
: undefined,
file_name: page.file_name,
local_path: page.local_path,
url_path: page.url_path,
});
}
if (artifacts?.[0]) {
await recordArtifacts({
artifacts,
page_file_paths,
});
}
const elapsed = (performance.now() - buildStart).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
return artifacts;
}
-8
View File
@@ -1,8 +0,0 @@
type Params = {
/**
* Locations of the pages Files.
*/
page_file_paths?: string[];
};
export default function allPagesBundler(params?: Params): Promise<void>;
export {};
-141
View File
@@ -1,141 +0,0 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import { log } from "../../utils/log";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabClientHydrationScript from "./grab-client-hydration-script";
import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result";
import { writeFileSync } from "fs";
import recordArtifacts from "./record-artifacts";
import stripServerSideLogic from "./strip-server-side-logic";
const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames();
let build_starts = 0;
const MAX_BUILD_STARTS = 10;
export default async function allPagesBundler(params) {
const { page_file_paths } = params || {};
const pages = grabAllPages({ exclude_api: true });
const target_pages = page_file_paths?.[0]
? pages.filter((p) => page_file_paths.includes(p.local_path))
: pages;
if (!page_file_paths) {
global.PAGE_FILES = pages;
}
const virtualEntries = {};
const dev = isDevelopment();
for (const page of target_pages) {
const key = page.local_path;
const txt = await grabClientHydrationScript({
page_local_path: page.local_path,
});
// if (page.url_path == "/index") {
// console.log("txt", txt);
// }
if (!txt)
continue;
// const final_tsx = stripServerSideLogic({
// txt_code: txt,
// file_path: key,
// });
// console.log("final_tsx", final_tsx);
virtualEntries[key] = txt;
}
const virtualPlugin = {
name: "virtual-entrypoints",
setup(build) {
build.onResolve({ filter: /^virtual:/ }, (args) => ({
path: args.path.replace("virtual:", ""),
namespace: "virtual",
}));
build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => ({
contents: virtualEntries[args.path],
loader: "tsx",
resolveDir: process.cwd(),
}));
},
};
let buildStart = 0;
const artifactTracker = {
name: "artifact-tracker",
setup(build) {
build.onStart(() => {
build_starts++;
buildStart = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
}
});
// build.onEnd((result) => {
// });
},
};
const entryPoints = Object.keys(virtualEntries).map((k) => `virtual:${k}`);
// let alias: any = {};
// const excludes = [
// "bun:sqlite",
// "path",
// "url",
// "events",
// "util",
// "crypto",
// "net",
// "tls",
// "fs",
// "node:path",
// "node:url",
// "node:process",
// "node:fs",
// "node:timers/promises",
// ];
// for (let i = 0; i < excludes.length; i++) {
// const exclude = excludes[i];
// alias[exclude] = "./empty.js";
// }
// console.log("alias", alias);
const result = await esbuild.build({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
minify: true,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [tailwindEsbuildPlugin, virtualPlugin, artifactTracker],
jsx: "automatic",
// splitting: true,
// logLevel: "silent",
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
],
// alias,
});
if (result.errors.length > 0) {
for (const error of result.errors) {
const loc = error.location;
const location = loc
? ` ${loc.file}:${loc.line}:${loc.column}`
: "";
log.error(`[Build]${location} ${error.text}`);
}
return;
}
// const artifacts = grabArtifactsFromBundledResults({
// result,
// });
// if (artifacts?.[0]) {
// await recordArtifacts({ artifacts });
// }
const elapsed = (performance.now() - buildStart).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
build_starts = 0;
}
@@ -1,58 +0,0 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabClientHydrationScript from "./grab-client-hydration-script";
import path from "path";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
export default async function allPagesESBuildContextBundlerFiles(params) {
const pages = grabAllPages({ exclude_api: true });
global.PAGE_FILES = pages;
const dev = isDevelopment();
const entryToPage = new Map();
for (const page of pages) {
const tsx = await grabClientHydrationScript({
page_local_path: page.local_path,
});
if (!tsx)
continue;
const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`);
await Bun.write(entryFile, tsx, { createPath: true });
entryToPage.set(entryFile, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()];
const ctx = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
splitting: true,
logLevel: "silent",
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
],
});
await ctx.rebuild();
global.BUNDLER_CTX = ctx;
}
@@ -1,7 +1,10 @@
import type { BundlerCTXMap } from "../../types";
type Params = {
post_build_fn?: (params: {
artifacts: any[];
artifacts: BundlerCTXMap[];
}) => Promise<void> | void;
build_only?: boolean;
start?: boolean;
};
export default function allPagesESBuildContextBundler(params?: Params): Promise<void>;
export {};
+77 -51
View File
@@ -7,59 +7,85 @@ import grabClientHydrationScript from "./grab-client-hydration-script";
import path from "path";
import virtualFilesPlugin from "./plugins/virtual-files-plugin";
import esbuildCTXArtifactTracker from "./plugins/esbuild-ctx-artifact-tracker";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames();
import { existsSync } from "fs";
const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE, } = grabDirNames();
export default async function allPagesESBuildContextBundler(params) {
// return await allPagesESBuildContextBundlerFiles(params);
const pages = grabAllPages({ exclude_api: true });
global.PAGE_FILES = pages;
const dev = isDevelopment();
const entryToPage = new Map();
for (const page of pages) {
const tsx = await grabClientHydrationScript({
page_local_path: page.local_path,
try {
const did_process_exit_because_of_bundler_error = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
const pages = grabAllPages({ exclude_api: true });
global.BUNEXT_PAGE_FILES = pages;
const dev = isDevelopment();
const entryToPage = new Map();
for (const page of pages) {
const tsx = await grabClientHydrationScript({
page_local_path: page.local_path,
});
if (!tsx) {
continue;
}
const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`);
// await Bun.write(entryFile, txt, { createPath: true });
entryToPage.set(entryFile, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()].map((e) => `hydration-virtual:${e}`);
global.BUNEXT_BUNDLER_CTX = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
forceExternalReact(),
tailwindEsbuildPlugin,
virtualFilesPlugin({
entryToPage,
}),
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
build_only: params?.build_only || params?.start,
}),
],
jsx: "automatic",
splitting: true,
treeShaking: true,
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
"react/jsx-dev-runtime",
...(global.BUNEXT_CONFIG.page_compiler_excludes || []),
],
logLevel: did_process_exit_because_of_bundler_error
? "silent"
: undefined,
});
if (!tsx)
continue;
const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`);
// await Bun.write(entryFile, txt, { createPath: true });
entryToPage.set(entryFile, { ...page, tsx });
await global.BUNEXT_BUNDLER_CTX.rebuild();
}
catch (error) {
console.log(`ESBUILD Error =>`, error);
}
const entryPoints = [...entryToPage.keys()].map((e) => `hydration-virtual:${e}`);
global.BUNDLER_CTX = await esbuild.context({
entryPoints,
outdir: HYDRATION_DST_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "es2020",
platform: "browser",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
}
function forceExternalReact() {
return {
name: "force-external-react",
setup(build) {
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, (args) => {
if (args.pluginData?.externalReact)
return null;
return {
path: args.path,
external: true,
};
});
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
virtualFilesPlugin({
entryToPage,
}),
esbuildCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
splitting: true,
logLevel: "silent",
// logLevel: "silent",
// logLevel: dev ? "error" : "silent",
external: [
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
"react/jsx-dev-runtime",
],
});
await global.BUNDLER_CTX.rebuild();
};
}
+1
View File
@@ -0,0 +1 @@
export default function apiRoutesBundler(): Promise<void>;
+40
View File
@@ -0,0 +1,40 @@
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindcss from "bun-plugin-tailwind";
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
export default async function apiRoutesBundler() {
const api_routes = grabAllPages({ api_only: true });
const dev = isDevelopment();
try {
const build = await Bun.build({
entrypoints: api_routes.map((r) => r.local_path),
target: "bun",
format: "esm",
jsx: {
runtime: "automatic",
development: dev,
},
minify: !dev,
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
outdir: BUNX_CWD_MODULE_CACHE_DIR,
plugins: [tailwindcss],
naming: {
entry: "api/[dir]/[name].[ext]",
chunk: "api/[dir]/chunks/[hash].[ext]",
},
// external: [
// "react",
// "react-dom",
// "react-dom/client",
// "react/jsx-runtime",
// ],
splitting: true,
});
}
catch (error) {
console.log(`API paths build ERROR:`, error);
}
}
@@ -0,0 +1 @@
export default function apiRoutesContextBundler(): Promise<void>;
+44
View File
@@ -0,0 +1,44 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import apiRoutesCTXArtifactTracker from "./plugins/api-routes-ctx-artifact-tracker";
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
export default async function apiRoutesContextBundler() {
const pages = grabAllPages({ api_only: true });
const dev = isDevelopment();
// if (global.API_ROUTES_BUNDLER_CTX) {
// await global.API_ROUTES_BUNDLER_CTX.dispose();
// global.API_ROUTES_BUNDLER_CTX = undefined;
// }
// global.API_ROUTES_BUNDLER_CTX = await esbuild.context({
// entryPoints: pages.map((p) => p.local_path),
// outdir: BUNX_CWD_MODULE_CACHE_DIR,
// bundle: true,
// minify: !dev,
// format: "esm",
// target: "esnext",
// platform: "node",
// define: {
// "process.env.NODE_ENV": JSON.stringify(
// dev ? "development" : "production",
// ),
// },
// entryNames: "api/[dir]/[hash]",
// metafile: true,
// plugins: [
// tailwindEsbuildPlugin,
// apiRoutesCTXArtifactTracker({ pages }),
// ],
// jsx: "automatic",
// external: [
// "react",
// "react-dom",
// "react/jsx-runtime",
// "react/jsx-dev-runtime",
// "bun:*",
// ],
// });
// await global.API_ROUTES_BUNDLER_CTX.rebuild();
}
@@ -0,0 +1,3 @@
type Params = {};
export default function buildOnstartErrorHandler(params?: Params): Promise<void>;
export {};
+18
View File
@@ -0,0 +1,18 @@
export default async function buildOnstartErrorHandler(params) {
// const error_msg = `Build Failed. Please check all your components and imports.`;
// log.error(error_msg);
if (global.BUNEXT_BUNDLER_CTX_DISPOSED) {
return;
}
// console.log(`Killing Bundler ...`);
// console.log(`global.BUNEXT_BUNDLER_CTX_DISPOSED`, global.BUNEXT_BUNDLER_CTX_DISPOSED);
global.BUNEXT_BUNDLER_CTX_DISPOSED = true;
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
await Promise.all([
global.BUNEXT_SSR_BUNDLER_CTX?.dispose(),
global.BUNEXT_BUNDLER_CTX?.dispose(),
]);
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
global.BUNEXT_BUNDLER_CTX = undefined;
}
+1
View File
@@ -0,0 +1 @@
export default function bunReactModulesBundler(): Promise<void>;
+74
View File
@@ -0,0 +1,74 @@
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import path from "path";
import { rmSync, mkdirSync, writeFileSync } from "fs";
const { BUNEXT_VENDOR_DIR, BUNX_CWD_DIR } = grabDirNames();
const VENDOR_ENTRIES = {
react: `
import React from "react";
export const {
Children, Component, Fragment, Profiler, PureComponent, StrictMode,
Suspense, cloneElement, createContext, createElement, createRef,
forwardRef, isValidElement, lazy, memo, startTransition,
useCallback, useContext, useDebugValue, useDeferredValue, useEffect,
useId, useImperativeHandle, useInsertionEffect, useLayoutEffect,
useMemo, useReducer, useRef, useState, useSyncExternalStore,
useTransition, version, use, cache, act,
} = React;
export default React;
`,
"react-dom": `
import ReactDOM from "react-dom";
export const {
createPortal, flushSync, version,
} = ReactDOM;
export default ReactDOM;
`,
"react-dom_client": `
import ReactDOMClient from "react-dom/client";
export const { createRoot, hydrateRoot } = ReactDOMClient;
export default ReactDOMClient;
`,
"react_jsx-runtime": `
import JSXRuntime from "react/jsx-runtime";
export const { jsx, jsxs, Fragment } = JSXRuntime;
`,
"react_jsx-dev-runtime": `
import JSXDevRuntime from "react/jsx-dev-runtime";
export const { jsxDEV, Fragment } = JSXDevRuntime;
`,
};
export default async function bunReactModulesBundler() {
const dev = isDevelopment();
rmSync(BUNEXT_VENDOR_DIR, { force: true, recursive: true });
const tmpDir = path.join(BUNEXT_VENDOR_DIR, "_tmp");
mkdirSync(tmpDir, { recursive: true });
const entrypoints = [];
for (const [name, contents] of Object.entries(VENDOR_ENTRIES)) {
const file = path.join(tmpDir, `${name}.mjs`);
writeFileSync(file, contents);
entrypoints.push(file);
}
await Bun.build({
entrypoints,
outdir: BUNEXT_VENDOR_DIR,
splitting: true,
format: "esm",
target: "browser",
minify: !dev,
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
});
rmSync(tmpDir, { force: true, recursive: true });
const PUBLIC_ROOT = BUNEXT_VENDOR_DIR.replace(BUNX_CWD_DIR, "/.bunext");
global.BUNEXT_REACT_IMPORTS_MAP = {
imports: {
react: `${PUBLIC_ROOT}/react.js`,
"react-dom": `${PUBLIC_ROOT}/react-dom.js`,
"react-dom/client": `${PUBLIC_ROOT}/react-dom_client.js`,
"react/jsx-runtime": `${PUBLIC_ROOT}/react_jsx-runtime.js`,
"react/jsx-dev-runtime": `${PUBLIC_ROOT}/react_jsx-dev-runtime.js`,
},
};
}
@@ -5,6 +5,7 @@ type Params = {
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
virtual_match?: string;
};
export default function grabArtifactsFromBundledResults({ result, entryToPage, }: Params): BundlerCTXMap[] | undefined;
export default function grabArtifactsFromBundledResults({ result, entryToPage, virtual_match, }: Params): BundlerCTXMap[] | undefined;
export {};
@@ -3,19 +3,18 @@ import * as esbuild from "esbuild";
import grabDirNames from "../../utils/grab-dir-names";
import { log } from "../../utils/log";
const { ROOT_DIR } = grabDirNames();
export default function grabArtifactsFromBundledResults({ result, entryToPage, }) {
export default function grabArtifactsFromBundledResults({ result, entryToPage, virtual_match = "hydration-virtual", }) {
if (result.errors.length > 0)
return;
const virtual_regex = new RegExp(`^${virtual_match}:`);
const artifacts = Object.entries(result.metafile.outputs)
.filter(([, meta]) => meta.entryPoint)
.map(([outputPath, meta]) => {
const entrypoint = meta.entryPoint?.match(/^hydration-virtual:/)
? meta.entryPoint?.replace(/^hydration-virtual:/, "")
const entrypoint = meta.entryPoint?.match(virtual_regex)
? meta.entryPoint?.replace(virtual_regex, "")
: meta.entryPoint
? path.join(ROOT_DIR, meta.entryPoint)
: "";
// const entrypoint = path.join(ROOT_DIR, meta.entryPoint || "");
// console.log("entrypoint", entrypoint);
const target_page = entryToPage.get(entrypoint);
if (!target_page || !meta.entryPoint) {
return undefined;
+3 -5
View File
@@ -1,11 +1,8 @@
import { existsSync } from "fs";
import path from "path";
import grabDirNames from "../../utils/grab-dir-names";
import AppNames from "../../utils/grab-app-names";
import grabConstants from "../../utils/grab-constants";
import pagePathTransform from "../../utils/page-path-transform";
import grabRootFilePath from "../server/web-pages/grab-root-file-path";
const { PAGES_DIR } = grabDirNames();
const { ROOT_DIR } = grabDirNames();
export default async function grabClientHydrationScript({ page_local_path, }) {
const { ClientRootElementIDName, ClientRootComponentWindowName, ClientWindowPagePropsName, } = grabConstants();
const { root_file_path } = grabRootFilePath();
@@ -30,7 +27,8 @@ export default async function grabClientHydrationScript({ page_local_path, }) {
return undefined;
}
let txt = ``;
txt += `import { hydrateRoot } from "react-dom/client";\n`;
txt += `import { hydrateRoot } from "${ROOT_DIR}/node_modules/react-dom/client.js";\n`;
// txt += `import react from "${ROOT_DIR}/node_modules/react/index.js";\n`;
if (root_file_path) {
txt += `import Root from "${root_file_path}";\n`;
}
@@ -3,5 +3,5 @@ type Params = {
artifacts: any[];
}) => Promise<void> | void;
};
export default function allPagesESBuildContextBundlerFiles(params?: Params): Promise<void>;
export default function pagesSSRBundler(params?: Params): Promise<void>;
export {};
+94
View File
@@ -0,0 +1,94 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabPageReactComponentString from "../server/web-pages/grab-page-react-component-string";
import grabRootFilePath from "../server/web-pages/grab-root-file-path";
import ssrVirtualFilesPlugin from "./plugins/ssr-virtual-files-plugin";
import ssrCTXArtifactTracker from "./plugins/ssr-ctx-artifact-tracker";
import { writeFileSync } from "fs";
import path from "path";
import { log } from "../../utils/log";
const { BUNX_CWD_MODULE_CACHE_DIR, BUNX_TMP_DIR } = grabDirNames();
export default async function pagesSSRBundler(params) {
const pages = grabAllPages({
include_server: true,
});
const dev = isDevelopment();
const config = global.BUNEXT_CONFIG;
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-pages.json"), JSON.stringify(pages, null, 4));
}
catch (error) { }
const entryToPage = new Map();
const { root_file_path } = grabRootFilePath();
for (const page of pages) {
if (page.local_path.match(/\/pages\/api\//) ||
page.local_path.match(/\.server\.tsx?$/)) {
const ts = await Bun.file(page.local_path).text();
if (ts.match(/(export default)|(export \w+ handler)|(export \w+ server)/)) {
entryToPage.set(page.local_path, { ...page, tsx: ts });
}
continue;
}
const tsx = grabPageReactComponentString({
file_path: page.local_path,
root_file_path,
});
if (!tsx)
continue;
if (!tsx.match(/export default/))
continue;
entryToPage.set(page.local_path, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()].map((e) => `ssr-virtual:${e}`);
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-entry-to-page.json"), JSON.stringify(Object(entryToPage), null, 4));
writeFileSync(path.join(BUNX_TMP_DIR, "ssr-entrypoints.json"), JSON.stringify(entryPoints, null, 4));
}
catch (error) { }
try {
await esbuild.build({
entryPoints,
outdir: BUNX_CWD_MODULE_CACHE_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "esnext",
platform: "node",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
ssrVirtualFilesPlugin({
entryToPage,
}),
ssrCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
external: [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"bun:*",
"bun",
"sqlite-vec",
"better-sqlite3",
...(config.ssr_compiler_excludes || []),
],
splitting: true,
});
}
catch (error) {
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
log.error(`SSR Bundler Error: ${error}`);
}
}
+7
View File
@@ -0,0 +1,7 @@
type Params = {
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
};
export default function pagesSSRContextBundler(params?: Params): Promise<void>;
export {};
+69
View File
@@ -0,0 +1,69 @@
import * as esbuild from "esbuild";
import grabAllPages from "../../utils/grab-all-pages";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin";
import grabPageReactComponentString from "../server/web-pages/grab-page-react-component-string";
import grabRootFilePath from "../server/web-pages/grab-root-file-path";
import ssrVirtualFilesPlugin from "./plugins/ssr-virtual-files-plugin";
import ssrCTXArtifactTracker from "./plugins/ssr-ctx-artifact-tracker";
const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
export default async function pagesSSRContextBundler(params) {
const pages = grabAllPages();
const dev = isDevelopment();
if (global.BUNEXT_SSR_BUNDLER_CTX) {
await global.BUNEXT_SSR_BUNDLER_CTX.dispose();
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
}
const entryToPage = new Map();
const { root_file_path } = grabRootFilePath();
for (const page of pages) {
if (page.local_path.match(/\/pages\/api\//)) {
const ts = await Bun.file(page.local_path).text();
entryToPage.set(page.local_path, { ...page, tsx: ts });
continue;
}
const tsx = grabPageReactComponentString({
file_path: page.local_path,
root_file_path,
});
if (!tsx)
continue;
entryToPage.set(page.local_path, { ...page, tsx });
}
const entryPoints = [...entryToPage.keys()].map((e) => `ssr-virtual:${e}`);
global.BUNEXT_SSR_BUNDLER_CTX = await esbuild.context({
entryPoints,
outdir: BUNX_CWD_MODULE_CACHE_DIR,
bundle: true,
minify: !dev,
format: "esm",
target: "esnext",
platform: "node",
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
entryNames: "[dir]/[hash]",
metafile: true,
plugins: [
tailwindEsbuildPlugin,
ssrVirtualFilesPlugin({
entryToPage,
}),
ssrCTXArtifactTracker({
entryToPage,
post_build_fn: params?.post_build_fn,
}),
],
jsx: "automatic",
external: [
"react",
"react-dom",
"react/jsx-runtime",
"react/jsx-dev-runtime",
"bun:*",
],
// logLevel: "silent",
});
await global.BUNEXT_SSR_BUNDLER_CTX.rebuild();
}
@@ -0,0 +1,7 @@
import { type Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
type Params = {
pages: PageFiles[];
};
export default function apiRoutesCTXArtifactTracker({ pages }: Params): Plugin;
export {};
@@ -0,0 +1,66 @@
import {} from "esbuild";
import buildOnstartErrorHandler from "../build-on-start-error-handler";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
import { log } from "../../../utils/log";
let build_start = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 2;
const { ROOT_DIR } = grabDirNames();
export default function apiRoutesCTXArtifactTracker({ pages }) {
const artifactTracker = {
name: "ssr-artifact-tracker",
setup(build) {
build.onStart(async () => {
build_starts++;
build_start = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
await buildOnstartErrorHandler();
}
});
build.onEnd((result) => {
if (result.errors.length > 0) {
console.log("result.errors", result.errors);
return;
}
const artifacts = Object.entries(result.metafile.outputs)
.filter(([, meta]) => meta.entryPoint)
.map(([outputPath, meta]) => {
const entrypoint = meta.entryPoint
? path.join(ROOT_DIR, meta.entryPoint)
: undefined;
const target_page = pages.find((p) => p.local_path == entrypoint);
if (!target_page || !meta.entryPoint) {
return undefined;
}
const { file_name, local_path, url_path } = target_page;
return {
path: outputPath,
hash: path.basename(outputPath, path.extname(outputPath)),
type: "text/javascript",
entrypoint: meta.entryPoint,
file_name,
local_path,
url_path,
};
});
// if (artifacts?.[0] && artifacts.length > 0) {
// for (let i = 0; i < artifacts.length; i++) {
// const artifact = artifacts[i];
// if (
// artifact?.local_path &&
// global.API_ROUTES_BUNDLER_CTX_MAP
// ) {
// global.API_ROUTES_BUNDLER_CTX_MAP[
// artifact.local_path
// ] = artifact;
// }
// }
// }
const elapsed = (performance.now() - build_start).toFixed(0);
log.success(`API Routes [Built] in ${elapsed}ms`);
});
},
};
return artifactTracker;
}
+3 -3
View File
@@ -5,7 +5,7 @@ const BunSkipNonBrowserPlugin = {
const skipFilter = /^(bun:|node:|fs$|path$|os$|crypto$|net$|events$|util$|tls$|url$|process$)/;
// const skipped_modules = new Set<string>();
build.onResolve({ filter: skipFilter }, (args) => {
global.SKIPPED_BROWSER_MODULES.add(args.path);
global.BUNEXT_SKIPPED_BROWSER_MODULES.add(args.path);
return {
path: args.path,
namespace: "skipped",
@@ -13,8 +13,8 @@ const BunSkipNonBrowserPlugin = {
};
});
// build.onEnd(() => {
// log.warn(`global.SKIPPED_BROWSER_MODULES`, [
// ...global.SKIPPED_BROWSER_MODULES,
// log.warn(`global.BUNEXT_SKIPPED_BROWSER_MODULES`, [
// ...global.BUNEXT_SKIPPED_BROWSER_MODULES,
// ]);
// });
// build.onResolve({ filter: /^[^./]/ }, (args) => {
+2
View File
@@ -0,0 +1,2 @@
import * as esbuild from "esbuild";
export default function reactVendorChunkPlugin(): esbuild.Plugin;
+97
View File
@@ -0,0 +1,97 @@
// plugins/react-vendor-chunk-plugin.ts
import * as esbuild from "esbuild";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
const { BUNEXT_VENDOR_DIR } = grabDirNames();
const REACT_MODULES = new Set([
"react",
"react-dom",
"react-dom/client",
"react/jsx-runtime",
"react/jsx-dev-runtime",
]);
const VENDOR_BASE = "/.bunext/public/vendor";
const REACT_ENTRIES = {
react: `
import React from "react";
export const {
Children, Component, Fragment, Profiler, PureComponent, StrictMode,
Suspense, cloneElement, createContext, createElement, createFactory,
createRef, forwardRef, isValidElement, lazy, memo, startTransition,
useCallback, useContext, useDebugValue, useDeferredValue, useEffect,
useId, useImperativeHandle, useInsertionEffect, useLayoutEffect,
useMemo, useReducer, useRef, useState, useSyncExternalStore,
useTransition, version,
} = React;
export default React;
`,
"react-dom": `
import ReactDOM from "react-dom";
export const {
createPortal, flushSync, findDOMNode, hydrate, render,
unmountComponentAtNode, version,
} = ReactDOM;
export default ReactDOM;
`,
"react-dom/client": `
import ReactDOMClient from "react-dom/client";
export const { createRoot, hydrateRoot } = ReactDOMClient;
export default ReactDOMClient;
`,
"react/jsx-runtime": `
import JSXRuntime from "react/jsx-runtime";
export const { jsx, jsxs, Fragment } = JSXRuntime;
export default JSXRuntime;
`,
"react/jsx-dev-runtime": `
import JSXDevRuntime from "react/jsx-dev-runtime";
export const { jsxDEV, Fragment } = JSXDevRuntime;
export default JSXDevRuntime;
`,
};
// Map bare specifier -> browser path
function vendorPath(specifier) {
const filename = specifier.replace(/\//g, "_") + ".js";
return `${VENDOR_BASE}/${filename}`;
}
function vendorOutfile(specifier) {
const filename = specifier.replace(/\//g, "_") + ".js";
return path.join(BUNEXT_VENDOR_DIR, filename);
}
export default function reactVendorChunkPlugin() {
let vendorReady;
return {
name: "react-vendor-chunk",
setup(build) {
vendorReady ??= buildAllVendorChunks(build.initialOptions);
build.onResolve({ filter: /^react(-dom)?(\/.*)?$/ }, async (args) => {
const bare = args.path.replace(/\/index(\.m?js)?$/, "");
if (!(bare in REACT_ENTRIES))
return;
await vendorReady;
return {
path: vendorPath(bare),
external: true,
};
});
},
};
}
async function buildAllVendorChunks(parentOptions) {
await Promise.all(Object.entries(REACT_ENTRIES).map(([specifier, contents]) => esbuild.build({
stdin: {
contents,
resolveDir: process.cwd(),
loader: "tsx",
},
outfile: vendorOutfile(specifier),
bundle: true,
minify: parentOptions.minify,
format: "esm",
target: parentOptions.target,
platform: "browser",
define: parentOptions.define,
mainFields: ["module", "main"],
conditions: ["import", "default"],
})));
}
@@ -1,4 +1,4 @@
import type { Plugin } from "esbuild";
import { type Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
type Params = {
entryToPage: Map<string, PageFiles & {
@@ -7,6 +7,7 @@ type Params = {
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
build_only?: boolean;
};
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, }: Params): Plugin;
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, build_only, }: Params): Plugin;
export {};
@@ -1,55 +1,102 @@
import {} from "esbuild";
import { log } from "../../../utils/log";
import grabArtifactsFromBundledResults from "../grab-artifacts-from-bundled-result";
let buildStart = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 10;
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, }) {
import buildOnstartErrorHandler from "../build-on-start-error-handler";
import _ from "lodash";
import pagesSSRBundler from "../pages-ssr-bundler";
import grabDirNames from "../../../utils/grab-dir-names";
import { cpSync, existsSync, mkdirSync, rmSync } from "fs";
import fullRebuild from "../../server/full-rebuild";
import path from "path";
import cleanupLogsDirs from "../../cleanup-logs-dir";
const { BUNX_BUNDLER_ERROR_EXIT_FILE, BUNX_ERROR_LOGS_DIR } = grabDirNames();
let build_start = 0;
const MAX_BUILD_STARTS = 2;
export default function esbuildCTXArtifactTracker({ entryToPage, post_build_fn, build_only, }) {
const artifactTracker = {
name: "artifact-tracker",
setup(build) {
build.onStart(() => {
build_starts++;
buildStart = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
const error_msg = `Build Failed. Please check all your components and imports.`;
log.error(error_msg);
global.RECOMPILING = false;
build.onStart(async () => {
global.BUNEXT_MAIN_CTX_BUILD_STARTS++;
build_start = performance.now();
const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
if (global.BUNEXT_MAIN_CTX_BUILD_STARTS >= MAX_BUILD_STARTS &&
!does_error_file_exist) {
await buildOnstartErrorHandler();
}
});
build.onEnd((result) => {
build.onEnd(async (result) => {
if (result.errors.length > 0) {
// for (const error of result.errors) {
// const loc = error.location;
// const location = loc
// ? ` ${loc.file}:${loc.line}:${loc.column}`
// : "";
// log.error(`[Build]${location} ${error.text}`);
// }
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
log.error(`Build errors:`);
for (const err of result.errors) {
log.error(` ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`);
}
for (let i = global.BUNEXT_HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.BUNEXT_HMR_CONTROLLERS[i];
try {
controller?.controller?.enqueue(`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`);
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
}
return;
}
const artifacts = grabArtifactsFromBundledResults({
result,
entryToPage,
});
// console.log("artifacts", artifacts);
if (artifacts?.[0] && artifacts.length > 0) {
for (let i = 0; i < artifacts.length; i++) {
const artifact = artifacts[i];
if (artifact?.local_path && global.BUNDLER_CTX_MAP) {
global.BUNDLER_CTX_MAP[artifact.local_path] =
artifact;
if (artifact?.local_path &&
global.BUNEXT_BUNDLER_CTX_MAP) {
global.BUNEXT_BUNDLER_CTX_MAP[artifact.local_path] =
_.merge(global.BUNEXT_BUNDLER_CTX_MAP[artifact.local_path], artifact);
}
}
post_build_fn?.({ artifacts });
// writeFileSync(
// HYDRATION_DST_DIR_MAP_JSON_FILE,
// JSON.stringify(artifacts, null, 4),
// );
}
const elapsed = (performance.now() - buildStart).toFixed(0);
const elapsed = (performance.now() - build_start).toFixed(0);
log.success(`[Built] in ${elapsed}ms`);
global.RECOMPILING = false;
build_starts = 0;
global.BUNEXT_MAIN_CTX_BUILD_STARTS = 0;
global.BUNEXT_BUNDLER_CTX_DISPOSED = false;
const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
// SSR must finish before HMR so server props are fresh
if (build_only) {
try {
await pagesSSRBundler();
}
catch (error) {
log.error(`SSR Bundler Error: ${error}`);
}
}
else if (does_error_file_exist) {
mkdirSync(BUNX_ERROR_LOGS_DIR, { recursive: true });
cpSync(BUNX_BUNDLER_ERROR_EXIT_FILE, path.join(BUNX_ERROR_LOGS_DIR, `${Date.now()}.log`));
rmSync(BUNX_BUNDLER_ERROR_EXIT_FILE, { force: true });
cleanupLogsDirs();
await fullRebuild();
}
else {
try {
await pagesSSRBundler();
}
catch (error) {
log.error(`SSR Bundler Error: ${error}`);
}
if (artifacts?.[0] && artifacts.length > 0) {
try {
await post_build_fn?.({ artifacts });
}
catch (error) {
log.error(`Post-build Error: ${error}`);
}
}
}
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
});
},
};
+3
View File
@@ -0,0 +1,3 @@
import type { Plugin } from "esbuild";
declare const reactAliasPlugin: Plugin;
export default reactAliasPlugin;
+22
View File
@@ -0,0 +1,22 @@
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
const { ROOT_DIR } = grabDirNames();
const reactAliasPlugin = {
name: "react-alias",
setup(build) {
const reactPath = path.join(ROOT_DIR, "node_modules");
build.onResolve({ filter: /^react$/ }, () => ({
path: path.join(reactPath, "react", "index.js"),
}));
build.onResolve({ filter: /^react-dom$/ }, () => ({
path: path.join(reactPath, "react-dom", "index.js"),
}));
build.onResolve({ filter: /^react\/jsx-runtime$/ }, () => ({
path: path.join(reactPath, "react", "jsx-runtime.js"),
}));
build.onResolve({ filter: /^react\/jsx-dev-runtime$/ }, () => ({
path: path.join(reactPath, "react", "jsx-dev-runtime.js"),
}));
},
};
export default reactAliasPlugin;
@@ -0,0 +1,12 @@
import { type Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
type Params = {
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
post_build_fn?: (params: {
artifacts: any[];
}) => Promise<void> | void;
};
export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }: Params): Plugin;
export {};
@@ -0,0 +1,65 @@
import {} from "esbuild";
import grabArtifactsFromBundledResults from "../grab-artifacts-from-bundled-result";
import { writeFileSync } from "fs";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
let build_start = 0;
let build_starts = 0;
const MAX_BUILD_STARTS = 2;
const { BUNX_TMP_DIR } = grabDirNames();
export default function ssrCTXArtifactTracker({ entryToPage, post_build_fn, }) {
const artifactTracker = {
name: "ssr-artifact-tracker",
setup(build) {
build.onStart(async () => {
build_starts++;
build_start = performance.now();
if (build_starts == MAX_BUILD_STARTS) {
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
await global.BUNEXT_SSR_BUNDLER_CTX?.dispose();
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
}
});
build.onEnd(async (result) => {
if (result.errors.length > 0) {
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = true;
try {
await global.BUNEXT_SSR_BUNDLER_CTX?.dispose();
}
catch { }
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
build_starts = 0;
for (const err of result.errors) {
console.error(`SSR Build error: ${err.text}${err.location ? ` (${err.location.file}:${err.location.line}:${err.location.column})` : ""}`);
}
return;
}
const artifacts = grabArtifactsFromBundledResults({
result,
entryToPage,
virtual_match: `ssr-virtual`,
});
if (artifacts?.[0] && artifacts.length > 0) {
for (let i = 0; i < artifacts.length; i++) {
const artifact = artifacts[i];
if (artifact?.local_path &&
global.BUNEXT_SSR_BUNDLER_CTX_MAP) {
global.BUNEXT_SSR_BUNDLER_CTX_MAP[artifact.local_path] = artifact;
}
}
// post_build_fn?.({ artifacts });
// const elapsed = (performance.now() - build_start).toFixed(
// 0,
// );
// log.success(`SSR [Built] in ${elapsed}ms`);
}
try {
writeFileSync(path.join(BUNX_TMP_DIR, "ctx-map.json"), JSON.stringify(global.BUNEXT_SSR_BUNDLER_CTX_MAP, null, 4));
}
catch (error) { }
global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED = false;
});
},
};
return artifactTracker;
}
@@ -0,0 +1,9 @@
import type { Plugin } from "esbuild";
import type { PageFiles } from "../../../types";
type Params = {
entryToPage: Map<string, PageFiles & {
tsx: string;
}>;
};
export default function ssrVirtualFilesPlugin({ entryToPage }: Params): Plugin;
export {};
@@ -0,0 +1,31 @@
import path from "path";
import { log } from "../../../utils/log";
export default function ssrVirtualFilesPlugin({ entryToPage }) {
const virtualPlugin = {
name: "ssr-virtual-hydration",
setup(build) {
build.onResolve({ filter: /^ssr-virtual:/ }, (args) => {
const final_path = args.path.replace(/ssr-virtual:/, "");
return {
path: final_path,
namespace: "ssr-virtual",
};
});
build.onLoad({ filter: /.*/, namespace: "ssr-virtual" }, (args) => {
const target = entryToPage.get(args.path);
if (!target?.tsx)
return null;
const contents = target.tsx;
if (!contents.match(/export/)) {
return null;
}
return {
contents: contents || "",
loader: "tsx",
resolveDir: path.dirname(target.local_path),
};
});
},
};
return virtualPlugin;
}
+12 -1
View File
@@ -1,5 +1,4 @@
import path from "path";
import { log } from "../../../utils/log";
export default function virtualFilesPlugin({ entryToPage }) {
const virtualPlugin = {
name: "virtual-hydration",
@@ -11,6 +10,18 @@ export default function virtualFilesPlugin({ entryToPage }) {
namespace: "hydration-virtual",
};
});
build.onResolve({ filter: /node_modules\/react(-dom)?/ }, (args) => ({
path: args.path.includes("react-dom")
? args.path.includes("client")
? "react-dom/client"
: "react-dom"
: args.path.includes("jsx-dev")
? "react/jsx-dev-runtime"
: args.path.includes("jsx")
? "react/jsx-runtime"
: "react",
external: true,
}));
build.onLoad({ filter: /.*/, namespace: "hydration-virtual" }, (args) => {
const target = entryToPage.get(args.path);
if (!target?.tsx)
+1
View File
@@ -0,0 +1 @@
export default function reactModulesBundler(): Promise<void>;
+77
View File
@@ -0,0 +1,77 @@
import * as esbuild from "esbuild";
import grabDirNames from "../../utils/grab-dir-names";
import isDevelopment from "../../utils/is-development";
import path from "path";
import { rmSync, mkdirSync, writeFileSync } from "fs";
const { BUNEXT_VENDOR_DIR, BUNX_CWD_DIR, ROOT_DIR } = grabDirNames();
const VENDOR_ENTRIES = {
react: `
import React from "react";
export const {
Children, Component, Fragment, Profiler, PureComponent, StrictMode,
Suspense, cloneElement, createContext, createElement, createRef,
forwardRef, isValidElement, lazy, memo, startTransition,
useCallback, useContext, useDebugValue, useDeferredValue, useEffect,
useId, useImperativeHandle, useInsertionEffect, useLayoutEffect,
useMemo, useReducer, useRef, useState, useSyncExternalStore,
useTransition, version, use, cache, act,
} = React;
export default React;
`,
"react-dom": `
import ReactDOM from "react-dom";
export const {
createPortal, flushSync, version,
} = ReactDOM;
export default ReactDOM;
`,
"react-dom_client": `
import ReactDOMClient from "react-dom/client";
export const { createRoot, hydrateRoot } = ReactDOMClient;
export default ReactDOMClient;
`,
"react_jsx-runtime": `
import JSXRuntime from "react/jsx-runtime";
export const { jsx, jsxs, Fragment } = JSXRuntime;
`,
"react_jsx-dev-runtime": `
import JSXDevRuntime from "react/jsx-dev-runtime";
export const { jsxDEV, Fragment } = JSXDevRuntime;
`,
};
export default async function reactModulesBundler() {
const dev = isDevelopment();
rmSync(BUNEXT_VENDOR_DIR, { force: true, recursive: true });
const tmpDir = path.join(BUNEXT_VENDOR_DIR, "_tmp");
mkdirSync(tmpDir, { recursive: true });
const entrypoints = {};
for (const [name, contents] of Object.entries(VENDOR_ENTRIES)) {
const file = path.join(tmpDir, `${name}.mjs`);
writeFileSync(file, contents);
entrypoints[name] = file;
}
await esbuild.build({
entryPoints: entrypoints,
outdir: BUNEXT_VENDOR_DIR,
bundle: true,
splitting: true,
format: "esm",
platform: "browser",
target: "es2020",
minify: !dev,
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
},
});
rmSync(tmpDir, { force: true, recursive: true });
const PUBLIC_ROOT = BUNEXT_VENDOR_DIR.replace(BUNX_CWD_DIR, "/.bunext");
global.BUNEXT_REACT_IMPORTS_MAP = {
imports: {
react: `${PUBLIC_ROOT}/react.js`,
"react-dom": `${PUBLIC_ROOT}/react-dom.js`,
"react-dom/client": `${PUBLIC_ROOT}/react-dom_client.js`,
"react/jsx-runtime": `${PUBLIC_ROOT}/react_jsx-runtime.js`,
"react/jsx-dev-runtime": `${PUBLIC_ROOT}/react_jsx-dev-runtime.js`,
},
};
}
+2 -2
View File
@@ -8,8 +8,8 @@ export default async function recordArtifacts({ artifacts, page_file_paths, }) {
artifacts_map[artifact.local_path] = artifact;
}
}
if (global.BUNDLER_CTX_MAP) {
global.BUNDLER_CTX_MAP = _.merge(global.BUNDLER_CTX_MAP, artifacts_map);
if (global.BUNEXT_BUNDLER_CTX_MAP) {
global.BUNEXT_BUNDLER_CTX_MAP = _.merge(global.BUNEXT_BUNDLER_CTX_MAP, artifacts_map);
}
// await Bun.write(
// HYDRATION_DST_DIR_MAP_JSON_FILE,
+45 -18
View File
@@ -1,27 +1,54 @@
import type { BundlerCTXMap, BunextConfig, GlobalHMRControllerObject, PageFiles } from "../types";
import type { FileSystemRouter, Server } from "bun";
import { type FSWatcher } from "fs";
import { type DirNames } from "../utils/grab-dir-names";
import type { BuildContext } from "esbuild";
import grabConstants from "../utils/grab-constants";
import type { FSWatcher } from "fs";
/**
* # Declare Global Variables
*/
declare global {
var CONFIG: BunextConfig;
var SERVER: Server<any> | undefined;
var RECOMPILING: boolean;
var WATCHER_TIMEOUT: any;
var ROUTER: FileSystemRouter;
var HMR_CONTROLLERS: GlobalHMRControllerObject[];
var LAST_BUILD_TIME: number;
var BUNDLER_CTX_MAP: {
var BUNEXT_CONFIG: BunextConfig;
var BUNEXT_SERVER: Server<any> | undefined;
var BUNEXT_RECOMPILING: boolean;
var BUNEXT_BUILDING_SSR: boolean;
var BUNEXT_IS_SERVER_COMPONENT: boolean;
var BUNEXT_WATCHER_TIMEOUT: any;
var BUNEXT_ROUTER: FileSystemRouter;
var BUNEXT_HMR_CONTROLLERS: GlobalHMRControllerObject[];
var BUNEXT_LAST_BUILD_TIME: number;
var BUNEXT_BUNDLER_CTX_MAP: {
[k: string]: BundlerCTXMap;
} | undefined;
var BUNDLER_REBUILDS: 0;
var PAGES_SRC_WATCHER: FSWatcher | undefined;
var CURRENT_VERSION: string | undefined;
var PAGE_FILES: PageFiles[];
var ROOT_FILE_UPDATED: boolean;
var SKIPPED_BROWSER_MODULES: Set<string>;
var BUNDLER_CTX: BuildContext | undefined;
};
var BUNEXT_SSR_BUNDLER_CTX_MAP: {
[k: string]: BundlerCTXMap;
};
var BUNEXT_BUNDLER_REBUILDS: 0;
var BUNEXT_PAGES_SRC_WATCHER: FSWatcher | undefined;
var BUNEXT_CURRENT_VERSION: string | undefined;
var BUNEXT_PAGE_FILES: PageFiles[];
var BUNEXT_ROOT_FILE_UPDATED: boolean;
var BUNEXT_SKIPPED_BROWSER_MODULES: Set<string>;
var BUNEXT_BUNDLER_CTX: BuildContext | undefined;
var BUNEXT_SSR_BUNDLER_CTX: BuildContext | undefined;
var BUNEXT_DIR_NAMES: DirNames;
var BUNEXT_REACT_IMPORTS_MAP: {
imports: Record<string, string>;
};
var BUNEXT_REACT_DOM_SERVER: any;
var BUNEXT_REACT_DOM_MODULE_CACHE: Map<string, {
main: any;
css: string;
}>;
var BUNEXT_BUNDLER_CTX_DISPOSED: boolean | undefined;
var BUNEXT_SSR_BUNDLER_CTX_DISPOSED: boolean | undefined;
var BUNEXT_REBUILD_RETRIES: number;
var BUNEXT_IS_404_PAGE: boolean;
var BUNEXT_CONSTANTS: ReturnType<typeof grabConstants>;
var BUNEXT_MAIN_CTX_BUILD_STARTS: number;
}
export default function bunextInit(): Promise<void>;
type Params = {
build_only?: boolean;
};
export default function bunextInit(params?: Params): Promise<void>;
export {};
+42 -11
View File
@@ -1,29 +1,60 @@
import grabDirNames from "../utils/grab-dir-names";
import {} from "fs";
import grabDirNames, {} from "../utils/grab-dir-names";
import init from "./init";
import isDevelopment from "../utils/is-development";
import { log } from "../utils/log";
import cron from "./server/cron";
import allPagesESBuildContextBundler from "./bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server/server-post-build-fn";
import reactModulesBundler from "./bundler/react-modules-bundler";
import grabConstants from "../utils/grab-constants";
import watcherEsbuildCTX from "./server/watcher-esbuild-ctx";
const { PAGES_DIR } = grabDirNames();
export default async function bunextInit() {
global.HMR_CONTROLLERS = [];
global.BUNDLER_CTX_MAP = {};
global.BUNDLER_REBUILDS = 0;
global.PAGE_FILES = [];
global.SKIPPED_BROWSER_MODULES = new Set();
const dirNames = grabDirNames();
const { PAGES_DIR } = dirNames;
export default async function bunextInit(params) {
global.BUNEXT_HMR_CONTROLLERS = [];
global.BUNEXT_BUNDLER_CTX_MAP = {};
global.BUNEXT_SSR_BUNDLER_CTX_MAP = {};
// global.BUNEXT_API_ROUTES_BUNDLER_CTX_MAP = {};
global.BUNEXT_BUNDLER_REBUILDS = 0;
global.BUNEXT_REBUILD_RETRIES = 0;
global.BUNEXT_PAGE_FILES = [];
global.BUNEXT_SKIPPED_BROWSER_MODULES = new Set();
global.BUNEXT_DIR_NAMES = dirNames;
global.BUNEXT_REACT_IMPORTS_MAP = { imports: {} };
global.BUNEXT_REACT_DOM_MODULE_CACHE = new Map();
global.BUNEXT_MAIN_CTX_BUILD_STARTS = 0;
await init();
log.banner();
global.BUNEXT_CONSTANTS = grabConstants();
await reactModulesBundler();
const router = new Bun.FileSystemRouter({
style: "nextjs",
dir: PAGES_DIR,
});
global.ROUTER = router;
global.BUNEXT_ROUTER = router;
const is_dev = isDevelopment();
if (is_dev) {
if (params?.build_only) {
log.build(`Building Modules ...`);
await allPagesESBuildContextBundler();
}
else if (is_dev) {
log.build(`Building Modules ...`);
await allPagesESBuildContextBundler({
post_build_fn: async () => {
await serverPostBuildFn();
},
});
watcherEsbuildCTX();
}
else {
log.build(`Building Modules ...`);
await allPagesESBuildContextBundler({ start: true });
cron();
}
}
// process.on("exit", (code) => {
// Bun.spawn([process.execPath, ...process.argv.slice(1)], {
// stdio: ["inherit", "inherit", "inherit"],
// env: process.env,
// });
// });
+4
View File
@@ -13,6 +13,10 @@ export default async function trimAllCache() {
const trim_key = await trimCacheKey({
key: cache_key,
});
if (trim_key.success) {
cached_items.splice(i, 1);
i--;
}
}
}
catch (error) {
+2 -2
View File
@@ -9,8 +9,8 @@ export default async function trimCacheKey({ key, }) {
const { cache_name, cache_meta_name } = grabCacheNames({
key,
});
const config = global.CONFIG;
const default_expiry_time_seconds = config.defaultCacheExpiry ||
const config = global.BUNEXT_CONFIG;
const default_expiry_time_seconds = config.default_cache_expiry ||
AppData["DefaultCacheExpiryTimeSeconds"];
const default_expiry_time_milliseconds = default_expiry_time_seconds * 1000;
const cache_content_path = path.join(BUNEXT_CACHE_DIR, cache_name);
+1
View File
@@ -0,0 +1 @@
export default function cleanupLogsDirs(): void;
+48
View File
@@ -0,0 +1,48 @@
import path from "path";
import { mkdirSync, readdirSync, rmSync, statSync, writeFileSync } from "fs";
import grabDirNames from "../utils/grab-dir-names";
import grabConstants from "../utils/grab-constants";
import { AppData } from "../data/app-data";
const { BUNX_LOGS_DIR } = grabDirNames();
export default function cleanupLogsDirs() {
const logs_dirs = readdirSync(BUNX_LOGS_DIR);
const { config } = grabConstants();
const MAX_LOGS = config.max_logs || AppData["DefaultMaxLogs"];
for (let i = 0; i < logs_dirs.length; i++) {
const dir = logs_dirs[i];
const full_path = path.join(BUNX_LOGS_DIR, dir);
const path_stats = statSync(full_path);
if (!path_stats.isDirectory()) {
continue;
}
const sub_dir_files = readdirSync(full_path).sort((a, b) => {
const timestamp_a = Number(a.split(".")[0]);
const timestamp_b = Number(b.split(".")[0]);
if (timestamp_a > timestamp_b)
return 1;
return -1;
});
for (let j = 0; j < sub_dir_files.length; j++) {
const sub_dir_file = sub_dir_files[j];
const sub_dir_file_full_path = path.join(full_path, sub_dir_file);
const sub_dir_file_Stats = statSync(sub_dir_file_full_path);
if (!sub_dir_file_Stats.isFile()) {
rmSync(sub_dir_file_full_path, {
force: true,
recursive: true,
});
continue;
}
if (j > MAX_LOGS - 1) {
rmSync(sub_dir_file_full_path, { force: true });
}
}
}
}
// log.info("Running development server ...");
// try {
// rmSync(HYDRATION_DST_DIR, { recursive: true });
// rmSync(BUNX_CWD_PAGES_REWRITE_DIR, { recursive: true });
// } catch (error) {}
// await bunextInit();
// await startServer();
+2 -2
View File
@@ -24,7 +24,7 @@ export default async function () {
try {
const package_json = await Bun.file(path.resolve(__dirname, "../../package.json")).json();
const current_version = package_json.version;
global.CURRENT_VERSION = current_version;
global.BUNEXT_CURRENT_VERSION = current_version;
}
catch (error) { }
const keys = Object.keys(dirNames);
@@ -45,7 +45,7 @@ export default async function () {
}
}
const config = (await grabConfig()) || {};
global.CONFIG = {
global.BUNEXT_CONFIG = {
...config,
development: is_dev,
};
+1
View File
@@ -0,0 +1 @@
export {};
+46
View File
@@ -0,0 +1,46 @@
import { spawn } from "bun";
// Only the "supervisor" respawns. The child sets this env var so it won't respawn itself.
const IS_CHILD = process.env.__RESPAWN_CHILD === "1";
let shuttingDown = false;
async function cleanup() {
// Put real cleanup here: close DB handles, servers, file descriptors, timers, etc.
// Must be awaitable — do NOT rely on process.on("exit") for this.
}
function respawn(code) {
const child = spawn({
cmd: [process.execPath, ...process.argv.slice(1)],
stdio: ["inherit", "inherit", "inherit"],
env: { ...process.env, __RESPAWN_CHILD: "1" },
// Detach so the child survives independently and gets its own process group.
// Without this, killing the parent's group can take the child with it.
});
// Let the child live on its own.
child.unref?.();
}
async function shutdown(code) {
if (shuttingDown)
return;
shuttingDown = true;
try {
await cleanup();
}
catch (e) {
console.error("cleanup failed:", e);
}
// Only the supervisor respawns, and only on abnormal exit.
if (!IS_CHILD && code !== 0) {
respawn(code);
}
process.exit(code);
}
// Catch the things that actually fire *before* exit, where async works.
process.on("SIGINT", () => shutdown(130));
process.on("SIGTERM", () => shutdown(143));
process.on("uncaughtException", (err) => {
console.error(err);
shutdown(1);
});
process.on("unhandledRejection", (err) => {
console.error(err);
shutdown(1);
});
+29 -14
View File
@@ -1,20 +1,26 @@
import handleWebPages from "./web-pages/handle-web-pages";
import handleRoutes from "./handle-routes";
import isDevelopment from "../../utils/is-development";
import grabConstants from "../../utils/grab-constants";
import handleHmr from "./handle-hmr";
import handlePublic from "./handle-public";
import handleFiles from "./handle-files";
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";
const HMR_RETRY_COOLDOWN_MS = 5000;
let lastHmrRetryTime = 0;
export default async function bunextRequestHandler({ req: initial_req, server, }) {
const is_dev = isDevelopment();
let req = initial_req.clone();
try {
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;
if (config?.middleware) {
const middleware_res = await config.middleware({
if (global.BUNEXT_CONSTANTS.config?.middleware) {
const middleware_res = await global.BUNEXT_CONSTANTS.config.middleware({
req: initial_req,
url,
});
@@ -25,14 +31,19 @@ export default async function bunextRequestHandler({ req: initial_req, server, }
req = middleware_res;
}
}
// const server_upgrade = server.upgrade(req);
// if (server_upgrade) {
// return undefined;
// }
if (url.pathname === "/__hmr" && is_dev) {
response = await handleHmr({ req });
if (is_dev && url.pathname == AppData["BunextHMRRetryRoute"]) {
const now = Date.now();
if (now - lastHmrRetryTime < HMR_RETRY_COOLDOWN_MS) {
return new Response("Too Many Requests", { status: 429 });
}
lastHmrRetryTime = now;
await fullRebuild({ msg: `HMR Retry Rebuild ...` });
return new Response("Modules Rebuilt");
}
else if (url.pathname.startsWith("/.bunext/public/pages")) {
if (url.pathname === "/__hmr" && is_dev) {
return handleHmr({ req });
}
else if (url.pathname.startsWith("/.bunext")) {
response = await handleBunextPublicAssets({ req });
}
else if (url.pathname.startsWith("/api/")) {
@@ -56,8 +67,12 @@ export default async function bunextRequestHandler({ req: initial_req, server, }
return response;
}
catch (error) {
return new Response(`Server Error: ${error.message}`, {
status: 500,
});
if (is_dev) {
return new Response(`Server Error: ${error.message}`, {
status: 500,
});
}
console.error(`Server Error: ${error.message}`, error);
return new Response("Internal Server Error", { status: 500 });
}
}
@@ -0,0 +1 @@
export default function chokadirWatcherEsbuildCTX(): Promise<void>;
+133
View File
@@ -0,0 +1,133 @@
import chokidar from "chokidar";
import path from "path";
import { existsSync } from "fs";
import grabDirNames from "../../utils/grab-dir-names";
import fullRebuild from "./full-rebuild";
import { AppData } from "../../data/app-data";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
import pagesSSRBundler from "../bundler/pages-ssr-bundler";
import { log } from "../../utils/log";
const { ROOT_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
export default async function chokadirWatcherEsbuildCTX() {
const watcher = chokidar.watch(ROOT_DIR, {
ignored: [
/(^|[\/\\])\../,
/node_modules/,
/public/,
/\.bunext/,
/\.git/,
/dist/,
/bun\.lockb/,
(path) => path.endsWith(AppData["BunextTmpFileExt"]),
],
persistent: true,
ignoreInitial: true,
depth: 99,
});
const handleEvent = async (event, filePath) => {
let owns_recompile = false;
try {
const filename = path.relative(ROOT_DIR, filePath);
if (existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE)) {
await fullRebuild();
return;
}
if (global.BUNEXT_BUNDLER_CTX_DISPOSED) {
await fullRebuild({ msg: `Restarting Bundler ...` });
}
if (global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED) {
await pagesSSRBundler().catch((error) => {
log.error(`SSR Bundler Error: ${error}`);
});
}
if (filename.match(/\/styles$/) || filename === "styles") {
owns_recompile = true;
global.BUNEXT_RECOMPILING = true;
await Bun.sleep(1000);
await fullRebuild({
msg: `Detected new \`styles\` directory. Rebuilding ...`,
});
return;
}
if (filename.match(/bunext.config\.ts/)) {
await fullRebuild({
msg: `bunext.config.ts file changed. Rebuilding server ...`,
});
return;
}
const target_files_match = /\.(tsx?|jsx?|css)$/;
if (event === "change") {
if (filename.match(target_files_match)) {
if (global.BUNEXT_RECOMPILING)
return;
owns_recompile = true;
global.BUNEXT_RECOMPILING = true;
if (filename.match(/.*\.server\.tsx?/)) {
global.BUNEXT_IS_SERVER_COMPONENT = true;
}
if (global.BUNEXT_BUNDLER_CTX) {
await global.BUNEXT_BUNDLER_CTX.rebuild();
}
if (filename.match(/(404|500)\.tsx?/)) {
for (let i = global.BUNEXT_HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.BUNEXT_HMR_CONTROLLERS[i];
try {
controller?.controller?.enqueue(`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`);
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
}
}
}
return;
}
if (["add", "unlink", "addDir", "unlinkDir"].includes(event)) {
const is_file_of_interest = !!filename.match(target_files_match) ||
event.includes("Dir");
if (!is_file_of_interest)
return;
if (!filename.match(/^src\/pages\/|\.css$/) ||
checkExcludedPatterns({ path: filename }) ||
filename.includes(" ")) {
return reloadWatcher();
}
if (global.BUNEXT_RECOMPILING)
return;
owns_recompile = true;
const action = event.startsWith("add") ? "created" : "deleted";
const type = filename.match(/\.css$/)
? "Stylesheet"
: event.includes("Dir")
? "Directory"
: filename.match(/\/pages\/api\//)
? "API Route"
: "Page";
await fullRebuild({
msg: `${type} ${action}: ${filename}. Rebuilding ...`,
});
}
}
catch (error) {
log.error(`Watcher rebuild failed: ${error}`);
}
finally {
if (owns_recompile) {
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
}
}
};
watcher
.on("add", (path) => handleEvent("add", path))
.on("change", (path) => handleEvent("change", path))
.on("unlink", (path) => handleEvent("unlink", path))
.on("addDir", (path) => handleEvent("addDir", path))
.on("unlinkDir", (path) => handleEvent("unlinkDir", path));
}
function reloadWatcher() {
if (global.BUNEXT_PAGES_SRC_WATCHER) {
global.BUNEXT_PAGES_SRC_WATCHER.close();
chokadirWatcherEsbuildCTX();
}
}
+1
View File
@@ -0,0 +1 @@
export default function clearRequireCache(modulePath: string): void;
+10
View File
@@ -0,0 +1,10 @@
export default function clearRequireCache(modulePath) {
const resolved = require.resolve(modulePath);
const mod = require.cache[resolved];
if (mod) {
mod.children?.forEach((child) => {
clearRequireCache(child.id);
});
delete require.cache[resolved];
}
}
+3
View File
@@ -0,0 +1,3 @@
export default function fullRebuild(params?: {
msg?: string;
}): Promise<void>;
+37
View File
@@ -0,0 +1,37 @@
import { log } from "../../utils/log";
import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server-post-build-fn";
import watcherEsbuildCTX from "./watcher-esbuild-ctx";
export default async function fullRebuild(params) {
try {
const { msg } = params || {};
global.BUNEXT_RECOMPILING = true;
if (msg) {
log.watch(msg);
}
global.BUNEXT_ROUTER.reload();
try {
await global.BUNEXT_BUNDLER_CTX?.dispose();
global.BUNEXT_BUNDLER_CTX = undefined;
await global.BUNEXT_SSR_BUNDLER_CTX?.dispose();
global.BUNEXT_SSR_BUNDLER_CTX = undefined;
}
catch (error) { }
await allPagesESBuildContextBundler({
post_build_fn: async () => {
await serverPostBuildFn();
},
});
}
catch (error) {
log.error(error);
}
finally {
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
}
if (global.BUNEXT_PAGES_SRC_WATCHER) {
global.BUNEXT_PAGES_SRC_WATCHER.close();
watcherEsbuildCTX();
}
}
+11 -11
View File
@@ -1,23 +1,23 @@
import grabDirNames from "../../utils/grab-dir-names";
import path from "path";
import isDevelopment from "../../utils/is-development";
import { existsSync } from "fs";
const { HYDRATION_DST_DIR } = grabDirNames();
import { readFileResponse } from "./handle-public";
import isSafePath from "../../utils/is-safe-path";
const { BUNEXT_PUBLIC_DIR } = grabDirNames();
export default async function ({ req }) {
try {
const is_dev = isDevelopment();
const url = new URL(req.url);
const file_path = path.join(HYDRATION_DST_DIR, url.pathname.replace(/\/\.bunext\/public\/pages\//, ""));
if (!file_path.startsWith(HYDRATION_DST_DIR + path.sep)) {
const file_path = path.join(BUNEXT_PUBLIC_DIR, url.pathname.replace(/\/\.bunext\/public\//, ""));
if (!isSafePath({ filePath: file_path, allowedDir: BUNEXT_PUBLIC_DIR })) {
return new Response("Forbidden", { status: 403 });
}
if (!existsSync(file_path)) {
return new Response(`File Doesn't Exist`, {
status: 404,
});
}
const file = Bun.file(file_path);
return new Response(file);
return readFileResponse({
file_path,
cache: url.pathname.includes("/vendor/")
? { duration: 3600 }
: undefined,
});
}
catch (error) {
return new Response(`File Not Found`, {
+7 -2
View File
@@ -2,13 +2,14 @@ import grabDirNames from "../../utils/grab-dir-names";
import path from "path";
import isDevelopment from "../../utils/is-development";
import { existsSync } from "fs";
import isSafePath from "../../utils/is-safe-path";
const { PUBLIC_DIR } = grabDirNames();
export default async function ({ req }) {
try {
const is_dev = isDevelopment();
const url = new URL(req.url);
const file_path = path.join(PUBLIC_DIR, url.pathname);
if (!file_path.startsWith(PUBLIC_DIR + path.sep)) {
if (!isSafePath({ filePath: file_path, allowedDir: PUBLIC_DIR })) {
return new Response("Forbidden", { status: 403 });
}
if (!existsSync(file_path)) {
@@ -17,7 +18,11 @@ export default async function ({ req }) {
});
}
const file = Bun.file(file_path);
return new Response(file);
const headers = new Headers();
if (!is_dev) {
headers.set("Cache-Control", "public, max-age=3600");
}
return new Response(file, { headers });
}
catch (error) {
return new Response(`File Not Found`, {
+24 -9
View File
@@ -1,18 +1,36 @@
function removeController(controller) {
const idx = global.BUNEXT_HMR_CONTROLLERS.findIndex((c) => c.controller == controller);
if (typeof idx == "number" && idx >= 0) {
global.BUNEXT_HMR_CONTROLLERS.splice(idx, 1);
}
}
export default async function ({ req }) {
const referer_url = new URL(req.headers.get("referer") || "");
const match = global.ROUTER.match(referer_url.pathname);
const referer = req.headers.get("referer");
const page_cookie = req.headers.get("cookie");
if (!referer) {
return new Response("Missing Referer Header", { status: 400 });
}
let referer_url;
try {
referer_url = new URL(referer);
}
catch {
return new Response("Invalid Referer Header", { status: 400 });
}
const match = global.BUNEXT_ROUTER.match(referer_url.pathname);
const target_map = match?.filePath
? global.BUNDLER_CTX_MAP?.[match.filePath]
? global.BUNEXT_BUNDLER_CTX_MAP?.[match.filePath]
: undefined;
let controller;
let heartbeat;
const stream = new ReadableStream({
start(c) {
controller = c;
global.HMR_CONTROLLERS.push({
global.BUNEXT_HMR_CONTROLLERS.push({
controller: c,
page_url: referer_url.href,
target_map,
page_cookie,
});
heartbeat = setInterval(() => {
try {
@@ -20,16 +38,13 @@ export default async function ({ req }) {
}
catch {
clearInterval(heartbeat);
removeController(controller);
}
}, 5000);
},
cancel() {
clearInterval(heartbeat);
const targetControllerIndex = global.HMR_CONTROLLERS.findIndex((c) => c.controller == controller);
if (typeof targetControllerIndex == "number" &&
targetControllerIndex >= 0) {
global.HMR_CONTROLLERS.splice(targetControllerIndex, 1);
}
removeController(controller);
},
});
return new Response(stream, {
+7
View File
@@ -2,4 +2,11 @@ type Params = {
req: Request;
};
export default function ({ req }: Params): Promise<Response>;
type FileResponse = {
file_path: string;
cache?: {
duration?: "infinite" | number;
};
};
export declare function readFileResponse({ file_path, cache }: FileResponse): Response;
export {};
+24 -9
View File
@@ -2,23 +2,17 @@ import grabDirNames from "../../utils/grab-dir-names";
import path from "path";
import isDevelopment from "../../utils/is-development";
import { existsSync } from "fs";
import isSafePath from "../../utils/is-safe-path";
const { PUBLIC_DIR } = grabDirNames();
export default async function ({ req }) {
try {
const is_dev = isDevelopment();
const url = new URL(req.url);
const file_path = path.join(PUBLIC_DIR, url.pathname.replace(/^\/public/, ""));
if (!file_path.startsWith(PUBLIC_DIR + path.sep)) {
if (!isSafePath({ filePath: file_path, allowedDir: PUBLIC_DIR })) {
return new Response("Forbidden", { status: 403 });
}
if (!existsSync(file_path)) {
return new Response(`Public File Doesn't Exist`, {
status: 404,
});
}
const file = Bun.file(file_path);
let res_opts = {};
return new Response(file, res_opts);
return readFileResponse({ file_path });
}
catch (error) {
return new Response(`Public File Not Found`, {
@@ -26,3 +20,24 @@ export default async function ({ req }) {
});
}
}
export function readFileResponse({ file_path, cache }) {
if (!existsSync(file_path)) {
return new Response(`Public File Doesn't Exist`, {
status: 404,
});
}
const file = Bun.file(file_path);
const headers = new Headers();
if (cache?.duration == "infinite" || (cache && !cache.duration)) {
headers.set("Cache-Control", "public, max-age=31536000, immutable");
}
else if (cache?.duration) {
headers.set("Cache-Control", `public, max-age=${cache.duration}`);
}
else if (!isDevelopment()) {
headers.set("Cache-Control", "public, max-age=3600");
}
return new Response(file, {
headers,
});
}
+87 -10
View File
@@ -2,6 +2,10 @@ import grabRouteParams from "../../utils/grab-route-params";
import grabConstants from "../../utils/grab-constants";
import grabRouter from "../../utils/grab-router";
import isDevelopment from "../../utils/is-development";
import _ from "lodash";
import path from "path";
import grabDirNames from "../../utils/grab-dir-names";
const { ROOT_DIR } = grabDirNames();
export default async function ({ req }) {
const url = new URL(req.url);
const is_dev = isDevelopment();
@@ -20,17 +24,30 @@ export default async function ({ req }) {
},
});
}
const routeParams = await grabRouteParams({ req });
const routeParams = await grabRouteParams({
req,
query: match.query,
});
let module;
const now = Date.now();
const import_path = is_dev ? `${match.filePath}?t=${now}` : match.filePath;
const module = await import(import_path);
if (is_dev && global.BUNEXT_SSR_BUNDLER_CTX_MAP?.[match.filePath]?.path) {
const target_import = path.join(ROOT_DIR, global.BUNEXT_SSR_BUNDLER_CTX_MAP[match.filePath].path);
module = await import(`${target_import}?t=${now}`);
}
else {
const import_path = is_dev
? `${match.filePath}?t=${now}`
: match.filePath;
module = await import(import_path);
}
const config = module.config;
const maxBodyBytes = config?.max_request_body_mb
? config.max_request_body_mb * MBInBytes
: ServerDefaultRequestBodyLimitBytes;
const contentLength = req.headers.get("content-length");
if (contentLength) {
const size = parseInt(contentLength, 10);
if ((config?.maxRequestBodyMB &&
size > config.maxRequestBodyMB * MBInBytes) ||
size > ServerDefaultRequestBodyLimitBytes) {
if (size > maxBodyBytes) {
return Response.json({
success: false,
msg: "Request Body Too Large!",
@@ -42,11 +59,71 @@ export default async function ({ req }) {
});
}
}
const res = await module["default"]({
else if (req.method !== "GET" && req.method !== "HEAD") {
const body = await req.arrayBuffer();
if (body.byteLength > maxBodyBytes) {
return Response.json({
success: false,
msg: "Request Body Too Large!",
}, {
status: 413,
headers: {
"Content-Type": "application/json",
},
});
}
routeParams.body = JSON.parse(new TextDecoder().decode(body) || "{}");
}
const target_module = (module["default"] ||
module["handler"]);
const res = await target_module?.({
...routeParams,
});
if (is_dev) {
res.headers.set("Cache-Control", "no-cache, no-store, must-revalidate");
if (res instanceof Response) {
if (is_dev) {
res.headers.set("Cache-Control", "no-cache, no-store, must-revalidate");
}
return res;
}
return res;
if (res) {
let final_res = Response.json(_.omit(res, [
"bunext_api_route_res_options",
"bunext_api_route_res_transform_fn",
]), {
...(res.bunext_api_route_res_options || undefined),
});
if (res.bunext_api_route_res_transform_fn) {
final_res = await res.bunext_api_route_res_transform_fn(final_res);
}
return final_res;
}
return Response.json({ err: `Route handler error` });
}
// const relative_path = match.filePath.replace(API_DIR, "");
// const relative_module_js_file = relative_path.replace(/\.tsx?$/, ".js");
// const bun_module_file = path.join(
// BUNX_CWD_MODULE_CACHE_DIR,
// "api",
// relative_module_js_file,
// );
// if (existsSync(bun_module_file)) {
// module = await import(`${bun_module_file}?t=${now}`);
// } else {
// const import_path = is_dev
// ? `${match.filePath}?t=${now}`
// : match.filePath;
// module = await import(import_path);
// }
// if (is_dev) {
// const tmp_path = `${match.filePath}.${now}${AppData["BunextTmpFileExt"]}`;
// cpSync(match.filePath, tmp_path);
// module = await import(`${tmp_path}?t=${now}`);
// try {
// unlinkSync(tmp_path);
// } catch (error) {}
// } else {
// // const import_path = is_dev ? `${match.filePath}?t=${now}` : match.filePath;
// module = await import(match.filePath);
// }
// const import_path = is_dev ? `${match.filePath}?t=${now}` : match.filePath;
// module = await import(import_path);
-5
View File
@@ -1,5 +0,0 @@
type Params = {
target_file_paths?: string[];
};
export default function rebuildBundler(params?: Params): Promise<void>;
export {};
-21
View File
@@ -1,21 +0,0 @@
import serverPostBuildFn from "./server-post-build-fn";
import { log } from "../../utils/log";
import allPagesBunBundler from "../bundler/all-pages-bun-bundler";
import cleanupArtifacts from "./cleanup-artifacts";
export default async function rebuildBundler(params) {
try {
global.ROUTER.reload();
// await global.BUNDLER_CTX?.dispose();
// global.BUNDLER_CTX = undefined;
const new_artifacts = await allPagesBunBundler({
page_file_paths: params?.target_file_paths,
});
await serverPostBuildFn();
if (new_artifacts?.[0]) {
cleanupArtifacts({ new_artifacts });
}
}
catch (error) {
log.error(error);
}
}
+1 -1
View File
@@ -15,6 +15,6 @@ export default async function () {
idleTimeout: development ? 0 : undefined,
development,
websocket: config?.websocket,
..._.omit(config?.serverOptions || {}, ["fetch"]),
..._.omit(config?.server_options || {}, ["fetch"]),
};
}
+5 -1
View File
@@ -1 +1,5 @@
export default function serverPostBuildFn(): Promise<void>;
type Params = {
reload_all_controllers?: boolean;
};
export default function serverPostBuildFn(params?: Params): Promise<void>;
export {};
+47 -18
View File
@@ -1,23 +1,53 @@
import _ from "lodash";
import grabPageComponent from "./web-pages/grab-page-component";
export default async function serverPostBuildFn() {
// if (!global.IS_FIRST_BUNDLE_READY) {
// global.IS_FIRST_BUNDLE_READY = true;
// }
if (!global.HMR_CONTROLLERS?.[0] || !global.BUNDLER_CTX_MAP) {
export default async function serverPostBuildFn(params) {
if (!global.BUNEXT_HMR_CONTROLLERS?.[0] || !global.BUNEXT_BUNDLER_CTX_MAP) {
return;
}
for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.HMR_CONTROLLERS[i];
if (!controller?.target_map?.local_path) {
const reload_payload = { reload: true };
const reload_enqueue = `event: update\ndata: ${JSON.stringify(reload_payload)}\n\n`;
for (let i = global.BUNEXT_HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.BUNEXT_HMR_CONTROLLERS[i];
if (!controller) {
continue;
}
const target_artifact = global.BUNDLER_CTX_MAP[controller.target_map.local_path];
const mock_req = new Request(controller.page_url);
const { serverRes } = await grabPageComponent({
if (!controller.target_map?.local_path) {
continue;
}
if (params?.reload_all_controllers) {
try {
controller.controller.enqueue(reload_enqueue);
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
continue;
}
const target_artifact = global.BUNEXT_BUNDLER_CTX_MAP[controller.target_map.local_path];
if (!target_artifact?.local_path) {
try {
controller.controller.enqueue(reload_enqueue);
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
continue;
}
const mock_req = target_artifact.req_url
? new Request(target_artifact.req_url, {})
: new Request(controller.page_url);
if (controller.page_cookie) {
mock_req.headers.set("cookie", controller.page_cookie);
}
const page_component = await grabPageComponent({
req: mock_req,
return_server_res_only: true,
is_hydration: true,
});
if (page_component instanceof Response) {
continue;
}
const { serverRes } = page_component || {};
const final_artifact = {
..._.omit(controller, ["controller"]),
target_map: target_artifact,
@@ -25,22 +55,21 @@ export default async function serverPostBuildFn() {
if (!target_artifact) {
delete final_artifact.target_map;
}
if (serverRes) {
final_artifact.page_props = serverRes;
}
// Always replace so prior error props cannot linger
final_artifact.page_props = serverRes || {};
try {
let final_data = {};
if (global.ROOT_FILE_UPDATED) {
final_data = { reload: true };
if (global.BUNEXT_ROOT_FILE_UPDATED) {
final_data = reload_payload;
}
else {
final_data = final_artifact;
}
controller.controller.enqueue(`event: update\ndata: ${JSON.stringify(final_data)}\n\n`);
global.ROOT_FILE_UPDATED = false;
global.BUNEXT_ROOT_FILE_UPDATED = false;
}
catch {
global.HMR_CONTROLLERS.splice(i, 1);
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
}
}
+12 -1
View File
@@ -1,10 +1,21 @@
import _ from "lodash";
import { log } from "../../utils/log";
import serverParamsGen from "./server-params-gen";
import isDevelopment from "../../utils/is-development";
import watcherEsbuildCTX from "./watcher-esbuild-ctx";
export default async function startServer() {
const serverParams = await serverParamsGen();
const server = Bun.serve(serverParams);
global.SERVER = server;
const is_dev = isDevelopment();
global.BUNEXT_SERVER = server;
log.server(`http://${server.hostname}:${server.port}`);
if (is_dev) {
setInterval(() => {
if (global.BUNEXT_PAGES_SRC_WATCHER) {
global.BUNEXT_PAGES_SRC_WATCHER.close();
watcherEsbuildCTX();
}
}, 5000);
}
return server;
}
+130 -75
View File
@@ -1,93 +1,148 @@
import { watch, existsSync } from "fs";
import { watch, existsSync, statSync, glob } from "fs";
import path from "path";
import grabDirNames from "../../utils/grab-dir-names";
import rebuildBundler from "./rebuild-bundler";
import fullRebuild from "./full-rebuild";
import { AppData } from "../../data/app-data";
import checkExcludedPatterns from "../../utils/check-excluded-patterns";
import pagesSSRBundler from "../bundler/pages-ssr-bundler";
import { log } from "../../utils/log";
import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler";
import serverPostBuildFn from "./server-post-build-fn";
const { ROOT_DIR } = grabDirNames();
const { ROOT_DIR, BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
export default async function watcherEsbuildCTX() {
const pages_src_watcher = watch(ROOT_DIR, {
recursive: true,
persistent: true,
}, async (event, filename) => {
if (!filename)
return;
if (filename.match(/^\.\w+/)) {
return;
}
const full_file_path = path.join(ROOT_DIR, filename);
if (full_file_path.match(/\/styles$/)) {
global.RECOMPILING = true;
await Bun.sleep(1000);
await fullRebuild({
msg: `Detected new \`styles\` directory. Rebuilding ...`,
});
return;
}
const excluded_match = /node_modules\/|^public\/|^\.bunext\/|^\.git\/|^dist\/|bun\.lockb$/;
if (filename.match(excluded_match))
return;
if (filename.match(/bunext.config\.ts/)) {
await fullRebuild({
msg: `bunext.config.ts file changed. Rebuilding server ...`,
});
return;
}
const target_files_match = /\.(tsx?|jsx?|css)$/;
if (event !== "rename") {
if (filename.match(target_files_match)) {
if (global.RECOMPILING)
return;
global.RECOMPILING = true;
await global.BUNDLER_CTX?.rebuild();
if (filename.match(/(404|500)\.tsx?/)) {
for (let i = global.HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.HMR_CONTROLLERS[i];
controller?.controller?.enqueue(`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`);
let owns_recompile = false;
try {
if (!filename)
return;
const full_file_path = path.join(ROOT_DIR, filename);
if (global.BUNEXT_CONFIG.exclude_watch_patterns) {
for (let i = 0; i < global.BUNEXT_CONFIG.exclude_watch_patterns.length; i++) {
const watch_pattern = global.BUNEXT_CONFIG.exclude_watch_patterns[i];
if (watch_pattern instanceof RegExp) {
const is_path_excluded = watch_pattern.test(filename);
if (is_path_excluded) {
return;
}
}
else {
const excluded_path = path.resolve(ROOT_DIR, watch_pattern);
if (excluded_path == full_file_path) {
return;
}
}
}
}
return;
if (existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE)) {
await fullRebuild();
return;
}
if (filename.match(/^\.\w+/)) {
return;
}
if (global.BUNEXT_BUNDLER_CTX_DISPOSED) {
await fullRebuild({ msg: `Restarting Bundler ...` });
return;
}
if (global.BUNEXT_SSR_BUNDLER_CTX_DISPOSED) {
await pagesSSRBundler().catch((error) => {
log.error(`SSR Bundler Error: ${error}`);
});
}
if (filename.endsWith(AppData["BunextTmpFileExt"])) {
return;
}
const does_file_exist = existsSync(full_file_path);
const file_stat = does_file_exist
? statSync(full_file_path)
: undefined;
if (full_file_path.match(/\/styles$/)) {
owns_recompile = true;
global.BUNEXT_RECOMPILING = true;
await Bun.sleep(1000);
await fullRebuild({
msg: `Detected new \`styles\` directory. Rebuilding ...`,
});
return;
}
const excluded_match = /node_modules\/|^public\/|^\.bunext\/|^\.git\/|^\.?dist\/|bun\.lockb$/;
if (filename.match(excluded_match))
return;
if (filename.match(/bunext.config\.ts/)) {
await fullRebuild({
msg: `bunext.config.ts file changed. Rebuilding server ...`,
});
return;
}
const target_files_match = /\.(tsx?|jsx?|css)$/;
if (event !== "rename") {
if (filename.match(target_files_match)) {
if (global.BUNEXT_RECOMPILING)
return;
owns_recompile = true;
global.BUNEXT_RECOMPILING = true;
if (filename.match(/.*\.server\.tsx?/)) {
global.BUNEXT_IS_SERVER_COMPONENT = true;
}
if (global.BUNEXT_BUNDLER_CTX) {
await global.BUNEXT_BUNDLER_CTX.rebuild();
}
if (filename.match(/(404|500)\.tsx?/)) {
for (let i = global.BUNEXT_HMR_CONTROLLERS.length - 1; i >= 0; i--) {
const controller = global.BUNEXT_HMR_CONTROLLERS[i];
try {
controller?.controller?.enqueue(`event: update\ndata: ${JSON.stringify({ reload: true })}\n\n`);
}
catch {
global.BUNEXT_HMR_CONTROLLERS.splice(i, 1);
}
}
}
}
return;
}
const is_file_of_interest = Boolean(filename.match(target_files_match)) ||
file_stat?.isDirectory();
if (!is_file_of_interest) {
return;
}
if (!filename.match(/^src\/pages\/|\.css$/))
return reloadWatcher();
if (checkExcludedPatterns({ path: filename }))
return reloadWatcher();
if (filename.match(/ /))
return reloadWatcher();
if (global.BUNEXT_RECOMPILING)
return;
owns_recompile = true;
const action = does_file_exist ? "created" : "deleted";
const type = filename.match(/\.css$/)
? "Sylesheet"
: file_stat?.isDirectory()
? "Directory"
: filename.match(/\/pages\/api\//)
? "API Route"
: "Page";
await fullRebuild({
msg: `${type} ${action}: ${filename}. Rebuilding ...`,
});
}
const is_file_of_interest = Boolean(filename.match(target_files_match));
if (!is_file_of_interest) {
return;
catch (error) {
log.error(`Watcher rebuild failed: ${error}`);
}
finally {
if (owns_recompile) {
global.BUNEXT_RECOMPILING = false;
global.BUNEXT_IS_SERVER_COMPONENT = false;
}
}
if (!filename.match(/^src\/pages\/|\.css$/))
return;
if (filename.match(/\/(--|\()/))
return;
if (global.RECOMPILING)
return;
const action = existsSync(full_file_path) ? "created" : "deleted";
const type = filename.match(/\.css$/) ? "Sylesheet" : "Page";
await fullRebuild({
msg: `${type} ${action}: ${filename}. Rebuilding ...`,
});
});
global.PAGES_SRC_WATCHER = pages_src_watcher;
global.BUNEXT_PAGES_SRC_WATCHER = pages_src_watcher;
}
async function fullRebuild(params) {
try {
const { msg } = params || {};
global.RECOMPILING = true;
if (msg) {
log.watch(msg);
}
global.ROUTER.reload();
await global.BUNDLER_CTX?.dispose();
global.BUNDLER_CTX = undefined;
global.BUNDLER_CTX_MAP = {};
allPagesESBuildContextBundler({
post_build_fn: serverPostBuildFn,
});
}
catch (error) {
log.error(error);
}
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
function reloadWatcher() {
if (global.BUNEXT_PAGES_SRC_WATCHER) {
global.BUNEXT_PAGES_SRC_WATCHER.close();
watcherEsbuildCTX();
}
}
-1
View File
@@ -1 +0,0 @@
export default function watcher(): Promise<void>;
-80
View File
@@ -1,80 +0,0 @@
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 { ROOT_DIR } = grabDirNames();
export default async function watcher() {
const pages_src_watcher = watch(ROOT_DIR, {
recursive: true,
persistent: true,
}, async (event, filename) => {
if (!filename)
return;
const full_file_path = path.join(ROOT_DIR, filename);
if (full_file_path.match(/\/styles$/)) {
global.RECOMPILING = true;
await Bun.sleep(1000);
await fullRebuild({
msg: `Detected new \`styles\` directory. Rebuilding ...`,
});
return;
}
const excluded_match = /node_modules\/|^public\/|^\.bunext\/|^\.git\/|^dist\/|bun\.lockb$/;
if (filename.match(excluded_match))
return;
if (filename.match(/bunext.config\.ts/)) {
await fullRebuild({
msg: `bunext.config.ts file changed. Rebuilding server ...`,
});
return;
}
const target_files_match = /\.(tsx?|jsx?|css)$/;
if (event !== "rename") {
if (filename.match(target_files_match)) {
if (global.RECOMPILING)
return;
global.RECOMPILING = true;
await fullRebuild();
}
return;
}
const is_file_of_interest = Boolean(filename.match(target_files_match));
if (!is_file_of_interest) {
return;
}
if (!filename.match(/^src\/pages\/|\.css$/))
return;
if (filename.match(/\/(--|\()/))
return;
if (global.RECOMPILING)
return;
const action = existsSync(full_file_path) ? "created" : "deleted";
const type = filename.match(/\.css$/) ? "Sylesheet" : "Page";
await fullRebuild({
msg: `${type} ${action}: ${filename}. Rebuilding ...`,
});
});
global.PAGES_SRC_WATCHER = pages_src_watcher;
}
async function fullRebuild(params) {
try {
const { msg } = params || {};
global.RECOMPILING = true;
const target_file_paths = global.HMR_CONTROLLERS.map((hmr) => hmr.target_map?.local_path).filter((f) => typeof f == "string");
if (msg) {
log.watch(msg);
}
await rebuildBundler({ target_file_paths });
}
catch (error) {
log.error(error);
}
finally {
global.RECOMPILING = false;
}
if (global.PAGES_SRC_WATCHER) {
global.PAGES_SRC_WATCHER.close();
watcher();
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
import type { LivePageDistGenParams } from "../../../types";
export default function genWebHTML({ component, pageProps, bundledMap, module, routeParams, debug, root_module, }: LivePageDistGenParams): Promise<string>;
export default function genWebHTML({ component: Main, pageProps, bundledMap, module, routeParams, debug, root_module, }: LivePageDistGenParams): Promise<string>;
+49 -36
View File
@@ -1,5 +1,4 @@
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
import { renderToString } from "react-dom/server";
import grabContants from "../../../utils/grab-constants";
import EJSON from "../../../utils/ejson";
import isDevelopment from "../../../utils/is-development";
@@ -7,19 +6,18 @@ import grabWebPageHydrationScript from "./grab-web-page-hydration-script";
import grabWebMetaHTML from "./grab-web-meta-html";
import { log } from "../../../utils/log";
import { AppData } from "../../../data/app-data";
import { readFileSync } from "fs";
import path from "path";
import _ from "lodash";
let _reactVersion = "19";
try {
_reactVersion = JSON.parse(readFileSync(path.join(process.cwd(), "node_modules/react/package.json"), "utf-8")).version;
}
catch { }
export default async function genWebHTML({ component, pageProps, bundledMap, module, routeParams, debug, root_module, }) {
import grabDirNames from "../../../utils/grab-dir-names";
const { ROOT_DIR } = grabDirNames();
export default async function genWebHTML({ component: Main, pageProps, bundledMap, module, routeParams, debug, root_module, }) {
const { ClientRootElementIDName, ClientWindowPagePropsName } = grabContants();
const { renderToReadableStream } = await import(`${ROOT_DIR}/node_modules/react-dom/server.js`);
const is_dev = isDevelopment();
if (debug) {
log.info("component", component);
log.info("component", Main);
}
if (!Main) {
throw new Error(`Main Component not found!`);
}
const serializedProps = (EJSON.stringify(pageProps || {}) || "{}").replace(/<\//g, "<\\/");
const page_hydration_script = await grabWebPageHydrationScript();
@@ -44,41 +42,56 @@ export default async function genWebHTML({ component, pageProps, bundledMap, mod
const Head = module?.Head;
const RootHead = root_module?.Head;
const dev = isDevelopment();
const devSuffix = dev ? "?dev" : "";
const browser_imports = {
react: `https://esm.sh/react@${_reactVersion}`,
"react-dom": `https://esm.sh/react-dom@${_reactVersion}`,
"react-dom/client": `https://esm.sh/react-dom@${_reactVersion}/client`,
"react/jsx-runtime": `https://esm.sh/react@${_reactVersion}/jsx-runtime`,
"react/jsx-dev-runtime": `https://esm.sh/react@${_reactVersion}/jsx-dev-runtime`,
};
// if (dev) {
// browser_imports["react/jsx-dev-runtime"] =
// `https://esm.sh/react@${_reactVersion}/jsx-dev-runtime`;
// }
const importMap = JSON.stringify({
imports: browser_imports,
});
const final_meta = _.merge(root_meta, page_meta);
let final_component = (_jsxs("html", { ...html_props, children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0" }), final_meta ? grabWebMetaHTML({ meta: final_meta }) : null, bundledMap?.css_path ? (_jsx("link", { rel: "stylesheet", href: `/${bundledMap.css_path}` })) : null, _jsx("script", { dangerouslySetInnerHTML: {
__html: `window.${ClientWindowPagePropsName} = ${serializedProps}`,
} }), bundledMap?.path ? (_jsxs(_Fragment, { children: [_jsx("script", { type: "importmap", dangerouslySetInnerHTML: {
__html: importMap,
}, defer: true }), _jsx("script", { src: `/${bundledMap.path}`, type: "module", id: AppData["BunextClientHydrationScriptID"], defer: true })] })) : null, is_dev ? (_jsx("script", { defer: true, dangerouslySetInnerHTML: {
const public_envs = Object.fromEntries(Object.entries(process.env).filter(([k]) => k.startsWith("BUNEXT_PUBLIC_")));
const client_process = {
env: {
NODE_ENV: dev ? "development" : "production",
...public_envs,
...global.BUNEXT_CONFIG.public_envs,
},
};
let final_component = (_jsxs("html", { ...html_props, children: [_jsxs("head", { children: [_jsx("meta", { charSet: "utf-8", "data-bunext-head": true }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0", "data-bunext-head": true }), final_meta ? grabWebMetaHTML({ meta: final_meta }) : null, bundledMap?.css_path ? (_jsx("link", { rel: "stylesheet", href: `/${bundledMap.css_path}`, "data-bunext-head": true })) : null, _jsx("script", { dangerouslySetInnerHTML: {
__html: `window.${ClientWindowPagePropsName} = ${serializedProps};\nwindow.process = ${JSON.stringify(client_process)}`,
}, "data-bunext-head": true }), RootHead ? (_jsx(RootHead, { serverRes: pageProps, ctx: routeParams })) : null, Head ? _jsx(Head, { serverRes: pageProps, ctx: routeParams }) : null, bundledMap?.path ? (_jsxs(_Fragment, { children: [_jsx("script", { type: "importmap", dangerouslySetInnerHTML: {
__html: JSON.stringify(global.BUNEXT_REACT_IMPORTS_MAP),
}, defer: true, "data-bunext-head": true }), _jsx("script", { src: `/${bundledMap.path}`, type: "module", id: AppData["BunextClientHydrationScriptID"], defer: true, "data-bunext-head": true })] })) : null, is_dev ? (_jsx("script", { defer: true, dangerouslySetInnerHTML: {
__html: page_hydration_script,
} })) : null, RootHead ? (_jsx(RootHead, { serverRes: pageProps, ctx: routeParams })) : null, Head ? _jsx(Head, { serverRes: pageProps, ctx: routeParams }) : null] }), _jsx("body", { children: _jsx("div", { id: ClientRootElementIDName, suppressHydrationWarning: !dev, children: component }) })] }));
}, "data-bunext-head": true })) : null] }), _jsx("body", { children: _jsx("div", { id: ClientRootElementIDName, suppressHydrationWarning: !dev, children: _jsx(Main, { ...pageProps }) }) })] }));
let html = `<!DOCTYPE html>\n`;
// const stream = await renderToReadableStream(final_component, {
// onError(error: any) {
// // This is where you "omit" or handle the errors
// // You can log it silently or ignore it
// if (error.message.includes('unique "key" prop')) return;
// console.error(error);
// },
// });
// // 2. Convert the Web Stream to a String (Bun-optimized)
// const htmlBody = await new Response(stream).text();
// html += htmlBody;
html += renderToString(final_component);
const originalConsole = {
log: console.log,
warn: console.warn,
error: console.error,
info: console.info,
debug: console.debug,
};
console.log = () => { };
console.warn = () => { };
console.error = () => { };
console.info = () => { };
console.debug = () => { };
let htmlBody;
try {
const stream = await renderToReadableStream(final_component, {
onError(error) {
if (error.message.includes('unique "key" prop'))
return;
originalConsole.error(error);
},
});
htmlBody = await new Response(stream).text();
}
finally {
Object.assign(console, originalConsole);
}
html += htmlBody;
return html;
}
@@ -3,8 +3,8 @@ import { log } from "../../../utils/log";
import writeCache from "../../cache/write-cache";
export default async function generateWebPageGetCachePage({ module, routeParams, serverRes, root_module, html, }) {
const config = _.merge(root_module?.config, module?.config);
const cache_page = config?.cachePage || serverRes?.cachePage || false;
const expiry_seconds = config?.cacheExpiry || serverRes?.cacheExpiry;
const cache_page = config?.cachePage || serverRes?.cache_page || false;
const expiry_seconds = config?.cacheExpiry || serverRes?.cache_expiry;
if (cache_page && routeParams?.url) {
try {
const is_cache = typeof cache_page == "boolean"
@@ -23,10 +23,10 @@ export default async function generateWebPageResponseFromComponentReturn({ compo
: serverRes.redirect.status_code || 302);
}
const res_opts = {
...serverRes?.responseOptions,
...serverRes?.response_options,
headers: {
"Content-Type": "text/html",
...serverRes?.responseOptions?.headers,
...serverRes?.response_options?.headers,
},
};
if (is_dev) {
@@ -47,8 +47,11 @@ export default async function generateWebPageResponseFromComponentReturn({ compo
});
}
const res = new Response(html, res_opts);
if (routeParams?.resTransform) {
return await routeParams.resTransform(res);
if (routeParams?.res_transform) {
return await routeParams.res_transform(res);
}
if (serverRes?.res_transform) {
return await serverRes.res_transform(res);
}
return res;
}
+2 -2
View File
@@ -14,7 +14,7 @@ export default async function grabFilePathModule({ file_path, out_file, }) {
format: "esm",
target: "es2020",
platform: "node",
external: ["react", "react-dom"],
// external: ["react", "react-dom"],
minify: true,
define: {
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
@@ -24,7 +24,7 @@ export default async function grabFilePathModule({ file_path, out_file, }) {
jsx: "automatic",
outfile: target_cache_file_path,
});
Loader.registry.delete(target_cache_file_path);
// Loader.registry.delete(target_cache_file_path);
const module = await import(`${target_cache_file_path}?t=${Date.now()}`);
return module;
}
@@ -1,8 +1,7 @@
import type { GrabPageReactBundledComponentRes } from "../../../types";
type Params = {
file_path: string;
root_file_path?: string;
server_res?: any;
return_tsx_only?: boolean;
};
export default function grabPageBundledReactComponent({ file_path, root_file_path, server_res, }: Params): Promise<GrabPageReactBundledComponentRes | undefined>;
export default function grabPageBundledReactComponent({ file_path, return_tsx_only, }: Params): Promise<GrabPageReactBundledComponentRes | undefined>;
export {};
@@ -1,26 +1,42 @@
import { jsx as _jsx } from "react/jsx-runtime";
import grabPageReactComponentString from "./grab-page-react-component-string";
import grabTsxStringModule from "./grab-tsx-string-module";
export default async function grabPageBundledReactComponent({ file_path, root_file_path, server_res, }) {
import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
const { ROOT_DIR } = grabDirNames();
export default async function grabPageBundledReactComponent({ file_path, return_tsx_only, }) {
try {
if (global.BUNEXT_SSR_BUNDLER_CTX_MAP?.[file_path]) {
const abs = path.join(ROOT_DIR, global.BUNEXT_SSR_BUNDLER_CTX_MAP[file_path].path);
// Loader.registry.delete(abs);
const mod = await import(`${abs}?t=${Date.now()}`);
const Main = mod.default;
return { component: Main };
}
const { root_file_path } = grabRootFilePath();
let tsx = grabPageReactComponentString({
file_path,
root_file_path,
server_res,
});
if (!tsx) {
return undefined;
}
const mod = await grabTsxStringModule({ tsx, file_path });
if (return_tsx_only) {
return { tsx };
}
const mod = await grabTsxStringModule({
tsx,
page_file_path: file_path,
});
const Main = mod.default;
const component = _jsx(Main, {});
return {
component,
server_res,
component: Main,
tsx,
};
}
catch (error) {
log.error(`grabPageBundledReactComponent Error: ${error.message}`, error);
return undefined;
}
}
@@ -3,40 +3,54 @@ import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import grabPageServerRes from "./grab-page-server-res";
import grabPageServerPath from "./grab-page-server-path";
import path from "path";
import grabDirNames from "../../../utils/grab-dir-names";
const { ROOT_DIR } = grabDirNames();
export default async function grabPageCombinedServerRes({ file_path, debug, url, query, routeParams, }) {
const now = Date.now();
const { root_file_path } = grabRootFilePath();
const { server_file_path: root_server_file_path } = root_file_path
? grabPageServerPath({ file_path: root_file_path })
: {};
const root_server_module = root_server_file_path
? await import(`${root_server_file_path}?t=${now}`)
const root_server_ctx_map = global.BUNEXT_SSR_BUNDLER_CTX_MAP[root_server_file_path || ""];
const final_root_server_path = root_server_ctx_map?.local_path
? path.join(ROOT_DIR, root_server_ctx_map.path)
: root_server_file_path;
if (final_root_server_path) {
// Loader.registry.delete(final_root_server_path);
}
const root_server_module = final_root_server_path
? await import(`${final_root_server_path}?t=${now}`)
: undefined;
const root_server_fn = root_server_module?.default || root_server_module?.server;
const rootServerRes = root_server_fn
? await grabPageServerRes({
server_function: root_server_fn,
url,
query,
routeParams,
})
: undefined;
const rootServerRes = await grabPageServerRes({
server_function: root_server_fn,
url,
query,
routeParams,
});
if (debug) {
log.info(`rootServerRes:`, rootServerRes);
}
const { server_file_path } = grabPageServerPath({ file_path });
const server_module = server_file_path
? await import(`${server_file_path}?t=${now}`)
const page_server_ctx = global.BUNEXT_SSR_BUNDLER_CTX_MAP[server_file_path || ""];
const final_page_server_path = page_server_ctx?.local_path
? path.join(ROOT_DIR, page_server_ctx.path)
: server_file_path;
if (final_page_server_path) {
// Loader.registry.delete(final_page_server_path);
}
const server_module = final_page_server_path
? await import(`${final_page_server_path}?t=${now}`)
: undefined;
const server_fn = server_module?.default || server_module?.server;
const serverRes = server_fn
? await grabPageServerRes({
server_function: server_fn,
url,
query,
routeParams,
})
: undefined;
const serverRes = await grabPageServerRes({
server_function: server_fn,
url,
query,
routeParams,
props: rootServerRes?.props || null,
});
const mergedServerRes = _.merge(rootServerRes || {}, serverRes || {});
return { serverRes: mergedServerRes };
}
+4 -1
View File
@@ -3,7 +3,10 @@ type Params = {
req?: Request;
file_path?: string;
debug?: boolean;
retry?: boolean;
return_server_res_only?: boolean;
skip_server_res?: boolean;
is_hydration?: boolean;
};
export default function grabPageComponent({ req, file_path: passed_file_path, debug, return_server_res_only, }: Params): Promise<GrabPageComponentRes>;
export default function grabPageComponent(params: Params): Promise<GrabPageComponentRes | Response>;
export {};
+84 -39
View File
@@ -1,19 +1,34 @@
import grabRouteParams from "../../../utils/grab-route-params";
import grabPageErrorComponent from "./grab-page-error-component";
import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
import _ from "lodash";
import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import grabPageServerRes from "./grab-page-server-res";
import grabPageServerPath from "./grab-page-server-path";
import grabPageModules from "./grab-page-modules";
import grabPageCombinedServerRes from "./grab-page-combined-server-res";
import fullRebuild from "../full-rebuild";
import serverPostBuildFn from "../server-post-build-fn";
import isDevelopment from "../../../utils/is-development";
import { existsSync } from "fs";
import grabDirNames from "../../../utils/grab-dir-names";
import watcherEsbuildCTX from "../watcher-esbuild-ctx";
const { BUNX_BUNDLER_ERROR_EXIT_FILE } = grabDirNames();
class NotFoundError extends Error {
status = 404;
constructor(message) {
super(message);
this.name = "NotFoundError";
}
}
export default async function grabPageComponent({ req, file_path: passed_file_path, debug, return_server_res_only, }) {
export default async function grabPageComponent(params) {
const { req, file_path: passed_file_path, debug, return_server_res_only, skip_server_res, is_hydration, } = params;
const url = req?.url ? new URL(req.url) : undefined;
const router = global.ROUTER;
const router = global.BUNEXT_ROUTER;
const is_dev = isDevelopment();
const forwarded_proto = req?.headers.get("x-forwarded-proto");
if (url && forwarded_proto) {
url.protocol = forwarded_proto;
}
let routeParams = undefined;
const does_error_file_exist = existsSync(BUNX_BUNDLER_ERROR_EXIT_FILE);
try {
routeParams = req ? await grabRouteParams({ req }) : undefined;
let url_path = url ? url.pathname : undefined;
@@ -36,12 +51,30 @@ 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];
let bundledMap = global.BUNEXT_BUNDLER_CTX_MAP[file_path];
if (!bundledMap?.path) {
console.log(global.BUNDLER_CTX_MAP);
const errMsg = `No Bundled File Path for this request path!`;
log.error(errMsg);
throw new Error(errMsg);
if (does_error_file_exist) {
throw new Error(`Application Error. Please Check your components. ${match?.filePath} likely exists but has no exported module.`);
}
let retries = 0;
const MAX_RETRIES = 2;
while (retries < MAX_RETRIES) {
await fullRebuild({
msg: `Retrying Bundle map for file \`${file_path}\``,
});
await Bun.sleep(1000);
bundledMap = global.BUNEXT_BUNDLER_CTX_MAP[file_path];
if (bundledMap?.path)
break;
}
if (!bundledMap?.path) {
const errMsg = `No Bundled File Path for this request path!`;
log.error(errMsg);
throw new Error(errMsg);
}
}
if (req && !is_hydration) {
global.BUNEXT_BUNDLER_CTX_MAP[file_path].req_url = req.url;
}
if (debug) {
log.info(`bundledMap:`, bundledMap);
@@ -56,13 +89,18 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
});
return { serverRes };
}
const { component, module, serverRes, root_module } = await grabPageModules({
const page_modules = await grabPageModules({
file_path,
debug,
query: match?.query,
routeParams,
url,
skip_server_res,
});
if (page_modules instanceof Response) {
return page_modules;
}
const { component, module, serverRes, root_module } = page_modules;
return {
component,
serverRes,
@@ -70,40 +108,47 @@ export default async function grabPageComponent({ req, file_path: passed_file_pa
module,
bundledMap,
root_module,
success: true,
};
}
catch (error) {
log.error(`Error Grabbing Page Component: ${error.message}`);
const is404 = error instanceof NotFoundError ||
error?.name === "NotFoundError" ||
error?.status === 404;
if (!params.retry && is_dev) {
while (global.BUNEXT_REBUILD_RETRIES < 2) {
global.BUNEXT_REBUILD_RETRIES =
global.BUNEXT_REBUILD_RETRIES + 1;
await fullRebuild();
await Bun.sleep(200);
const component_retried = await grabPageComponent({
...params,
retry: true,
});
if (component_retried instanceof Response ||
component_retried.success) {
global.BUNEXT_REBUILD_RETRIES = 0;
await serverPostBuildFn();
return component_retried;
}
}
global.BUNEXT_REBUILD_RETRIES = 0;
}
if (is404) {
global.BUNEXT_IS_404_PAGE = true;
}
else {
log.error(`Error Grabbing Page Component: ${error.message}`);
log.error(`Page: ${passed_file_path || url?.pathname}`);
if (is_dev) {
fullRebuild();
}
}
return await grabPageErrorComponent({
error,
routeParams,
is404: error instanceof NotFoundError,
is404,
url,
});
}
}
// 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<any> | undefined;
// let module: BunextPageModule;
// if (isDevelopment()) {
// module = await grabFilePathModule({ file_path });
// } else {
// module = await import(file_path);
// }
// const Component = main_module.default as FC<any>;
// const component = RootComponent ? (
// <RootComponent {...serverRes}>
// <Component {...serverRes} />
// </RootComponent>
// ) : (
// <Component {...serverRes} />
// );
@@ -5,5 +5,5 @@ type Params = {
is404?: boolean;
url?: URL;
};
export default function grabPageErrorComponent({ error, routeParams, is404, url, }: Params): Promise<GrabPageComponentRes>;
export default function grabPageErrorComponent({ error, routeParams, is404, url, }: Params): Promise<GrabPageComponentRes | Response>;
export {};
@@ -2,15 +2,18 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import grabDirNames from "../../../utils/grab-dir-names";
import grabPageModules from "./grab-page-modules";
import _ from "lodash";
import fullRebuild from "../full-rebuild";
import isDevelopment from "../../../utils/is-development";
export default async function grabPageErrorComponent({ error, routeParams, is404, url, }) {
const router = global.ROUTER;
const router = global.BUNEXT_ROUTER;
const is_dev = isDevelopment();
const { BUNX_ROOT_500_PRESET_COMPONENT, BUNX_ROOT_404_PRESET_COMPONENT } = grabDirNames();
const errorRoute = is404 ? "/404" : "/500";
const presetComponent = is404
? BUNX_ROOT_404_PRESET_COMPONENT
: BUNX_ROOT_500_PRESET_COMPONENT;
const default_server_res = {
responseOptions: {
response_options: {
status: is404 ? 404 : 500,
},
};
@@ -19,7 +22,9 @@ export default async function grabPageErrorComponent({ error, routeParams, is404
if (!match?.filePath) {
const default_module = await import(presetComponent);
const Component = default_module.default;
const default_jsx = (_jsx(Component, { children: _jsx("span", { children: error.message }) }));
const default_jsx = () => {
return _jsx(Component, { children: _jsx("span", { children: error.message }) });
};
return {
component: default_jsx,
module: default_module,
@@ -28,13 +33,17 @@ export default async function grabPageErrorComponent({ error, routeParams, is404
};
}
const file_path = match.filePath;
const bundledMap = global.BUNDLER_CTX_MAP?.[file_path];
const { component, module, serverRes, root_module } = await grabPageModules({
const bundledMap = global.BUNEXT_BUNDLER_CTX_MAP?.[file_path];
const page_component = await grabPageModules({
file_path: file_path,
query: match?.query,
routeParams,
url,
});
if (page_component instanceof Response) {
return page_component;
}
const { component, module, serverRes, root_module } = page_component;
return {
component,
routeParams,
@@ -45,6 +54,9 @@ export default async function grabPageErrorComponent({ error, routeParams, is404
};
}
catch {
if (is_dev) {
fullRebuild();
}
const DefaultNotFound = () => (_jsxs("div", { style: {
width: "100vw",
height: "100vh",
@@ -54,7 +66,7 @@ export default async function grabPageErrorComponent({ error, routeParams, is404
flexDirection: "column",
}, children: [_jsx("h1", { children: is404 ? "404 Not Found" : "500 Internal Server Error" }), _jsx("span", { children: error.message })] }));
return {
component: _jsx(DefaultNotFound, {}),
component: DefaultNotFound,
routeParams,
module: { default: DefaultNotFound },
serverRes: default_server_res,
+7 -5
View File
@@ -1,16 +1,18 @@
import type { BunextPageModule, BunextPageModuleServerReturn, BunxRouteParams } from "../../../types";
import type { JSX } from "react";
import type { FC } from "react";
type Params = {
file_path: string;
debug?: boolean;
url?: URL;
query?: any;
routeParams?: BunxRouteParams;
skip_server_res?: boolean;
};
export default function grabPageModules({ file_path, debug, url, query, routeParams, }: Params): Promise<{
component: JSX.Element;
serverRes: BunextPageModuleServerReturn;
type Return = {
component: FC;
serverRes: BunextPageModuleServerReturn | undefined;
module: BunextPageModule;
root_module: BunextPageModule | undefined;
}>;
};
export default function grabPageModules({ file_path, debug, url, query, routeParams, skip_server_res, }: Params): Promise<Return | Response>;
export {};
+15 -12
View File
@@ -2,11 +2,23 @@ import grabPageBundledReactComponent from "./grab-page-bundled-react-component";
import _ from "lodash";
import { log } from "../../../utils/log";
import grabRootFilePath from "./grab-root-file-path";
import grabPageServerRes from "./grab-page-server-res";
import grabPageServerPath from "./grab-page-server-path";
import grabPageCombinedServerRes from "./grab-page-combined-server-res";
export default async function grabPageModules({ file_path, debug, url, query, routeParams, }) {
export default async function grabPageModules({ file_path, debug, url, query, routeParams, skip_server_res, }) {
const now = Date.now();
const { serverRes } = skip_server_res
? {}
: await grabPageCombinedServerRes({
file_path,
debug,
query,
routeParams,
url,
});
if (serverRes?.redirect?.destination) {
return Response.redirect(serverRes.redirect.destination, serverRes.redirect.permanent
? 301
: serverRes.redirect.status_code || 302);
}
const { root_file_path } = grabRootFilePath();
const root_module = root_file_path
? await import(`${root_file_path}?t=${now}`)
@@ -15,17 +27,8 @@ export default async function grabPageModules({ file_path, debug, url, query, ro
if (debug) {
log.info(`module:`, module);
}
const { serverRes } = await grabPageCombinedServerRes({
file_path,
debug,
query,
routeParams,
url,
});
const { component } = (await grabPageBundledReactComponent({
file_path,
root_file_path,
server_res: serverRes,
})) || {};
if (!component) {
throw new Error(`Couldn't grab page component`);
@@ -1,7 +1,6 @@
type Params = {
file_path: string;
root_file_path?: string;
server_res?: any;
};
export default function grabPageReactComponentString({ file_path, root_file_path, server_res, }: Params): string | undefined;
export default function grabPageReactComponentString({ file_path, root_file_path, }: Params): string | undefined;
export {};

Some files were not shown because too many files have changed in this diff Show More