技术栈:vue3、typescript、qiankun(阿里的微前端框架)、vue-cli4、vue2、webpack
qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统
安装 qiankun
yarn add qiankun
或者
npm i qiankun -S
import 'whatwg-fetch';
import 'custom-event-polyfill';
import 'core-js/stable/promise';
import 'core-js/stable/symbol';
import 'core-js/stable/string/starts-with';
import 'core-js/web/url';
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps, start } from 'qiankun';
import testComponent from "./components/test-component" //引入组件
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
window.commonComponent = { testComponent }; // 全局组件
localStorage.setItem("test",111) // 缓存数据
registerMicroApps([
{
name: 'app-vue-history',
entry: 'http://127.0.0.1:2222',
container: '#appContainer',
activeRule: '/app-vue-history',
props: { data : store }
},
{
name: 'vue-test',
entry: 'http://127.0.0.1:3333',
container: '#appContainer',
activeRule: '/vue-test',
props: { data : store }
},
]);
start();
利用vue-cli 创建项目
vue create vue-test
cd vue-test
yarn add vue-router vuex
yarn
const { name } = require('./package');
module.exports = {
devServer: {
port: 2222,
headers: {
'Access-Control-Allow-Origin': '*',
},
},
chainWebpack: (config) => {
config.module
.rule('fonts')
.test(/.(ttf|otf|eot|woff|woff2)$/)
.use('url-loader')
.loader('url-loader')
.tap(options => ({ name: '/fonts/[name].[hash:8].[ext]' }))
.end()
},
// 自定义webpack配置
configureWebpack: {
output: {
library: `${name}-[name]`,
libraryTarget: 'umd',// 把子应用打包成 umd 库格式
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
import './public-path';
import Vue from 'vue';
import VueRouter from 'vue-router';
import App from './App.vue';
import routes from './router';
import testStore from './store';
Vue.config.productionTip = false;
let router = null;
let instance = null;
function render({container, store} = {}) {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? '/vue-test' : '/', mode: 'history', routes,
});
Vue.prototype.testStore = testStore
instance = new Vue({
router, store, render: h => h(App),
}).$mount(container ? container.querySelector('#app') : '#app');
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
//测试全局变量污染
window.a = 1;
export async function bootstrap() {
console.log('vue app bootstraped');
}
export async function mount(props) {
console.log('props from main framework', props);
render(props);
// 测试一下 body 的事件,不会被沙箱移除
// document.body.addEventListener('click', e => console.log('document.body.addEventListener'))
// document.body.onclick = e => console.log('document.body.addEventListener')
}
export async function unmount() {
instance.$destroy();
instance.$el.innerHTML = "";
instance = null;
router = null;
}
主应用
import { initGlobalState, MicroAppStateActions } from 'qiankun';
// 初始化 state
const actions: MicroAppStateActions = initGlobalState(state);
actions.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
actions.setGlobalState(state);
actions.offGlobalStateChange();
子应用
// 从生命周期 mount 中获取通信方法,使用方式和 master 一致
export function mount(props) {
props.onGlobalStateChange((state, prev) => {
// state: 变更后的状态; prev 变更前的状态
console.log(state, prev);
});
props.setGlobalState(state);
}
主项目main.js
import testComponent from "./components/test-component"
window.commonComponent = { testComponent };
子项目使用
<template>
<div id="app">
<testComponent msg="测试子项目公共组件2"></testComponent>
</div>
</template>
<script>
export default {
name: 'App',
components: {
testComponent:testComponent:window.__POWERED_BY_QIANKUN__ ? window.commonComponent.testComponent : import('@/components/test-component')
},
}
</script>
主项目
import store from './store'
registerMicroApps([
{
name: 'vue-test',
entry: 'http://127.0.0.1:3333',
container: '#appContainer',
activeRule: '/vue-test',
props: { data : store }
},
]
子项目
import testStore from './store';
Vue.prototype.testStore = testStore
// 项目使用
this.microStore.state.microCount
window
localstorage
cookie
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { name } from './package.json'
import html from '@rollup/plugin-html';
// https://vitejs.dev/config/
export default defineConfig({
base: 'http://localhost:3333/',
plugins: [vue(), html({
// copy 自 https://github.com/rollup/plugins/blob/db4a3f2e8ebd3328b5d43bcb272589866dfd5729/packages/html/src/index.ts#L34
template: ({ attributes, files, meta, publicPath, title }) => {
const makeHtmlAttributes = (attributes) => {
if (!attributes) {
return '';
}
const keys = Object.keys(attributes);
return keys.reduce((result, key) => (result += ` ${key}="${attributes[key]}"`), '');
};
const scripts = (files.js || [])
.map(({ fileName }) => {
const attrs = makeHtmlAttributes(attributes.script);
return ``;
})
.join('\n');
const links = (files.css || [])
.map(({ fileName }) => {
const attrs = makeHtmlAttributes(attributes.link);
return `${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
})
.join('\n');
const metas = meta
.map((input) => {
const attrs = makeHtmlAttributes(input);
return `${attrs}>`;
})
.join('\n');
return `
${makeHtmlAttributes(attributes.html)}>
${metas}
${title}
${links}
${scripts}
`;
}
})],
build: {
// assetsInlineLimit: 0, // 为了让图片单独打包
target: "esnext",
lib: {
name,
entry: 'src/main.ts',
formats: ['umd'],
},
},
})