/** * doxa-maps Vite config — per-bundle IIFE build. * WHY? -> !!! EACH BUNDLE ONLY BUNDLES WHAT IT NEEDS BASED ON ITS APP PROFILE :) * Layout: * library/ ← shared library (components, composables, config, etc.; aliased as @map) * plugin-designs// ← bundle source folder * ├── index.html ← Vite html entry (staging page; loads index.js as a module in dev) * ├── index.js ← bundle entry — registers the custom element (globs ./*.vue) * └── *.vue ← profile components at the bundle root (auto-discovered via import.meta.glob('./*.vue')) * app/doxa-maps/ ← build output tree (see below) * * Build mode: each invocation builds ONE bundle (selected via BUNDLE env var) so we can * use IIFE format with inlineDynamicImports. The npm `build` script runs vite once per * plugin-designs// folder, emitting app/doxa-maps//.js. After all * bundles are built, `generate-tree.mjs` writes the manifest + self-contained staging * HTML into the tree and copies the whole doxa-maps/ folder to every destination * (default ./app/ + each line in vite.destination.md). * * Dev mode (`npm run dev`): no BUNDLE selection — Vite serves all bundles at their html * paths under the dev server. */ import { defineConfig, loadEnv } from 'vite' import vue from '@vitejs/plugin-vue' import cssInjectedByJsPlugin from 'vite-plugin-css-injected-by-js' import { autoSpaDevPlugin } from './internal/plugin-gallery/dev-plugin.mjs' import { stripShadowCssCommentsPlugin } from './internal/vite-plugin-strip-shadow-css-comments.mjs' import { fileURLToPath } from 'node:url' import { resolve, dirname } from 'node:path' import { readdirSync, statSync, existsSync, readFileSync } from 'node:fs' const __dirname = dirname(fileURLToPath(import.meta.url)) const profilesDir = resolve(__dirname, 'plugin-designs') // The multi-MFE output folder name is configured in vite.doxa-maps-build-config.json (`name`), // so you can name the output tree (e.g. "doxa-maps") independently of this bundler // folder's own name. Falls back to "doxa-maps" if the file is missing/unset. function readMfeName() { try { const cfg = JSON.parse(readFileSync(resolve(__dirname, 'vite.doxa-maps-build-config.json'), 'utf8')) if (cfg && typeof cfg.name === 'string' && cfg.name.trim()) return cfg.name.trim() } catch { /* fall through to default */ } return 'doxa-maps' } const MFE_NAME = readMfeName() // Default output tree. generate-tree.mjs reads the same name + copies it onward. const OUT_ROOT = resolve(__dirname, 'public', MFE_NAME) function discoverBundles() { if (!existsSync(profilesDir)) return {} const entries = {} for (const name of readdirSync(profilesDir)) { if (name.startsWith('_') || name.startsWith('.')) continue const dir = resolve(profilesDir, name) if (!statSync(dir).isDirectory()) continue const html = resolve(dir, 'index.html') const js = resolve(dir, 'index.js') if (!existsSync(html) || !existsSync(js)) continue entries[name] = { html, js } } return entries } export default defineConfig(({ command, mode }) => { // .env values (VITE_MAPBOX_TOKEN) are NOT on process.env inside this config — // load them explicitly so the dev plugin gallery can hand the token to each map. const env = loadEnv(mode, __dirname, '') const all = discoverBundles() const allNames = Object.keys(all) const target = process.env.BUNDLE const isBuild = command === 'build' if (isBuild && !target) { throw new Error( `[doxa-maps] Set BUNDLE= for build, or use \`npm run build\` (loops all).\n` + `Available: ${allNames.join(', ') || '(none)'}` ) } if (isBuild && !all[target]) { // A plugin-designs// folder was passed as BUNDLE but has no index.html+index.js // entry pair (e.g. a half-authored map, or a folder still being scaffolded). Fail with // a clear, actionable message instead of a `Cannot read properties of undefined` stack. throw new Error( `[doxa-maps] Bundle "${target}" is not buildable: plugin-designs/${target}/ is missing ` + `index.html and/or index.js (the custom-element entry pair every bundle needs).\n` + `Buildable bundles: ${allNames.join(', ') || '(none)'}` ) } const buildEntries = isBuild ? { [target]: all[target].js } : Object.fromEntries(allNames.map(n => [n, all[n].html])) return { // COMPILE-TIME QA GATE (release governance): the QA corner widget references // the maintainer's internal platform and must NOT exist in public // artifacts. Default FALSE strips it from every bundle (dead-branch + // treeshake); an explicitly-flagged platform build sets DOXA_QA=1. define: { __DOXA_QA__: JSON.stringify(process.env.DOXA_QA === '1') }, // Build only: shadow-DOM CSS is injected from useShadowStyles(`…`) literals that // ship verbatim, so their block comments are stripped before the Vue compiler // runs (enforce: 'pre'). Dev keeps the comments. plugins: [ stripShadowCssCommentsPlugin(), // ─── STRUCTURAL shadow-DOM CSS fix (customElement mode) ────────────────── // WHY: every bundle here is a `defineCustomElement(...)` that renders inside a // SHADOW ROOT. In @vitejs/plugin-vue's DEFAULT mode, each SFC