changed build workflow – for auth it's required to have better control over html and assets

This commit is contained in:
dswbx
2024-11-27 16:19:37 +01:00
parent d36c4b07e0
commit c31bc2ccb0
21 changed files with 559 additions and 285 deletions

View File

@@ -5,7 +5,8 @@ export default {
connection: {
type: "libsql",
config: {
url: "http://localhost:8080"
//url: "http://localhost:8080"
url: ":memory:"
}
}
}

View File

@@ -1,42 +0,0 @@
import process from "node:process";
import { $ } from "bun";
import * as esbuild from "esbuild";
import type { BuildOptions } from "esbuild";
const isDev = process.env.NODE_ENV !== "production";
const metafile = true;
const sourcemap = false;
const config: BuildOptions = {
entryPoints: ["worker.ts"],
bundle: true,
format: "esm",
external: ["__STATIC_CONTENT_MANIFEST", "@xyflow/react"],
platform: "browser",
conditions: ["worker", "browser"],
target: "es2022",
sourcemap,
metafile,
minify: !isDev,
loader: {
".html": "copy"
},
outfile: "dist/worker.js"
};
const dist = config.outfile!.split("/")[0];
if (!isDev) {
await $`rm -rf ${dist}`;
}
const result = await esbuild.build(config);
if (result.metafile) {
console.log("writing metafile to", `${dist}/meta.json`);
await Bun.write(`${dist}/meta.json`, JSON.stringify(result.metafile!));
}
if (!isDev) {
await $`gzip ${dist}/worker.js -c > ${dist}/worker.js.gz`;
}

257
app/build.esbuild.ts Normal file
View File

@@ -0,0 +1,257 @@
import { $, type Subprocess } from "bun";
import * as esbuild from "esbuild";
import postcss from "esbuild-postcss";
import { entryOutputMeta } from "./internal/esbuild.entry-output-meta.plugin";
import { guessMimeType } from "./src/media/storage/mime-types";
const args = process.argv.slice(2);
const watch = args.includes("--watch");
const minify = args.includes("--minify");
const types = args.includes("--types");
const sourcemap = args.includes("--sourcemap");
type BuildOptions = esbuild.BuildOptions & { name: string };
const baseOptions: Partial<Omit<esbuild.BuildOptions, "plugins">> & { plugins?: any[] } = {
minify,
sourcemap,
metafile: true,
format: "esm",
drop: ["console", "debugger"],
loader: {
".svg": "dataurl"
},
define: {
__isDev: "0"
}
};
// @ts-ignore
type BuildFn = (format?: "esm" | "cjs") => BuildOptions;
// build BE
const builds: Record<string, BuildFn> = {
backend: (format = "esm") => ({
...baseOptions,
name: `backend ${format}`,
entryPoints: [
"src/index.ts",
"src/data/index.ts",
"src/core/index.ts",
"src/core/utils/index.ts",
"src/ui/index.ts",
"src/ui/main.css"
],
outdir: "dist",
outExtension: { ".js": format === "esm" ? ".js" : ".cjs" },
platform: "browser",
splitting: false,
bundle: true,
plugins: [postcss()],
//target: "es2022",
format
}),
/*components: (format = "esm") => ({
...baseOptions,
name: `components ${format}`,
entryPoints: ["src/ui/index.ts", "src/ui/main.css"],
outdir: "dist/ui",
outExtension: { ".js": format === "esm" ? ".js" : ".cjs" },
format,
platform: "browser",
splitting: false,
//target: "es2022",
bundle: true,
//external: ["react", "react-dom", "@tanstack/react-query-devtools"],
plugins: [postcss()],
loader: {
".svg": "dataurl",
".js": "jsx"
}
}),*/
static: (format = "esm") => ({
...baseOptions,
name: `static ${format}`,
entryPoints: ["src/ui/main.tsx", "src/ui/main.css"],
entryNames: "[dir]/[name]-[hash]",
outdir: "dist/static",
outExtension: { ".js": format === "esm" ? ".js" : ".cjs" },
platform: "browser",
bundle: true,
splitting: true,
inject: ["src/ui/inject.js"],
target: "es2022",
format,
loader: {
".svg": "dataurl",
".js": "jsx"
},
define: {
__isDev: "0",
"process.env.NODE_ENV": '"production"'
},
chunkNames: "chunks/[name]-[hash]",
plugins: [
postcss(),
entryOutputMeta(async (info) => {
const manifest: Record<string, object> = {};
const toAsset = (output: string) => {
const name = output.split("/").pop()!;
return {
name,
path: output,
mime: guessMimeType(name)
};
};
for (const { output, meta } of info) {
manifest[meta.entryPoint as string] = toAsset(output);
if (meta.cssBundle) {
manifest["src/ui/main.css"] = toAsset(meta.cssBundle);
}
}
const manifest_file = "dist/static/manifest.json";
await Bun.write(manifest_file, JSON.stringify(manifest, null, 2));
console.log(`Manifest written to ${manifest_file}`, manifest);
})
]
})
};
function adapter(adapter: string, overrides: Partial<esbuild.BuildOptions> = {}): BuildOptions {
return {
...baseOptions,
name: `adapter ${adapter} ${overrides?.format === "cjs" ? "cjs" : "esm"}`,
entryPoints: [`src/adapter/${adapter}`],
platform: "neutral",
outfile: `dist/adapter/${adapter}/index.${overrides?.format === "cjs" ? "cjs" : "js"}`,
external: [
"cloudflare:workers",
"@hono*",
"hono*",
"bknd*",
"*.html",
"node*",
"react*",
"next*",
"libsql",
"@libsql*"
],
splitting: false,
treeShaking: true,
bundle: true,
...overrides
};
}
const adapters = [
adapter("vite", { platform: "node" }),
adapter("cloudflare"),
adapter("nextjs", { platform: "node", format: "esm" }),
adapter("nextjs", { platform: "node", format: "cjs" }),
adapter("remix", { format: "esm" }),
adapter("remix", { format: "cjs" }),
adapter("bun"),
adapter("node", { platform: "node", format: "esm" }),
adapter("node", { platform: "node", format: "cjs" })
];
const collect = [
builds.static(),
builds.backend(),
//builds.components(),
builds.backend("cjs"),
//builds.components("cjs"),
...adapters
];
if (watch) {
const _state: {
timeout: Timer | undefined;
cleanup: Subprocess | undefined;
building: Subprocess | undefined;
} = {
timeout: undefined,
cleanup: undefined,
building: undefined
};
async function rebuildTypes() {
if (!types) return;
if (_state.timeout) {
clearTimeout(_state.timeout);
if (_state.cleanup) _state.cleanup.kill();
if (_state.building) _state.building.kill();
}
_state.timeout = setTimeout(async () => {
_state.cleanup = Bun.spawn(["bun", "clean:types"], {
onExit: () => {
_state.cleanup = undefined;
_state.building = Bun.spawn(["bun", "build:types"], {
onExit: () => {
_state.building = undefined;
console.log("Types rebuilt");
}
});
}
});
}, 1000);
}
for (const { name, ...build } of collect) {
const ctx = await esbuild.context({
...build,
plugins: [
...(build.plugins ?? []),
{
name: "rebuild-notify",
setup(build) {
build.onEnd((result) => {
console.log(`rebuilt ${name} with ${result.errors.length} errors`);
rebuildTypes();
});
}
}
]
});
ctx.watch();
}
} else {
await $`rm -rf dist`;
async function _build() {
let i = 0;
const count = collect.length;
for await (const { name, ...build } of collect) {
await esbuild.build({
...build,
plugins: [
...(build.plugins || []),
{
name: "progress",
setup(build) {
i++;
build.onEnd((result) => {
const errors = result.errors.length;
const from = String(i).padStart(String(count).length);
console.log(`[${from}/${count}] built ${name} with ${errors} errors`);
});
}
}
]
});
}
console.log("All builds complete");
}
async function _buildtypes() {
if (!types) return;
Bun.spawn(["bun", "build:types"], {
onExit: () => {
console.log("Types rebuilt");
}
});
}
await Promise.all([_build(), _buildtypes()]);
}

175
app/build.ts Normal file
View File

@@ -0,0 +1,175 @@
import { $ } from "bun";
import * as esbuild from "esbuild";
import postcss from "esbuild-postcss";
import * as tsup from "tsup";
import { guessMimeType } from "./src/media/storage/mime-types";
const args = process.argv.slice(2);
const watch = args.includes("--watch");
const minify = args.includes("--minify");
const types = args.includes("--types");
const sourcemap = args.includes("--sourcemap");
await $`rm -rf dist`;
if (types) {
Bun.spawn(["bun", "build:types"], {
onExit: () => {
console.log("Types built");
}
});
}
/**
* Build static assets
* Using esbuild because tsup doesn't include "react"
*/
const result = await esbuild.build({
minify,
sourcemap,
entryPoints: ["src/ui/main.tsx"],
entryNames: "[dir]/[name]-[hash]",
outdir: "dist/static",
platform: "browser",
bundle: true,
splitting: true,
metafile: true,
drop: ["console", "debugger"],
inject: ["src/ui/inject.js"],
target: "es2022",
format: "esm",
plugins: [postcss()],
loader: {
".svg": "dataurl",
".js": "jsx"
},
define: {
__isDev: "0",
"process.env.NODE_ENV": '"production"'
},
chunkNames: "chunks/[name]-[hash]"
});
// Write manifest
{
const manifest: Record<string, object> = {};
const toAsset = (output: string) => {
const name = output.split("/").pop()!;
return {
name,
path: output,
mime: guessMimeType(name)
};
};
const info = Object.entries(result.metafile.outputs)
.filter(([, meta]) => {
return meta.entryPoint && meta.entryPoint === "src/ui/main.tsx";
})
.map(([output, meta]) => ({ output, meta }));
for (const { output, meta } of info) {
manifest[meta.entryPoint as string] = toAsset(output);
if (meta.cssBundle) {
manifest["src/ui/main.css"] = toAsset(meta.cssBundle);
}
}
const manifest_file = "dist/static/manifest.json";
await Bun.write(manifest_file, JSON.stringify(manifest, null, 2));
console.log(`Manifest written to ${manifest_file}`, manifest);
}
/**
* Building backend and general API
*/
await tsup.build({
minify,
sourcemap,
watch,
entry: ["src/index.ts", "src/data/index.ts", "src/core/index.ts", "src/core/utils/index.ts"],
outDir: "dist",
external: ["bun:test"],
metafile: true,
platform: "browser",
format: ["esm", "cjs"],
splitting: false,
loader: {
".svg": "dataurl"
}
});
/**
* Building UI for direct imports
*/
await tsup.build({
minify,
sourcemap,
watch,
entry: ["src/ui/index.ts", "src/ui/client/index.ts", "src/ui/main.css"],
outDir: "dist/ui",
external: ["bun:test"],
metafile: true,
platform: "browser",
format: ["esm", "cjs"],
splitting: true,
loader: {
".svg": "dataurl"
},
onSuccess: async () => {
console.log("--- ui built");
},
esbuildOptions: (options) => {
options.chunkNames = "chunks/[name]-[hash]";
}
});
/**
* Building adapters
*/
function baseConfig(adapter: string): tsup.Options {
return {
minify,
sourcemap,
watch,
entry: [`src/adapter/${adapter}`],
format: ["esm"],
platform: "neutral",
outDir: `dist/adapter/${adapter}`,
define: {
__isDev: "0"
},
external: [
/^cloudflare*/,
/^@?(hono|libsql).*?/,
/^(bknd|react|next|node).*?/,
/.*\.(html)$/
],
metafile: true,
splitting: false,
treeshake: true
};
}
await tsup.build({
...baseConfig("vite"),
platform: "node"
});
await tsup.build({
...baseConfig("cloudflare")
});
await tsup.build({
...baseConfig("nextjs"),
format: ["esm", "cjs"],
platform: "node"
});
await tsup.build({
...baseConfig("remix"),
format: ["esm", "cjs"]
});
await tsup.build({
...baseConfig("bun")
});

View File

@@ -1,13 +0,0 @@
<!doctype html>
<html lang="en" class="light">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<title>BKND</title>
</head>
<body>
<div id="app"></div>
<!-- BKND_CONTEXT -->
<script type="module" src="/src/ui/main.tsx"></script>
</body>
</html>

View File

@@ -0,0 +1,33 @@
import type { Metafile, Plugin } from "esbuild";
export const entryOutputMeta = (
onComplete?: (
outputs: {
output: string;
meta: Metafile["outputs"][string];
}[]
) => void | Promise<void>
): Plugin => ({
name: "report-entry-output-plugin",
setup(build) {
build.initialOptions.metafile = true; // Ensure metafile is enabled
build.onEnd(async (result) => {
console.log("result", result);
if (result?.metafile?.outputs) {
const entries = build.initialOptions.entryPoints! as string[];
const outputs = Object.entries(result.metafile.outputs)
.filter(([, meta]) => {
return meta.entryPoint && entries.includes(meta.entryPoint);
})
.map(([output, meta]) => ({ output, meta }));
if (outputs.length === 0) {
return;
}
await onComplete?.(outputs);
}
});
}
});

View File

@@ -5,18 +5,16 @@
"bin": "./dist/cli/index.js",
"version": "0.0.13",
"scripts": {
"build:all": "rm -rf dist && bun build:css && bun run build && bun build:vite && bun build:adapters && bun build:cli",
"build:all": "bun run build && bun run build:cli",
"dev": "vite",
"test": "ALL_TESTS=1 bun test --bail",
"build": "bun tsup && bun build:types",
"watch": "bun tsup --watch --onSuccess 'bun run build:types'",
"build": "bun run build.ts --minify --types",
"watch": "bun run build.ts --types --watch",
"types": "bun tsc --noEmit",
"clean:types": "find ./dist -name '*.d.ts' -delete && rm -f ./dist/tsconfig.tsbuildinfo",
"build:types": "tsc --emitDeclarationOnly",
"build:css": "bun tailwindcss -i ./src/ui/styles.css -o ./dist/styles.css",
"watch:css": "bun tailwindcss --watch -i ./src/ui/styles.css -o ./dist/styles.css",
"build:vite": "NODE_ENV=production vite build",
"build:adapters": "bun tsup.adapters.ts --minify",
"watch:adapters": "bun tsup.adapters.ts --watch",
"build:css": "bun tailwindcss -i src/ui/main.css -o ./dist/static/styles.css",
"watch:css": "bun tailwindcss --watch -i src/ui/main.css -o ./dist/styles.css",
"updater": "bun x npm-check-updates -ui",
"build:cli": "bun build src/cli/index.ts --target node --outdir dist/cli --minify",
"cli": "LOCAL=1 bun src/cli/index.ts"
@@ -77,6 +75,7 @@
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"autoprefixer": "^10.4.20",
"esbuild-postcss": "^0.0.4",
"node-fetch": "^3.3.2",
"openapi-types": "^12.1.3",
"postcss": "^8.4.47",
@@ -88,20 +87,6 @@
"vite-plugin-static-copy": "^2.0.0",
"vite-tsconfig-paths": "^5.0.1"
},
"tsup": {
"entry": ["src/index.ts", "src/ui/index.ts", "src/data/index.ts", "src/core/index.ts", "src/core/utils/index.ts"],
"minify": true,
"outDir": "dist",
"external": ["bun:test", "bknd/dist/manifest.json"],
"sourcemap": true,
"metafile": true,
"platform": "browser",
"format": ["esm", "cjs"],
"splitting": true,
"loader": {
".svg": "dataurl"
}
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
@@ -120,6 +105,11 @@
"import": "./dist/ui/index.js",
"require": "./dist/ui/index.cjs"
},
"./client": {
"types": "./dist/ui/client/index.d.ts",
"import": "./dist/ui/client/index.js",
"require": "./dist/ui/client/index.cjs"
},
"./data": {
"types": "./dist/data/index.d.ts",
"import": "./dist/data/index.js",
@@ -170,10 +160,8 @@
"import": "./dist/adapter/node/index.js",
"require": "./dist/adapter/node/index.cjs"
},
"./dist/static/manifest.json": "./dist/static/.vite/manifest.json",
"./dist/styles.css": "./dist/styles.css",
"./dist/index.html": "./dist/static/index.html",
"./dist/manifest.json": "./dist/static/.vite/manifest.json"
"./dist/styles.css": "./dist/ui/main.css",
"./dist/manifest.json": "./dist/static/manifest.json"
},
"files": [
"dist",

View File

@@ -1,59 +1,34 @@
import { readFile } from "node:fs/promises";
import { serveStatic } from "@hono/node-server/serve-static";
import type { BkndConfig } from "bknd";
import { App } from "bknd";
async function getHtml() {
return readFile("index.html", "utf8");
}
function addViteScripts(html: string) {
return html.replace(
"<head>",
`<script type="module">
import RefreshRuntime from "/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
</script>
<script type="module" src="/@vite/client"></script>
`
);
}
function createApp(config: BkndConfig, env: any) {
const create_config = typeof config.app === "function" ? config.app(env) : config.app;
return App.create(create_config);
}
function setAppBuildListener(app: App, config: BkndConfig, html: string) {
function setAppBuildListener(app: App, config: BkndConfig, html?: string) {
app.emgr.on(
"app-built",
async () => {
await config.onBuilt?.(app);
app.registerAdminController();
app.module.server.client.get("/assets/!*", serveStatic({ root: "./" }));
if (config.setAdminHtml) {
app.registerAdminController({ html, forceDev: true });
app.module.server.client.get("/assets/*", serveStatic({ root: "./" }));
}
},
"sync"
);
}
export async function serveFresh(config: BkndConfig, _html?: string) {
let html = _html;
if (!html) {
html = await getHtml();
}
html = addViteScripts(html);
return {
async fetch(request: Request, env: any, ctx: ExecutionContext) {
const app = createApp(config, env);
setAppBuildListener(app, config, html);
setAppBuildListener(app, config, _html);
await app.build();
//console.log("routes", app.module.server.client.routes);
return app.fetch(request, env, ctx);
}
};
@@ -61,18 +36,11 @@ export async function serveFresh(config: BkndConfig, _html?: string) {
let app: App;
export async function serveCached(config: BkndConfig, _html?: string) {
let html = _html;
if (!html) {
html = await getHtml();
}
html = addViteScripts(html);
return {
async fetch(request: Request, env: any, ctx: ExecutionContext) {
if (!app) {
app = createApp(config, env);
setAppBuildListener(app, config, html);
setAppBuildListener(app, config, _html);
await app.build();
}

View File

@@ -28,7 +28,7 @@ export async function serveStatic(server: Platform): Promise<MiddlewareHandler>
}
export async function attachServeStatic(app: any, platform: Platform) {
app.module.server.client.get("/assets/*", await serveStatic(platform));
app.module.server.client.get("/*", await serveStatic(platform));
}
export async function startServer(server: Platform, app: any, options: { port: number }) {

View File

@@ -54,7 +54,7 @@ async function makeApp(config: MakeAppConfig) {
"app-built",
async () => {
await attachServeStatic(app, config.server?.platform ?? "node");
app.registerAdminController({ html: await getHtml() });
app.registerAdminController();
if (config.onBuilt) {
await config.onBuilt(app);
@@ -75,7 +75,7 @@ export async function makeConfigApp(config: BkndConfig, platform?: Platform) {
"app-built",
async () => {
await attachServeStatic(app, platform ?? "node");
app.registerAdminController({ html: await getHtml() });
app.registerAdminController();
if (config.onBuilt) {
await config.onBuilt(app);

View File

@@ -7,20 +7,12 @@ import { Hono } from "hono";
import { html } from "hono/html";
import { Fragment } from "hono/jsx";
import * as SystemPermissions from "modules/permissions";
import type { Manifest } from "vite";
const viteInject = `
import RefreshRuntime from "/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true
`;
const htmlBkndContextReplace = "<!-- BKND_CONTEXT -->";
export type AdminControllerOptions = {
html?: string;
viteManifest?: Manifest;
forceDev?: boolean;
};
export class AdminController implements ClassController {
@@ -114,44 +106,36 @@ export class AdminController implements ClassController {
}
console.warn(
"Custom HTML needs to include '<!-- BKND_CONTEXT -->' to inject BKND context"
`Custom HTML needs to include '${htmlBkndContextReplace}' to inject BKND context`
);
return this.options.html as string;
}
const configs = this.app.modules.configs();
const isProd = !isDebug() && !this.options.forceDev;
// @todo: implement guard redirect once cookie sessions arrive
const assets = {
js: "main.js",
css: "styles.css"
};
const isProd = !isDebug();
let script: string | undefined;
let css: string[] = [];
// @todo: check why nextjs imports manifest, it's not required
if (isProd) {
const manifest: Manifest = this.options.viteManifest
? this.options.viteManifest
: isProd
? // @ts-ignore cases issues when building types
await import("bknd/dist/manifest.json", { assert: { type: "json" } }).then(
(m) => m.default
)
: {};
//console.log("manifest", manifest, manifest["index.html"]);
const entry = Object.values(manifest).find((f: any) => f.isEntry === true);
if (!entry) {
// do something smart
return;
try {
// @ts-ignore
const manifest = await import("bknd/dist/manifest.json", {
assert: { type: "json" }
}).then((m) => m.default);
assets.js = manifest["src/ui/main.tsx"].name;
assets.css = manifest["src/ui/main.css"].name;
} catch (e) {
console.error("Error loading manifest", e);
}
script = "/" + entry.file;
css = entry.css?.map((c: string) => "/" + c) ?? [];
}
return (
<Fragment>
{/* dnd complains otherwise */}
{html`<!doctype html>`}
{html`<!DOCTYPE html>`}
<html lang="en" class={configs.server.admin.color_scheme ?? "light"}>
<head>
<meta charset="UTF-8" />
@@ -162,14 +146,21 @@ export class AdminController implements ClassController {
<title>BKND</title>
{isProd ? (
<Fragment>
<script type="module" CrossOrigin src={script} />
{css.map((c) => (
<link rel="stylesheet" CrossOrigin href={c} key={c} />
))}
<script type="module" CrossOrigin src={"/" + assets?.js} />
<link rel="stylesheet" crossOrigin href={"/" + assets?.css} />
</Fragment>
) : (
<Fragment>
<script type="module" dangerouslySetInnerHTML={{ __html: viteInject }} />
<script
type="module"
dangerouslySetInnerHTML={{
__html: `import RefreshRuntime from "/@react-refresh"
RefreshRuntime.injectIntoGlobalHook(window)
window.$RefreshReg$ = () => {}
window.$RefreshSig$ = () => (type) => type
window.__vite_plugin_react_preamble_installed__ = true`
}}
/>
<script type="module" src={"/@vite/client"} />
</Fragment>
)}

View File

@@ -2,3 +2,4 @@ export { ClientProvider, useClient, useBaseUrl } from "./ClientProvider";
export { BkndProvider, useBknd } from "./BkndProvider";
export { useAuth } from "./schema/auth/use-auth";
export { Api } from "../../Api";

5
app/src/ui/inject.js Normal file
View File

@@ -0,0 +1,5 @@
// react shim
import React from "react";
import ReactDOM from "react-dom/client";
export { React, ReactDOM };

View File

@@ -3,7 +3,6 @@
@import "@mantine/core/styles.css";
@import '@mantine/notifications/styles.css';
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,6 +1,6 @@
import React, { StrictMode } from "react";
import ReactDOM from "react-dom/client";
import "./styles.css";
import * as React from "react";
import * as ReactDOM from "react-dom/client";
import "./main.css";
import Admin from "./Admin";
@@ -13,9 +13,9 @@ const rootElement = document.getElementById("app")!;
if (!rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<React.StrictMode>
<ClientApp />
</StrictMode>
</React.StrictMode>
);
}

View File

@@ -35,5 +35,5 @@
}
},
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./env.d.ts"],
"exclude": ["node_modules", "./dist/**/*", "../examples/bun"]
}
"exclude": ["node_modules", "dist/**/*", "../examples/bun"]
}

View File

@@ -1,63 +0,0 @@
import { type Options, build } from "tsup";
const args = process.argv.slice(2);
const watch = args.includes("--watch");
const minify = args.includes("--minify");
function baseConfig(adapter: string): Options {
return {
entry: [`src/adapter/${adapter}`],
format: ["esm"],
platform: "neutral",
minify: false,
outDir: `dist/adapter/${adapter}`,
watch,
define: {
__isDev: "0"
},
external: [
"cloudflare:workers",
/^@?hono.*?/,
/^bknd.*?/,
/.*\.html$/,
/^node.*/,
/^react.*?/
],
metafile: true,
splitting: false,
treeshake: true
};
}
await build({
...baseConfig("vite"),
platform: "node"
});
await build({
...baseConfig("cloudflare")
});
await build({
...baseConfig("nextjs"),
format: ["esm", "cjs"],
platform: "node",
external: [...baseConfig("nextjs").external!, /^next.*/]
});
await build({
...baseConfig("remix"),
format: ["esm", "cjs"]
});
await build({
...baseConfig("bun"),
external: [/^hono.*?/, /^bknd.*?/, "node:path"]
});
await build({
...baseConfig("node"),
format: ["esm", "cjs"],
platform: "node"
});

View File

@@ -1,7 +1,6 @@
import devServer from "@hono/vite-dev-server";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import { viteStaticCopy } from "vite-plugin-static-copy";
import { defineConfig, loadEnv } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
// https://vitejs.dev/config/
@@ -45,18 +44,5 @@ export default defineConfig(async () => {
};
}
return {
define: {
__isDev: "0"
},
publicDir: "./src/ui/assets",
build: {
manifest: true,
outDir: "dist/static"
/*rollupOptions: { // <-- use this to not require index.html
input: "./src/ui/main.tsx"
}*/
},
plugins: [react(), tsconfigPaths()]
} as any;
throw new Error("Don't use vite for building in production");
});

View File

@@ -1,6 +1,6 @@
import { serveStatic } from "@hono/node-server/serve-static";
import { createClient } from "@libsql/client/node";
import { App, type BkndConfig, type CreateAppConfig } from "./src";
import { App } from "./src";
import { LibsqlConnection } from "./src/data";
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
import { registries } from "./src/modules/registries";
@@ -10,41 +10,30 @@ registries.media.add("local", {
schema: StorageLocalAdapter.prototype.getSchema()
});
const connection = new LibsqlConnection(
createClient({
url: "file:.db/new.db"
})
);
function createApp(config: BkndConfig, env: any) {
const create_config = typeof config.app === "function" ? config.app(env) : config.app;
return App.create(create_config as CreateAppConfig);
const credentials = {
url: import.meta.env.VITE_DB_URL!,
authToken: import.meta.env.VITE_DB_TOKEN!
};
if (!credentials.url) {
throw new Error("Missing VITE_DB_URL env variable. Add it to .env file");
}
export async function serveFresh(config: BkndConfig) {
return {
async fetch(request: Request, env: any) {
const app = createApp(config, env);
const connection = new LibsqlConnection(createClient(credentials));
app.emgr.on(
"app-built",
async () => {
await config.onBuilt?.(app as any);
app.registerAdminController();
app.module.server.client.get("/assets/*", serveStatic({ root: "./" }));
},
"sync"
);
await app.build();
export default {
async fetch(request: Request) {
const app = App.create({ connection });
return app.fetch(request, env);
}
};
}
app.emgr.on(
"app-built",
async () => {
app.registerAdminController({ forceDev: true });
app.module.server.client.get("/assets/*", serveStatic({ root: "./" }));
},
"sync"
);
await app.build();
export default await serveFresh({
app: {
connection
},
setAdminHtml: true
});
return app.fetch(request);
}
};