diff --git a/dist/commands/build/index.js b/dist/commands/build/index.js index 0bb08e3..83b6340 100644 --- a/dist/commands/build/index.js +++ b/dist/commands/build/index.js @@ -6,6 +6,7 @@ import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; import grabDirNames from "../../utils/grab-dir-names"; import { rmSync } from "fs"; import allPagesBundler from "../../functions/bundler/all-pages-bundler"; +import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames(); export default function () { return new Command("build") @@ -21,7 +22,9 @@ export default function () { await init(); log.banner(); log.build("Building Project ..."); - await allPagesBunBundler(); + // await allPagesBunBundler(); // await allPagesBundler(); + await allPagesESBuildContextBundler(); + process.exit(); }); } diff --git a/dist/commands/dev/index.js b/dist/commands/dev/index.js index fe5645e..024ed8b 100644 --- a/dist/commands/dev/index.js +++ b/dist/commands/dev/index.js @@ -5,6 +5,8 @@ import bunextInit from "../../functions/bunext-init"; import grabDirNames from "../../utils/grab-dir-names"; import { rmSync } from "fs"; import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; +import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; +import serverPostBuildFn from "../../functions/server/server-post-build-fn"; const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames(); export default function () { return new Command("dev") @@ -18,7 +20,8 @@ export default function () { } catch (error) { } await bunextInit(); - await allPagesBunBundler(); + // await allPagesBunBundler(); + await allPagesESBuildContextBundler(); await startServer(); }); } diff --git a/dist/commands/start/index.js b/dist/commands/start/index.js index e87c3e0..cd2dadb 100644 --- a/dist/commands/start/index.js +++ b/dist/commands/start/index.js @@ -3,13 +3,16 @@ import startServer from "../../functions/server/start-server"; import { log } from "../../utils/log"; import bunextInit from "../../functions/bunext-init"; import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; +import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; export default function () { return new Command("start") .description("Start production server") .action(async () => { + process.env.NODE_ENV = "production"; log.info("Starting production server ..."); await bunextInit(); - await allPagesBunBundler(); + // await allPagesBunBundler(); + await allPagesESBuildContextBundler(); await startServer(); }); } diff --git a/dist/functions/bundler/all-pages-bun-bundler.js b/dist/functions/bundler/all-pages-bun-bundler.js index 4622a3c..e5c9dd8 100644 --- a/dist/functions/bundler/all-pages-bun-bundler.js +++ b/dist/functions/bundler/all-pages-bun-bundler.js @@ -7,8 +7,7 @@ import path from "path"; import grabClientHydrationScript from "./grab-client-hydration-script"; import { mkdirSync, rmSync } from "fs"; import recordArtifacts from "./record-artifacts"; -import BunSkipNonBrowserPlugin from "./plugins/bun-skip-browser-plugin"; -const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR, BUNX_TMP_DIR } = grabDirNames(); +const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames(); export default async function allPagesBunBundler(params) { const { target = "browser", page_file_paths } = params || {}; const pages = grabAllPages({ exclude_api: true }); @@ -64,7 +63,6 @@ export default async function allPagesBunBundler(params) { "react/jsx-runtime", ], }); - await Bun.write(path.join(BUNX_TMP_DIR, "bundle.json"), JSON.stringify(result, null, 4), { createPath: true }); if (!result.success) { for (const entry of result.logs) { log.error(`[Build] ${entry.message}`); diff --git a/dist/functions/bundler/all-pages-bundler.js b/dist/functions/bundler/all-pages-bundler.js index 3d5a197..6c79d55 100644 --- a/dist/functions/bundler/all-pages-bundler.js +++ b/dist/functions/bundler/all-pages-bundler.js @@ -129,13 +129,12 @@ export default async function allPagesBundler(params) { } return; } - const artifacts = grabArtifactsFromBundledResults({ - pages: target_pages, - result, - }); - if (artifacts?.[0]) { - await recordArtifacts({ artifacts }); - } + // const artifacts = grabArtifactsFromBundledResults({ + // result, + // }); + // if (artifacts?.[0]) { + // await recordArtifacts({ artifacts }); + // } const elapsed = (performance.now() - buildStart).toFixed(0); log.success(`[Built] in ${elapsed}ms`); global.RECOMPILING = false; diff --git a/dist/functions/bundler/all-pages-esbuild-context-bundler.d.ts b/dist/functions/bundler/all-pages-esbuild-context-bundler.d.ts index 6ac7707..b725a35 100644 --- a/dist/functions/bundler/all-pages-esbuild-context-bundler.d.ts +++ b/dist/functions/bundler/all-pages-esbuild-context-bundler.d.ts @@ -1,7 +1,3 @@ -type Params = { - post_build_fn?: (params: { - artifacts: any[]; - }) => Promise | void; -}; +type Params = {}; export default function allPagesESBuildContextBundler(params?: Params): Promise; export {}; diff --git a/dist/functions/bundler/all-pages-esbuild-context-bundler.js b/dist/functions/bundler/all-pages-esbuild-context-bundler.js index 9221fdd..80bb440 100644 --- a/dist/functions/bundler/all-pages-esbuild-context-bundler.js +++ b/dist/functions/bundler/all-pages-esbuild-context-bundler.js @@ -7,40 +7,25 @@ import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin"; import grabClientHydrationScript from "./grab-client-hydration-script"; import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result"; import { writeFileSync } from "fs"; -const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames(); +import path from "path"; +const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE, BUNX_HYDRATION_SRC_DIR, } = grabDirNames(); let build_starts = 0; const MAX_BUILD_STARTS = 10; export default async function allPagesESBuildContextBundler(params) { const pages = grabAllPages({ exclude_api: true }); global.PAGE_FILES = pages; - const virtualEntries = {}; const dev = isDevelopment(); + const entryToPage = new Map(); for (const page of pages) { - const key = page.transformed_path; const txt = await grabClientHydrationScript({ page_local_path: page.local_path, }); - // if (page.url_path == "/index") { - // console.log("txt", txt); - // } if (!txt) continue; - virtualEntries[key] = txt; + const entryFile = path.join(BUNX_HYDRATION_SRC_DIR, `${page.url_path}.tsx`); + await Bun.write(entryFile, txt, { createPath: true }); + entryToPage.set(path.resolve(entryFile), page); } - const virtualPlugin = { - name: "virtual-entrypoints", - setup(build) { - build.onResolve({ filter: /^virtual:/ }, (args) => ({ - path: args.path.replace("virtual:", ""), - namespace: "virtual", - })); - build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => ({ - contents: virtualEntries[args.path], - loader: "tsx", - resolveDir: process.cwd(), - })); - }, - }; let buildStart = 0; const artifactTracker = { name: "artifact-tracker", @@ -66,8 +51,8 @@ export default async function allPagesESBuildContextBundler(params) { return; } const artifacts = grabArtifactsFromBundledResults({ - pages, result, + entryToPage, }); if (artifacts?.[0] && artifacts.length > 0) { for (let i = 0; i < artifacts.length; i++) { @@ -77,8 +62,11 @@ export default async function allPagesESBuildContextBundler(params) { artifact; } } - params?.post_build_fn?.({ artifacts }); - writeFileSync(HYDRATION_DST_DIR_MAP_JSON_FILE, JSON.stringify(artifacts, null, 4)); + // params?.post_build_fn?.({ artifacts }); + // writeFileSync( + // HYDRATION_DST_DIR_MAP_JSON_FILE, + // JSON.stringify(artifacts, null, 4), + // ); } const elapsed = (performance.now() - buildStart).toFixed(0); log.success(`[Built] in ${elapsed}ms`); @@ -87,12 +75,12 @@ export default async function allPagesESBuildContextBundler(params) { }); }, }; - const entryPoints = Object.keys(virtualEntries).map((k) => `virtual:${k}`); + const entryPoints = [...entryToPage.keys()]; const ctx = await esbuild.context({ entryPoints, outdir: HYDRATION_DST_DIR, bundle: true, - minify: true, + minify: !dev, format: "esm", target: "es2020", platform: "browser", @@ -101,7 +89,7 @@ export default async function allPagesESBuildContextBundler(params) { }, entryNames: "[dir]/[hash]", metafile: true, - plugins: [tailwindEsbuildPlugin, virtualPlugin, artifactTracker], + plugins: [tailwindEsbuildPlugin, artifactTracker], jsx: "automatic", splitting: true, // logLevel: "silent", @@ -113,5 +101,8 @@ export default async function allPagesESBuildContextBundler(params) { ], }); await ctx.rebuild(); - // global.BUNDLER_CTX = ctx; + // if (params?.watch) { + // await ctx.watch(); + // } + global.BUNDLER_CTX = ctx; } diff --git a/dist/functions/bundler/grab-artifacts-from-bundled-result.d.ts b/dist/functions/bundler/grab-artifacts-from-bundled-result.d.ts index d99e746..fbc1b73 100644 --- a/dist/functions/bundler/grab-artifacts-from-bundled-result.d.ts +++ b/dist/functions/bundler/grab-artifacts-from-bundled-result.d.ts @@ -2,7 +2,7 @@ import * as esbuild from "esbuild"; import type { BundlerCTXMap, PageFiles } from "../../types"; type Params = { result: esbuild.BuildResult; - pages: PageFiles[]; + entryToPage: Map; }; -export default function grabArtifactsFromBundledResults({ result, pages, }: Params): BundlerCTXMap[] | undefined; +export default function grabArtifactsFromBundledResults({ result, entryToPage, }: Params): BundlerCTXMap[] | undefined; export {}; diff --git a/dist/functions/bundler/grab-artifacts-from-bundled-result.js b/dist/functions/bundler/grab-artifacts-from-bundled-result.js index 986d5fb..f5d7171 100644 --- a/dist/functions/bundler/grab-artifacts-from-bundled-result.js +++ b/dist/functions/bundler/grab-artifacts-from-bundled-result.js @@ -1,27 +1,27 @@ import path from "path"; import * as esbuild from "esbuild"; -export default function grabArtifactsFromBundledResults({ result, pages, }) { +import grabDirNames from "../../utils/grab-dir-names"; +const { ROOT_DIR } = grabDirNames(); +export default function grabArtifactsFromBundledResults({ result, entryToPage, }) { 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.transformed_path}`; - }); + const entrypoint = path.join(ROOT_DIR, meta.entryPoint || ""); + const target_page = entryToPage.get(entrypoint); if (!target_page || !meta.entryPoint) { return undefined; } const { file_name, local_path, url_path, transformed_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, + entrypoint, + css_path: meta.cssBundle, file_name, local_path, url_path, diff --git a/dist/functions/bunext-init.d.ts b/dist/functions/bunext-init.d.ts index 8c78335..776473e 100644 --- a/dist/functions/bunext-init.d.ts +++ b/dist/functions/bunext-init.d.ts @@ -1,12 +1,11 @@ -import { type Ora } from "ora"; import type { BundlerCTXMap, BunextConfig, GlobalHMRControllerObject, PageFiles } from "../types"; import type { FileSystemRouter, Server } from "bun"; import { type FSWatcher } from "fs"; +import type { BuildContext } from "esbuild"; /** * # Declare Global Variables */ declare global { - var ORA_SPINNER: Ora; var CONFIG: BunextConfig; var SERVER: Server | undefined; var RECOMPILING: boolean; @@ -23,5 +22,6 @@ declare global { var PAGE_FILES: PageFiles[]; var ROOT_FILE_UPDATED: boolean; var SKIPPED_BROWSER_MODULES: Set; + var BUNDLER_CTX: BuildContext | undefined; } export default function bunextInit(): Promise; diff --git a/dist/functions/bunext-init.js b/dist/functions/bunext-init.js index 54501fe..7193ac7 100644 --- a/dist/functions/bunext-init.js +++ b/dist/functions/bunext-init.js @@ -1,16 +1,12 @@ -import ora, {} from "ora"; import grabDirNames from "../utils/grab-dir-names"; import {} from "fs"; import init from "./init"; import isDevelopment from "../utils/is-development"; -import watcher from "./server/watcher"; import { log } from "../utils/log"; import cron from "./server/cron"; -import allPagesBunBundler from "./bundler/all-pages-bun-bundler"; -const { PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames(); +import watcherEsbuildCTX from "./server/watcher-esbuild-ctx"; +const { PAGES_DIR } = grabDirNames(); export default async function bunextInit() { - global.ORA_SPINNER = ora(); - global.ORA_SPINNER.clear(); global.HMR_CONTROLLERS = []; global.BUNDLER_CTX_MAP = {}; global.BUNDLER_REBUILDS = 0; @@ -25,7 +21,7 @@ export default async function bunextInit() { global.ROUTER = router; const is_dev = isDevelopment(); if (is_dev) { - watcher(); + watcherEsbuildCTX(); } else { cron(); diff --git a/dist/functions/server/watcher-esbuild-ctx.d.ts b/dist/functions/server/watcher-esbuild-ctx.d.ts new file mode 100644 index 0000000..e2345ff --- /dev/null +++ b/dist/functions/server/watcher-esbuild-ctx.d.ts @@ -0,0 +1 @@ +export default function watcherEsbuildCTX(): Promise; diff --git a/dist/functions/server/watcher-esbuild-ctx.js b/dist/functions/server/watcher-esbuild-ctx.js new file mode 100644 index 0000000..1274428 --- /dev/null +++ b/dist/functions/server/watcher-esbuild-ctx.js @@ -0,0 +1,82 @@ +import { watch, existsSync } from "fs"; +import path from "path"; +import grabDirNames from "../../utils/grab-dir-names"; +import { log } from "../../utils/log"; +import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler"; +const { ROOT_DIR } = grabDirNames(); +export default async function watcherEsbuildCTX() { + const pages_src_watcher = watch(ROOT_DIR, { + recursive: true, + persistent: true, + }, async (event, filename) => { + if (!filename) + return; + const full_file_path = path.join(ROOT_DIR, filename); + if (full_file_path.match(/\/styles$/)) { + global.RECOMPILING = true; + await Bun.sleep(1000); + await fullRebuild({ + msg: `Detected new \`styles\` directory. Rebuilding ...`, + }); + return; + } + const excluded_match = /node_modules\/|^public\/|^\.bunext\/|^\.git\/|^dist\/|bun\.lockb$/; + if (filename.match(excluded_match)) + return; + if (filename.match(/bunext.config\.ts/)) { + await fullRebuild({ + msg: `bunext.config.ts file changed. Rebuilding server ...`, + }); + return; + } + const target_files_match = /\.(tsx?|jsx?|css)$/; + if (event !== "rename") { + if (filename.match(target_files_match)) { + if (global.RECOMPILING) + return; + global.RECOMPILING = true; + await global.BUNDLER_CTX?.rebuild(); + } + return; + } + const is_file_of_interest = Boolean(filename.match(target_files_match)); + if (!is_file_of_interest) { + return; + } + if (!filename.match(/^src\/pages\/|\.css$/)) + return; + if (filename.match(/\/(--|\()/)) + return; + if (global.RECOMPILING) + return; + const action = existsSync(full_file_path) ? "created" : "deleted"; + const type = filename.match(/\.css$/) ? "Sylesheet" : "Page"; + await fullRebuild({ + msg: `${type} ${action}: ${filename}. Rebuilding ...`, + }); + }); + global.PAGES_SRC_WATCHER = pages_src_watcher; +} +async function fullRebuild(params) { + try { + const { msg } = params || {}; + global.RECOMPILING = true; + if (msg) { + log.watch(msg); + } + global.ROUTER.reload(); + await global.BUNDLER_CTX?.dispose(); + global.BUNDLER_CTX = undefined; + await allPagesESBuildContextBundler(); + } + catch (error) { + log.error(error); + } + finally { + global.RECOMPILING = false; + } + if (global.PAGES_SRC_WATCHER) { + global.PAGES_SRC_WATCHER.close(); + watcherEsbuildCTX(); + } +} diff --git a/dist/functions/server/watcher.js b/dist/functions/server/watcher.js index 4dc191e..7c1fa63 100644 --- a/dist/functions/server/watcher.js +++ b/dist/functions/server/watcher.js @@ -3,7 +3,6 @@ import path from "path"; import grabDirNames from "../../utils/grab-dir-names"; import rebuildBundler from "./rebuild-bundler"; import { log } from "../../utils/log"; -// import rewritePagesModule from "../../utils/rewrite-pages-module"; const { ROOT_DIR } = grabDirNames(); export default async function watcher() { const pages_src_watcher = watch(ROOT_DIR, { @@ -62,15 +61,11 @@ async function fullRebuild(params) { try { const { msg } = params || {}; global.RECOMPILING = true; - // const target_file_paths = global.HMR_CONTROLLERS.map( - // (hmr) => hmr.target_map?.local_path, - // ).filter((f) => typeof f == "string"); - // await rewritePagesModule(); + const target_file_paths = global.HMR_CONTROLLERS.map((hmr) => hmr.target_map?.local_path).filter((f) => typeof f == "string"); if (msg) { log.watch(msg); } - await rebuildBundler(); - // await rebuildBundler({ target_file_paths }); + await rebuildBundler({ target_file_paths }); } catch (error) { log.error(error); diff --git a/package.json b/package.json index 32119e0..d9693a3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@moduletrace/bunext", "module": "index.ts", "type": "module", - "version": "1.0.30", + "version": "1.0.31", "main": "dist/index.js", "types": "dist/index.d.ts", "exports": { diff --git a/src/commands/build/index.ts b/src/commands/build/index.ts index b8f98ee..6efa0c1 100644 --- a/src/commands/build/index.ts +++ b/src/commands/build/index.ts @@ -6,6 +6,7 @@ import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; import grabDirNames from "../../utils/grab-dir-names"; import { rmSync } from "fs"; import allPagesBundler from "../../functions/bundler/all-pages-bundler"; +import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames(); @@ -26,8 +27,11 @@ export default function () { log.banner(); log.build("Building Project ..."); - await allPagesBunBundler(); + // await allPagesBunBundler(); // await allPagesBundler(); + await allPagesESBuildContextBundler(); + + process.exit(); }); } diff --git a/src/commands/dev/index.ts b/src/commands/dev/index.ts index 9d866d8..dc20607 100644 --- a/src/commands/dev/index.ts +++ b/src/commands/dev/index.ts @@ -5,6 +5,8 @@ import bunextInit from "../../functions/bunext-init"; import grabDirNames from "../../utils/grab-dir-names"; import { rmSync } from "fs"; import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; +import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; +import serverPostBuildFn from "../../functions/server/server-post-build-fn"; const { HYDRATION_DST_DIR, BUNX_CWD_PAGES_REWRITE_DIR } = grabDirNames(); @@ -22,7 +24,10 @@ export default function () { } catch (error) {} await bunextInit(); - await allPagesBunBundler(); + + // await allPagesBunBundler(); + + await allPagesESBuildContextBundler(); await startServer(); }); diff --git a/src/commands/start/index.ts b/src/commands/start/index.ts index 882144a..5e8bdd0 100644 --- a/src/commands/start/index.ts +++ b/src/commands/start/index.ts @@ -3,16 +3,19 @@ import startServer from "../../functions/server/start-server"; import { log } from "../../utils/log"; import bunextInit from "../../functions/bunext-init"; import allPagesBunBundler from "../../functions/bundler/all-pages-bun-bundler"; +import allPagesESBuildContextBundler from "../../functions/bundler/all-pages-esbuild-context-bundler"; export default function () { return new Command("start") .description("Start production server") .action(async () => { + process.env.NODE_ENV = "production"; log.info("Starting production server ..."); await bunextInit(); - await allPagesBunBundler(); + // await allPagesBunBundler(); + await allPagesESBuildContextBundler(); await startServer(); }); diff --git a/src/functions/bundler/all-pages-bun-bundler.ts b/src/functions/bundler/all-pages-bun-bundler.ts index 49a192e..39493e1 100644 --- a/src/functions/bundler/all-pages-bun-bundler.ts +++ b/src/functions/bundler/all-pages-bun-bundler.ts @@ -8,10 +8,8 @@ import path from "path"; import grabClientHydrationScript from "./grab-client-hydration-script"; import { mkdirSync, rmSync } from "fs"; import recordArtifacts from "./record-artifacts"; -import BunSkipNonBrowserPlugin from "./plugins/bun-skip-browser-plugin"; -const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR, BUNX_TMP_DIR } = - grabDirNames(); +const { HYDRATION_DST_DIR, BUNX_HYDRATION_SRC_DIR } = grabDirNames(); type Params = { target?: "bun" | "browser"; @@ -86,12 +84,6 @@ export default async function allPagesBunBundler(params?: Params) { ], }); - await Bun.write( - path.join(BUNX_TMP_DIR, "bundle.json"), - JSON.stringify(result, null, 4), - { createPath: true }, - ); - if (!result.success) { for (const entry of result.logs) { log.error(`[Build] ${entry.message}`); diff --git a/src/functions/bundler/all-pages-bundler.ts b/src/functions/bundler/all-pages-bundler.ts index 0e49121..5c6809d 100644 --- a/src/functions/bundler/all-pages-bundler.ts +++ b/src/functions/bundler/all-pages-bundler.ts @@ -166,14 +166,13 @@ export default async function allPagesBundler(params?: Params) { return; } - const artifacts = grabArtifactsFromBundledResults({ - pages: target_pages, - result, - }); + // const artifacts = grabArtifactsFromBundledResults({ + // result, + // }); - if (artifacts?.[0]) { - await recordArtifacts({ artifacts }); - } + // if (artifacts?.[0]) { + // await recordArtifacts({ artifacts }); + // } const elapsed = (performance.now() - buildStart).toFixed(0); log.success(`[Built] in ${elapsed}ms`); diff --git a/src/functions/bundler/all-pages-esbuild-context-bundler.ts b/src/functions/bundler/all-pages-esbuild-context-bundler.ts index 86ba26a..0b45054 100644 --- a/src/functions/bundler/all-pages-esbuild-context-bundler.ts +++ b/src/functions/bundler/all-pages-esbuild-context-bundler.ts @@ -7,14 +7,21 @@ import tailwindEsbuildPlugin from "../server/web-pages/tailwind-esbuild-plugin"; import grabClientHydrationScript from "./grab-client-hydration-script"; import grabArtifactsFromBundledResults from "./grab-artifacts-from-bundled-result"; import { writeFileSync } from "fs"; +import type { PageFiles } from "../../types"; +import path from "path"; -const { HYDRATION_DST_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames(); +const { + HYDRATION_DST_DIR, + HYDRATION_DST_DIR_MAP_JSON_FILE, + BUNX_HYDRATION_SRC_DIR, +} = grabDirNames(); let build_starts = 0; const MAX_BUILD_STARTS = 10; type Params = { - post_build_fn?: (params: { artifacts: any[] }) => Promise | void; + // post_build_fn?: (params: { artifacts: any[] }) => Promise | void; + // watch?: boolean; }; export default async function allPagesESBuildContextBundler(params?: Params) { @@ -22,41 +29,24 @@ export default async function allPagesESBuildContextBundler(params?: Params) { global.PAGE_FILES = pages; - const virtualEntries: Record = {}; const dev = isDevelopment(); - for (const page of pages) { - const key = page.transformed_path; + const entryToPage = new Map(); + for (const page of pages) { const txt = await grabClientHydrationScript({ page_local_path: page.local_path, }); - - // if (page.url_path == "/index") { - // console.log("txt", txt); - // } - if (!txt) continue; - virtualEntries[key] = txt; + const entryFile = path.join( + BUNX_HYDRATION_SRC_DIR, + `${page.url_path}.tsx`, + ); + await Bun.write(entryFile, txt, { createPath: true }); + entryToPage.set(path.resolve(entryFile), page); } - const virtualPlugin: esbuild.Plugin = { - name: "virtual-entrypoints", - setup(build) { - build.onResolve({ filter: /^virtual:/ }, (args) => ({ - path: args.path.replace("virtual:", ""), - namespace: "virtual", - })); - - build.onLoad({ filter: /.*/, namespace: "virtual" }, (args) => ({ - contents: virtualEntries[args.path], - loader: "tsx", - resolveDir: process.cwd(), - })); - }, - }; - let buildStart = 0; const artifactTracker: esbuild.Plugin = { @@ -86,8 +76,8 @@ export default async function allPagesESBuildContextBundler(params?: Params) { } const artifacts = grabArtifactsFromBundledResults({ - pages, result, + entryToPage, }); if (artifacts?.[0] && artifacts.length > 0) { @@ -99,12 +89,12 @@ export default async function allPagesESBuildContextBundler(params?: Params) { } } - params?.post_build_fn?.({ artifacts }); + // params?.post_build_fn?.({ artifacts }); - writeFileSync( - HYDRATION_DST_DIR_MAP_JSON_FILE, - JSON.stringify(artifacts, null, 4), - ); + // writeFileSync( + // HYDRATION_DST_DIR_MAP_JSON_FILE, + // JSON.stringify(artifacts, null, 4), + // ); } const elapsed = (performance.now() - buildStart).toFixed(0); @@ -117,13 +107,13 @@ export default async function allPagesESBuildContextBundler(params?: Params) { }, }; - const entryPoints = Object.keys(virtualEntries).map((k) => `virtual:${k}`); + const entryPoints = [...entryToPage.keys()]; const ctx = await esbuild.context({ entryPoints, outdir: HYDRATION_DST_DIR, bundle: true, - minify: true, + minify: !dev, format: "esm", target: "es2020", platform: "browser", @@ -134,7 +124,7 @@ export default async function allPagesESBuildContextBundler(params?: Params) { }, entryNames: "[dir]/[hash]", metafile: true, - plugins: [tailwindEsbuildPlugin, virtualPlugin, artifactTracker], + plugins: [tailwindEsbuildPlugin, artifactTracker], jsx: "automatic", splitting: true, // logLevel: "silent", @@ -148,5 +138,9 @@ export default async function allPagesESBuildContextBundler(params?: Params) { await ctx.rebuild(); - // global.BUNDLER_CTX = ctx; + // if (params?.watch) { + // await ctx.watch(); + // } + + global.BUNDLER_CTX = ctx; } diff --git a/src/functions/bundler/grab-artifacts-from-bundled-result.ts b/src/functions/bundler/grab-artifacts-from-bundled-result.ts index 2e886fe..d076948 100644 --- a/src/functions/bundler/grab-artifacts-from-bundled-result.ts +++ b/src/functions/bundler/grab-artifacts-from-bundled-result.ts @@ -1,15 +1,18 @@ import path from "path"; import * as esbuild from "esbuild"; import type { BundlerCTXMap, PageFiles } from "../../types"; +import grabDirNames from "../../utils/grab-dir-names"; + +const { ROOT_DIR } = grabDirNames(); type Params = { result: esbuild.BuildResult; - pages: PageFiles[]; + entryToPage: Map; }; export default function grabArtifactsFromBundledResults({ result, - pages, + entryToPage, }: Params) { if (result.errors.length > 0) return; @@ -18,9 +21,9 @@ export default function grabArtifactsFromBundledResults({ ) .filter(([, meta]) => meta.entryPoint) .map(([outputPath, meta]) => { - const target_page = pages.find((p) => { - return meta.entryPoint === `virtual:${p.transformed_path}`; - }); + const entrypoint = path.join(ROOT_DIR, meta.entryPoint || ""); + + const target_page = entryToPage.get(entrypoint); if (!target_page || !meta.entryPoint) { return undefined; @@ -29,16 +32,14 @@ export default function grabArtifactsFromBundledResults({ const { file_name, local_path, url_path, transformed_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, + entrypoint, + css_path: meta.cssBundle, file_name, local_path, url_path, diff --git a/src/functions/bunext-init.ts b/src/functions/bunext-init.ts index db657c1..0b46904 100644 --- a/src/functions/bunext-init.ts +++ b/src/functions/bunext-init.ts @@ -1,4 +1,3 @@ -import ora, { type Ora } from "ora"; import type { BundlerCTXMap, BunextConfig, @@ -10,16 +9,15 @@ import grabDirNames from "../utils/grab-dir-names"; import { type FSWatcher } from "fs"; import init from "./init"; import isDevelopment from "../utils/is-development"; -import watcher from "./server/watcher"; import { log } from "../utils/log"; import cron from "./server/cron"; -import allPagesBunBundler from "./bundler/all-pages-bun-bundler"; +import type { BuildContext } from "esbuild"; +import watcherEsbuildCTX from "./server/watcher-esbuild-ctx"; /** * # Declare Global Variables */ declare global { - var ORA_SPINNER: Ora; var CONFIG: BunextConfig; var SERVER: Server | undefined; var RECOMPILING: boolean; @@ -34,13 +32,12 @@ declare global { var PAGE_FILES: PageFiles[]; var ROOT_FILE_UPDATED: boolean; var SKIPPED_BROWSER_MODULES: Set; + var BUNDLER_CTX: BuildContext | undefined; } -const { PAGES_DIR, HYDRATION_DST_DIR_MAP_JSON_FILE } = grabDirNames(); +const { PAGES_DIR } = grabDirNames(); export default async function bunextInit() { - global.ORA_SPINNER = ora(); - global.ORA_SPINNER.clear(); global.HMR_CONTROLLERS = []; global.BUNDLER_CTX_MAP = {}; global.BUNDLER_REBUILDS = 0; @@ -60,7 +57,7 @@ export default async function bunextInit() { const is_dev = isDevelopment(); if (is_dev) { - watcher(); + watcherEsbuildCTX(); } else { cron(); } diff --git a/src/functions/server/watcher-esbuild-ctx.ts b/src/functions/server/watcher-esbuild-ctx.ts new file mode 100644 index 0000000..01d5dd3 --- /dev/null +++ b/src/functions/server/watcher-esbuild-ctx.ts @@ -0,0 +1,104 @@ +import { watch, existsSync } from "fs"; +import path from "path"; +import grabDirNames from "../../utils/grab-dir-names"; +import { log } from "../../utils/log"; +import allPagesESBuildContextBundler from "../bundler/all-pages-esbuild-context-bundler"; + +const { ROOT_DIR } = grabDirNames(); + +export default async function watcherEsbuildCTX() { + const pages_src_watcher = watch( + ROOT_DIR, + { + recursive: true, + persistent: true, + }, + async (event, filename) => { + if (!filename) return; + + const full_file_path = path.join(ROOT_DIR, filename); + + if (full_file_path.match(/\/styles$/)) { + global.RECOMPILING = true; + await Bun.sleep(1000); + await fullRebuild({ + msg: `Detected new \`styles\` directory. Rebuilding ...`, + }); + return; + } + + const excluded_match = + /node_modules\/|^public\/|^\.bunext\/|^\.git\/|^dist\/|bun\.lockb$/; + + if (filename.match(excluded_match)) return; + + if (filename.match(/bunext.config\.ts/)) { + await fullRebuild({ + msg: `bunext.config.ts file changed. Rebuilding server ...`, + }); + return; + } + + const target_files_match = /\.(tsx?|jsx?|css)$/; + + if (event !== "rename") { + if (filename.match(target_files_match)) { + if (global.RECOMPILING) return; + global.RECOMPILING = true; + await global.BUNDLER_CTX?.rebuild(); + } + return; + } + + const is_file_of_interest = Boolean( + filename.match(target_files_match), + ); + + if (!is_file_of_interest) { + return; + } + + if (!filename.match(/^src\/pages\/|\.css$/)) return; + if (filename.match(/\/(--|\()/)) return; + + if (global.RECOMPILING) return; + + const action = existsSync(full_file_path) ? "created" : "deleted"; + const type = filename.match(/\.css$/) ? "Sylesheet" : "Page"; + + await fullRebuild({ + msg: `${type} ${action}: ${filename}. Rebuilding ...`, + }); + }, + ); + + global.PAGES_SRC_WATCHER = pages_src_watcher; +} + +async function fullRebuild(params?: { msg?: string }) { + try { + const { msg } = params || {}; + + global.RECOMPILING = true; + + if (msg) { + log.watch(msg); + } + + global.ROUTER.reload(); + + await global.BUNDLER_CTX?.dispose(); + global.BUNDLER_CTX = undefined; + + await allPagesESBuildContextBundler(); + } catch (error: any) { + log.error(error); + } finally { + global.RECOMPILING = false; + } + + if (global.PAGES_SRC_WATCHER) { + global.PAGES_SRC_WATCHER.close(); + watcherEsbuildCTX(); + } +} diff --git a/src/functions/server/watcher.ts b/src/functions/server/watcher.ts index eb66613..2a0dc48 100644 --- a/src/functions/server/watcher.ts +++ b/src/functions/server/watcher.ts @@ -3,7 +3,6 @@ import path from "path"; import grabDirNames from "../../utils/grab-dir-names"; import rebuildBundler from "./rebuild-bundler"; import { log } from "../../utils/log"; -// import rewritePagesModule from "../../utils/rewrite-pages-module"; const { ROOT_DIR } = grabDirNames(); @@ -82,18 +81,15 @@ async function fullRebuild(params?: { msg?: string }) { global.RECOMPILING = true; - // const target_file_paths = global.HMR_CONTROLLERS.map( - // (hmr) => hmr.target_map?.local_path, - // ).filter((f) => typeof f == "string"); - - // await rewritePagesModule(); + const target_file_paths = global.HMR_CONTROLLERS.map( + (hmr) => hmr.target_map?.local_path, + ).filter((f) => typeof f == "string"); if (msg) { log.watch(msg); } - await rebuildBundler(); - // await rebuildBundler({ target_file_paths }); + await rebuildBundler({ target_file_paths }); } catch (error: any) { log.error(error); } finally {