qiankun + vue + element 微前端实践

房东的猫 提交于 2020-01-10 17:10:57

qiankun + vue + element 的微前端架构项目,主项目与子应用均使用vue。支持三大前端框架可根据自己需求调整。

微前端 qiankun

微前端是什么、为什么要做微前端、qiankun是什么这些笔者将不再叙述,在前端微服务话提出的两年里已经有过了很多次的讨论和“定义”。
qiankun有兴趣的可以搜一下。
暂时还对这方面未有过了解的同学-> 传送门:可能是你见过最完善的微前端解决方案, qiankun

简单使用教程

鉴于qiankun文档只有寥寥十几行,这里做一个简单的概述(搬运)。
话不多说上步骤及代码:

  1. 创建一个主项目工程目录
  2. npm install qiankun
  3. 改造主项目入口文件:
    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 增加一个渲染子应用的盒子
    <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>
    
  4. 创建一个子项目工程目录并改造子应用
    vue.comfig.js
    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.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__;
    }
    
  5. 需要注意的是,我们对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;
    
    
  6. 经过上述改造,一个简易的微前端环境就草草建成了,是不是很简单,你是不是已经跃跃欲试了?

父子应用通信

在上述所建微前端应用中,父子间的通信是极其普遍且无法绕过的需求,而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吧~

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!