From eec0df83cd8d3be3d13089a3295dba6e08ac0768 Mon Sep 17 00:00:00 2001 From: Benjamin Toby Date: Wed, 18 Mar 2026 17:37:24 +0100 Subject: [PATCH] Add dist --- .gitignore | 1 - dist/build/build.js | 131 +++++++++++++++++ dist/commands/build/index.js | 21 +++ dist/commands/dev/index.js | 18 +++ dist/commands/start/index.js | 15 ++ dist/data/app-data.js | 5 + dist/functions/bundler/all-pages-bundler.js | 138 ++++++++++++++++++ dist/functions/cache/get-cache.js | 15 ++ dist/functions/cache/grab-cache-names.js | 6 + dist/functions/cache/trim-all-cache.js | 21 +++ dist/functions/cache/trim-cache-key.js | 40 +++++ dist/functions/cache/write-cache.js | 38 +++++ dist/functions/grab-config.js | 19 +++ dist/functions/init.js | 22 +++ dist/functions/server/cron.js | 8 + dist/functions/server/handle-routes.js | 46 ++++++ dist/functions/server/rebuild-bundler.js | 16 ++ dist/functions/server/server-params-gen.js | 91 ++++++++++++ dist/functions/server/server-post-build-fn.js | 26 ++++ dist/functions/server/start-server.js | 46 ++++++ dist/functions/server/watcher.js | 31 ++++ .../server/web-pages/generate-web-html.js | 42 ++++++ .../server/web-pages/grab-page-component.js | 109 ++++++++++++++ .../web-pages/grab-page-error-component.js | 42 ++++++ .../server/web-pages/grab-web-meta-html.js | 60 ++++++++ .../grab-web-page-hydration-script.js | 55 +++++++ .../server/web-pages/handle-web-pages.js | 77 ++++++++++ dist/index.js | 44 ++++++ dist/presets/bunext.config.js | 2 + dist/presets/not-found.js | 13 ++ dist/presets/server-error.js | 13 ++ .../functions/bundler/all-pages-bundler.js | 52 +++++++ dist/src/functions/grab-config.js | 19 +++ dist/src/functions/init.js | 20 +++ dist/src/functions/router/get-route.js | 26 ++++ dist/src/functions/server/handle-routes.js | 36 +++++ .../src/functions/server/server-params-gen.js | 62 ++++++++ dist/src/functions/server/start-server.js | 14 ++ dist/src/functions/server/watcher.js | 78 ++++++++++ .../server/web-pages/generate-web-html.js | 32 ++++ .../server/web-pages/grab-page-component.js | 62 ++++++++ .../server/web-pages/handle-web-pages.js | 30 ++++ .../write-web-page-hydration-script.js | 23 +++ dist/src/presets/server-error.js | 11 ++ dist/src/types/index.js | 1 + dist/src/utils/bundle.js | 15 ++ dist/src/utils/deserialize-query.js | 18 +++ dist/src/utils/ejson.js | 33 +++++ dist/src/utils/exit-with-error.js | 4 + dist/src/utils/grab-all-pages.js | 57 ++++++++ dist/src/utils/grab-app-names.js | 7 + dist/src/utils/grab-app-port.js | 17 +++ dist/src/utils/grab-assets-prefix.js | 8 + dist/src/utils/grab-constants.js | 15 ++ dist/src/utils/grab-dir-names.js | 34 +++++ dist/src/utils/grab-origin.js | 8 + dist/src/utils/grab-page-name.js | 10 ++ dist/src/utils/grab-route-params.js | 20 +++ dist/src/utils/grab-router.js | 11 ++ dist/src/utils/is-development.js | 6 + dist/src/utils/numberfy.js | 31 ++++ dist/types/index.js | 1 + dist/utils/bundle.js | 47 ++++++ dist/utils/deserialize-query.js | 18 +++ dist/utils/ejson.js | 33 +++++ dist/utils/exit-with-error.js | 4 + dist/utils/grab-all-pages.js | 64 ++++++++ dist/utils/grab-app-names.js | 8 + dist/utils/grab-app-port.js | 17 +++ dist/utils/grab-assets-prefix.js | 8 + dist/utils/grab-constants.js | 18 +++ dist/utils/grab-dir-names.js | 43 ++++++ dist/utils/grab-origin.js | 8 + dist/utils/grab-page-name.js | 17 +++ dist/utils/grab-route-params.js | 21 +++ dist/utils/grab-router.js | 6 + dist/utils/is-development.js | 10 ++ dist/utils/numberfy.js | 31 ++++ dist/utils/refresh-router.js | 9 ++ 79 files changed, 2333 insertions(+), 1 deletion(-) create mode 100644 dist/build/build.js create mode 100644 dist/commands/build/index.js create mode 100644 dist/commands/dev/index.js create mode 100644 dist/commands/start/index.js create mode 100644 dist/data/app-data.js create mode 100644 dist/functions/bundler/all-pages-bundler.js create mode 100644 dist/functions/cache/get-cache.js create mode 100644 dist/functions/cache/grab-cache-names.js create mode 100644 dist/functions/cache/trim-all-cache.js create mode 100644 dist/functions/cache/trim-cache-key.js create mode 100644 dist/functions/cache/write-cache.js create mode 100644 dist/functions/grab-config.js create mode 100644 dist/functions/init.js create mode 100644 dist/functions/server/cron.js create mode 100644 dist/functions/server/handle-routes.js create mode 100644 dist/functions/server/rebuild-bundler.js create mode 100644 dist/functions/server/server-params-gen.js create mode 100644 dist/functions/server/server-post-build-fn.js create mode 100644 dist/functions/server/start-server.js create mode 100644 dist/functions/server/watcher.js create mode 100644 dist/functions/server/web-pages/generate-web-html.js create mode 100644 dist/functions/server/web-pages/grab-page-component.js create mode 100644 dist/functions/server/web-pages/grab-page-error-component.js create mode 100644 dist/functions/server/web-pages/grab-web-meta-html.js create mode 100644 dist/functions/server/web-pages/grab-web-page-hydration-script.js create mode 100644 dist/functions/server/web-pages/handle-web-pages.js create mode 100755 dist/index.js create mode 100644 dist/presets/bunext.config.js create mode 100644 dist/presets/not-found.js create mode 100644 dist/presets/server-error.js create mode 100644 dist/src/functions/bundler/all-pages-bundler.js create mode 100644 dist/src/functions/grab-config.js create mode 100644 dist/src/functions/init.js create mode 100644 dist/src/functions/router/get-route.js create mode 100644 dist/src/functions/server/handle-routes.js create mode 100644 dist/src/functions/server/server-params-gen.js create mode 100644 dist/src/functions/server/start-server.js create mode 100644 dist/src/functions/server/watcher.js create mode 100644 dist/src/functions/server/web-pages/generate-web-html.js create mode 100644 dist/src/functions/server/web-pages/grab-page-component.js create mode 100644 dist/src/functions/server/web-pages/handle-web-pages.js create mode 100644 dist/src/functions/server/web-pages/write-web-page-hydration-script.js create mode 100644 dist/src/presets/server-error.js create mode 100644 dist/src/types/index.js create mode 100644 dist/src/utils/bundle.js create mode 100644 dist/src/utils/deserialize-query.js create mode 100644 dist/src/utils/ejson.js create mode 100644 dist/src/utils/exit-with-error.js create mode 100644 dist/src/utils/grab-all-pages.js create mode 100644 dist/src/utils/grab-app-names.js create mode 100644 dist/src/utils/grab-app-port.js create mode 100644 dist/src/utils/grab-assets-prefix.js create mode 100644 dist/src/utils/grab-constants.js create mode 100644 dist/src/utils/grab-dir-names.js create mode 100644 dist/src/utils/grab-origin.js create mode 100644 dist/src/utils/grab-page-name.js create mode 100644 dist/src/utils/grab-route-params.js create mode 100644 dist/src/utils/grab-router.js create mode 100644 dist/src/utils/is-development.js create mode 100644 dist/src/utils/numberfy.js create mode 100644 dist/types/index.js create mode 100644 dist/utils/bundle.js create mode 100644 dist/utils/deserialize-query.js create mode 100644 dist/utils/ejson.js create mode 100644 dist/utils/exit-with-error.js create mode 100644 dist/utils/grab-all-pages.js create mode 100644 dist/utils/grab-app-names.js create mode 100644 dist/utils/grab-app-port.js create mode 100644 dist/utils/grab-assets-prefix.js create mode 100644 dist/utils/grab-constants.js create mode 100644 dist/utils/grab-dir-names.js create mode 100644 dist/utils/grab-origin.js create mode 100644 dist/utils/grab-page-name.js create mode 100644 dist/utils/grab-route-params.js create mode 100644 dist/utils/grab-router.js create mode 100644 dist/utils/is-development.js create mode 100644 dist/utils/numberfy.js create mode 100644 dist/utils/refresh-router.js diff --git a/.gitignore b/.gitignore index 84f3e9a..49252e7 100644 --- a/.gitignore +++ b/.gitignore @@ -118,7 +118,6 @@ out # Nuxt.js build / generate output .nuxt -dist # Gatsby files diff --git a/dist/build/build.js b/dist/build/build.js new file mode 100644 index 0000000..6a3798b --- /dev/null +++ b/dist/build/build.js @@ -0,0 +1,131 @@ +#!/usr/bin/env bun +import plugin from "bun-plugin-tailwind"; +import { existsSync } from "fs"; +import { rm } from "fs/promises"; +import path from "path"; +if (process.argv.includes("--help") || process.argv.includes("-h")) { + console.log(` +šŸ—ļø Bun Build Script + +Usage: bun run build.ts [options] + +Common Options: + --outdir Output directory (default: "dist") + --minify Enable minification (or --minify.whitespace, --minify.syntax, etc) + --sourcemap Sourcemap type: none|linked|inline|external + --target Build target: browser|bun|node + --format Output format: esm|cjs|iife + --splitting Enable code splitting + --packages Package handling: bundle|external + --public-path Public path for assets + --env Environment handling: inline|disable|prefix* + --conditions Package.json export conditions (comma separated) + --external External packages (comma separated) + --banner Add banner text to output + --footer Add footer text to output + --define Define global constants (e.g. --define.VERSION=1.0.0) + --help, -h Show this help message + +Example: + bun run build.ts --outdir=dist --minify --sourcemap=linked --external=react,react-dom +`); + process.exit(0); +} +const toCamelCase = (str) => str.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); +const parseValue = (value) => { + if (value === "true") + return true; + if (value === "false") + return false; + if (/^\d+$/.test(value)) + return parseInt(value, 10); + if (/^\d*\.\d+$/.test(value)) + return parseFloat(value); + if (value.includes(",")) + return value.split(",").map((v) => v.trim()); + return value; +}; +function parseArgs() { + const config = {}; + const args = process.argv.slice(2); + for (let i = 0; i < args.length; i++) { + const arg = args[i]; + if (arg === undefined) + continue; + if (!arg.startsWith("--")) + continue; + if (arg.startsWith("--no-")) { + const key = toCamelCase(arg.slice(5)); + config[key] = false; + continue; + } + if (!arg.includes("=") && + (i === args.length - 1 || args[i + 1]?.startsWith("--"))) { + const key = toCamelCase(arg.slice(2)); + config[key] = true; + continue; + } + let key; + let value; + if (arg.includes("=")) { + [key, value] = arg.slice(2).split("=", 2); + } + else { + key = arg.slice(2); + value = args[++i] ?? ""; + } + key = toCamelCase(key); + if (key.includes(".")) { + const [parentKey, childKey] = key.split("."); + config[parentKey] = config[parentKey] || {}; + config[parentKey][childKey] = parseValue(value); + } + else { + config[key] = parseValue(value); + } + } + return config; +} +const formatFileSize = (bytes) => { + const units = ["B", "KB", "MB", "GB"]; + let size = bytes; + let unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + return `${size.toFixed(2)} ${units[unitIndex]}`; +}; +console.log("\nšŸš€ Starting build process...\n"); +const cliConfig = parseArgs(); +const outdir = cliConfig.outdir || path.join(process.cwd(), "dist"); +if (existsSync(outdir)) { + console.log(`šŸ—‘ļø Cleaning previous build at ${outdir}`); + await rm(outdir, { recursive: true, force: true }); +} +const start = performance.now(); +const entrypoints = [...new Bun.Glob("**.html").scanSync("src/app")] + .map((a) => path.resolve("src/app", a)) + .filter((dir) => !dir.includes("node_modules")); +console.log(`šŸ“„ Found ${entrypoints.length} HTML ${entrypoints.length === 1 ? "file" : "files"} to process\n`); +const result = await Bun.build({ + entrypoints, + outdir, + plugins: [plugin], + minify: true, + target: "browser", + sourcemap: "linked", + define: { + "process.env.NODE_ENV": JSON.stringify("production"), + }, + ...cliConfig, +}); +const end = performance.now(); +const outputTable = result.outputs.map((output) => ({ + File: path.relative(process.cwd(), output.path), + Type: output.kind, + Size: formatFileSize(output.size), +})); +console.table(outputTable); +const buildTime = (end - start).toFixed(2); +console.log(`\nāœ… Build completed in ${buildTime}ms\n`); diff --git a/dist/commands/build/index.js b/dist/commands/build/index.js new file mode 100644 index 0000000..6b2ffac --- /dev/null +++ b/dist/commands/build/index.js @@ -0,0 +1,21 @@ +import { Command } from "commander"; +import grabConfig from "../../functions/grab-config"; +import init from "../../functions/init"; +import allPagesBundler from "../../functions/bundler/all-pages-bundler"; +export default function () { + return new Command("build") + .description("Build Project") + .action(async () => { + console.log(`Building Project ...`); + process.env.NODE_ENV = "production"; + await init(); + const config = (await grabConfig()) || {}; + global.CONFIG = { + ...config, + development: true, + }; + allPagesBundler({ + exit_after_first_build: true, + }); + }); +} diff --git a/dist/commands/dev/index.js b/dist/commands/dev/index.js new file mode 100644 index 0000000..d7375ad --- /dev/null +++ b/dist/commands/dev/index.js @@ -0,0 +1,18 @@ +import { Command } from "commander"; +import grabConfig from "../../functions/grab-config"; +import startServer from "../../functions/server/start-server"; +import init from "../../functions/init"; +export default function () { + return new Command("dev") + .description("Run development server") + .action(async () => { + console.log(`Running development server ...`); + await init(); + const config = (await grabConfig()) || {}; + global.CONFIG = { + ...config, + development: true, + }; + await startServer({ dev: true }); + }); +} diff --git a/dist/commands/start/index.js b/dist/commands/start/index.js new file mode 100644 index 0000000..f75d9c7 --- /dev/null +++ b/dist/commands/start/index.js @@ -0,0 +1,15 @@ +import { Command } from "commander"; +import grabConfig from "../../functions/grab-config"; +import startServer from "../../functions/server/start-server"; +import init from "../../functions/init"; +export default function () { + return new Command("start") + .description("Start production server") + .action(async () => { + console.log(`Starting production server ...`); + await init(); + const config = await grabConfig(); + global.CONFIG = { ...config }; + await startServer(); + }); +} diff --git a/dist/data/app-data.js b/dist/data/app-data.js new file mode 100644 index 0000000..cc37daf --- /dev/null +++ b/dist/data/app-data.js @@ -0,0 +1,5 @@ +export const AppData = { + DefaultCacheExpiryTimeSeconds: 60 * 60, + DefaultCronInterval: 30000, + BunextStaticFilesCacheExpiry: 60 * 60 * 24 * 7, +}; diff --git a/dist/functions/bundler/all-pages-bundler.js b/dist/functions/bundler/all-pages-bundler.js new file mode 100644 index 0000000..c9698a7 --- /dev/null +++ b/dist/functions/bundler/all-pages-bundler.js @@ -0,0 +1,138 @@ +import { existsSync, writeFileSync } from "fs"; +import path from "path"; +import * as esbuild from "esbuild"; +import postcss from "postcss"; +import tailwindcss from "@tailwindcss/postcss"; +import { readFile } from "fs/promises"; +import grabAllPages from "../../utils/grab-all-pages"; +import grabDirNames from "../../utils/grab-dir-names"; +import AppNames from "../../utils/grab-app-names"; +import isDevelopment from "../../utils/is-development"; +import { execSync } from "child_process"; +import grabConstants from "../../utils/grab-constants"; +const { HYDRATION_DST_DIR, PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames(); +const tailwindPlugin = { + name: "tailwindcss", + setup(build) { + build.onLoad({ filter: /\.css$/ }, async (args) => { + const source = await readFile(args.path, "utf-8"); + const result = await postcss([tailwindcss()]).process(source, { + from: args.path, + }); + return { + contents: result.css, + loader: "css", + }; + }); + }, +}; +export default async function allPagesBundler(params) { + const pages = grabAllPages({ exclude_api: true }); + const { ClientRootElementIDName, ClientRootComponentWindowName } = grabConstants(); + const virtualEntries = {}; + const dev = isDevelopment(); + const root_component_path = path.join(PAGES_DIR, `${AppNames["RootPagesComponentName"]}.tsx`); + const does_root_exist = existsSync(root_component_path); + for (const page of pages) { + const key = page.local_path; + let txt = ``; + txt += `import { hydrateRoot } from "react-dom/client";\n`; + if (does_root_exist) { + txt += `import Root from "${root_component_path}";\n`; + } + txt += `import Page from "${page.local_path}";\n\n`; + txt += `const pageProps = window.__PAGE_PROPS__ || {};\n`; + if (does_root_exist) { + txt += `const component = \n`; + } + else { + txt += `const component = \n`; + } + txt += `const root = hydrateRoot(document.getElementById("${ClientRootElementIDName}"), component);\n\n`; + txt += `window.${ClientRootComponentWindowName} = root;\n`; + 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(), + })); + }, + }; + const artifactTracker = { + name: "artifact-tracker", + setup(build) { + build.onStart(() => { + console.time("build"); + }); + build.onEnd((result) => { + if (result.errors.length > 0) + return; + const artifacts = Object.entries(result.metafile.outputs) + .filter(([, meta]) => meta.entryPoint) + .map(([outputPath, meta]) => { + const target_page = pages.find((p) => { + return (meta.entryPoint === `virtual:${p.local_path}`); + }); + if (!target_page || !meta.entryPoint) { + return undefined; + } + const { file_name, local_path, url_path } = target_page; + const cssPath = meta.cssBundle || undefined; + return { + path: outputPath, + hash: path.basename(outputPath, path.extname(outputPath)), + type: outputPath.endsWith(".css") + ? "text/css" + : "text/javascript", + entrypoint: meta.entryPoint, + css_path: cssPath, + file_name, + local_path, + url_path, + }; + }); + if (artifacts.length > 0) { + const final_artifacts = artifacts.filter((a) => Boolean(a?.entrypoint)); + global.BUNDLER_CTX_MAP = final_artifacts; + params?.post_build_fn?.({ artifacts: final_artifacts }); + writeFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, JSON.stringify(artifacts)); + } + console.timeEnd("build"); + if (params?.exit_after_first_build) { + process.exit(); + } + }); + }, + }; + execSync(`rm -rf ${HYDRATION_DST_DIR}`); + const ctx = await esbuild.context({ + entryPoints: Object.keys(virtualEntries).map((k) => `virtual:${k}`), + 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]/[name]/[hash]", + metafile: true, + plugins: [tailwindPlugin, virtualPlugin, artifactTracker], + jsx: "automatic", + splitting: true, + }); + await ctx.rebuild(); + if (params?.watch) { + global.BUNDLER_CTX = ctx; + global.BUNDLER_CTX.watch(); + } +} diff --git a/dist/functions/cache/get-cache.js b/dist/functions/cache/get-cache.js new file mode 100644 index 0000000..b83143a --- /dev/null +++ b/dist/functions/cache/get-cache.js @@ -0,0 +1,15 @@ +import { readFileSync } from "fs"; +import grabDirNames from "../../utils/grab-dir-names"; +import grabCacheNames from "./grab-cache-names"; +import path from "path"; +export default function getCache({ key, paradigm }) { + try { + const { BUNEXT_CACHE_DIR } = grabDirNames(); + const { cache_name } = grabCacheNames({ key, paradigm }); + const content = readFileSync(path.join(BUNEXT_CACHE_DIR, cache_name), "utf-8"); + return content; + } + catch (error) { + return undefined; + } +} diff --git a/dist/functions/cache/grab-cache-names.js b/dist/functions/cache/grab-cache-names.js new file mode 100644 index 0000000..f4f8ee5 --- /dev/null +++ b/dist/functions/cache/grab-cache-names.js @@ -0,0 +1,6 @@ +export default function grabCacheNames({ key, paradigm = "html" }) { + const parsed_key = encodeURIComponent(key); + const cache_name = `${parsed_key}.res.${paradigm}`; + const cache_meta_name = `${parsed_key}.meta.json`; + return { cache_name, cache_meta_name }; +} diff --git a/dist/functions/cache/trim-all-cache.js b/dist/functions/cache/trim-all-cache.js new file mode 100644 index 0000000..a600f99 --- /dev/null +++ b/dist/functions/cache/trim-all-cache.js @@ -0,0 +1,21 @@ +import { readdirSync } from "fs"; +import grabDirNames from "../../utils/grab-dir-names"; +import trimCacheKey from "./trim-cache-key"; +export default async function trimAllCache() { + try { + const { BUNEXT_CACHE_DIR } = grabDirNames(); + const cached_items = readdirSync(BUNEXT_CACHE_DIR); + for (let i = 0; i < cached_items.length; i++) { + const cached_item = cached_items[i]; + if (!cached_item.endsWith(`.meta.json`)) + continue; + const cache_key = decodeURIComponent(cached_item.replace(/\.meta\.json/, "")); + const trim_key = await trimCacheKey({ + key: cache_key, + }); + } + } + catch (error) { + return undefined; + } +} diff --git a/dist/functions/cache/trim-cache-key.js b/dist/functions/cache/trim-cache-key.js new file mode 100644 index 0000000..f6129b3 --- /dev/null +++ b/dist/functions/cache/trim-cache-key.js @@ -0,0 +1,40 @@ +import { readFileSync, unlinkSync } from "fs"; +import grabDirNames from "../../utils/grab-dir-names"; +import grabCacheNames from "./grab-cache-names"; +import path from "path"; +import { AppData } from "../../data/app-data"; +export default async function trimCacheKey({ key, }) { + try { + const { BUNEXT_CACHE_DIR } = grabDirNames(); + const { cache_name, cache_meta_name } = grabCacheNames({ + key, + }); + const config = global.CONFIG; + const default_expiry_time_seconds = config.defaultCacheExpiry || + AppData["DefaultCacheExpiryTimeSeconds"]; + const default_expiry_time_milliseconds = default_expiry_time_seconds * 1000; + const cache_content_path = path.join(BUNEXT_CACHE_DIR, cache_name); + const cache_meta_path = path.join(BUNEXT_CACHE_DIR, cache_meta_name); + const cache_meta = JSON.parse(readFileSync(cache_meta_path, "utf-8")); + const expiry_milliseconds = cache_meta.expiry_seconds + ? cache_meta.expiry_seconds * 1000 + : default_expiry_time_milliseconds; + if (Date.now() - cache_meta.date_created < expiry_milliseconds) { + return { + success: false, + msg: `Cache has not expired yet`, + }; + } + unlinkSync(cache_content_path); + unlinkSync(cache_meta_path); + return { + success: true, + }; + } + catch (error) { + return { + success: false, + msg: `Trim cache key ERROR: ${error.message}`, + }; + } +} diff --git a/dist/functions/cache/write-cache.js b/dist/functions/cache/write-cache.js new file mode 100644 index 0000000..e892ee3 --- /dev/null +++ b/dist/functions/cache/write-cache.js @@ -0,0 +1,38 @@ +import { existsSync, writeFileSync } from "fs"; +import grabDirNames from "../../utils/grab-dir-names"; +import grabCacheNames from "./grab-cache-names"; +import path from "path"; +export default async function writeCache({ key, value, paradigm = "html", expiry_seconds, }) { + try { + const { BUNEXT_CACHE_DIR } = grabDirNames(); + const { cache_meta_name, cache_name } = grabCacheNames({ + key, + paradigm, + }); + const target_path = path.join(BUNEXT_CACHE_DIR, cache_name); + if (existsSync(target_path)) { + return { + success: false, + msg: `Cache entry already exists`, + }; + } + writeFileSync(path.join(target_path), value); + const cache_file_meta = { + date_created: Date.now(), + paradigm, + }; + if (expiry_seconds) { + cache_file_meta.expiry_seconds = expiry_seconds; + } + writeFileSync(path.join(BUNEXT_CACHE_DIR, cache_meta_name), JSON.stringify(cache_file_meta)); + return { + success: true, + }; + } + catch (error) { + return { + success: false, + msg: error.message, + }; + } +} diff --git a/dist/functions/grab-config.js b/dist/functions/grab-config.js new file mode 100644 index 0000000..5acb65f --- /dev/null +++ b/dist/functions/grab-config.js @@ -0,0 +1,19 @@ +import { existsSync } from "fs"; +import grabDirNames from "../utils/grab-dir-names"; +import exitWithError from "../utils/exit-with-error"; +export default async function grabConfig() { + try { + const { CONFIG_FILE } = grabDirNames(); + if (!existsSync(CONFIG_FILE)) { + exitWithError(`Config file \`${CONFIG_FILE}\` doesn't exist!`); + } + const config = (await import(CONFIG_FILE)).default; + if (!config) { + exitWithError(`Config file \`${CONFIG_FILE}\` is invalid! Please provide a valid default export in your config file.`); + } + return config; + } + catch (error) { + return undefined; + } +} diff --git a/dist/functions/init.js b/dist/functions/init.js new file mode 100644 index 0000000..28cfea2 --- /dev/null +++ b/dist/functions/init.js @@ -0,0 +1,22 @@ +import { existsSync, mkdirSync, statSync, writeFileSync } from "fs"; +import grabDirNames from "../utils/grab-dir-names"; +import { execSync } from "child_process"; +export default async function () { + const dirNames = grabDirNames(); + execSync(`rm -rf ${dirNames.BUNEXT_CACHE_DIR}`); + const keys = Object.keys(dirNames); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const dir = dirNames[key]; + if (!existsSync(dir) && !dir.match(/\.\w+$/)) { + mkdirSync(dir, { recursive: true }); + continue; + } + if (key == "CONFIG_FILE" && !existsSync(dir)) { + let basicConfig = ``; + basicConfig += `const config = {};\n`; + basicConfig += `export default config;\n`; + writeFileSync(dir, basicConfig); + } + } +} diff --git a/dist/functions/server/cron.js b/dist/functions/server/cron.js new file mode 100644 index 0000000..09cc147 --- /dev/null +++ b/dist/functions/server/cron.js @@ -0,0 +1,8 @@ +import { AppData } from "../../data/app-data"; +import trimAllCache from "../cache/trim-all-cache"; +export default async function cron() { + while (true) { + await trimAllCache(); + await Bun.sleep(AppData["DefaultCronInterval"]); + } +} diff --git a/dist/functions/server/handle-routes.js b/dist/functions/server/handle-routes.js new file mode 100644 index 0000000..8a8b92a --- /dev/null +++ b/dist/functions/server/handle-routes.js @@ -0,0 +1,46 @@ +import grabRouteParams from "../../utils/grab-route-params"; +import grabConstants from "../../utils/grab-constants"; +import grabRouter from "../../utils/grab-router"; +export default async function ({ req, server }) { + const url = new URL(req.url); + const { MBInBytes, ServerDefaultRequestBodyLimitBytes } = grabConstants(); + const router = grabRouter(); + const match = router.match(url.pathname); + if (!match?.filePath) { + const errMsg = `Route ${url.pathname} not found`; + return Response.json({ + success: false, + msg: errMsg, + }, { + status: 401, + headers: { + "Content-Type": "application/json", + }, + }); + } + const routeParams = await grabRouteParams({ req }); + const module = await import(match.filePath); + const config = module.config; + const contentLength = req.headers.get("content-length"); + if (contentLength) { + const size = parseInt(contentLength, 10); + if ((config?.maxRequestBodyMB && + size > config.maxRequestBodyMB * MBInBytes) || + size > ServerDefaultRequestBodyLimitBytes) { + return Response.json({ + success: false, + msg: "Request Body Too Large!", + }, { + status: 413, + headers: { + "Content-Type": "application/json", + }, + }); + } + } + const res = await module["default"]({ + ...routeParams, + server, + }); + return res; +} diff --git a/dist/functions/server/rebuild-bundler.js b/dist/functions/server/rebuild-bundler.js new file mode 100644 index 0000000..8057be8 --- /dev/null +++ b/dist/functions/server/rebuild-bundler.js @@ -0,0 +1,16 @@ +import allPagesBundler from "../bundler/all-pages-bundler"; +import serverPostBuildFn from "./server-post-build-fn"; +export default async function rebuildBundler() { + try { + global.ROUTER.reload(); + await global.BUNDLER_CTX?.dispose(); + global.BUNDLER_CTX = undefined; + await allPagesBundler({ + watch: true, + post_build_fn: serverPostBuildFn, + }); + } + catch (error) { + console.error(error); + } +} diff --git a/dist/functions/server/server-params-gen.js b/dist/functions/server/server-params-gen.js new file mode 100644 index 0000000..f51eaf9 --- /dev/null +++ b/dist/functions/server/server-params-gen.js @@ -0,0 +1,91 @@ +import path from "path"; +import grabAppPort from "../../utils/grab-app-port"; +import grabDirNames from "../../utils/grab-dir-names"; +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 { AppData } from "../../data/app-data"; +export default async function (params) { + const port = grabAppPort(); + const { PUBLIC_DIR } = grabDirNames(); + const is_dev = isDevelopment(); + return { + async fetch(req, server) { + try { + const url = new URL(req.url); + const { config } = grabConstants(); + if (config?.middleware) { + const middleware_res = await config.middleware({ + req, + url, + server, + }); + if (typeof middleware_res == "object") { + return middleware_res; + } + } + if (url.pathname === "/__hmr" && is_dev) { + const referer_url = new URL(req.headers.get("referer") || ""); + const match = global.ROUTER.match(referer_url.pathname); + const target_map = match?.filePath + ? global.BUNDLER_CTX_MAP?.find((m) => m.local_path == match.filePath) + : undefined; + let controller; + const stream = new ReadableStream({ + start(c) { + controller = c; + global.HMR_CONTROLLERS.push({ + controller: c, + page_url: referer_url.href, + target_map, + }); + }, + cancel() { + const targetControllerIndex = global.HMR_CONTROLLERS.findIndex((c) => c.controller == controller); + if (typeof targetControllerIndex == "number" && + targetControllerIndex >= 0) { + global.HMR_CONTROLLERS.splice(targetControllerIndex, 1); + } + }, + }); + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); + } + if (url.pathname.startsWith("/api/")) { + return await handleRoutes({ req, server }); + } + if (url.pathname.startsWith("/public/")) { + const file = Bun.file(path.join(PUBLIC_DIR, url.pathname.replace(/^\/public/, ""))); + let res_opts = {}; + if (!is_dev && url.pathname.match(/__bunext/)) { + res_opts.headers = { + "Cache-Control": `public, max-age=${AppData["BunextStaticFilesCacheExpiry"]}, must-revalidate`, + }; + } + return new Response(file, res_opts); + } + if (url.pathname.startsWith("/favicon.")) { + const file = Bun.file(path.join(PUBLIC_DIR, url.pathname)); + return new Response(file); + } + return await handleWebPages({ req }); + } + catch (error) { + return new Response(`Server Error: ${error.message}`, { + status: 500, + }); + } + }, + port, + idleTimeout: 0, + development: { + hmr: true, + }, + }; +} diff --git a/dist/functions/server/server-post-build-fn.js b/dist/functions/server/server-post-build-fn.js new file mode 100644 index 0000000..23496cc --- /dev/null +++ b/dist/functions/server/server-post-build-fn.js @@ -0,0 +1,26 @@ +import _ from "lodash"; +export default async function serverPostBuildFn({ artifacts }) { + if (!global.IS_FIRST_BUNDLE_READY) { + global.IS_FIRST_BUNDLE_READY = true; + } + if (!global.HMR_CONTROLLERS?.[0]) { + return; + } + for (let i = 0; i < global.HMR_CONTROLLERS.length; i++) { + const controller = global.HMR_CONTROLLERS[i]; + const target_artifact = artifacts.find((a) => controller.target_map?.local_path == a.local_path); + const final_artifact = { + ..._.omit(controller, ["controller"]), + target_map: target_artifact, + }; + if (!target_artifact) { + delete final_artifact.target_map; + } + try { + controller.controller.enqueue(`event: update\ndata: ${JSON.stringify(final_artifact)}\n\n`); + } + catch { + global.HMR_CONTROLLERS.splice(i, 1); + } + } +} diff --git a/dist/functions/server/start-server.js b/dist/functions/server/start-server.js new file mode 100644 index 0000000..be91117 --- /dev/null +++ b/dist/functions/server/start-server.js @@ -0,0 +1,46 @@ +import _ from "lodash"; +import AppNames from "../../utils/grab-app-names"; +import allPagesBundler from "../bundler/all-pages-bundler"; +import serverParamsGen from "./server-params-gen"; +import watcher from "./watcher"; +import serverPostBuildFn from "./server-post-build-fn"; +import grabDirNames from "../../utils/grab-dir-names"; +import EJSON from "../../utils/ejson"; +import { readFileSync } from "fs"; +import cron from "./cron"; +const { HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames(); +export default async function startServer(params) { + const { name } = AppNames; + const serverParams = await serverParamsGen(); + if (params?.dev) { + await allPagesBundler({ + watch: true, + post_build_fn: serverPostBuildFn, + }); + watcher(); + } + else { + const artifacts = EJSON.parse(readFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, "utf-8")); + if (!artifacts?.[0]) { + console.error(`Please build first.`); + process.exit(1); + } + global.BUNDLER_CTX_MAP = artifacts; + global.IS_FIRST_BUNDLE_READY = true; + cron(); + } + let bundle_ready_retries = 0; + const MAX_BUNDLE_READY_RETRIES = 10; + while (!global.IS_FIRST_BUNDLE_READY) { + if (bundle_ready_retries > MAX_BUNDLE_READY_RETRIES) { + console.error(`Couldn't grab first bundle for dev environment`); + process.exit(1); + } + bundle_ready_retries++; + await Bun.sleep(500); + } + const server = Bun.serve(serverParams); + global.SERVER = server; + console.log(`${name} Server Running on http://localhost:${server.port} ...`); + return server; +} diff --git a/dist/functions/server/watcher.js b/dist/functions/server/watcher.js new file mode 100644 index 0000000..5fa1478 --- /dev/null +++ b/dist/functions/server/watcher.js @@ -0,0 +1,31 @@ +import { watch, existsSync } from "fs"; +import path from "path"; +import grabDirNames from "../../utils/grab-dir-names"; +import rebuildBundler from "./rebuild-bundler"; +const { SRC_DIR } = grabDirNames(); +export default function watcher() { + watch(SRC_DIR, { + recursive: true, + persistent: true, + }, async (event, filename) => { + if (!filename) + return; + if (event !== "rename") + return; + if (global.RECOMPILING) + return; + const fullPath = path.join(SRC_DIR, filename); + const action = existsSync(fullPath) ? "created" : "deleted"; + try { + global.RECOMPILING = true; + console.log(`Page ${action}: ${filename}. Rebuilding ...`); + await rebuildBundler(); + } + catch (error) { + console.error(error); + } + finally { + global.RECOMPILING = false; + } + }); +} diff --git a/dist/functions/server/web-pages/generate-web-html.js b/dist/functions/server/web-pages/generate-web-html.js new file mode 100644 index 0000000..3281742 --- /dev/null +++ b/dist/functions/server/web-pages/generate-web-html.js @@ -0,0 +1,42 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import path from "path"; +import grabContants from "../../../utils/grab-constants"; +import EJSON from "../../../utils/ejson"; +import isDevelopment from "../../../utils/is-development"; +import grabWebPageHydrationScript from "./grab-web-page-hydration-script"; +import grabWebMetaHTML from "./grab-web-meta-html"; +export default async function genWebHTML({ component, pageProps, bundledMap, head: Head, module, meta, routeParams, }) { + const { ClientRootElementIDName, ClientWindowPagePropsName } = grabContants(); + const { renderToString } = await import(path.join(process.cwd(), "node_modules", "react-dom", "server")); + const componentHTML = renderToString(component); + const headHTML = Head + ? renderToString(_jsx(Head, { serverRes: pageProps, ctx: routeParams })) + : ""; + let html = `\n`; + html += `\n`; + html += ` \n`; + html += ` \n`; + html += ` \n`; + if (meta) { + html += ` ${grabWebMetaHTML({ meta })}\n`; + } + if (bundledMap?.css_path) { + html += ` \n`; + } + html += ` \n`; + if (bundledMap?.path) { + html += ` \n`; + } + if (isDevelopment()) { + html += `\n`; + } + if (headHTML) { + html += ` ${headHTML}\n`; + } + html += ` \n`; + html += ` \n`; + html += `
${componentHTML}
\n`; + html += ` \n`; + html += `\n`; + return html; +} diff --git a/dist/functions/server/web-pages/grab-page-component.js b/dist/functions/server/web-pages/grab-page-component.js new file mode 100644 index 0000000..7b770ef --- /dev/null +++ b/dist/functions/server/web-pages/grab-page-component.js @@ -0,0 +1,109 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import grabDirNames from "../../../utils/grab-dir-names"; +import grabRouteParams from "../../../utils/grab-route-params"; +import path from "path"; +import AppNames from "../../../utils/grab-app-names"; +import { existsSync } from "fs"; +import grabPageErrorComponent from "./grab-page-error-component"; +class NotFoundError extends Error { +} +export default async function grabPageComponent({ req, file_path: passed_file_path, }) { + const url = req?.url ? new URL(req.url) : undefined; + const router = global.ROUTER; + const { PAGES_DIR } = grabDirNames(); + let routeParams = undefined; + try { + routeParams = req ? await grabRouteParams({ req }) : undefined; + let url_path = url ? url.pathname : undefined; + if (url_path && url?.search) { + url_path += url.search; + } + const match = url_path ? router.match(url_path) : undefined; + if (!match?.filePath && url?.pathname) { + throw new NotFoundError(`Page ${url.pathname} not found`); + } + const file_path = match?.filePath || passed_file_path; + if (!file_path) { + const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`; + // console.error(errMsg); + throw new Error(errMsg); + } + const bundledMap = global.BUNDLER_CTX_MAP?.find((m) => m.local_path == file_path); + if (!bundledMap?.path) { + const errMsg = `No Bundled File Path for this request path!`; + console.error(errMsg); + throw new Error(errMsg); + } + // const pageName = grabPageName({ path: file_path }); + const root_pages_component_ts_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.ts`; + const root_pages_component_tsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.tsx`; + const root_pages_component_js_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.js`; + const root_pages_component_jsx_file = `${path.join(PAGES_DIR, AppNames["RootPagesComponentName"])}.jsx`; + const root_file = existsSync(root_pages_component_tsx_file) + ? root_pages_component_tsx_file + : existsSync(root_pages_component_ts_file) + ? root_pages_component_ts_file + : existsSync(root_pages_component_jsx_file) + ? root_pages_component_jsx_file + : existsSync(root_pages_component_js_file) + ? root_pages_component_js_file + : undefined; + const now = Date.now(); + const root_module = root_file + ? await import(`${root_file}?t=${now}`) + : undefined; + const RootComponent = root_module?.default; + // const component_file_path = root_module + // ? `${file_path}` + // : `${file_path}?t=${global.LAST_BUILD_TIME ?? 0}`; + const module = await import(`${file_path}?t=${now}`); + const serverRes = await (async () => { + try { + if (routeParams) { + const serverData = await module["server"]?.(routeParams); + return { + ...serverData, + query: match?.query, + }; + } + return { + query: match?.query, + }; + } + catch (error) { + return { + query: match?.query, + }; + } + })(); + const meta = module.meta + ? typeof module.meta == "function" && routeParams + ? await module.meta({ + ctx: routeParams, + serverRes, + }) + : typeof module.meta == "object" + ? module.meta + : undefined + : undefined; + const Component = module.default; + const Head = module.Head; + const component = RootComponent ? (_jsx(RootComponent, { ...serverRes, children: _jsx(Component, { ...serverRes }) })) : (_jsx(Component, { ...serverRes })); + return { + component, + serverRes, + routeParams, + module, + bundledMap, + meta, + head: Head, + }; + } + catch (error) { + return await grabPageErrorComponent({ + error, + routeParams, + is404: error instanceof NotFoundError, + }); + } +} diff --git a/dist/functions/server/web-pages/grab-page-error-component.js b/dist/functions/server/web-pages/grab-page-error-component.js new file mode 100644 index 0000000..60311aa --- /dev/null +++ b/dist/functions/server/web-pages/grab-page-error-component.js @@ -0,0 +1,42 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +import grabDirNames from "../../../utils/grab-dir-names"; +export default async function grabPageErrorComponent({ error, routeParams, is404, }) { + const router = global.ROUTER; + 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; + try { + const match = router.match(errorRoute); + const filePath = match?.filePath || presetComponent; + const bundledMap = match?.filePath + ? (global.BUNDLER_CTX_MAP?.find((m) => m.local_path === match.filePath) ?? {}) + : {}; + const module = await import(filePath); + const Component = module.default; + const component = _jsx(Component, { children: _jsx("span", { children: error.message }) }); + return { + component, + routeParams, + module, + bundledMap, + }; + } + catch { + const DefaultNotFound = () => (_jsxs("div", { style: { + width: "100vw", + height: "100vh", + display: "flex", + alignItems: "center", + justifyContent: "center", + flexDirection: "column", + }, children: [_jsx("h1", { children: is404 ? "404 Not Found" : "500 Internal Server Error" }), _jsx("span", { children: error.message })] })); + return { + component: _jsx(DefaultNotFound, {}), + routeParams, + module: { default: DefaultNotFound }, + bundledMap: {}, + }; + } +} diff --git a/dist/functions/server/web-pages/grab-web-meta-html.js b/dist/functions/server/web-pages/grab-web-meta-html.js new file mode 100644 index 0000000..0ee566f --- /dev/null +++ b/dist/functions/server/web-pages/grab-web-meta-html.js @@ -0,0 +1,60 @@ +export default function grabWebMetaHTML({ meta }) { + let html = ``; + if (meta.title) { + html += ` ${meta.title}\n`; + } + if (meta.description) { + html += ` \n`; + } + if (meta.keywords) { + const keywords = Array.isArray(meta.keywords) + ? meta.keywords.join(", ") + : meta.keywords; + html += ` \n`; + } + if (meta.author) { + html += ` \n`; + } + if (meta.robots) { + html += ` \n`; + } + if (meta.canonical) { + html += ` \n`; + } + if (meta.themeColor) { + html += ` \n`; + } + if (meta.og) { + const { og } = meta; + if (og.title) + html += ` \n`; + if (og.description) + html += ` \n`; + if (og.image) + html += ` \n`; + if (og.url) + html += ` \n`; + if (og.type) + html += ` \n`; + if (og.siteName) + html += ` \n`; + if (og.locale) + html += ` \n`; + } + if (meta.twitter) { + const { twitter } = meta; + if (twitter.card) + html += ` \n`; + if (twitter.title) + html += ` \n`; + if (twitter.description) + html += ` \n`; + if (twitter.image) + html += ` \n`; + if (twitter.site) + html += ` \n`; + if (twitter.creator) + html += ` \n`; + } + return html; +} diff --git a/dist/functions/server/web-pages/grab-web-page-hydration-script.js b/dist/functions/server/web-pages/grab-web-page-hydration-script.js new file mode 100644 index 0000000..4f5a8d4 --- /dev/null +++ b/dist/functions/server/web-pages/grab-web-page-hydration-script.js @@ -0,0 +1,55 @@ +import grabDirNames from "../../../utils/grab-dir-names"; +const { BUNX_HYDRATION_SRC_DIR } = grabDirNames(); +export default async function ({ bundledMap }) { + let script = ""; + // script += `import React from "react";\n`; + // script += `import { hydrateRoot } from "react-dom/client";\n`; + // script += `import App from "${page_file}";\n`; + // script += `declare global {\n`; + // script += ` interface Window {\n`; + // script += ` ${ClientWindowPagePropsName}: any;\n`; + // script += ` }\n`; + // script += `}\n`; + // script += `let root: any = null;\n\n`; + // script += `const component = ;\n\n`; + // script += `const container = document.getElementById("${ClientRootElementIDName}");\n\n`; + // script += `if (container) {\n`; + // script += ` root = hydrateRoot(container, component);\n`; + // script += `}\n\n`; + script += `console.log(\`Development Environment\`);\n`; + // script += `console.log(import.meta);\n`; + // script += `if (import.meta.hot) {\n`; + // script += ` console.log(\`HMR active\`);\n`; + // script += ` import.meta.hot.dispose(() => {\n`; + // script += ` console.log("dispose");\n`; + // script += ` });\n`; + // script += `}\n`; + script += `const hmr = new EventSource("/__hmr");\n`; + script += `hmr.addEventListener("update", async (event) => {\n`; + // script += ` console.log(\`HMR even received:\`, event);\n`; + script += ` if (event.data) {\n`; + script += ` console.log(\`HMR Changes Detected. Reloading ...\`);\n`; + // script += ` console.log("event", event);\n`; + // script += ` console.log("window.${ClientRootComponentWindowName}", window.${ClientRootComponentWindowName});\n\n`; + // script += ` const event_data = JSON.parse(event.data);\n\n`; + // script += ` const new_js_path = \`/\${event_data.target_map.path}\`;\n\n`; + // script += ` console.log("event_data", event_data);\n\n`; + // script += ` console.log("new_js_path", new_js_path);\n\n`; + // script += ` if (window.${ClientRootComponentWindowName}) {\n`; + // script += ` const new_component = await import(new_js_path);\n`; + // script += ` window.${ClientRootComponentWindowName}.render(new_component);\n`; + // script += ` }\n`; + // script += ` import("${page_file}?t=" + event.data.update).then((module) => {\n`; + // script += ` root.render(module.default);\n`; + // script += ` })\n`; + // script += ` console.log("root", root);\n`; + // script += ` root.unmount();\n`; + // script += ` const container = document.getElementById("${ClientRootElementIDName}");\n\n`; + // script += ` root = hydrateRoot(container!, component);\n`; + // script += ` window.history.pushState({ page: 1 }, "New Page Title", \`\${window.location.pathname}?v=\${Date.now()}\`);\n`; + // script += ` root.render(component);\n`; + script += ` window.location.reload();\n`; + script += ` }\n`; + script += ` });\n`; + return script; +} diff --git a/dist/functions/server/web-pages/handle-web-pages.js b/dist/functions/server/web-pages/handle-web-pages.js new file mode 100644 index 0000000..ef3e814 --- /dev/null +++ b/dist/functions/server/web-pages/handle-web-pages.js @@ -0,0 +1,77 @@ +import isDevelopment from "../../../utils/is-development"; +import getCache from "../../cache/get-cache"; +import writeCache from "../../cache/write-cache"; +import genWebHTML from "./generate-web-html"; +import grabPageComponent from "./grab-page-component"; +import grabPageErrorComponent from "./grab-page-error-component"; +export default async function handleWebPages({ req, }) { + try { + if (!isDevelopment()) { + const url = new URL(req.url); + const key = url.pathname + (url.search || ""); + const existing_cache = getCache({ key, paradigm: "html" }); + if (existing_cache) { + const res_opts = { + headers: { + "Content-Type": "text/html", + "X-Bunext-Cache": "HIT", + }, + }; + return new Response(existing_cache, res_opts); + } + } + const componentRes = await grabPageComponent({ req }); + return await generateRes(componentRes); + } + catch (error) { + const componentRes = await grabPageErrorComponent({ error }); + return await generateRes(componentRes); + } +} +async function generateRes({ component, module, bundledMap, head, meta, routeParams, serverRes, }) { + const html = await genWebHTML({ + component, + pageProps: serverRes, + bundledMap, + module, + meta, + head, + routeParams, + }); + if (serverRes?.redirect?.destination) { + return Response.redirect(serverRes.redirect.destination, serverRes.redirect.permanent + ? 301 + : serverRes.redirect.status_code || 302); + } + const res_opts = { + ...serverRes?.responseOptions, + headers: { + "Content-Type": "text/html", + ...serverRes?.responseOptions?.headers, + }, + }; + if (isDevelopment()) { + res_opts.headers = { + ...res_opts.headers, + "Cache-Control": "no-cache, no-store, must-revalidate", + Pragma: "no-cache", + Expires: "0", + }; + } + const cache_page = module.config?.cachePage || serverRes?.cachePage || false; + const expiry_seconds = module.config?.cacheExpiry || serverRes?.cacheExpiry; + if (cache_page && routeParams?.url) { + const key = routeParams.url.pathname + (routeParams.url.search || ""); + writeCache({ + key, + value: html, + paradigm: "html", + expiry_seconds, + }); + } + const res = new Response(html, res_opts); + if (routeParams?.resTransform) { + return await routeParams.resTransform(res); + } + return res; +} diff --git a/dist/index.js b/dist/index.js new file mode 100755 index 0000000..61edf9f --- /dev/null +++ b/dist/index.js @@ -0,0 +1,44 @@ +#!/usr/bin/env bun +import { program } from "commander"; +import start from "./commands/start"; +import dev from "./commands/dev"; +import ora, {} from "ora"; +import init from "./functions/init"; +import grabDirNames from "./utils/grab-dir-names"; +import build from "./commands/build"; +global.ORA_SPINNER = ora(); +global.ORA_SPINNER.clear(); +global.HMR_CONTROLLERS = []; +global.IS_FIRST_BUNDLE_READY = false; +global.BUNDLER_REBUILDS = 0; +await init(); +const { PAGES_DIR } = grabDirNames(); +const router = new Bun.FileSystemRouter({ + style: "nextjs", + dir: PAGES_DIR, +}); +global.ROUTER = router; +/** + * # Describe Program + */ +program + .name(`bunext`) + .description(`A React Next JS replacement built with bun JS`) + .version(`1.0.0`); +/** + * # Declare Commands + */ +program.addCommand(dev()); +program.addCommand(start()); +program.addCommand(build()); +/** + * # Handle Unavailable Commands + */ +program.on("command:*", () => { + console.error("Invalid command: %s\nSee --help for a list of available commands.", program.args.join(" ")); + process.exit(1); +}); +/** + * # Parse Arguments + */ +program.parse(Bun.argv); diff --git a/dist/presets/bunext.config.js b/dist/presets/bunext.config.js new file mode 100644 index 0000000..e9e8d50 --- /dev/null +++ b/dist/presets/bunext.config.js @@ -0,0 +1,2 @@ +const config = {}; +export default config; diff --git a/dist/presets/not-found.js b/dist/presets/not-found.js new file mode 100644 index 0000000..6be8f8c --- /dev/null +++ b/dist/presets/not-found.js @@ -0,0 +1,13 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +export default function DefaultNotFoundPage({ children }) { + return (_jsxs("div", { style: { + width: "100vw", + height: "100vh", + overflow: "hidden", + display: "flex", + alignItems: "center", + justifyContent: "center", + flexDirection: "column", + gap: "20px", + }, children: [_jsx("h1", { children: "404 Not Found" }), _jsx("span", { children: children })] })); +} diff --git a/dist/presets/server-error.js b/dist/presets/server-error.js new file mode 100644 index 0000000..ece79b0 --- /dev/null +++ b/dist/presets/server-error.js @@ -0,0 +1,13 @@ +import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +export default function DefaultServerErrorPage({ children, }) { + return (_jsxs("div", { style: { + width: "100vw", + height: "100vh", + overflow: "hidden", + display: "flex", + alignItems: "center", + justifyContent: "center", + flexDirection: "column", + gap: "20px", + }, children: [_jsx("h1", { children: "500 Internal Server Error" }), _jsx("span", { children: children })] })); +} diff --git a/dist/src/functions/bundler/all-pages-bundler.js b/dist/src/functions/bundler/all-pages-bundler.js new file mode 100644 index 0000000..92d9d41 --- /dev/null +++ b/dist/src/functions/bundler/all-pages-bundler.js @@ -0,0 +1,52 @@ +import { readdirSync, statSync, unlinkSync } from "fs"; +import grabAllPages from "../../utils/grab-all-pages"; +import grabDirNames from "../../utils/grab-dir-names"; +import grabPageName from "../../utils/grab-page-name"; +import writeWebPageHydrationScript from "../server/web-pages/write-web-page-hydration-script"; +import path from "path"; +import bundle from "../../utils/bundle"; +const { BUNX_HYDRATION_SRC_DIR, HYDRATION_DST_DIR } = grabDirNames(); +export default async function allPagesBundler() { + console.time("build"); + const pages = grabAllPages({ exclude_api: true }); + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + const pageName = grabPageName({ path: page.local_path }); + writeWebPageHydrationScript({ + pageName, + page_file: page.local_path, + }); + } + const hydration_files = readdirSync(BUNX_HYDRATION_SRC_DIR); + for (let i = 0; i < hydration_files.length; i++) { + const hydration_file = hydration_files[i]; + const valid_file = pages.find((p) => { + const pageName = grabPageName({ path: p.local_path }); + const file_tsx_name = `${pageName}.tsx`; + if (file_tsx_name == hydration_file) { + return true; + } + return false; + }); + if (!valid_file) { + unlinkSync(path.join(BUNX_HYDRATION_SRC_DIR, hydration_file)); + } + } + const entrypoints = readdirSync(BUNX_HYDRATION_SRC_DIR) + .filter((f) => f.endsWith(".tsx")) + .map((f) => path.join(BUNX_HYDRATION_SRC_DIR, f)) + .filter((f) => statSync(f).isFile()); + // await Bun.build({ + // entrypoints, + // outdir: HYDRATION_DST_DIR, + // minify: true, + // target: "browser", + // format: "esm", + // }); + bundle({ + src: entrypoints.join(" "), + out_dir: HYDRATION_DST_DIR, + exec_options: { stdio: "ignore" }, + }); + console.timeEnd("build"); +} diff --git a/dist/src/functions/grab-config.js b/dist/src/functions/grab-config.js new file mode 100644 index 0000000..5acb65f --- /dev/null +++ b/dist/src/functions/grab-config.js @@ -0,0 +1,19 @@ +import { existsSync } from "fs"; +import grabDirNames from "../utils/grab-dir-names"; +import exitWithError from "../utils/exit-with-error"; +export default async function grabConfig() { + try { + const { CONFIG_FILE } = grabDirNames(); + if (!existsSync(CONFIG_FILE)) { + exitWithError(`Config file \`${CONFIG_FILE}\` doesn't exist!`); + } + const config = (await import(CONFIG_FILE)).default; + if (!config) { + exitWithError(`Config file \`${CONFIG_FILE}\` is invalid! Please provide a valid default export in your config file.`); + } + return config; + } + catch (error) { + return undefined; + } +} diff --git a/dist/src/functions/init.js b/dist/src/functions/init.js new file mode 100644 index 0000000..d4ffbf4 --- /dev/null +++ b/dist/src/functions/init.js @@ -0,0 +1,20 @@ +import { existsSync, mkdirSync, statSync, writeFileSync } from "fs"; +import grabDirNames from "../utils/grab-dir-names"; +export default async function () { + const dirNames = grabDirNames(); + const keys = Object.keys(dirNames); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const dir = dirNames[key]; + if (!existsSync(dir) && !dir.match(/\.\w+$/)) { + mkdirSync(dir, { recursive: true }); + continue; + } + if (key == "CONFIG_FILE" && !existsSync(dir)) { + let basicConfig = ``; + basicConfig += `const config = {};\n`; + basicConfig += `export default config;\n`; + writeFileSync(dir, basicConfig); + } + } +} diff --git a/dist/src/functions/router/get-route.js b/dist/src/functions/router/get-route.js new file mode 100644 index 0000000..3c3bd66 --- /dev/null +++ b/dist/src/functions/router/get-route.js @@ -0,0 +1,26 @@ +import grabDirNames from "../../utils/grab-dir-names"; +import grabAssetsPrefix from "../../utils/grab-assets-prefix"; +import grabOrigin from "../../utils/grab-origin"; +import grabRouter from "../../utils/grab-router"; +export default async function getRoute({ route, }) { + const {} = grabDirNames(); + if (route.match(/\(/)) { + return null; + } + const router = grabRouter(); + const match = router.match(route); + if (!match?.filePath) { + console.error(`Route ${route} not found`); + return null; + } + const module = await import(match.filePath); + return { + match, + module, + component: module.default, + serverProps: module.serverProps, + staticProps: module.staticProps, + staticPaths: module.staticPaths, + staticParams: module.staticParams, + }; +} diff --git a/dist/src/functions/server/handle-routes.js b/dist/src/functions/server/handle-routes.js new file mode 100644 index 0000000..f61f43f --- /dev/null +++ b/dist/src/functions/server/handle-routes.js @@ -0,0 +1,36 @@ +import grabRouteParams from "../../utils/grab-route-params"; +import grabConstants from "../../utils/grab-constants"; +import grabRouter from "../../utils/grab-router"; +export default async function ({ req, server, }) { + const url = new URL(req.url); + const { MBInBytes, ServerDefaultRequestBodyLimitBytes } = await grabConstants(); + const router = grabRouter(); + const match = router.match(url.pathname); + if (!match?.filePath) { + const errMsg = `Route ${url.pathname} not found`; + console.error(errMsg); + return { + success: false, + status: 401, + msg: errMsg, + }; + } + const routeParams = await grabRouteParams({ req }); + const module = await import(match.filePath); + const config = module.config; + const contentLength = req.headers.get("content-length"); + if (contentLength) { + const size = parseInt(contentLength, 10); + if ((config?.maxRequestBodyMB && + size > config.maxRequestBodyMB * MBInBytes) || + size > ServerDefaultRequestBodyLimitBytes) { + return { + success: false, + status: 413, + msg: "Request Body Too Large!", + }; + } + } + const res = await module["default"](routeParams); + return res; +} diff --git a/dist/src/functions/server/server-params-gen.js b/dist/src/functions/server/server-params-gen.js new file mode 100644 index 0000000..0da56e5 --- /dev/null +++ b/dist/src/functions/server/server-params-gen.js @@ -0,0 +1,62 @@ +import path from "path"; +import grabAppPort from "../../utils/grab-app-port"; +import grabDirNames from "../../utils/grab-dir-names"; +import handleWebPages from "./web-pages/handle-web-pages"; +import handleRoutes from "./handle-routes"; +import isDevelopment from "../../utils/is-development"; +export default async function (params) { + const port = grabAppPort(); + const { PUBLIC_DIR } = grabDirNames(); + return { + async fetch(req, server) { + try { + const url = new URL(req.url); + if (url.pathname === "/__hmr" && isDevelopment()) { + let controller; + const stream = new ReadableStream({ + start(c) { + controller = c; + global.HMR_CONTROLLERS.add(c); + }, + cancel() { + global.HMR_CONTROLLERS.delete(controller); + }, + }); + return new Response(stream, { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); + } + else if (url.pathname.startsWith("/api/")) { + const res = await handleRoutes({ req, server }); + return new Response(JSON.stringify(res), { + status: res?.status, + headers: { + "Content-Type": "application/json", + }, + }); + } + else if (url.pathname.startsWith("/public/")) { + const file = Bun.file(path.join(PUBLIC_DIR, url.pathname.replace(/^\/public/, ""))); + return new Response(file); + } + else if (url.pathname.startsWith("/favicon.")) { + const file = Bun.file(path.join(PUBLIC_DIR, url.pathname)); + return new Response(file); + } + else { + return await handleWebPages({ req }); + } + } + catch (error) { + return new Response(`Server Error: ${error.message}`, { + status: 500, + }); + } + }, + port, + }; +} diff --git a/dist/src/functions/server/start-server.js b/dist/src/functions/server/start-server.js new file mode 100644 index 0000000..1b125fc --- /dev/null +++ b/dist/src/functions/server/start-server.js @@ -0,0 +1,14 @@ +import AppNames from "../../utils/grab-app-names"; +import serverParamsGen from "./server-params-gen"; +import watcher from "./watcher"; +export default async function startServer(params) { + const { name } = AppNames; + const serverParams = await serverParamsGen(); + const server = Bun.serve(serverParams); + global.SERVER = server; + console.log(`${name} Server Running on http://localhost:${server.port} ...`); + if (params?.dev) { + watcher(); + } + return server; +} diff --git a/dist/src/functions/server/watcher.js b/dist/src/functions/server/watcher.js new file mode 100644 index 0000000..f84b447 --- /dev/null +++ b/dist/src/functions/server/watcher.js @@ -0,0 +1,78 @@ +import { watch } from "fs"; +import grabDirNames from "../../utils/grab-dir-names"; +import grabPageName from "../../utils/grab-page-name"; +import path from "path"; +import serverParamsGen from "./server-params-gen"; +import bundle from "../../utils/bundle"; +import grabRouter from "../../utils/grab-router"; +import allPagesBundler from "../bundler/all-pages-bundler"; +const { ROOT_DIR, BUNX_HYDRATION_SRC_DIR, HYDRATION_DST_DIR, PAGES_DIR } = grabDirNames(); +export default function watcher() { + watch(ROOT_DIR, { recursive: true, persistent: true }, async (event, filename) => { + if (global.RECOMPILING) + return; + if (!filename) + return; + if (filename.match(/ /)) + return; + if (filename.match(/^node_modules\//)) + return; + if (filename.match(/\.bunext|\/?public\//)) + return; + if (!filename.match(/\.(tsx|ts|css|js|jsx)$/)) + return; + if (filename.match(/\/pages\//)) { + try { + clearTimeout(global.WATCHER_TIMEOUT); + global.RECOMPILING = true; + await allPagesBundler(); + global.LAST_BUILD_TIME = Date.now(); + for (const controller of global.HMR_CONTROLLERS) { + controller.enqueue(`event: update\ndata: reload\n\n`); + } + global.RECOMPILING = false; + } + catch (error) { + console.error(`Bundler ERROR => ${error.message.substring(0, 120)} ...`); + } + // if (event == "change") { + // } else if (event == "rename") { + // await reloadServer(); + // } + } + else if (filename.match(/\.(js|ts|tsx|jsx)$/)) { + clearTimeout(global.WATCHER_TIMEOUT); + await reloadServer(); + } + }); + // watch(BUNX_HYDRATION_SRC_DIR, async (event, filename) => { + // if (!filename) return; + // const targetFile = path.join(BUNX_HYDRATION_SRC_DIR, filename); + // await Bun.build({ + // entrypoints: [targetFile], + // outdir: HYDRATION_DST_DIR, + // minify: true, + // target: "browser", + // format: "esm", + // }); + // global.SERVER?.publish("__bun_hmr", "update"); + // setTimeout(() => { + // global.RECOMPILING = false; + // }, 200); + // }); + // watch(HYDRATION_DST_DIR, async (event, filename) => { + // const encoder = new TextEncoder(); + // global.HMR_CONTROLLER?.enqueue(encoder.encode(`event: update\ndata: reload\n\n`)); + // global.RECOMPILING = false; + // }); + // let cmd = `bun build`; + // cmd += ` ${BUNX_HYDRATION_SRC_DIR}/*.tsx --outdir ${HYDRATION_DST_DIR}`; + // cmd += ` --watch --minify`; + // execSync(cmd, { stdio: "inherit" }); +} +async function reloadServer() { + const serverParams = await serverParamsGen(); + console.log(`Reloading Server ...`); + global.SERVER?.stop(); + global.SERVER = Bun.serve(serverParams); +} diff --git a/dist/src/functions/server/web-pages/generate-web-html.js b/dist/src/functions/server/web-pages/generate-web-html.js new file mode 100644 index 0000000..cdca8d0 --- /dev/null +++ b/dist/src/functions/server/web-pages/generate-web-html.js @@ -0,0 +1,32 @@ +import path from "path"; +import { renderToString } from "react-dom/server"; +import grabContants from "../../../utils/grab-constants"; +import EJSON from "../../../utils/ejson"; +import isDevelopment from "../../../utils/is-development"; +export default async function genWebHTML({ component, pageProps, pageName, module, }) { + const { ClientRootElementIDName, ClientWindowPagePropsName } = await grabContants(); + const componentHTML = renderToString(component); + const SCRIPT_SRC = path.join("/public/pages", pageName + ".js"); + let html = `\n`; + html += `\n`; + html += ` \n`; + html += ` \n`; + if (isDevelopment()) { + html += `\n`; + } + html += ` \n`; + html += ` \n`; + html += `
${componentHTML}
\n`; + html += ` \n`; + html += ` \n`; + html += ` \n`; + html += `\n`; + return html; +} diff --git a/dist/src/functions/server/web-pages/grab-page-component.js b/dist/src/functions/server/web-pages/grab-page-component.js new file mode 100644 index 0000000..5a61441 --- /dev/null +++ b/dist/src/functions/server/web-pages/grab-page-component.js @@ -0,0 +1,62 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +import grabDirNames from "../../../utils/grab-dir-names"; +import grabPageName from "../../../utils/grab-page-name"; +import grabRouteParams from "../../../utils/grab-route-params"; +import grabRouter from "../../../utils/grab-router"; +import bundle from "../../../utils/bundle"; +export default async function grabPageComponent({ req, file_path: passed_file_path, }) { + const url = req?.url ? new URL(req.url) : undefined; + const router = grabRouter(); + const { BUNX_ROOT_500_PRESET_COMPONENT, HYDRATION_DST_DIR, BUNX_ROOT_500_FILE_NAME, } = grabDirNames(); + const routeParams = req ? await grabRouteParams({ req }) : undefined; + try { + const match = url ? router.match(url.pathname) : undefined; + if (!match?.filePath && url?.pathname) { + const errMsg = `Page ${url.pathname} not found`; + console.error(errMsg); + throw new Error(errMsg); + } + const file_path = match?.filePath || passed_file_path; + if (!file_path) { + const errMsg = `No File Path (\`file_path\`) or Request Object (\`req\`) provided not found`; + console.error(errMsg); + throw new Error(errMsg); + } + const pageName = grabPageName({ path: file_path }); + const module = await import(`${file_path}?t=${global.LAST_BUILD_TIME ?? 0}`); + const serverRes = await (async () => { + try { + if (routeParams) { + return await module["server"]?.(routeParams); + } + return {}; + } + catch (error) { + return {}; + } + })(); + const Component = module.default; + const component = _jsx(Component, { ...serverRes }); + return { component, serverRes, routeParams, pageName, module }; + } + catch (error) { + const match = router.match("/500"); + const filePath = match?.filePath || BUNX_ROOT_500_PRESET_COMPONENT; + // if (!match?.filePath) { + // bundle({ + // out_dir: HYDRATION_DST_DIR, + // src: `${BUNX_ROOT_500_PRESET_COMPONENT}`, + // debug: true, + // }); + // } + const module = await import(`${filePath}?t=${global.LAST_BUILD_TIME ?? 0}`); + const Component = module.default; + const component = _jsx(Component, {}); + return { + component, + pageName: BUNX_ROOT_500_FILE_NAME, + routeParams, + module, + }; + } +} diff --git a/dist/src/functions/server/web-pages/handle-web-pages.js b/dist/src/functions/server/web-pages/handle-web-pages.js new file mode 100644 index 0000000..313be4d --- /dev/null +++ b/dist/src/functions/server/web-pages/handle-web-pages.js @@ -0,0 +1,30 @@ +import genWebHTML from "./generate-web-html"; +import grabPageComponent from "./grab-page-component"; +import writeWebPageHydrationScript from "./write-web-page-hydration-script"; +export default async function ({ req }) { + try { + const { component, pageName, module, serverRes } = await grabPageComponent({ req }); + const html = await genWebHTML({ + component, + pageProps: serverRes, + pageName, + module, + }); + // writeWebPageHydrationScript({ + // component, + // pageName, + // module, + // pageProps: serverRes, + // }); + return new Response(html, { + headers: { + "Content-Type": "text/html", + }, + }); + } + catch (error) { + return new Response(error.message || `Page Not Found`, { + status: 404, + }); + } +} diff --git a/dist/src/functions/server/web-pages/write-web-page-hydration-script.js b/dist/src/functions/server/web-pages/write-web-page-hydration-script.js new file mode 100644 index 0000000..40727b9 --- /dev/null +++ b/dist/src/functions/server/web-pages/write-web-page-hydration-script.js @@ -0,0 +1,23 @@ +import { writeFileSync } from "fs"; +import path from "path"; +import grabDirNames from "../../../utils/grab-dir-names"; +import grabContants from "../../../utils/grab-constants"; +const { BUNX_HYDRATION_SRC_DIR } = grabDirNames(); +export default async function (params) { + const { pageName, page_file } = params; + const { ClientRootElementIDName, ClientWindowPagePropsName } = await grabContants(); + const pageSrcTsFileName = `${pageName}.tsx`; + let script = ""; + script += `import React from "react";\n`; + script += `import { hydrateRoot } from "react-dom/client";\n`; + script += `import App from "${page_file}";\n`; + script += `declare global {\n`; + script += ` interface Window {\n`; + script += ` ${ClientWindowPagePropsName}: any;\n`; + script += ` }\n`; + script += `}\n`; + script += `const container = document.getElementById("${ClientRootElementIDName}");\n`; + script += `hydrateRoot(container, );\n`; + const SRC_WRITE_FILE = path.join(BUNX_HYDRATION_SRC_DIR, pageSrcTsFileName); + writeFileSync(SRC_WRITE_FILE, script, "utf-8"); +} diff --git a/dist/src/presets/server-error.js b/dist/src/presets/server-error.js new file mode 100644 index 0000000..66dab43 --- /dev/null +++ b/dist/src/presets/server-error.js @@ -0,0 +1,11 @@ +import { jsx as _jsx } from "react/jsx-runtime"; +export default function DefaultServerErrorPage() { + return (_jsx("div", { style: { + width: "100vw", + height: "100vh", + overflow: "hidden", + display: "flex", + alignItems: "center", + justifyContent: "center", + }, children: _jsx("span", { children: "500 Internal Server Error" }) })); +} diff --git a/dist/src/types/index.js b/dist/src/types/index.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/src/types/index.js @@ -0,0 +1 @@ +export {}; diff --git a/dist/src/utils/bundle.js b/dist/src/utils/bundle.js new file mode 100644 index 0000000..96274f4 --- /dev/null +++ b/dist/src/utils/bundle.js @@ -0,0 +1,15 @@ +import { execSync } from "child_process"; +export default function bundle({ out_dir, src, minify = true, exec_options, debug, }) { + let cmd = `bun build`; + cmd += ` ${src} --outdir ${out_dir}`; + if (minify) { + cmd += ` --minify`; + } + if (debug) { + console.log("cmd =>", cmd); + } + execSync(cmd, { + stdio: "inherit", + ...exec_options, + }); +} diff --git a/dist/src/utils/deserialize-query.js b/dist/src/utils/deserialize-query.js new file mode 100644 index 0000000..4cc68f5 --- /dev/null +++ b/dist/src/utils/deserialize-query.js @@ -0,0 +1,18 @@ +import EJSON from "./ejson"; +/** + * # Convert Serialized Query back to object + */ +export default function deserializeQuery(query) { + let queryObject = typeof query == "object" ? query : Object(EJSON.parse(query)); + const keys = Object.keys(queryObject); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = queryObject[key]; + if (typeof value == "string") { + if (value.match(/^\{|^\[/)) { + queryObject[key] = EJSON.parse(value); + } + } + } + return queryObject; +} diff --git a/dist/src/utils/ejson.js b/dist/src/utils/ejson.js new file mode 100644 index 0000000..799eb22 --- /dev/null +++ b/dist/src/utils/ejson.js @@ -0,0 +1,33 @@ +/** + * # EJSON parse string + */ +function parse(string, reviver) { + if (!string) + return undefined; + if (typeof string == "object") + return string; + if (typeof string !== "string") + return undefined; + try { + return JSON.parse(string, reviver); + } + catch (error) { + return undefined; + } +} +/** + * # EJSON stringify object + */ +function stringify(value, replacer, space) { + try { + return JSON.stringify(value, replacer || undefined, space); + } + catch (error) { + return undefined; + } +} +const EJSON = { + parse, + stringify, +}; +export default EJSON; diff --git a/dist/src/utils/exit-with-error.js b/dist/src/utils/exit-with-error.js new file mode 100644 index 0000000..221bf53 --- /dev/null +++ b/dist/src/utils/exit-with-error.js @@ -0,0 +1,4 @@ +export default function exitWithError(msg, code) { + console.error(msg); + process.exit(code || 1); +} diff --git a/dist/src/utils/grab-all-pages.js b/dist/src/utils/grab-all-pages.js new file mode 100644 index 0000000..11e1b18 --- /dev/null +++ b/dist/src/utils/grab-all-pages.js @@ -0,0 +1,57 @@ +import { existsSync, readdirSync, statSync } from "fs"; +import grabDirNames from "./grab-dir-names"; +import path from "path"; +export default function grabAllPages(params) { + const { PAGES_DIR } = grabDirNames(); + const pages = grabPageDirRecursively({ page_dir: PAGES_DIR }); + if (params?.exclude_api) { + return pages.filter((p) => !Boolean(p.url_path.startsWith("/api/"))); + } + return pages; +} +function grabPageDirRecursively({ page_dir }) { + const pages = readdirSync(page_dir); + const pages_files = []; + const root_pages_file = grabPageFileObject({ file_path: `` }); + if (root_pages_file) { + pages_files.push(root_pages_file); + } + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + const full_page_path = path.join(page_dir, page); + if (!existsSync(full_page_path)) { + continue; + } + const page_stat = statSync(full_page_path); + if (page_stat.isDirectory()) { + if (page.match(/\(|\)/)) + continue; + const new_page_files = grabPageDirRecursively({ + page_dir: full_page_path, + }); + pages_files.push(...new_page_files); + } + else if (page.match(/\.(ts|js)x?$/)) { + const pages_file = grabPageFileObject({ + file_path: full_page_path, + }); + if (pages_file) { + pages_files.push(pages_file); + } + } + } + return pages_files; +} +function grabPageFileObject({ file_path, }) { + let url_path = file_path + .replace(/.*\/pages\//, "/") + ?.replace(/\.(ts|js)x?$/, ""); + let file_name = url_path.split("/").pop(); + if (!file_name) + return; + return { + local_path: file_path, + url_path, + file_name, + }; +} diff --git a/dist/src/utils/grab-app-names.js b/dist/src/utils/grab-app-names.js new file mode 100644 index 0000000..8b8eae4 --- /dev/null +++ b/dist/src/utils/grab-app-names.js @@ -0,0 +1,7 @@ +const AppNames = { + defaultPort: 7000, + defaultAssetPrefix: "_bunext/static", + name: "Bunext", + defaultDistDir: ".bunext", +}; +export default AppNames; diff --git a/dist/src/utils/grab-app-port.js b/dist/src/utils/grab-app-port.js new file mode 100644 index 0000000..9f25de5 --- /dev/null +++ b/dist/src/utils/grab-app-port.js @@ -0,0 +1,17 @@ +import AppNames from "./grab-app-names"; +import numberfy from "./numberfy"; +export default function grabAppPort() { + const { defaultPort } = AppNames; + try { + if (process.env.PORT) { + return numberfy(process.env.PORT); + } + if (global.CONFIG.port) { + return global.CONFIG.port; + } + return numberfy(defaultPort); + } + catch (error) { + return numberfy(defaultPort); + } +} diff --git a/dist/src/utils/grab-assets-prefix.js b/dist/src/utils/grab-assets-prefix.js new file mode 100644 index 0000000..1802e51 --- /dev/null +++ b/dist/src/utils/grab-assets-prefix.js @@ -0,0 +1,8 @@ +import AppNames from "./grab-app-names"; +export default function grabAssetsPrefix() { + if (global.CONFIG.assetsPrefix) { + return global.CONFIG.assetsPrefix; + } + const { defaultAssetPrefix } = AppNames; + return defaultAssetPrefix; +} diff --git a/dist/src/utils/grab-constants.js b/dist/src/utils/grab-constants.js new file mode 100644 index 0000000..bc9ccfb --- /dev/null +++ b/dist/src/utils/grab-constants.js @@ -0,0 +1,15 @@ +import path from "path"; +import grabConfig from "../functions/grab-config"; +export default async function grabConstants() { + const config = await grabConfig(); + const MB_IN_BYTES = 1024 * 1024; + const ClientWindowPagePropsName = "__PAGE_PROPS__"; + const ClientRootElementIDName = "__bunext"; + const ServerDefaultRequestBodyLimitBytes = MB_IN_BYTES * 10; + return { + ClientRootElementIDName, + ClientWindowPagePropsName, + MBInBytes: MB_IN_BYTES, + ServerDefaultRequestBodyLimitBytes, + }; +} diff --git a/dist/src/utils/grab-dir-names.js b/dist/src/utils/grab-dir-names.js new file mode 100644 index 0000000..96f7ee8 --- /dev/null +++ b/dist/src/utils/grab-dir-names.js @@ -0,0 +1,34 @@ +import path from "path"; +export default function grabDirNames() { + const ROOT_DIR = process.cwd(); + const SRC_DIR = path.join(ROOT_DIR, "src"); + const PAGES_DIR = path.join(SRC_DIR, "pages"); + const API_DIR = path.join(PAGES_DIR, "api"); + const PUBLIC_DIR = path.join(ROOT_DIR, "public"); + const HYDRATION_DST_DIR = path.join(PUBLIC_DIR, "pages"); + const CONFIG_FILE = path.join(ROOT_DIR, "bunext.config.ts"); + const BUNX_CWD_DIR = path.resolve(ROOT_DIR, ".bunext"); + const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp"); + const BUNX_HYDRATION_SRC_DIR = path.resolve(BUNX_CWD_DIR, "client", "hydration-src"); + const BUNX_ROOT_DIR = path.resolve(__dirname, "../../"); + const BUNX_ROOT_SRC_DIR = path.join(BUNX_ROOT_DIR, "src"); + const BUNX_ROOT_PRESETS_DIR = path.join(BUNX_ROOT_SRC_DIR, "presets"); + const BUNX_ROOT_500_FILE_NAME = `server-error`; + const BUNX_ROOT_500_PRESET_COMPONENT = path.join(BUNX_ROOT_PRESETS_DIR, `${BUNX_ROOT_500_FILE_NAME}.tsx`); + return { + ROOT_DIR, + SRC_DIR, + PAGES_DIR, + API_DIR, + PUBLIC_DIR, + HYDRATION_DST_DIR, + BUNX_ROOT_DIR, + CONFIG_FILE, + BUNX_TMP_DIR, + BUNX_HYDRATION_SRC_DIR, + BUNX_ROOT_SRC_DIR, + BUNX_ROOT_PRESETS_DIR, + BUNX_ROOT_500_PRESET_COMPONENT, + BUNX_ROOT_500_FILE_NAME, + }; +} diff --git a/dist/src/utils/grab-origin.js b/dist/src/utils/grab-origin.js new file mode 100644 index 0000000..badc4d7 --- /dev/null +++ b/dist/src/utils/grab-origin.js @@ -0,0 +1,8 @@ +import grabAppPort from "./grab-app-port"; +export default function grabOrigin() { + if (global.CONFIG.origin) { + return global.CONFIG.origin; + } + const port = grabAppPort(); + return `http://localhost:${port}`; +} diff --git a/dist/src/utils/grab-page-name.js b/dist/src/utils/grab-page-name.js new file mode 100644 index 0000000..8cf090c --- /dev/null +++ b/dist/src/utils/grab-page-name.js @@ -0,0 +1,10 @@ +export default function grabPageName(params) { + const pathArr = params.path.split("/"); + const routesIndex = pathArr.findIndex((p) => p == "pages"); + const newPathArr = [...pathArr].slice(routesIndex + 1); + const filename = newPathArr + .filter((p) => Boolean(p.match(/./))) + .map((p) => p.replace(/\.\w+$/, "").replace(/[^a-z]/g, "")) + .join("-"); + return filename; +} diff --git a/dist/src/utils/grab-route-params.js b/dist/src/utils/grab-route-params.js new file mode 100644 index 0000000..9b4a3ac --- /dev/null +++ b/dist/src/utils/grab-route-params.js @@ -0,0 +1,20 @@ +import deserializeQuery from "./deserialize-query"; +export default async function grabRouteParams({ req, }) { + const url = new URL(req.url); + const query = deserializeQuery(Object.fromEntries(url.searchParams)); + const body = await (async () => { + try { + return req.method == "GET" ? undefined : await req.json(); + } + catch (error) { + return {}; + } + })(); + const routeParams = { + req, + url, + query, + body, + }; + return routeParams; +} diff --git a/dist/src/utils/grab-router.js b/dist/src/utils/grab-router.js new file mode 100644 index 0000000..9852198 --- /dev/null +++ b/dist/src/utils/grab-router.js @@ -0,0 +1,11 @@ +import grabDirNames from "./grab-dir-names"; +export default function grabRouter() { + const { PAGES_DIR } = grabDirNames(); + if (process.env.NODE_ENV == "production") { + return global.ROUTER; + } + return new Bun.FileSystemRouter({ + style: "nextjs", + dir: PAGES_DIR, + }); +} diff --git a/dist/src/utils/is-development.js b/dist/src/utils/is-development.js new file mode 100644 index 0000000..91ae2d9 --- /dev/null +++ b/dist/src/utils/is-development.js @@ -0,0 +1,6 @@ +export default function isDevelopment() { + const config = global.CONFIG; + if (config.development) + return true; + return false; +} diff --git a/dist/src/utils/numberfy.js b/dist/src/utils/numberfy.js new file mode 100644 index 0000000..c4ac120 --- /dev/null +++ b/dist/src/utils/numberfy.js @@ -0,0 +1,31 @@ +export default function numberfy(num, decimals) { + try { + const numberString = String(num) + .replace(/[^0-9\.]/g, "") + .replace(/\.$/, ""); + if (!numberString.match(/./)) + return 0; + const existingDecimals = numberString.match(/\./) + ? numberString.split(".").pop()?.length + : undefined; + const numberfiedNum = Number(numberString); + if (typeof numberfiedNum !== "number") + return 0; + if (isNaN(numberfiedNum)) + return 0; + if (decimals == 0) { + return Math.round(Number(numberfiedNum)); + } + else if (decimals) { + return Number(numberfiedNum.toFixed(decimals)); + } + if (existingDecimals) + return Number(numberfiedNum.toFixed(existingDecimals)); + return Math.round(numberfiedNum); + } + catch (error) { + console.log(`Numberfy ERROR: ${error.message}`); + return 0; + } +} +export const _n = numberfy; diff --git a/dist/types/index.js b/dist/types/index.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/dist/types/index.js @@ -0,0 +1 @@ +export {}; diff --git a/dist/utils/bundle.js b/dist/utils/bundle.js new file mode 100644 index 0000000..f5dbab4 --- /dev/null +++ b/dist/utils/bundle.js @@ -0,0 +1,47 @@ +import plugin from "bun-plugin-tailwind"; +import { execSync } from "child_process"; +const BuildKeys = [ + { key: "production" }, + { key: "bytecode" }, + { key: "conditions" }, + { key: "format" }, + { key: "root" }, + { key: "splitting" }, + { key: "cdd-chunking" }, +]; +export default function bundle({ out_dir, src, minify = true, exec_options, debug, entry_naming, sourcemap, target, build_options, }) { + let cmd = `bun build`; + if (minify) { + cmd += ` --minify`; + } + if (entry_naming) { + cmd += ` --entry-naming "${entry_naming}"`; + } + if (sourcemap) { + cmd += ` --sourcemap`; + } + if (target) { + cmd += ` --target ${target}`; + } + if (build_options) { + const keys = Object.keys(build_options); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = build_options[key]; + if (typeof value == "boolean" && value) { + cmd += ` --${key}`; + } + else if (key && value) { + cmd += ` --${key} ${value}`; + } + } + } + cmd += ` ${src} --outdir ${out_dir}`; + if (debug) { + console.log("cmd =>", cmd); + } + execSync(cmd, { + stdio: "inherit", + ...exec_options, + }); +} diff --git a/dist/utils/deserialize-query.js b/dist/utils/deserialize-query.js new file mode 100644 index 0000000..4cc68f5 --- /dev/null +++ b/dist/utils/deserialize-query.js @@ -0,0 +1,18 @@ +import EJSON from "./ejson"; +/** + * # Convert Serialized Query back to object + */ +export default function deserializeQuery(query) { + let queryObject = typeof query == "object" ? query : Object(EJSON.parse(query)); + const keys = Object.keys(queryObject); + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const value = queryObject[key]; + if (typeof value == "string") { + if (value.match(/^\{|^\[/)) { + queryObject[key] = EJSON.parse(value); + } + } + } + return queryObject; +} diff --git a/dist/utils/ejson.js b/dist/utils/ejson.js new file mode 100644 index 0000000..799eb22 --- /dev/null +++ b/dist/utils/ejson.js @@ -0,0 +1,33 @@ +/** + * # EJSON parse string + */ +function parse(string, reviver) { + if (!string) + return undefined; + if (typeof string == "object") + return string; + if (typeof string !== "string") + return undefined; + try { + return JSON.parse(string, reviver); + } + catch (error) { + return undefined; + } +} +/** + * # EJSON stringify object + */ +function stringify(value, replacer, space) { + try { + return JSON.stringify(value, replacer || undefined, space); + } + catch (error) { + return undefined; + } +} +const EJSON = { + parse, + stringify, +}; +export default EJSON; diff --git a/dist/utils/exit-with-error.js b/dist/utils/exit-with-error.js new file mode 100644 index 0000000..221bf53 --- /dev/null +++ b/dist/utils/exit-with-error.js @@ -0,0 +1,4 @@ +export default function exitWithError(msg, code) { + console.error(msg); + process.exit(code || 1); +} diff --git a/dist/utils/grab-all-pages.js b/dist/utils/grab-all-pages.js new file mode 100644 index 0000000..ae7355c --- /dev/null +++ b/dist/utils/grab-all-pages.js @@ -0,0 +1,64 @@ +import { existsSync, readdirSync, statSync } from "fs"; +import grabDirNames from "./grab-dir-names"; +import path from "path"; +import AppNames from "./grab-app-names"; +export default function grabAllPages(params) { + const { PAGES_DIR } = grabDirNames(); + const pages = grabPageDirRecursively({ page_dir: PAGES_DIR }); + if (params?.exclude_api) { + return pages.filter((p) => !Boolean(p.url_path.startsWith("/api/"))); + } + return pages; +} +function grabPageDirRecursively({ page_dir }) { + const pages = readdirSync(page_dir); + const pages_files = []; + const root_pages_file = grabPageFileObject({ file_path: `` }); + if (root_pages_file) { + pages_files.push(root_pages_file); + } + for (let i = 0; i < pages.length; i++) { + const page = pages[i]; + const full_page_path = path.join(page_dir, page); + if (!existsSync(full_page_path)) { + continue; + } + if (page.match(new RegExp(`${AppNames["RootPagesComponentName"]}`))) { + continue; + } + if (page.match(/\(|\)|--/)) { + continue; + } + const page_stat = statSync(full_page_path); + if (page_stat.isDirectory()) { + if (page.match(/\(|\)/)) + continue; + const new_page_files = grabPageDirRecursively({ + page_dir: full_page_path, + }); + pages_files.push(...new_page_files); + } + else if (page.match(/\.(ts|js)x?$/)) { + const pages_file = grabPageFileObject({ + file_path: full_page_path, + }); + if (pages_file) { + pages_files.push(pages_file); + } + } + } + return pages_files; +} +function grabPageFileObject({ file_path, }) { + let url_path = file_path + .replace(/.*\/pages\//, "/") + ?.replace(/\.(ts|js)x?$/, ""); + let file_name = url_path.split("/").pop(); + if (!file_name) + return; + return { + local_path: file_path, + url_path, + file_name, + }; +} diff --git a/dist/utils/grab-app-names.js b/dist/utils/grab-app-names.js new file mode 100644 index 0000000..f6f5acb --- /dev/null +++ b/dist/utils/grab-app-names.js @@ -0,0 +1,8 @@ +const AppNames = { + defaultPort: 7000, + defaultAssetPrefix: "_bunext/static", + name: "Bunext", + defaultDistDir: ".bunext", + RootPagesComponentName: "__root", +}; +export default AppNames; diff --git a/dist/utils/grab-app-port.js b/dist/utils/grab-app-port.js new file mode 100644 index 0000000..9f25de5 --- /dev/null +++ b/dist/utils/grab-app-port.js @@ -0,0 +1,17 @@ +import AppNames from "./grab-app-names"; +import numberfy from "./numberfy"; +export default function grabAppPort() { + const { defaultPort } = AppNames; + try { + if (process.env.PORT) { + return numberfy(process.env.PORT); + } + if (global.CONFIG.port) { + return global.CONFIG.port; + } + return numberfy(defaultPort); + } + catch (error) { + return numberfy(defaultPort); + } +} diff --git a/dist/utils/grab-assets-prefix.js b/dist/utils/grab-assets-prefix.js new file mode 100644 index 0000000..1802e51 --- /dev/null +++ b/dist/utils/grab-assets-prefix.js @@ -0,0 +1,8 @@ +import AppNames from "./grab-app-names"; +export default function grabAssetsPrefix() { + if (global.CONFIG.assetsPrefix) { + return global.CONFIG.assetsPrefix; + } + const { defaultAssetPrefix } = AppNames; + return defaultAssetPrefix; +} diff --git a/dist/utils/grab-constants.js b/dist/utils/grab-constants.js new file mode 100644 index 0000000..626fdef --- /dev/null +++ b/dist/utils/grab-constants.js @@ -0,0 +1,18 @@ +export default function grabConstants() { + const config = global.CONFIG; + const MB_IN_BYTES = 1024 * 1024; + const ClientWindowPagePropsName = "__PAGE_PROPS__"; + const ClientRootElementIDName = "__bunext"; + const ClientRootComponentWindowName = "BUNEXT_ROOT"; + const ServerDefaultRequestBodyLimitBytes = MB_IN_BYTES * 10; + const MaxBundlerRebuilds = 5; + return { + ClientRootElementIDName, + ClientWindowPagePropsName, + MBInBytes: MB_IN_BYTES, + ServerDefaultRequestBodyLimitBytes, + ClientRootComponentWindowName, + MaxBundlerRebuilds, + config, + }; +} diff --git a/dist/utils/grab-dir-names.js b/dist/utils/grab-dir-names.js new file mode 100644 index 0000000..f19dd28 --- /dev/null +++ b/dist/utils/grab-dir-names.js @@ -0,0 +1,43 @@ +import path from "path"; +export default function grabDirNames() { + const ROOT_DIR = process.cwd(); + const SRC_DIR = path.join(ROOT_DIR, "src"); + const PAGES_DIR = path.join(SRC_DIR, "pages"); + const API_DIR = path.join(PAGES_DIR, "api"); + const PUBLIC_DIR = path.join(ROOT_DIR, "public"); + const BUNEXT_PUBLIC_DIR = path.join(PUBLIC_DIR, "__bunext"); + const HYDRATION_DST_DIR = path.join(BUNEXT_PUBLIC_DIR, "pages"); + const BUNEXT_CACHE_DIR = path.join(BUNEXT_PUBLIC_DIR, "cache"); + const HYDRATION_DST_DIR_MAP_JSON_FILE = path.join(HYDRATION_DST_DIR, "map.json"); + const CONFIG_FILE = path.join(ROOT_DIR, "bunext.config.ts"); + const BUNX_CWD_DIR = path.resolve(ROOT_DIR, ".bunext"); + const BUNX_TMP_DIR = path.resolve(BUNX_CWD_DIR, ".tmp"); + const BUNX_HYDRATION_SRC_DIR = path.resolve(BUNX_CWD_DIR, "client", "hydration-src"); + const BUNX_ROOT_DIR = path.resolve(__dirname, "../../"); + const BUNX_ROOT_SRC_DIR = path.join(BUNX_ROOT_DIR, "src"); + const BUNX_ROOT_PRESETS_DIR = path.join(BUNX_ROOT_SRC_DIR, "presets"); + const BUNX_ROOT_500_FILE_NAME = `server-error`; + const BUNX_ROOT_500_PRESET_COMPONENT = path.join(BUNX_ROOT_PRESETS_DIR, `${BUNX_ROOT_500_FILE_NAME}.tsx`); + const BUNX_ROOT_404_FILE_NAME = `not-found`; + const BUNX_ROOT_404_PRESET_COMPONENT = path.join(BUNX_ROOT_PRESETS_DIR, `${BUNX_ROOT_404_FILE_NAME}.tsx`); + return { + ROOT_DIR, + SRC_DIR, + PAGES_DIR, + API_DIR, + PUBLIC_DIR, + HYDRATION_DST_DIR, + BUNX_ROOT_DIR, + CONFIG_FILE, + BUNX_TMP_DIR, + BUNX_HYDRATION_SRC_DIR, + BUNX_ROOT_SRC_DIR, + BUNX_ROOT_PRESETS_DIR, + BUNX_ROOT_500_PRESET_COMPONENT, + BUNX_ROOT_500_FILE_NAME, + BUNX_ROOT_404_PRESET_COMPONENT, + BUNX_ROOT_404_FILE_NAME, + HYDRATION_DST_DIR_MAP_JSON_FILE, + BUNEXT_CACHE_DIR, + }; +} diff --git a/dist/utils/grab-origin.js b/dist/utils/grab-origin.js new file mode 100644 index 0000000..badc4d7 --- /dev/null +++ b/dist/utils/grab-origin.js @@ -0,0 +1,8 @@ +import grabAppPort from "./grab-app-port"; +export default function grabOrigin() { + if (global.CONFIG.origin) { + return global.CONFIG.origin; + } + const port = grabAppPort(); + return `http://localhost:${port}`; +} diff --git a/dist/utils/grab-page-name.js b/dist/utils/grab-page-name.js new file mode 100644 index 0000000..80eb3f4 --- /dev/null +++ b/dist/utils/grab-page-name.js @@ -0,0 +1,17 @@ +export default function grabPageName(params) { + const pathArr = params.path.split("/"); + const routesIndex = pathArr.findIndex((p) => p == "pages"); + const newPathArr = [...pathArr].slice(routesIndex + 1); + const filename = newPathArr + .filter((p) => Boolean(p.match(/./))) + .map((p) => p + .replace(/\.\w+$/, "") + .replace(/\[/g, "-") + .replace(/\.\.\./g, "-") + .replace(/[^a-z\-]/g, "")) + .join("-"); + if (filename.endsWith(`-index`)) { + return filename.replace(/-index$/, ""); + } + return filename; +} diff --git a/dist/utils/grab-route-params.js b/dist/utils/grab-route-params.js new file mode 100644 index 0000000..03d5ebe --- /dev/null +++ b/dist/utils/grab-route-params.js @@ -0,0 +1,21 @@ +import deserializeQuery from "./deserialize-query"; +export default async function grabRouteParams({ req, }) { + const url = new URL(req.url); + const query = deserializeQuery(Object.fromEntries(url.searchParams)); + const body = await (async () => { + try { + return req.method == "GET" ? undefined : await req.json(); + } + catch (error) { + return {}; + } + })(); + const routeParams = { + req, + url, + query, + body, + server: global.SERVER, + }; + return routeParams; +} diff --git a/dist/utils/grab-router.js b/dist/utils/grab-router.js new file mode 100644 index 0000000..a2b7775 --- /dev/null +++ b/dist/utils/grab-router.js @@ -0,0 +1,6 @@ +export default function grabRouter() { + // if (process.env.NODE_ENV !== "production") { + // global.ROUTER.reload(); + // } + return global.ROUTER; +} diff --git a/dist/utils/is-development.js b/dist/utils/is-development.js new file mode 100644 index 0000000..5074564 --- /dev/null +++ b/dist/utils/is-development.js @@ -0,0 +1,10 @@ +export default function isDevelopment() { + const config = global.CONFIG; + if (process.env.NODE_ENV == "production") { + return false; + } + if (config.development) { + return true; + } + return false; +} diff --git a/dist/utils/numberfy.js b/dist/utils/numberfy.js new file mode 100644 index 0000000..c4ac120 --- /dev/null +++ b/dist/utils/numberfy.js @@ -0,0 +1,31 @@ +export default function numberfy(num, decimals) { + try { + const numberString = String(num) + .replace(/[^0-9\.]/g, "") + .replace(/\.$/, ""); + if (!numberString.match(/./)) + return 0; + const existingDecimals = numberString.match(/\./) + ? numberString.split(".").pop()?.length + : undefined; + const numberfiedNum = Number(numberString); + if (typeof numberfiedNum !== "number") + return 0; + if (isNaN(numberfiedNum)) + return 0; + if (decimals == 0) { + return Math.round(Number(numberfiedNum)); + } + else if (decimals) { + return Number(numberfiedNum.toFixed(decimals)); + } + if (existingDecimals) + return Number(numberfiedNum.toFixed(existingDecimals)); + return Math.round(numberfiedNum); + } + catch (error) { + console.log(`Numberfy ERROR: ${error.message}`); + return 0; + } +} +export const _n = numberfy; diff --git a/dist/utils/refresh-router.js b/dist/utils/refresh-router.js new file mode 100644 index 0000000..e1ce2e1 --- /dev/null +++ b/dist/utils/refresh-router.js @@ -0,0 +1,9 @@ +import grabDirNames from "./grab-dir-names"; +export default function refreshRouter() { + const { PAGES_DIR } = grabDirNames(); + const router = new Bun.FileSystemRouter({ + style: "nextjs", + dir: PAGES_DIR, + }); + global.ROUTER = router; +}