qiankun + vue + element 的微前端架构项目,主项目与子应用均使用vue。支持三大前端框架可根据本身需求调整。css
微前端是什么、为何要作微前端、qiankun是什么这些笔者将再也不叙述,在前端微服务话提出的两年里已经有过了不少次的讨论和“定义”。
qiankun有兴趣的能够搜一下。
暂时还对这方面未有过了解的同窗-> 传送门:多是你见过最完善的微前端解决方案, qiankunhtml
鉴于qiankun文档只有寥寥十几行,这里作一个简单的概述(搬运)。
话很少说上步骤及代码:前端
改造主项目入口文件:
main.jsvue
// 导入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 增长一个渲染子应用的盒子react
<template> <div id="root" class="main-container"> <div class="main-container-menu"> </div> <div id="root-view" class="app-view-box" v- html="content"></div> </div> </template> <script> export default { name: "root-view", props: { loading: Boolean, content: String } }; </script>
建立一个子项目工程目录并改造子应用
vue.comfig.jswebpack
const 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.jsgit
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。或者你喜欢的其余地方。github
// 仅仅配置下公共路径 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)。web
// 咱们将导出的实例化以后的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注册子应用:vue-router
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();
固然咱们须要考虑的还有不少,可是我前天刚买的狼叔的【前端架构:从入门到微前端】告诉咱们,架构是一件持续性和渐进式的事儿,其余的后续再说吧~~~
另附Github上的demo地址:wl-qiankun。不想看我在这罗里吧嗦的直接代码跑起吧~,若是你以为还有一点点能够,就请留个star吧~~