Compare commits
59
Commits
a84ac10b24
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bf1b651db | ||
|
|
247a64c873 | ||
|
|
f590deb11b | ||
|
|
3426c7b53b | ||
|
|
afd1af1827 | ||
|
|
22d3dedab2 | ||
|
|
8d12329f01 | ||
|
|
78e86b3999 | ||
|
|
87948340b0 | ||
|
|
86ea86e7bd | ||
|
|
596b9de047 | ||
|
|
9da1e16318 | ||
|
|
823c5bb1ca | ||
|
|
88ead3b3d6 | ||
|
|
45509deff8 | ||
|
|
a19863b3e9 | ||
|
|
a9cd51d71c | ||
|
|
e3a0f5fbeb | ||
|
|
1d0ac4aa80 | ||
|
|
817beacc7a | ||
|
|
61a9d8d612 | ||
|
|
cd9ac833dc | ||
|
|
e2b8b95a4b | ||
|
|
c06cb73181 | ||
|
|
f3bb972a20 | ||
|
|
40a987b983 | ||
|
|
ceeb6fbdaf | ||
|
|
4f5445e3df | ||
|
|
b702e26bf6 | ||
|
|
3b26292124 | ||
|
|
cb5126c947 | ||
|
|
f018b228a8 | ||
|
|
95fcee36b2 | ||
|
|
41e28d7a3e | ||
|
|
b2e92e5792 | ||
|
|
9a427412f3 | ||
|
|
a5f25d522e | ||
|
|
938411653d | ||
|
|
b597e1420e | ||
|
|
d6f0a7962e | ||
|
|
f0aae8a8fa | ||
|
|
84d490b189 | ||
|
|
532d0d6b56 | ||
|
|
e0c2ab5872 | ||
|
|
35f7a6fc85 | ||
|
|
814a289460 | ||
|
|
c4f7cf9164 | ||
|
|
af8c207ac1 | ||
|
|
5a0972beb8 | ||
|
|
257adfec39 | ||
|
|
349b99bacf | ||
|
|
972f6945c2 | ||
|
|
6477f446d1 | ||
|
|
7fb1784b95 | ||
|
|
40fc7778a8 | ||
|
|
f3b087a1f3 | ||
|
|
eb0721f94b | ||
|
|
ab6fc3be26 | ||
|
|
6b7d29bc53 |
+2
-1
@@ -181,4 +181,5 @@ __fixtures__
|
||||
/.data
|
||||
/.dump
|
||||
/.vscode
|
||||
/source.md
|
||||
/source.md
|
||||
SECURITY.md
|
||||
@@ -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"`).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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",
|
||||
@@ -26,6 +27,7 @@
|
||||
},
|
||||
"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=="],
|
||||
@@ -291,6 +297,8 @@
|
||||
|
||||
"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.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
|
||||
|
||||
Vendored
+4
-10
@@ -1,9 +1,8 @@
|
||||
import { Command } from "commander";
|
||||
import { log } from "../../utils/log";
|
||||
import init from "../../functions/init";
|
||||
import grabDirNames from "../../utils/grab-dir-names";
|
||||
import { rmSync } from "fs";
|
||||
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")
|
||||
@@ -14,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();
|
||||
});
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Vendored
+25
@@ -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}`);
|
||||
}
|
||||
Vendored
+53
-17
@@ -1,25 +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 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);
|
||||
}
|
||||
|
||||
Vendored
-2
@@ -4,7 +4,6 @@ import start from "./start";
|
||||
import dev from "./dev";
|
||||
import build from "./build";
|
||||
import { log } from "../utils/log";
|
||||
import rewritePages from "./rewrite-pages";
|
||||
/**
|
||||
* # Describe Program
|
||||
*/
|
||||
@@ -18,7 +17,6 @@ program
|
||||
program.addCommand(dev());
|
||||
program.addCommand(start());
|
||||
program.addCommand(build());
|
||||
program.addCommand(rewritePages());
|
||||
/**
|
||||
* # Handle Unavailable Commands
|
||||
*/
|
||||
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
import { Command } from "commander";
|
||||
export default function (): Command;
|
||||
Vendored
-16
@@ -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();
|
||||
});
|
||||
}
|
||||
Vendored
+53
-7
@@ -1,14 +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 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 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);
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Vendored
+6
@@ -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();
|
||||
Vendored
+3
@@ -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;
|
||||
};
|
||||
|
||||
Vendored
+3
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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 {};
|
||||
|
||||
+63
-50
@@ -7,59 +7,72 @@ 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) {
|
||||
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"),
|
||||
},
|
||||
entryNames: "[dir]/[hash]",
|
||||
metafile: true,
|
||||
plugins: [
|
||||
forceExternalReact(),
|
||||
tailwindEsbuildPlugin,
|
||||
virtualFilesPlugin({
|
||||
entryToPage,
|
||||
}),
|
||||
esbuildCTXArtifactTracker({
|
||||
entryToPage,
|
||||
post_build_fn: params?.post_build_fn,
|
||||
}),
|
||||
],
|
||||
jsx: "automatic",
|
||||
splitting: true,
|
||||
treeShaking: true,
|
||||
external: [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react-dom/client",
|
||||
"react/jsx-runtime",
|
||||
"react/jsx-dev-runtime",
|
||||
],
|
||||
});
|
||||
await global.BUNDLER_CTX.rebuild();
|
||||
}
|
||||
function forceExternalReact() {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export default function apiRoutesBundler(): Promise<void>;
|
||||
+40
@@ -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>;
|
||||
@@ -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 {};
|
||||
@@ -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
-1
@@ -62,7 +62,7 @@ export default async function bunReactModulesBundler() {
|
||||
});
|
||||
rmSync(tmpDir, { force: true, recursive: true });
|
||||
const PUBLIC_ROOT = BUNEXT_VENDOR_DIR.replace(BUNX_CWD_DIR, "/.bunext");
|
||||
global.REACT_IMPORTS_MAP = {
|
||||
global.BUNEXT_REACT_IMPORTS_MAP = {
|
||||
imports: {
|
||||
react: `${PUBLIC_ROOT}/react.js`,
|
||||
"react-dom": `${PUBLIC_ROOT}/react-dom.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;
|
||||
|
||||
+1
-1
@@ -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
@@ -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}`);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
type Params = {
|
||||
post_build_fn?: (params: {
|
||||
artifacts: any[];
|
||||
}) => Promise<void> | void;
|
||||
};
|
||||
export default function pagesSSRContextBundler(params?: Params): Promise<void>;
|
||||
export {};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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 {};
|
||||
|
||||
+78
-31
@@ -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;
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import path from "path";
|
||||
import { log } from "../../../utils/log";
|
||||
export default function virtualFilesPlugin({ entryToPage }) {
|
||||
const virtualPlugin = {
|
||||
name: "virtual-hydration",
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ export default async function reactModulesBundler() {
|
||||
});
|
||||
rmSync(tmpDir, { force: true, recursive: true });
|
||||
const PUBLIC_ROOT = BUNEXT_VENDOR_DIR.replace(BUNX_CWD_DIR, "/.bunext");
|
||||
global.REACT_IMPORTS_MAP = {
|
||||
global.BUNEXT_REACT_IMPORTS_MAP = {
|
||||
imports: {
|
||||
react: `${PUBLIC_ROOT}/react.js`,
|
||||
"react-dom": `${PUBLIC_ROOT}/react-dom.js`,
|
||||
|
||||
+2
-2
@@ -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,
|
||||
|
||||
Vendored
+43
-22
@@ -1,33 +1,54 @@
|
||||
import type { BundlerCTXMap, BunextConfig, GlobalHMRControllerObject, PageFiles } from "../types";
|
||||
import type { FileSystemRouter, Server } from "bun";
|
||||
import grabDirNames from "../utils/grab-dir-names";
|
||||
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 DIR_NAMES: ReturnType<typeof grabDirNames>;
|
||||
var REACT_IMPORTS_MAP: {
|
||||
};
|
||||
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 REACT_DOM_SERVER: any;
|
||||
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 {};
|
||||
|
||||
Vendored
+36
-17
@@ -1,41 +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 watcherEsbuildCTX from "./server/watcher-esbuild-ctx";
|
||||
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 dirNames = grabDirNames();
|
||||
const { PAGES_DIR } = dirNames;
|
||||
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();
|
||||
global.DIR_NAMES = dirNames;
|
||||
global.REACT_IMPORTS_MAP = { imports: {} };
|
||||
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();
|
||||
// await bunReactModulesBundler();
|
||||
await reactModulesBundler();
|
||||
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: serverPostBuildFn,
|
||||
post_build_fn: async () => {
|
||||
await serverPostBuildFn();
|
||||
},
|
||||
});
|
||||
watcherEsbuildCTX();
|
||||
}
|
||||
else {
|
||||
await allPagesESBuildContextBundler();
|
||||
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
@@ -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
@@ -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);
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export default function cleanupLogsDirs(): void;
|
||||
Vendored
+48
@@ -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();
|
||||
Vendored
+2
-2
@@ -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,
|
||||
};
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
export {};
|
||||
Vendored
+46
@@ -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);
|
||||
});
|
||||
+27
-12
@@ -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,12 +31,17 @@ export default async function bunextRequestHandler({ req: initial_req, server, }
|
||||
req = middleware_res;
|
||||
}
|
||||
}
|
||||
// const server_upgrade = server.upgrade(req);
|
||||
// if (server_upgrade) {
|
||||
// return undefined;
|
||||
// }
|
||||
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");
|
||||
}
|
||||
if (url.pathname === "/__hmr" && is_dev) {
|
||||
response = await handleHmr({ req });
|
||||
return handleHmr({ req });
|
||||
}
|
||||
else if (url.pathname.startsWith("/.bunext")) {
|
||||
response = await handleBunextPublicAssets({ req });
|
||||
@@ -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>;
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export default function clearRequireCache(modulePath: string): void;
|
||||
+10
@@ -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
@@ -0,0 +1,3 @@
|
||||
export default function fullRebuild(params?: {
|
||||
msg?: string;
|
||||
}): Promise<void>;
|
||||
+37
@@ -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();
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -2,13 +2,14 @@ import grabDirNames from "../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
import isDevelopment from "../../utils/is-development";
|
||||
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(BUNEXT_PUBLIC_DIR, url.pathname.replace(/\/\.bunext\/public\//, ""));
|
||||
if (!file_path.startsWith(BUNEXT_PUBLIC_DIR + path.sep)) {
|
||||
if (!isSafePath({ filePath: file_path, allowedDir: BUNEXT_PUBLIC_DIR })) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
return readFileResponse({
|
||||
|
||||
+7
-2
@@ -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`, {
|
||||
|
||||
Vendored
+24
-9
@@ -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, {
|
||||
|
||||
+5
-1
@@ -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.replace(/^\/public/, ""));
|
||||
if (!file_path.startsWith(PUBLIC_DIR + path.sep)) {
|
||||
if (!isSafePath({ filePath: file_path, allowedDir: PUBLIC_DIR })) {
|
||||
return new Response("Forbidden", { status: 403 });
|
||||
}
|
||||
return readFileResponse({ file_path });
|
||||
@@ -33,6 +34,9 @@ export function readFileResponse({ file_path, cache }) {
|
||||
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
@@ -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
@@ -1,5 +0,0 @@
|
||||
type Params = {
|
||||
target_file_paths?: string[];
|
||||
};
|
||||
export default function rebuildBundler(params?: Params): Promise<void>;
|
||||
export {};
|
||||
-21
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
-95
@@ -1,113 +1,148 @@
|
||||
import { watch, existsSync, statSync } from "fs";
|
||||
import { watch, existsSync, statSync, glob } from "fs";
|
||||
import path from "path";
|
||||
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";
|
||||
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) => {
|
||||
// log.info(`event: ${event}`);
|
||||
// log.info(`filename: ${filename}`);
|
||||
if (!filename)
|
||||
return;
|
||||
if (filename.match(/^\.\w+/)) {
|
||||
return;
|
||||
}
|
||||
const full_file_path = path.join(ROOT_DIR, filename);
|
||||
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$/)) {
|
||||
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)) ||
|
||||
file_stat?.isDirectory();
|
||||
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 reloadWatcher();
|
||||
if (filename.match(/\/(--|\()/))
|
||||
return reloadWatcher();
|
||||
if (filename.match(/ /))
|
||||
return reloadWatcher();
|
||||
if (global.RECOMPILING)
|
||||
return;
|
||||
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 ...`,
|
||||
});
|
||||
});
|
||||
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();
|
||||
watcherEsbuildCTX();
|
||||
}
|
||||
}
|
||||
function reloadWatcher(params) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -1 +0,0 @@
|
||||
export default function watcher(): Promise<void>;
|
||||
Vendored
-80
@@ -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
@@ -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
-13
@@ -9,12 +9,15 @@ import { AppData } from "../../../data/app-data";
|
||||
import _ from "lodash";
|
||||
import grabDirNames from "../../../utils/grab-dir-names";
|
||||
const { ROOT_DIR } = grabDirNames();
|
||||
export default async function genWebHTML({ component, pageProps, bundledMap, module, routeParams, debug, root_module, }) {
|
||||
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();
|
||||
@@ -40,22 +43,55 @@ export default async function genWebHTML({ component, pageProps, bundledMap, mod
|
||||
const RootHead = root_module?.Head;
|
||||
const dev = isDevelopment();
|
||||
const final_meta = _.merge(root_meta, page_meta);
|
||||
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}`,
|
||||
__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.REACT_IMPORTS_MAP),
|
||||
__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,
|
||||
}, "data-bunext-head": true })) : 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) {
|
||||
if (error.message.includes('unique "key" prop'))
|
||||
return;
|
||||
console.error(error);
|
||||
},
|
||||
});
|
||||
const htmlBody = await new Response(stream).text();
|
||||
// const stream = await renderToReadableStream(final_component, {
|
||||
// onError(error: any) {
|
||||
// if (error.message.includes('unique "key" prop')) return;
|
||||
// console.error(error);
|
||||
// },
|
||||
// });
|
||||
// const htmlBody = await new Response(stream).text();
|
||||
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"
|
||||
|
||||
+7
-4
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,23 +1,37 @@
|
||||
import { jsx as _jsx } from "react/jsx-runtime";
|
||||
import grabPageReactComponentString from "./grab-page-react-component-string";
|
||||
import grabTsxStringModule from "./grab-tsx-string-module";
|
||||
import { log } from "../../../utils/log";
|
||||
export default async function grabPageBundledReactComponent({ file_path, root_file_path, server_res, }) {
|
||||
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 });
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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
-35
@@ -4,12 +4,31 @@ import _ from "lodash";
|
||||
import { log } from "../../../utils/log";
|
||||
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;
|
||||
@@ -32,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);
|
||||
@@ -52,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,
|
||||
@@ -66,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,
|
||||
|
||||
+8
-5
@@ -1,15 +1,18 @@
|
||||
import type { BunextPageModule, BunxRouteParams } from "../../../types";
|
||||
import type { BunextPageModule, BunextPageModuleServerReturn, BunxRouteParams } from "../../../types";
|
||||
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: import("react").JSX.Element;
|
||||
serverRes: import("../../../types").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
-10
@@ -3,8 +3,22 @@ import _ from "lodash";
|
||||
import { log } from "../../../utils/log";
|
||||
import grabRootFilePath from "./grab-root-file-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}`)
|
||||
@@ -13,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 {};
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
import EJSON from "../../../utils/ejson";
|
||||
import isDevelopment from "../../../utils/is-development";
|
||||
import { log } from "../../../utils/log";
|
||||
export default function grabPageReactComponentString({ file_path, root_file_path, server_res, }) {
|
||||
const now = Date.now();
|
||||
const dev = isDevelopment();
|
||||
export default function grabPageReactComponentString({ file_path, root_file_path, }) {
|
||||
try {
|
||||
const import_suffix = dev ? `?t=${now}` : "";
|
||||
let tsx = ``;
|
||||
const server_res_json = JSON.stringify(EJSON.stringify(server_res || {}) ?? "{}");
|
||||
// Import Root from its original source path so that all sub-components
|
||||
// that import __root (e.g. AppContext) resolve to the same module instance.
|
||||
// Using the rewritten .bunext/pages/__root would create a separate
|
||||
// createContext() call, breaking context for any sub-component that
|
||||
// imports AppContext via a relative path to the source __root.
|
||||
if (root_file_path) {
|
||||
tsx += `import Root from "${root_file_path}${import_suffix}"\n`;
|
||||
tsx += `import Root from "${root_file_path}"\n`;
|
||||
}
|
||||
tsx += `import Page from "${file_path}${import_suffix}"\n`;
|
||||
tsx += `export default function Main() {\n\n`;
|
||||
tsx += `const props = JSON.parse(${server_res_json})\n\n`;
|
||||
tsx += `import Page from "${file_path}"\n`;
|
||||
tsx += `export default function Main({...props}) {\n\n`;
|
||||
tsx += ` return (\n`;
|
||||
if (root_file_path) {
|
||||
tsx += ` <Root {...props}><Page {...props} /></Root>\n`;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { BunextPageModuleServerReturn, BunextPageServerFn, BunxRouteParams } from "../../../types";
|
||||
type Params = {
|
||||
url?: URL;
|
||||
server_function: BunextPageServerFn;
|
||||
server_function?: BunextPageServerFn;
|
||||
query?: Record<string, string>;
|
||||
routeParams?: BunxRouteParams;
|
||||
props?: Record<string, any> | null;
|
||||
};
|
||||
export default function grabPageServerRes({ url, query, routeParams, server_function, }: Params): Promise<BunextPageModuleServerReturn>;
|
||||
export default function grabPageServerRes({ url, query, routeParams, server_function, props, }: Params): Promise<BunextPageModuleServerReturn>;
|
||||
export {};
|
||||
|
||||
+8
-12
@@ -1,6 +1,6 @@
|
||||
import _ from "lodash";
|
||||
import { log } from "../../../utils/log";
|
||||
export default async function grabPageServerRes({ url, query, routeParams, server_function, }) {
|
||||
export default async function grabPageServerRes({ url, query, routeParams, server_function, props, }) {
|
||||
const default_props = {
|
||||
url: url
|
||||
? {
|
||||
@@ -22,26 +22,22 @@ export default async function grabPageServerRes({ url, query, routeParams, serve
|
||||
: null,
|
||||
query,
|
||||
};
|
||||
const init_props = props || null;
|
||||
try {
|
||||
if (routeParams) {
|
||||
if (routeParams && server_function) {
|
||||
const serverData = await server_function({
|
||||
...routeParams,
|
||||
query: { ...routeParams.query, ...query },
|
||||
props: init_props || undefined,
|
||||
});
|
||||
return {
|
||||
...serverData,
|
||||
...default_props,
|
||||
};
|
||||
return _.merge(default_props, serverData);
|
||||
}
|
||||
return {
|
||||
...default_props,
|
||||
};
|
||||
return _.merge(default_props);
|
||||
}
|
||||
catch (error) {
|
||||
log.error(`Page ${url?.pathname} Server Error => ${error.message}\n`, error);
|
||||
return {
|
||||
...default_props,
|
||||
return _.merge(default_props, {
|
||||
error: error.message,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
type Params = {
|
||||
tsx: string;
|
||||
};
|
||||
export default function grabTsxStringModule<T>({ tsx, }: Params): Promise<T>;
|
||||
export {};
|
||||
@@ -0,0 +1,73 @@
|
||||
import isDevelopment from "../../../utils/is-development";
|
||||
import * as esbuild from "esbuild";
|
||||
export default async function grabTsxStringModule({ tsx, }) {
|
||||
const dev = isDevelopment();
|
||||
const now = Date.now();
|
||||
const final_tsx = dev ? tsx + `\n// v_${now}` : tsx;
|
||||
const result = await esbuild.transform(final_tsx, {
|
||||
loader: "tsx",
|
||||
format: "esm",
|
||||
jsx: "automatic",
|
||||
minify: !dev,
|
||||
});
|
||||
const blob = new Blob([result.code], { type: "text/javascript" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const mod = await import(url);
|
||||
URL.revokeObjectURL(url);
|
||||
return mod;
|
||||
}
|
||||
// export default async function grabTsxStringModule<T extends any = any>({
|
||||
// tsx,
|
||||
// }: Params): Promise<T> {
|
||||
// const dev = isDevelopment();
|
||||
// const now = Date.now();
|
||||
// const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
||||
// const target_cache_file_path = path.join(
|
||||
// BUNX_CWD_MODULE_CACHE_DIR,
|
||||
// `server-render-${now}.js`,
|
||||
// );
|
||||
// await esbuild.build({
|
||||
// stdin: {
|
||||
// contents: dev ? tsx + `\n// v_${now}` : tsx,
|
||||
// resolveDir: process.cwd(),
|
||||
// loader: "tsx",
|
||||
// },
|
||||
// bundle: true,
|
||||
// format: "esm",
|
||||
// target: "es2020",
|
||||
// platform: "node",
|
||||
// external: [
|
||||
// "react",
|
||||
// "react-dom",
|
||||
// "react/jsx-runtime",
|
||||
// "react/jsx-dev-runtime",
|
||||
// ],
|
||||
// minify: !dev,
|
||||
// define: {
|
||||
// "process.env.NODE_ENV": JSON.stringify(
|
||||
// dev ? "development" : "production",
|
||||
// ),
|
||||
// },
|
||||
// jsx: "automatic",
|
||||
// outfile: target_cache_file_path,
|
||||
// plugins: [tailwindEsbuildPlugin],
|
||||
// });
|
||||
// Loader.registry.delete(target_cache_file_path);
|
||||
// const mod = await import(`${target_cache_file_path}?t=${now}`);
|
||||
// return mod as T;
|
||||
// }
|
||||
// if (!dev) {
|
||||
// const now = Date.now();
|
||||
// const final_tsx = dev ? tsx + `\n// v_${now}` : tsx;
|
||||
// const result = await esbuild.transform(final_tsx, {
|
||||
// loader: "tsx",
|
||||
// format: "esm",
|
||||
// jsx: "automatic",
|
||||
// minify: !dev,
|
||||
// });
|
||||
// const blob = new Blob([result.code], { type: "text/javascript" });
|
||||
// const url = URL.createObjectURL(blob);
|
||||
// const mod = await import(url);
|
||||
// URL.revokeObjectURL(url);
|
||||
// return mod as T;
|
||||
// }
|
||||
@@ -1,5 +1,4 @@
|
||||
type Params = {
|
||||
tsx: string;
|
||||
};
|
||||
export default function grabTsxStringModule<T extends any = any>({ tsx, }: Params): Promise<T>;
|
||||
import type { GrabTSXModuleBatchParams, GrabTSXModuleSingleParams } from "../../../types";
|
||||
type Params = GrabTSXModuleSingleParams | GrabTSXModuleBatchParams;
|
||||
export default function grabTsxStringModule<T>(params: Params): Promise<T | T[]>;
|
||||
export {};
|
||||
|
||||
+191
-67
@@ -1,75 +1,199 @@
|
||||
import isDevelopment from "../../../utils/is-development";
|
||||
import { transform } from "esbuild";
|
||||
export default async function grabTsxStringModule({ tsx, }) {
|
||||
import * as esbuild from "esbuild";
|
||||
import grabDirNames from "../../../utils/grab-dir-names";
|
||||
import path from "path";
|
||||
import tailwindEsbuildPlugin from "./tailwind-esbuild-plugin";
|
||||
import { existsSync, rmSync, unlinkSync } from "fs";
|
||||
const { PAGES_DIR, BUNX_CWD_MODULE_CACHE_DIR, ROOT_DIR } = grabDirNames();
|
||||
function toModPath(page_file_path) {
|
||||
return path.join(BUNX_CWD_MODULE_CACHE_DIR, page_file_path.replace(PAGES_DIR, "").replace(/\.(t|j)sx?$/, ".js"));
|
||||
}
|
||||
function isBatch(params) {
|
||||
return "tsx_map" in params;
|
||||
}
|
||||
async function buildEntries({ entries, clean_cache }) {
|
||||
const dev = isDevelopment();
|
||||
const now = Date.now();
|
||||
const final_tsx = dev ? tsx + `\n// v_${now}` : tsx;
|
||||
const result = await transform(final_tsx, {
|
||||
loader: "tsx",
|
||||
const toBuild = [];
|
||||
for (const entry of entries) {
|
||||
const mod_file_path = toModPath(entry.page_file_path);
|
||||
// try {
|
||||
// if (clean_cache && existsSync(mod_file_path)) {
|
||||
// console.log(`Removing ${mod_file_path}`);
|
||||
// await Bun.file(mod_file_path).delete();
|
||||
// }
|
||||
// } catch (error) {}
|
||||
const does_mod_file_path_exists = existsSync(mod_file_path);
|
||||
if (!does_mod_file_path_exists) {
|
||||
toBuild.push({
|
||||
tsx: entry.tsx,
|
||||
mod_file_path,
|
||||
});
|
||||
}
|
||||
}
|
||||
if (toBuild.length === 0)
|
||||
return;
|
||||
const virtualEntries = {};
|
||||
for (const { tsx, mod_file_path } of toBuild) {
|
||||
virtualEntries[mod_file_path] = tsx;
|
||||
}
|
||||
const virtualPlugin = {
|
||||
name: "virtual-tsx-entries",
|
||||
setup(build) {
|
||||
const entryPaths = new Set(Object.keys(virtualEntries));
|
||||
build.onResolve({ filter: /.*/ }, (args) => {
|
||||
if (entryPaths.has(args.path)) {
|
||||
return {
|
||||
path: args.path,
|
||||
namespace: "virtual",
|
||||
};
|
||||
}
|
||||
});
|
||||
build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => ({
|
||||
contents: virtualEntries[args.path],
|
||||
resolveDir: process.cwd(),
|
||||
loader: "tsx",
|
||||
}));
|
||||
build.onEnd((result) => {
|
||||
if (result.errors.length > 0) {
|
||||
console.log(`Build Errors =>`, result.errors);
|
||||
return;
|
||||
}
|
||||
// const artifacts: any[] = Object.entries(
|
||||
// result.metafile!.outputs,
|
||||
// )
|
||||
// .filter(([, meta]) => meta.entryPoint)
|
||||
// .map(([outputPath, meta]) => {
|
||||
// return {
|
||||
// path: outputPath,
|
||||
// hash: path.basename(
|
||||
// outputPath,
|
||||
// path.extname(outputPath),
|
||||
// ),
|
||||
// type: outputPath.endsWith(".css")
|
||||
// ? "text/css"
|
||||
// : "text/javascript",
|
||||
// entrypoint: meta.entryPoint,
|
||||
// css_path: meta.cssBundle,
|
||||
// };
|
||||
// });
|
||||
// console.log("artifacts", artifacts);
|
||||
});
|
||||
},
|
||||
};
|
||||
const entryPoints = Object.keys(virtualEntries);
|
||||
const build = await esbuild.build({
|
||||
entryPoints,
|
||||
bundle: true,
|
||||
format: "esm",
|
||||
jsx: "automatic",
|
||||
target: "es2020",
|
||||
platform: "node",
|
||||
external: [
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"react/jsx-dev-runtime",
|
||||
],
|
||||
minify: !dev,
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify(dev ? "development" : "production"),
|
||||
},
|
||||
jsx: "automatic",
|
||||
outdir: BUNX_CWD_MODULE_CACHE_DIR,
|
||||
plugins: [virtualPlugin, tailwindEsbuildPlugin],
|
||||
metafile: true,
|
||||
// logLevel: "silent",
|
||||
});
|
||||
}
|
||||
async function loadEntry(page_file_path) {
|
||||
const now = Date.now();
|
||||
const mod_file_path = toModPath(page_file_path);
|
||||
const mod_css_path = mod_file_path.replace(/\.js$/, ".css");
|
||||
if (global.BUNEXT_REACT_DOM_MODULE_CACHE.has(page_file_path)) {
|
||||
return global.BUNEXT_REACT_DOM_MODULE_CACHE.get(page_file_path)
|
||||
?.main;
|
||||
}
|
||||
const mod = await import(`${mod_file_path}?t=${now}`);
|
||||
global.BUNEXT_REACT_DOM_MODULE_CACHE.set(page_file_path, {
|
||||
main: mod,
|
||||
css: mod_css_path,
|
||||
});
|
||||
const blob = new Blob([result.code], { type: "text/javascript" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const mod = await import(url);
|
||||
URL.revokeObjectURL(url);
|
||||
return mod;
|
||||
}
|
||||
// const trimmed_file_path = file_path
|
||||
// .replace(/.*\/src\/pages\//, "")
|
||||
// .replace(/\.tsx$/, "");
|
||||
// const src_file_path = path.join(
|
||||
// BUNX_CWD_MODULE_CACHE_DIR,
|
||||
// `${trimmed_file_path}.tsx`,
|
||||
// );
|
||||
// const out_file_path = path.join(
|
||||
// BUNX_CWD_MODULE_CACHE_DIR,
|
||||
// `${trimmed_file_path}.js`,
|
||||
// );
|
||||
// await Bun.write(src_file_path, tsx);
|
||||
// const build = await Bun.build({
|
||||
// entrypoints: [src_file_path],
|
||||
// format: "esm",
|
||||
// target: "bun",
|
||||
// // external: ["react", "react-dom"],
|
||||
// minify: true,
|
||||
// define: {
|
||||
// "process.env.NODE_ENV": JSON.stringify(
|
||||
// dev ? "development" : "production",
|
||||
// ),
|
||||
// },
|
||||
// metafile: true,
|
||||
// plugins: [tailwindcss, BunSkipNonBrowserPlugin],
|
||||
// jsx: {
|
||||
// runtime: "automatic",
|
||||
// development: dev,
|
||||
// },
|
||||
// outdir: BUNX_CWD_MODULE_CACHE_DIR,
|
||||
// });
|
||||
// Loader.registry.delete(out_file_path);
|
||||
// const module = await import(`${out_file_path}?t=${Date.now()}`);
|
||||
// return module as T;
|
||||
// await esbuild.build({
|
||||
// stdin: {
|
||||
// contents: tsx,
|
||||
// resolveDir: process.cwd(),
|
||||
export default async function grabTsxStringModule(params) {
|
||||
if (isBatch(params)) {
|
||||
try {
|
||||
await buildEntries({ entries: params.tsx_map });
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`SSR Batch Build Error\n`);
|
||||
console.log(error);
|
||||
}
|
||||
return Promise.all(params.tsx_map.map((entry) => loadEntry(entry.page_file_path)));
|
||||
}
|
||||
try {
|
||||
await buildEntries({
|
||||
entries: [params],
|
||||
clean_cache: true,
|
||||
});
|
||||
}
|
||||
catch (error) {
|
||||
console.error(`SSR Single Build Error\n`);
|
||||
console.log(error);
|
||||
}
|
||||
return loadEntry(params.page_file_path);
|
||||
}
|
||||
// export default async function grabTsxStringModule<T extends any = any>({
|
||||
// tsx,
|
||||
// }: Params): Promise<T> {
|
||||
// const dev = isDevelopment();
|
||||
// const now = Date.now();
|
||||
// const { BUNX_CWD_MODULE_CACHE_DIR } = grabDirNames();
|
||||
// const target_cache_file_path = path.join(
|
||||
// BUNX_CWD_MODULE_CACHE_DIR,
|
||||
// `server-render-${now}.js`,
|
||||
// );
|
||||
// await esbuild.build({
|
||||
// stdin: {
|
||||
// contents: dev ? tsx + `\n// v_${now}` : tsx,
|
||||
// resolveDir: process.cwd(),
|
||||
// loader: "tsx",
|
||||
// },
|
||||
// bundle: true,
|
||||
// format: "esm",
|
||||
// target: "es2020",
|
||||
// platform: "node",
|
||||
// external: [
|
||||
// "react",
|
||||
// "react-dom",
|
||||
// "react/jsx-runtime",
|
||||
// "react/jsx-dev-runtime",
|
||||
// ],
|
||||
// minify: !dev,
|
||||
// define: {
|
||||
// "process.env.NODE_ENV": JSON.stringify(
|
||||
// dev ? "development" : "production",
|
||||
// ),
|
||||
// },
|
||||
// jsx: "automatic",
|
||||
// outfile: target_cache_file_path,
|
||||
// plugins: [tailwindEsbuildPlugin],
|
||||
// });
|
||||
// Loader.registry.delete(target_cache_file_path);
|
||||
// const mod = await import(`${target_cache_file_path}?t=${now}`);
|
||||
// return mod as T;
|
||||
// }
|
||||
// if (!dev) {
|
||||
// const now = Date.now();
|
||||
// const final_tsx = dev ? tsx + `\n// v_${now}` : tsx;
|
||||
// const result = await esbuild.transform(final_tsx, {
|
||||
// loader: "tsx",
|
||||
// },
|
||||
// bundle: true,
|
||||
// format: "esm",
|
||||
// target: "es2020",
|
||||
// platform: "node",
|
||||
// external: ["react", "react-dom"],
|
||||
// minify: true,
|
||||
// define: {
|
||||
// "process.env.NODE_ENV": JSON.stringify(
|
||||
// dev ? "development" : "production",
|
||||
// ),
|
||||
// },
|
||||
// metafile: true,
|
||||
// plugins: [tailwindEsbuildPlugin],
|
||||
// jsx: "automatic",
|
||||
// write: true,
|
||||
// outfile: out_file_path,
|
||||
// });
|
||||
// format: "esm",
|
||||
// jsx: "automatic",
|
||||
// minify: !dev,
|
||||
// });
|
||||
// const blob = new Blob([result.code], { type: "text/javascript" });
|
||||
// const url = URL.createObjectURL(blob);
|
||||
// const mod = await import(url);
|
||||
// URL.revokeObjectURL(url);
|
||||
// return mod as T;
|
||||
// }
|
||||
|
||||
@@ -12,6 +12,9 @@ export default async function (params) {
|
||||
const supress_condition = errors_to_supress
|
||||
.map((e) => `args[0].includes("${e}")`)
|
||||
.join(" || ");
|
||||
script += `let retries = 0;\n`;
|
||||
script += `let retries_exhausted = false;\n`;
|
||||
script += `const MAX_RETRIES = 1;\n`;
|
||||
script += `const _ce = console.error.bind(console);\n`;
|
||||
script += `console.error = (...args) => {\n`;
|
||||
script += ` if (typeof args[0] === "string" && (${supress_condition})) return;\n`;
|
||||
@@ -23,8 +26,15 @@ export default async function (params) {
|
||||
script += ` const overlay = document.createElement("div");\n`;
|
||||
script += ` overlay.id = "__bunext_error_overlay";\n`;
|
||||
script += ` overlay.style.cssText = "position:fixed;inset:0;z-index:99999;background:#1a1a1a;color:#ff6b6b;font-family:monospace;font-size:14px;padding:24px;overflow:auto;";\n`;
|
||||
script += ` overlay.innerHTML = \`<div style="max-width:900px;margin:0 auto"><div style="font-size:18px;font-weight:bold;margin-bottom:12px;color:#ff4444">Runtime Error</div><div style="color:#fff;margin-bottom:16px">\${message}</div>\${source ? \`<div style="color:#888;margin-bottom:16px">\${source}</div>\` : ""}\${stack ? \`<pre style="background:#111;padding:16px;border-radius:6px;overflow:auto;color:#ffa07a;white-space:pre-wrap">\${stack}</pre>\` : ""}<button onclick="this.closest('#__bunext_error_overlay').remove()" style="margin-top:16px;padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer">Dismiss</button></div>\`;\n`;
|
||||
script += ` overlay.innerHTML = \`<div style="max-width:900px;margin:auto"><div style="font-size:18px;font-weight:bold;margin-bottom:12px;color:#ff4444">Runtime Error</div><div style="color:#fff;margin-bottom:16px">\${message}</div>\${source ? \`<div style="color:#888;margin-bottom:16px">\${source}</div>\` : ""}\${stack ? \`<pre style="background:#111;padding:16px;border-radius:6px;overflow:auto;color:#ffa07a;white-space:pre-wrap">\${stack}</pre>\` : ""}<button onclick="this.closest('#__bunext_error_overlay').remove()" style="margin-top:16px;padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer">Dismiss</button></div>\`;\n`;
|
||||
script += ` document.body.appendChild(overlay);\n`;
|
||||
script += ` if (retries < MAX_RETRIES) {\n`;
|
||||
script += ` retries++\n`;
|
||||
script += ` console.log(\`Retrying \${retries} ...\`)\n`;
|
||||
script += ` fetch("${AppData["BunextHMRRetryRoute"]}")\n`;
|
||||
script += ` } else {\n`;
|
||||
script += ` retries_exhausted = true\n`;
|
||||
script += ` }\n`;
|
||||
script += `}\n\n`;
|
||||
script += `function __bunext_should_suppress_runtime_error(message) {\n`;
|
||||
script += ` return false;\n`;
|
||||
@@ -53,7 +63,14 @@ export default async function (params) {
|
||||
script += `hmr.addEventListener("update", async (event) => {\n`;
|
||||
script += ` if (event?.data) {\n`;
|
||||
script += ` try {\n`;
|
||||
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
|
||||
script += ` if (retries_exhausted) {\n`;
|
||||
script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
|
||||
script += ` retries = 0;\n`;
|
||||
script += ` retries_exhausted = false;\n`;
|
||||
script += ` }\n`;
|
||||
// script += ` if (retries >= MAX_RETRIES && document.getElementById("__bunext_error_overlay")) {\n`;
|
||||
// script += ` retries = 0;\n`;
|
||||
// script += ` }\n`;
|
||||
script += ` const data = JSON.parse(event.data);\n`;
|
||||
// script += ` console.log("data", data);\n`;
|
||||
script += ` if (data.reload) {\n`;
|
||||
@@ -98,7 +115,9 @@ export default async function (params) {
|
||||
// script += ` window.location.reload();\n`;
|
||||
// script += ` }\n`;
|
||||
// script += ` console.log("newScript", newScript);\n`;
|
||||
// script += ` document.getElementById("__bunext_error_overlay")?.remove();\n`;
|
||||
script += ` document.head.appendChild(newScript);\n\n`;
|
||||
// script += ` retries = 0;\n\n`;
|
||||
script += ` } catch (err) {\n`;
|
||||
script += ` console.error("HMR update failed, falling back to reload:", err.message);\n`;
|
||||
script += ` window.location.reload();\n`;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user