Design philosophy and architectural decisions for demo-showcase. Use when modifying core behavior, adding features, or understanding design rationale.
demo-showcase solves a single problem: "Write only demo scripts, get a complete demo site automatically."
Library developers need demo pages to verify and showcase behavior. This tool eliminates HTML boilerplate by generating everything from JS/TS source files.
demo_*.{js,ts})Successor to webpack-based @masatomakino/gulptask-demo-page. Vite was chosen for:
Templates use plain HTML with {{PLACEHOLDER}} string replacement. No template engine dependency (no EJS, Handlebars, etc.).
Rationale: The replacement targets are fixed and few (TITLE, STYLE, SCRIPT, BODY, PACKAGE_NAME, REPOSITORY, DEMO_PATHS, DEMO_MENU_ITEMS). A template engine adds complexity without benefit for this use case.
| Mode | Purpose | HTML Strategy |
|---|---|---|
| dev | Live preview | Dynamic generation via Vite plugin middleware |
| build | Static output | Staging directory → Vite build → cleanup |
Both modes share the same entry discovery and HTML generation logic (buildIndexHtmlString() in HtmlGenerator.ts) but differ in execution strategy. Dev mode avoids filesystem writes by serving HTML from memory; build mode uses a staging directory because Vite's build API requires physical files.
Two independent mechanisms handle file changes:
| Mechanism | Scope | Trigger |
|---|---|---|
| Vite module graph | All files reachable via import chain from root | Any file in the dependency graph changes |
vite-plugin-full-reload | srcDir/**/*.{js,ts} only | File change in srcDir |
| DemoPlugin watcher | srcDir directory | Demo file added/removed → server restart |
Why full reload instead of HMR: Demo scripts are plain JS/TS modules without HMR API (import.meta.hot). Vite falls back to full page reload for these. The vite-plugin-full-reload plugin ensures explicit reload rather than relying on Vite's fallback behavior.
External module changes: Files imported by demo scripts but located outside srcDir (e.g., library source in src/) are tracked by Vite's module graph. Changes trigger full page reload through Vite's built-in fallback. Files outside project root or in node_modules are not watched.
Build mode creates a temporary directory (os.tmpdir()) rather than writing to the project tree because:
try/finallyThe index page is a sidebar + iframe layout (Pure.css), not a single-page application.
?demo=name) enables deep linking to specific demosindexScript.js is an ES module (export function getDemoNameFromPath), imported by the inline <script type="module"> in index.html and bundled by Vite in build modeIn build mode, HtmlGenerator.copyIndexScript() copies indexScript.js to the staging directory, allowing Vite to resolve the import from the filesystem naturally.
In dev mode, index.html is generated dynamically by DemoPlugin middleware — no physical HTML file exists. Vite extracts the inline <script type="module"> as a virtual module (/index.html?html-proxy&index=0.js) and attempts to resolve ./indexScript.js from that virtual path, which fails because the file is in the template/ directory, not in the project root.
DemoPlugin.resolveId() bridges this gap by mapping ./indexScript.js (when imported from index.html) to the actual template/indexScript.js path. This is a necessary workaround for the dynamic HTML generation architecture — build mode does not need it because the staging directory provides filesystem-based resolution.
The predecessor's --rule option (webpack custom loader) was used in only 1 of 38+ dependent projects (threejs-lab, for webpack-glsl-loader to import .vert/.frag as strings). Vite's built-in ?raw suffix (import shader from "./shader.frag?raw") replaces this use case without any plugin or config. No known use case requires external Vite config injection, so the feature was removed to keep the interface simple.
The --host CLI default is localhost, not 0.0.0.0.
localhost binds to loopback only — no LAN exposure. Safe for users running without container isolation.0.0.0.0 binds to all interfaces — required for Docker port forwarding, but exposes the server to the local network.Users who need container-external access (e.g., DevContainer with -p 0:3456) explicitly pass --host 0.0.0.0. This project's npm run dev includes it because development uses DevContainer isolation.
import imgUrl from "./images/red.png" resolves correctly in both dev and build modesbase: './' enables deployment to any subdirectory (e.g., GitHub Pages docs/demo/)