docs/ecosystem/vue.md
The official Vue bindings for qiankun provide a declarative way to integrate micro applications into your Vue main application. The @qiankunjs/vue package offers a powerful <MicroApp /> component with Vue 2/3 compatibility, composition API support, and slot-based customization.
npm install @qiankunjs/vue
Requirements:
@vue/composition-api<template>
<div class="main-app">
<h1>Main Application</h1>
<MicroApp
name="dashboard"
entry="//localhost:8080"
/>
</div>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
</script>
<template>
<div class="main-app">
<h1>Main Application</h1>
<micro-app
name="dashboard"
entry="//localhost:8080"
/>
</div>
</template>
<script>
import { MicroApp } from '@qiankunjs/vue';
export default {
components: {
MicroApp
}
}
</script>
<template>
<MicroApp
name="dashboard"
entry="//localhost:8080"
auto-set-loading
/>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
</script>
<template>
<MicroApp
name="dashboard"
entry="//localhost:8080"
auto-set-loading
auto-capture-error
/>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
</script>
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | ✅ | - | Unique name for the micro application |
entry | string | ✅ | - | Entry URL of the micro application |
autoSetLoading | boolean | ❌ | false | Automatically manage loading state |
autoCaptureError | boolean | ❌ | false | Automatically handle errors |
className | string | ❌ | undefined | CSS class for the micro app container |
wrapperClassName | string | ❌ | undefined | CSS class for the wrapper (when using slots) |
appProps | Record<string, any> | ❌ | undefined | Props passed to the micro application |
settings | AppConfiguration | ❌ | {} | qiankun configuration options |
lifeCycles | LifeCycles | ❌ | undefined | Lifecycle hooks |
| Slot | Description | Parameters |
|---|---|---|
loader | Custom loading component | { loading: boolean } |
errorBoundary | Custom error component | { error: Error } |
<template>
<MicroApp name="dashboard" entry="//localhost:8080">
<template #loader="{ loading }">
<div v-if="loading" class="custom-loader">
<div class="spinner"></div>
<p>Loading micro application...</p>
</div>
</template>
</MicroApp>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
</script>
<style scoped>
.custom-loader {
display: flex;
flex-direction: column;
align-items: center;
padding: 50px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
<template>
<MicroApp name="dashboard" entry="//localhost:8080">
<template #error-boundary="{ error }">
<div class="error-container">
<h3>🚨 Application Error</h3>
<p>{{ error.message }}</p>
<button @click="handleRetry">Retry</button>
</div>
</template>
</MicroApp>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
const handleRetry = () => {
window.location.reload();
};
</script>
<style scoped>
.error-container {
padding: 20px;
background: #fee;
border: 1px solid #fcc;
border-radius: 4px;
text-align: center;
}
button {
margin-top: 10px;
padding: 8px 16px;
background: #e74c3c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
<template>
<MicroApp
name="dashboard"
entry="//localhost:8080"
class-name="micro-app-container"
wrapper-class-name="micro-app-wrapper"
auto-set-loading
/>
</template>
<style scoped>
:deep(.micro-app-wrapper) {
border: 1px solid #e8e8e8;
border-radius: 6px;
overflow: hidden;
}
:deep(.micro-app-container) {
min-height: 400px;
background: #fafafa;
}
</style>
<template>
<div class="multi-app-container">
<div class="tabs">
<button
v-for="tab in tabs"
:key="tab.key"
:class="{ active: activeTab === tab.key }"
@click="activeTab = tab.key"
>
{{ tab.label }}
</button>
</div>
<div class="tab-content">
<MicroApp
v-if="activeTab === 'dashboard'"
name="dashboard"
entry="//localhost:8080"
auto-set-loading
/>
<MicroApp
v-else-if="activeTab === 'analytics'"
name="analytics"
entry="//localhost:8081"
auto-set-loading
/>
<MicroApp
v-else-if="activeTab === 'settings'"
name="settings"
entry="//localhost:8082"
auto-set-loading
/>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { MicroApp } from '@qiankunjs/vue';
const activeTab = ref('dashboard');
const tabs = [
{ key: 'dashboard', label: 'Dashboard' },
{ key: 'analytics', label: 'Analytics' },
{ key: 'settings', label: 'Settings' }
];
</script>
<style scoped>
.tabs {
display: flex;
border-bottom: 1px solid #ccc;
}
.tabs button {
padding: 10px 20px;
border: none;
background: none;
cursor: pointer;
}
.tabs button.active {
background: #007bff;
color: white;
}
.tab-content {
padding: 20px 0;
}
</style>
<template>
<div>
<div v-if="!user">
<p>Please log in to continue</p>
<button @click="login">Login</button>
</div>
<div v-else>
<button @click="toggleMicroApp">
{{ showMicroApp ? 'Hide' : 'Show' }} Micro App
</button>
<MicroApp
v-if="showMicroApp"
name="protected-app"
entry="//localhost:8080"
:app-props="{
userId: user.id,
permissions: user.permissions
}"
auto-set-loading
auto-capture-error
/>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { MicroApp } from '@qiankunjs/vue';
const user = ref(null);
const showMicroApp = ref(false);
const login = () => {
user.value = {
id: '123',
name: 'John Doe',
permissions: ['read', 'write']
};
};
const toggleMicroApp = () => {
showMicroApp.value = !showMicroApp.value;
};
</script>
<template>
<div>
<select v-model="environment">
<option value="development">Development</option>
<option value="staging">Staging</option>
<option value="production">Production</option>
</select>
<MicroApp
name="dynamic-app"
:entry="entryUrls[environment]"
:app-props="{ environment }"
auto-set-loading
/>
</div>
</template>
<script setup>
import { ref } from 'vue';
import { MicroApp } from '@qiankunjs/vue';
const environment = ref('development');
const entryUrls = {
development: '//localhost:8080',
staging: '//staging.example.com',
production: '//app.example.com'
};
</script>
<!-- Main App -->
<template>
<div class="main-app">
<Navigation />
<MicroAppContainer />
</div>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
import { useAppStore } from '@/stores/app';
const store = useAppStore();
</script>
// stores/app.ts
import { defineStore } from 'pinia';
export const useAppStore = defineStore('app', {
state: () => ({
user: null,
theme: 'dark',
language: 'en'
}),
actions: {
setUser(user) {
this.user = user;
},
setTheme(theme) {
this.theme = theme;
}
}
});
<!-- MicroApp Container -->
<template>
<MicroApp
name="micro-app"
entry="//localhost:8080"
:app-props="appProps"
auto-set-loading
/>
</template>
<script setup>
import { computed } from 'vue';
import { MicroApp } from '@qiankunjs/vue';
import { useAppStore } from '@/stores/app';
const store = useAppStore();
const appProps = computed(() => ({
user: store.user,
theme: store.theme,
language: store.language
}));
</script>
<template>
<div class="app-communication">
<div class="app-container">
<h3>App 1</h3>
<MicroApp
ref="microApp1"
name="app1"
entry="//localhost:8080"
auto-set-loading
/>
</div>
<div class="app-container">
<h3>App 2</h3>
<MicroApp
ref="microApp2"
name="app2"
entry="//localhost:8081"
auto-set-loading
/>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { MicroApp } from '@qiankunjs/vue';
const microApp1 = ref();
const microApp2 = ref();
const setupCommunication = () => {
// Set up global communication channel
window.appCommunication = {
sendMessage: (from, to, message) => {
const event = new CustomEvent('microAppMessage', {
detail: { from, to, message }
});
window.dispatchEvent(event);
}
};
// Listen for messages
const handleMessage = (event) => {
console.log('Message received:', event.detail);
};
window.addEventListener('microAppMessage', handleMessage);
return () => {
window.removeEventListener('microAppMessage', handleMessage);
delete window.appCommunication;
};
};
onMounted(() => {
const cleanup = setupCommunication();
onUnmounted(() => {
cleanup();
});
});
</script>
<style scoped>
.app-communication {
display: flex;
gap: 20px;
}
.app-container {
flex: 1;
border: 1px solid #ccc;
padding: 20px;
}
</style>
<template>
<MicroApp
name="user-profile"
entry="//localhost:8080"
:app-props="userProps"
auto-set-loading
/>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { MicroApp } from '@qiankunjs/vue';
interface User {
id: string;
name: string;
email: string;
}
interface UserProfileProps {
userId: string;
theme: 'light' | 'dark';
permissions: string[];
}
const user: User = getCurrentUser();
const userProps = computed<UserProfileProps>(() => ({
userId: user.id,
theme: 'dark',
permissions: user.permissions || []
}));
</script>
// composables/useMicroApp.ts
import { ref, onMounted, onUnmounted, type Ref } from 'vue';
import type { MicroApp as MicroAppType } from 'qiankun';
interface UseMicroAppOptions {
onStatusChange?: (status: string) => void;
onError?: (error: Error) => void;
}
export function useMicroApp(options: UseMicroAppOptions = {}) {
const microAppRef: Ref<any> = ref();
const status = ref<string>('NOT_LOADED');
const error = ref<Error | null>(null);
const checkStatus = () => {
if (microAppRef.value?.microApp) {
const currentStatus = microAppRef.value.microApp.getStatus();
if (currentStatus !== status.value) {
status.value = currentStatus;
options.onStatusChange?.(currentStatus);
}
}
};
const handleError = (err: Error) => {
error.value = err;
options.onError?.(err);
};
let interval: number;
onMounted(() => {
interval = window.setInterval(checkStatus, 1000);
});
onUnmounted(() => {
if (interval) {
clearInterval(interval);
}
});
return {
microAppRef,
status,
error,
handleError
};
}
<template>
<div>
<p>Status: {{ status }}</p>
<p v-if="error">Error: {{ error.message }}</p>
<MicroApp
ref="microAppRef"
name="dashboard"
entry="//localhost:8080"
auto-set-loading
/>
</div>
</template>
<script setup lang="ts">
import { MicroApp } from '@qiankunjs/vue';
import { useMicroApp } from '@/composables/useMicroApp';
const { microAppRef, status, error } = useMicroApp({
onStatusChange: (status) => console.log('Status changed:', status),
onError: (error) => console.error('App error:', error)
});
</script>
<template>
<Suspense>
<template #default>
<LazyMicroApp
name="dashboard"
entry="//localhost:8080"
auto-set-loading
/>
</template>
<template #fallback>
<div>Loading component...</div>
</template>
</Suspense>
</template>
<script setup>
import { defineAsyncComponent } from 'vue';
const LazyMicroApp = defineAsyncComponent(() =>
import('@qiankunjs/vue').then(module => module.MicroApp)
);
</script>
<template>
<MicroApp
name="optimized-app"
entry="//localhost:8080"
:app-props="memoizedProps"
auto-set-loading
/>
</template>
<script setup>
import { computed } from 'vue';
import { MicroApp } from '@qiankunjs/vue';
const props = defineProps(['user', 'settings']);
const memoizedProps = computed(() => ({
userId: props.user?.id,
theme: props.settings?.theme,
language: props.settings?.language
}));
</script>
<template>
<div>
<nav>
<router-link to="/dashboard">Dashboard</router-link>
<router-link to="/analytics">Analytics</router-link>
</nav>
<keep-alive>
<router-view />
</keep-alive>
</div>
</template>
<script setup>
// Routes configuration
const routes = [
{
path: '/dashboard',
component: () => import('@/views/DashboardView.vue')
},
{
path: '/analytics',
component: () => import('@/views/AnalyticsView.vue')
}
];
</script>
<!-- DashboardView.vue -->
<template>
<MicroApp
name="dashboard"
entry="//localhost:8080"
auto-set-loading
/>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
</script>
<template>
<MicroApp
name="dashboard"
entry="//localhost:8080"
auto-set-loading
>
<template #error-boundary="{ error }">
<ErrorDisplay :error="error" :is-development="isDevelopment" />
</template>
</MicroApp>
</template>
<script setup>
import { MicroApp } from '@qiankunjs/vue';
import ErrorDisplay from '@/components/ErrorDisplay.vue';
const isDevelopment = process.env.NODE_ENV === 'development';
</script>
<!-- ErrorDisplay.vue -->
<template>
<div class="error-container">
<div v-if="isDevelopment" class="dev-error">
<h3>🚨 Development Error</h3>
<pre>{{ error.stack }}</pre>
<button @click="reload">Reload App</button>
</div>
<div v-else class="prod-error">
<h3>Something went wrong</h3>
<p>Please try again later.</p>
<button @click="reload">Retry</button>
</div>
</div>
</template>
<script setup>
defineProps(['error', 'isDevelopment']);
const reload = () => {
window.location.reload();
};
</script>
<style scoped>
.error-container {
padding: 20px;
text-align: center;
}
.dev-error {
background: #ffe6e6;
border: 1px solid #ff9999;
}
.prod-error {
background: #f8f9fa;
border: 1px solid #dee2e6;
}
pre {
text-align: left;
background: #f5f5f5;
padding: 10px;
overflow: auto;
}
</style>
<template>
<div class="main-app">
<h1>Vue 2 Main Application</h1>
<micro-app
name="dashboard"
entry="//localhost:8080"
:app-props="appProps"
auto-set-loading
>
<template v-slot:loader="{ loading }">
<div v-if="loading">Loading...</div>
</template>
</micro-app>
</div>
</template>
<script>
import { MicroApp } from '@qiankunjs/vue';
export default {
name: 'MainApp',
components: {
MicroApp
},
data() {
return {
user: {
id: '123',
name: 'John'
}
};
},
computed: {
appProps() {
return {
userId: this.user.id,
userName: this.user.name
};
}
}
};
</script>
<template>
<micro-app
name="dashboard"
entry="//localhost:8080"
:app-props="appProps"
auto-set-loading
/>
</template>
<script>
import { defineComponent, ref, computed } from '@vue/composition-api';
import { MicroApp } from '@qiankunjs/vue';
export default defineComponent({
components: {
MicroApp
},
setup() {
const user = ref({
id: '123',
name: 'John'
});
const appProps = computed(() => ({
userId: user.value.id,
userName: user.value.name
}));
return {
appProps
};
}
});
</script>
<!-- ✅ Good: Descriptive names -->
<MicroApp name="user-dashboard" entry="//localhost:8080" />
<MicroApp name="order-management" entry="//localhost:8081" />
<!-- ❌ Bad: Generic names -->
<MicroApp name="app1" entry="//localhost:8080" />
<MicroApp name="app2" entry="//localhost:8081" />
<!-- ✅ Good: Handle loading states -->
<MicroApp
name="dashboard"
entry="//localhost:8080"
auto-set-loading
>
<template #loader="{ loading }">
<CustomSpinner v-if="loading" />
</template>
</MicroApp>
<!-- ❌ Bad: No loading indication -->
<MicroApp name="dashboard" entry="//localhost:8080" />
<!-- ✅ Good: Handle errors gracefully -->
<MicroApp
name="dashboard"
entry="//localhost:8080"
auto-capture-error
>
<template #error-boundary="{ error }">
<ErrorFallback :error="error" />
</template>
</MicroApp>
<!-- ✅ Good: Reactive props -->
<MicroApp
name="dashboard"
entry="//localhost:8080"
:app-props="reactiveProps"
/>
<script setup>
import { computed } from 'vue';
const reactiveProps = computed(() => ({
theme: store.theme,
user: store.user
}));
</script>
<!-- ✅ Good: Environment-aware -->
<template>
<MicroApp
name="dashboard"
:entry="config.entry"
:app-props="config.props"
/>
</template>
<script setup>
import { computed } from 'vue';
const config = computed(() => {
const env = import.meta.env.MODE;
return {
development: {
entry: '//localhost:8080',
props: { debug: true }
},
production: {
entry: '//app.example.com',
props: { debug: false }
}
}[env];
});
</script>