Build a Hot-Swappable Vue Component System with Vite's Lib Mode
This pattern solves a real pain point for any team shipping desktop apps or complex SPAs where content needs to evolve independently from the shell. It demonstrates a practical, production-grade micro-frontend pattern for Vue 3 that avoids heavy frameworks and relies on Vite's native capabilities.
A desktop content display project built on Electron + Vue3 needed card components that could be updated independently without repackaging the entire application. The solution is a four-layer architecture where a dedicated component development project (Layer 3) handles independent packaging, deployment, and versioning of each component.
Vite's lib mode is the packaging engine. A single configuration file dynamically sets the entry point and output name via environment variables, so running `npm run component abc` packages only the `abc` component into a UMD file. Vue is externalized to avoid duplication, and the `vite-plugin-css-injected-by-js` plugin bundles CSS directly into the JS file, eliminating separate style imports.
On the consumer side, the Electron app dynamically creates `<script>` tags to load the UMD files at runtime. A `Promise.all` pattern ensures all components are registered before the Vue app mounts, preventing the common bug where components disappear on page refresh. The host app must also expose `window.Vue` globally to satisfy the implicit contract with the externalized components.
The implicit `window.Vue` contract between the packaged component and the host app is a clean but fragile coupling — any mismatch in Vue versions could cause subtle runtime errors.
Choosing UMD over ESM for dynamic script loading is a pragmatic trade-off: it trades modern module semantics for universal browser compatibility and simpler loading logic.
This architecture effectively implements a lightweight micro-frontend pattern for Vue 3 without introducing a framework like Module Federation or qiankun.
The decision to inline CSS into JS prioritizes deployment simplicity over caching granularity — every component load re-injects styles, which could impact performance with many components.
The `npm run component <name>` interface is a clever developer experience pattern that could be extended to support parallel builds or dependency-aware build ordering.