import path from "node:path"; import process from "node:process"; import { fileURLToPath } from "node:url"; import builtins from "builtin-modules"; import esbuild from "esbuild"; import sveltePlugin from "esbuild-svelte"; export const root = path.dirname(fileURLToPath(import.meta.url)); export const LIB = path.resolve(root, "../beaver-gateway/ui/src/lib"); const EXTERNAL = [ "obsidian", "electron", "@codemirror/autocomplete", "@codemirror/collab", "@codemirror/commands", "@codemirror/language", "@codemirror/lint", "@codemirror/search", "@codemirror/state", "@codemirror/view", "@lezer/common", "@lezer/highlight", "@lezer/lr", ...builtins, ]; const LIB_IMPORT = /^\$lib\//; const APP_IMPORT = /^\$app\//; const SONNER_IMPORT = /^svelte-sonner$/; const OBSIDIAN_IMPORT = /^obsidian$/; const BARE_IMPORT = /^[^./#]/; function isExternal(specifier) { return ( specifier.startsWith("node:") || EXTERNAL.some( (name) => specifier === name || specifier.startsWith(`${name}/`) ) ); } // The panel is beaver-gateway's ``ui/src/lib``, compiled into this plugin. // Two rules keep it one bundle: ``$lib/*`` points at that directory, and // every bare import - svelte, bits-ui, the icons - resolves from this // plugin's node_modules, never from the gateway's, so there is exactly one // Svelte runtime. ``svelte-sonner`` becomes Obsidian notices; ``$app/*`` // is SvelteKit-only and refused. export function sharedLib({ obsidian = "external" } = {}) { return { name: "beaver-shared-lib", setup(build) { build.onResolve({ filter: LIB_IMPORT }, (args) => build.resolve(`./${args.path.slice("$lib/".length)}`, { kind: args.kind, pluginData: { pinned: true }, resolveDir: LIB, }) ); build.onResolve({ filter: APP_IMPORT }, (args) => ({ errors: [ { text: `${args.path} is SvelteKit-only; the panel must not import it`, }, ], })); build.onResolve({ filter: SONNER_IMPORT }, () => ({ path: path.join(root, "src/shims/sonner.ts"), })); build.onResolve({ filter: OBSIDIAN_IMPORT }, () => obsidian === "external" ? { external: true, path: "obsidian" } : { path: obsidian } ); build.onResolve({ filter: BARE_IMPORT }, (args) => { if (args.pluginData?.pinned) { return; } if (isExternal(args.path)) { return { external: true, path: args.path }; } return build.resolve(args.path, { kind: args.kind, pluginData: { pinned: true }, resolveDir: root, }); }); }, }; } export const svelte = () => sveltePlugin({ compilerOptions: { css: "injected", runes: true }, filterWarnings: (warning) => !warning.filename?.includes("node_modules"), }); const prod = process.argv[2] === "production"; if (process.argv[1] === fileURLToPath(import.meta.url)) { const ctx = await esbuild.context({ bundle: true, conditions: ["svelte", "browser"], entryPoints: ["src/main.ts"], format: "cjs", logLevel: "info", mainFields: ["svelte", "browser", "module", "main"], minify: prod, outfile: "main.js", plugins: [sharedLib(), svelte()], sourcemap: prod ? false : "inline", target: "es2022", treeShaking: true, }); if (prod) { await ctx.rebuild(); await ctx.dispose(); } else { await ctx.watch(); } }