import * as esbuild from "npm:esbuild@0.20.2";
import { ReactCompilerEsbuildPlugin } from "./reactcompileresbuild.ts";
import { cache } from "npm:esbuild-plugin-cache";
import tailwindconfig from "../tailwind.config.ts";
import tailwindcss from "https://esm.sh/tailwindcss@3";
import postcss from "https://esm.sh/postcss@8";
import autoprefixer from "https://esm.sh/autoprefixer@10";
import { renderToString } from "https://esm.sh/react-dom@19.1.1/server";
import { createElement } from "https://esm.sh/react@19.1.1";
import { Root } from "./browser/landing-shared.tsx";
// helper build function
async function build({
entry,
initialData,
}: {
entry: "index" | "view";
initialData?: {
config: {
inviteOnly: boolean;
//port: number;
did: string;
host: string;
indexPriority?: string[];
};
users: {
did: string;
role: string;
registrationdate: string;
onboardingstatus: string;
pfp?: string;
displayname: string;
handle: string;
}[];
};
}) {
const template = await Deno.readTextFile(
`./shared-landing/template-${entry}.html`
);
const importmap = {
imports: {
"react/jsx-runtime": "https://esm.sh/react@19.1.1/jsx-runtime",
"react/compiler-runtime": "https://esm.sh/react@19.1.1/compiler-runtime",
},
};
const result = await esbuild.build({
entryPoints: [`./shared-landing/browser/landing-${entry}.tsx`],
bundle: true,
format: "esm",
jsx: "transform",
write: false, // keep in memory
loader: { ".tsx": "tsx" },
jsxFactory: "React.createElement",
jsxFragment: "React.Fragment",
platform: "neutral", //"browser",
external: ["react/jsx-runtime", "react/compiler-runtime"],
plugins: [
cache({ importmap, directory: "./cache" }),
ReactCompilerEsbuildPlugin({
filter: /\.tsx?$/,
sourceMaps: true,
runtimeModulePath: "https://esm.sh/react@19.1.1/jsx-runtime",
}),
],
});
const rawcss = await Deno.readTextFile("./shared-landing/app.css");
// @ts-ignore its fiiine
const cssResult = await postcss([
tailwindcss(tailwindconfig),
autoprefixer(),
]).process(rawcss, {
from: "./shared-landing/app.css",
map: false,
});
const js = result.outputFiles[0].text;
const jshash = hashString(js);
const csshash = hashString(cssResult.css);
const html = template.replace(
"",
`
`
);
// const html = template.replace(
// "",
// `
// `
// );
const ssr = renderToString(
createElement(
() =>
Root({
type: entry,
initialData
}),
null
)
);
const ssrhtml = html.replace("", `${ssr}`);
return { js, html: ssrhtml, css: cssResult.css };
}
// public compile function
export async function compile({
target,
initialData
}: {
target: "index" | "view";
initialData?: {
config: {
inviteOnly: boolean;
//port: number;
did: string;
host: string;
indexPriority?: string[];
};
users: {
did: string;
role: string;
registrationdate: string;
onboardingstatus: string;
pfp?: string;
displayname: string;
handle: string;
}[];
};
}) {
return await build({entry: target, initialData});
}
// watch loop
export async function devWatch({target,initialData,onBuild}:{
target: "index" | "view",
initialData?: {
config: {
inviteOnly: boolean;
//port: number;
did: string;
host: string;
indexPriority?: string[];
};
users: {
did: string;
role: string;
registrationdate: string;
onboardingstatus: string;
pfp?: string;
displayname: string;
handle: string;
}[];
},
onBuild: (data: { js: string; html: string; css: string }) => void
}
) {
for await (const event of Deno.watchFs(".")) {
if (event.paths.some((p) => p.endsWith(".tsx"))) {
console.log("Rebuilding bundle…");
const data = await compile({target,initialData});
onBuild(data);
}
}
}
async function hashString(content: string): Promise {
const encoder = new TextEncoder();
const data = encoder.encode(content);
// SHA-256 hash
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
// Convert buffer to hex string
return Array.from(new Uint8Array(hashBuffer))
.map((b) => b.toString(16).padStart(2, "0"))
.join("")
.slice(0, 8); // optional: shorten hash for filenames
}