/** * Build static client assets (admin, scanner, iframe-resizer, embed loader). */ import { denoPlugins } from "@luca/esbuild-deno-loader"; import { fromFileUrl, resolve } from "@std/path"; import * as esbuild from "esbuild"; import * as sass from "sass"; import { projectRoot } from "./project-root.ts"; import { withStaticAssetBuildLock } from "./static-assets/build-lock.ts"; import { writeStaticAssetManifest } from "./static-assets/cache.ts"; import { fileInputs } from "./static-assets/inputs.ts"; import { CSS_ENTRY, STATIC_ASSET_OUTFILES, staticAssetOutputFiles, } from "./static-assets/outfiles.ts"; import { withGeneratedOutputRollback } from "./static-assets/output-rollback.ts"; import { createStaticAssetBuild, disposeStaticBundleContexts, rebuildStaticBundleContexts, type StaticAssetBuild, type StaticBundle, settleAll, } from "./static-assets/session.ts"; /** * deno.json path the deno-loader uses to resolve every bundle's imports — * the `#` import map, npm/jsr specifiers, and each package's browser entry — * exactly as the edge build (`build-edge.ts`) does. This replaces the * per-package hand-rolled resolve plugins this file used to carry. */ const configPath = fromFileUrl(new URL("../deno.json", import.meta.url)); /** * The loader ships its own esbuild type declarations, whose PluginBuild type can * drift from the npm esbuild package even though the runtime plugin shape is the * same. Keep that incompatibility at this adapter boundary. */ const denoLoaderPlugins = (): esbuild.Plugin[] => denoPlugins({ configPath }) as unknown as esbuild.Plugin[]; /** Compile the SCSS stylesheet to the served CSS file, reporting every * stylesheet it read so the build cache can watch them all. */ const buildCss = async (quiet = false): Promise => { const { css, loadedUrls } = sass.compile(CSS_ENTRY, { style: "compressed" }); await Deno.writeTextFile(STATIC_ASSET_OUTFILES.css, css); if (!quiet) console.log(`CSS build complete: ${STATIC_ASSET_OUTFILES.css}`); return loadedUrls .filter((url) => url.protocol === "file:") .map((url) => fromFileUrl(url)); }; /** A browser bundle built the standard way (bundled, minified, IIFE, deno * loader plugins). `extra` overrides that default per bundle — the order * widget needs ESM, the iframe-resizer child needs a licence banner. */ const browserBundle = ( label: string, entryPoint: string, outfile: string, extra: Omit = {}, ): StaticBundle => ({ label, options: { bundle: true, entryPoints: [entryPoint], format: "iife", minify: true, outfile, platform: "browser", plugins: denoLoaderPlugins(), ...extra, }, }); export const STATIC_JS_BUNDLES: StaticBundle[] = [ // ESM (not IIFE): the scanner is loaded as `