跪拜 Guibai
← All articles
Frontend · Vue.js · Frontend Framework

Build a Hot-Swappable Vue Component System with Vite's Lib Mode

By 锋行天下 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

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.

Summary

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.

Takeaways
Vite's lib mode can package individual Vue components into standalone UMD files, with entry and output name controlled by environment variables.
Externalizing Vue (`external: ['vue']`) prevents duplicate Vue instances and reduces component file size, but requires the host to expose `window.Vue`.
The `vite-plugin-css-injected-by-js` plugin inlines CSS into the JS bundle, so consumers only need to load a single script file.
Components are loaded at runtime by dynamically creating `<script>` tags and reading the component from `window[componentName + 'Component']`.
Using `Promise.all` to wait for all component scripts to load before calling `app.mount()` prevents the 'components disappear on refresh' bug.
Each component's `index.js` exports both an `install` method (for `app.use()`) and the component itself (for direct import).
Conclusions

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.

Concepts & terms
Vite Lib Mode
A Vite build mode designed for packaging libraries (not applications). It outputs formats like UMD, ESM, and CJS, and allows externalizing dependencies like Vue to keep the bundle small.
UMD (Universal Module Definition)
A JavaScript module format that works with CommonJS, AMD, and plain `<script>` tag loading. It exposes the module as a global variable on `window`, making it ideal for dynamic script injection.
Externalization
A build configuration that tells the bundler not to include a dependency (like Vue) in the output file. The dependency is expected to be provided by the host environment at runtime, reducing bundle size and preventing duplicate instances.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗