Prepare a Vite micro-app
qiankun v3 loads Vite applications as native ESM. The integration has one path: install the Vite plugin, export the micro-app lifecycles from the entry module, and load the app from the host with loadMicroApp. No UMD wrapper, SystemJS transform, or global lifecycle object is needed.
Starting a new app?
The Agent skill lets a coding agent generate this setup for you. This guide is for adapting an existing React or Vue application.
1. Install and configure the plugin
Install the bundler plugin in the Vite application:
npm install --save-dev @qiankunjs/bundler-plugin@rcAdd qiankun() alongside the framework plugin and use a fixed development port:
import { qiankun } from '@qiankunjs/bundler-plugin/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [react(), qiankun()],
server: {
port: 7101,
strictPort: true,
},
});import { qiankun } from '@qiankunjs/bundler-plugin/vite';
import vue from '@vitejs/plugin-vue';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [vue(), qiankun()],
server: {
port: 7101,
strictPort: true,
},
});The plugin takes no options. It provides the two pieces Vite needs for qiankun:
- Development and preview servers return permissive CORS headers, allowing the host to fetch the HTML entry and module graph.
- Production builds mark the entry module script with the single
entryattribute expected by qiankun.
Import from @qiankunjs/bundler-plugin/vite; the package's bare import is the Webpack plugin.
2. Export native ESM lifecycles
Export bootstrap, mount, and unmount directly from the module referenced by index.html. Create the framework instance in mount, render inside props.container, and destroy it in unmount.
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
declare global {
interface Window {
__POWERED_BY_QIANKUN__?: boolean;
}
}
type MountProps = { container: HTMLElement };
let root: ReactDOM.Root | undefined;
function render(scope: ParentNode) {
const node = scope.querySelector('#root');
if (!node) throw new Error('#root not found');
root = ReactDOM.createRoot(node);
root.render(<App />);
}
export async function bootstrap() {}
export async function mount({ container }: MountProps) {
render(container);
}
export async function unmount() {
root?.unmount();
root = undefined;
}
if (!window.__POWERED_BY_QIANKUN__) {
render(document);
}import { createApp, type App as VueApp } from 'vue';
import App from './App.vue';
declare global {
interface Window {
__POWERED_BY_QIANKUN__?: boolean;
}
}
type MountProps = { container: HTMLElement };
let app: VueApp<Element> | undefined;
function render(scope: ParentNode) {
const node = scope.querySelector('#app');
if (!node) throw new Error('#app not found');
app = createApp(App);
app.mount(node);
}
export async function bootstrap() {}
export async function mount({ container }: MountProps) {
render(container);
}
export async function unmount() {
app?.unmount();
app = undefined;
}
if (!window.__POWERED_BY_QIANKUN__) {
render(document);
}The important parts of this pattern are:
- Native ESM exports are the lifecycle contract. Do not assign the lifecycle object to
window. props.containerbelongs to this micro-app instance. Query#rootor#appinside it instead of using a page-global selector.__POWERED_BY_QIANKUN__prevents the entry from rendering itself when qiankun will callmount; the standalone development server still renders immediately.- Every
mountmust create a usable application, and everyunmountmust completely reverse it. Top-level module code does not run again on remount.
See Micro-app lifecycle and props for the complete contract.
3. Keep index.html as a native module entry
Keep the normal Vite HTML structure with one module entry. The mount-node id must match the selector used by your lifecycle code:
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>You do not need to add entry to the source file. During production builds, the Vite plugin adds it to the generated entry script. The built HTML must contain no more than one script carrying that attribute.
If the application is deployed below a path or on a separate asset origin, configure Vite's base so the URLs emitted into dist/index.html are reachable from the browser.
4. Load it from the host
Pass the Vite server or deployment URL to loadMicroApp, keep the returned handle, and unmount it before removing the container:
import { loadMicroApp } from 'qiankun';
const container = document.getElementById('micro-app-slot');
if (!container) throw new Error('micro-app-slot not found');
const microApp = loadMicroApp({
name: 'account-app',
entry: 'http://localhost:7101/',
container,
props: { accountId: '42' },
});
await microApp.mountPromise;
// When the host view is disposed:
await microApp.unmount();loadMicroApp does not require registerMicroApps or an explicit start() call. React and Vue hosts may instead use their <MicroApp> integrations, which manage the same handle with the component lifecycle.
5. Configure cross-origin deployment
The plugin enables CORS only for Vite's development and preview servers. In production, the server or CDN must allow the host origin to fetch:
- the HTML entry;
- JavaScript modules and dynamically imported chunks;
- CSS, images, and other assets referenced by the app.
Test the final asset URLs, redirects, MIME types, and CORS headers from the host page. If the app requires cookies, a wildcard Access-Control-Allow-Origin is not sufficient; configure an explicit origin, credential-aware headers, and the host's custom fetch together.
6. Verify development and production
- Run the Vite app by itself and confirm the standalone branch renders.
- Run the host, call
loadMicroAppwithhttp://localhost:7101/, and confirm the app renders inside the supplied container. - Call
await microApp.unmount()and thenawait microApp.mount(); confirm there are no duplicated roots, listeners, or stale views. - Run
npm run buildin the Vite app and inspectdist/index.html: exactly one generated module script should have theentryattribute. - Run
npm run preview, point the host at the preview URL, and repeat the mount/unmount check. - Before release, test the deployed entry from every supported browser and host origin. See Native ESM support for browser constraints.
Related
- HTML entry — the entry contract and CORS boundaries
- Native ESM support — observable ESM behavior and compatibility
@qiankunjs/bundler-plugin— plugin reference- Run multiple instances — remounting and cleanup patterns
- Prepare a Webpack app — the Classic-build alternative
