qiankun + vue + element 的微前端架构项目,主项目与子应用均使用vue。支持三大前端框架可根据自己需求调整。
微前端 qiankun
微前端是什么、为什么要做微前端、qiankun是什么这些笔者将不再叙述,在前端微服务话提出的两年里已经有过了很多次的讨论和“定义”。
qiankun有兴趣的可以搜一下。
暂时还对这方面未有过了解的同学-> 传送门:可能是你见过最完善的微前端解决方案, qiankun
简单使用教程
鉴于qiankun文档只有寥寥十几行,这里做一个简单的概述(搬运)。
话不多说上步骤及代码:
- 创建一个主项目工程目录
- npm install qiankun
-
改造主项目入口文件:
main.js// 导入qiankun依赖 import { registerMicroApps, runAfterFirstMounted, setDefaultMountApp, start } from "qiankun"; function render({ appContent, loading }) { if (!app) { app = new Vue({ el: "#container", router, store, data() { return { content: appContent, loading }; }, render(h) { return h(App, { props: { content: this.content, loading: this.loading } }); } }); } else { app.content = appContent; app.loading = loading; } }; function genActiveRule(routerPrefix) { return location => location.pathname.startsWith(routerPrefix); } render({loading: true}); // 注册子应用 registerMicroApps( [{ name: "app1" entry: "//localhost:7771", render, activeRule: genActiveRule("/app1") props: 'mg' // 传递给子应用 }], { beforeLoad: [ app => { console.log("before load", app); } ], beforeMount: [ app => { console.log("before mount", app); } ], afterUnmount: [ app => { console.log("after unload", app); } ] } ) // 设置默认子应用 setDefaultMountApp("/app1"); // 第一个子应用加载完毕回调 runAfterFirstMounted(); // 启动微服务 start(); // 注意, 主应用的el绑定dom为#container,因此你也需要修改一下index.hrml模板中的id
app.vue 增加一个渲染子应用的盒子
-
创建一个子项目工程目录并改造子应用
vue.comfig.jsconst path = require("path"); const packageName = require("./package.json").name; function resolve(dir) { return path.join(__dirname, dir); } const port = 7771; // dev port module.exports = { outputDir: "dist", assetsDir: "static", // 默认在生成的静态资源文件名中包含hash以控制缓存 filenameHashing: true, lintOnSave: false, devServer: { hot: true, disableHostCheck: true, port, overlay: { warnings: false, errors: true }, headers: { "Access-Control-Allow-Origin": "*" } }, // 自定义webpack配置 configureWebpack: { resolve: { alias: { "@": resolve("src") } }, output: { //把子应用打包成 umd 库格式 library: `${packageName}-[name]`, libraryTarget: "umd", jsonpFunction: `webpackJsonp_${packageName}` } } };
main.js
import Vue from "vue"; import VueRouter from "vue-router"; import App from "./App.vue"; import "./public-path"; import routes from "./router"; import store from "./store"; import "./plugins/element.js"; import "@/assets/css/demo.min.css" Vue.config.productionTip = false; let router = null; let instance = null; // 导出子应用生命周期到父应用 export async function bootstrap(props) { console.log(props) } export async function mount() { router = new VueRouter({ base: window.__POWERED_BY_QIANKUN__ ? "/app1" : "/", mode: "history", routes }); instance = new Vue({ router, store, render: h => h(App) }).$mount("#app"); } export async function unmount() { instance.$destroy(); instance = null; router = null; } // 子应用单独开发环境 window.__POWERED_BY_QIANKUN__ || mount();
眼尖的同学可能看到了我们在main.js引入了一个public-path,其他的大家都眼熟那么这个public-path是什么东西?
main.js同级增加public-path.js。或者你喜欢的其他地方。// 仅仅配置下公共路径 if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
-
需要注意的是,我们对vue的router.js导出做了一点小小的改动。(你也可以不改动,在main.js也就无需router = null)。
// 我们将导出的实例化之后的router改为只导出了路由数据 const routes = [ { path: "/", name: "home", component: Home }, { path: "/about", name: "about", // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ "../views/About.vue") } ]; /* const router = new VueRouter({ mode: "history", routes }); */ export default routes;
- 经过上述改造,一个简易的微前端环境就草草建成了,是不是很简单,你是不是已经跃跃欲试了?
父子应用通信
在上述所建微前端应用中,父子间的通信是极其普遍且无法绕过的需求,而qiankun在这方面当然有所考虑。
在上述构建项目步骤中,有一步是在主应用main.js注册子应用:
registerMicroApps(
[{
name: "app1"
entry: "//localhost:7771",
render,
activeRule: genActiveRule("/app1")
props: 'mg' // 传递给子应用
}],
)
其中props参数即为传递给子应用的数据,其内容你可以自由定制。
子应用在main.js导出的生命周期函数中均可接收到所传props数据:
export async function bootstrap(props) {
console.log(props)
}
其props的应用类似于react框架的父子组件通信,传入data数据供自组件使用,传入fn函数给子组件触发向上回调。
按照这个思路我们将主应用的main.js和子应用的main.js都改造一番:
改造后的主应用main.js
...
// 定义传入子应用的数据
let msg = {
data: {
auth: false
},
fns: [
{
name: "LOGOUT_",
LOGOUT_(data) {
alert('父应用返回信息:' + data)
}
}
]
};
// 注册子应用
registerMicroApps(
[{
name: "app1"
entry: "//localhost:7771",
render,
activeRule: genActiveRule("/app1")
props: msg // 将定义好的数据传递给子应用
// 注意:通常这里会通过获取后台数据异步传入,具体不再细说
}],
{
beforeLoad: [
app => {
console.log("before load", app);
}
],
beforeMount: [
app => {
console.log("before mount", app);
}
],
afterUnmount: [
app => {
console.log("after unload", app);
}
]
}
)
// 设置默认子应用
setDefaultMountApp("/app1");
// 第一个子应用加载完毕回调
runAfterFirstMounted();
// 启动微服务
start();
改造后的子应用main.js
...
let router = null;
let instance = null;
// 导出子应用生命周期到父应用
export async function bootstrap(props = {}) {
// 将主应用传递过来的函数挂在vue原型方面全局使用
// 你也可以在mount中接收挂在methods或者直接设定传入mixin,坏处便是框架耦合度太大不符合微前端思想
Array.isArray(props.fns) && props.fns.map(i => {
Vue.prototype[i.name] = i[i.name]
});
}
export async function mount() {
router = new VueRouter({
base: window.__POWERED_BY_QIANKUN__ ? "/app1" : "/",
mode: "history",
routes
});
instance = new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
}
export async function unmount() {
instance.$destroy();
instance = null;
router = null;
}
// 子应用单独开发环境
window.__POWERED_BY_QIANKUN__ || mount();
一个简单的基于qiankun和vue的示例就这么结束啦
当然我们需要考虑的还有很多,但是我前天刚买的狼叔的【前端架构:从入门到微前端】告诉我们,架构是一件持续性和渐进式的事儿,其他的后续再说吧~~~
另附Github上的demo地址:wl-qiankun。不想看我在这罗里吧嗦的直接代码跑起吧~,如果你觉得还有一点点可以,就请留个star吧~~