最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 无星的微前端之旅(三)——qiankun改造

    正文概述 掘金(XingXiaoWu)   2021-03-21   856

    微前端改造

    这里以Vue3为例子,主应用和子应用均使用vue3

    路由的话,建议主应用和子应用使用相同模式,即均为history或者均为hash

    以下先使用为history模式讲解,最后会写如何使用hash模式。


    History模式

    主应用改造

    一般情况下,我们会将带导航的layout的部分,直接放在主应用中。当然不是说不能拆,是能拆的,因为导航的layout明显是个路由不敏感部分,完全可以拆解为单独的子应用。

    1.添加qiankun

    yarn add qiankun
    

    2.vue.config.js

    其实没什么要改的,但我这还是建议把这两个加上

    module.exports = {
      publicPath: '/main/',
      // 修改打包名
      outputDir: 'main',
    };
    
    

    3.router.js

    const router = createRouter({
      // 这里就是publicPath了
      history: createWebHistory(process.env.BASE_URL),
      routes,
    });
    export default router;
    

    4.加载微应用的改造,可以在src下建一个micro目录

    |----index.js     //注册
    |----store.js     //应用间通信
    |----subapps.js   //配置信息
    

    4.1 index.js

    import { registerMicroApps } from 'qiankun';
    import subapps from './subapps';
    // 判断是否以某字符串开头
    // 注册并加载
    function register() {
      // 注册微应用
      registerMicroApps(subapps, {
        beforeLoad: (app) => console.log('before load', app.name),
        beforeMount: [
          (app) => console.log('before mount', app.name),
        ],
        afterMount: [
          (app) => console.log('before mount', app.name),
        ],
        beforeUnmount: [
          (app) => console.log('before mount', app.name),
        ],
        afterUnmount: [
          (app) => console.log('before mount', app.name),
        ],
      });
    }
    
    export default register;
    

    4.2 store.js

    应用间通信,qiankun提供了一个简单的apiinitGlobalState

    但是这玩意不是“响应式”的,换句话说,它改变不会引起页面变化。

    得益于vue3提供的reactive,我们可以很方便的构造一个响应式。 (同时也因为非常方便,所以也有很多大佬喊出了不再需要vuex,建议搜搜看,很有意思,当然这是题外话。)

    并将这个响应式用于页面展示。

    // 应用间通信
    import { initGlobalState } from 'qiankun';
    import { reactive } from 'vue';
    
    // 初始化state,加reative使其变为响应式
    const initialState = reactive({
      token: '123',
    });
    
    const actions = initGlobalState(initialState);
    
    // 监听全局变化
    actions.onGlobalStateChange((newState, oldState) => {
      console.log('主应用监听', '变化前', oldState, '变化后', newState);
      Object.keys(newState).forEach((key) => {
        initialState[key] = newState[key];
      });
    }, true);
    
    // 获取globalState
    function getGlobalState(key) {
      return key ? initialState[key] : initialState;
    }
    
    // 更新通知所有微应用
    function setGlobalState(globalState) {
      actions.setGlobalState(globalState);
    }
    // 卸载全局变化
    function offGlobalStateChange() {
      actions.offGlobalStateChange();
    }
    
    export default {
      actions,
      getGlobalState,
      setGlobalState,
      offGlobalStateChange,
    };
    
    

    4.2 subapps.js

    import router from '@/router/index';
    // 此时在开发,测试环境,理论上已经挂载了subapps的入口
    const baseUrl = process.env.BASE_URL;
    
    const subapps = [
      {
        name: 'sub-login',
        entry: process.env.VUE_APP_SUB_LOGIN,
        container: '#sub-apps',
        activeRule: `${baseUrl}sub-login`,
        props: {
          routeBasePath: '/sub-login',
          mainRouter: router,
        },
      },
      {
        name: 'sub-user-manage',
        entry: process.env.VUE_APP_SUB_USER_MANAGE,
        container: '#sub-apps',
        activeRule: `${baseUrl}sub-user-manage`,
        props: {
          routeBasePath: '/sub-user-manage',
          mainRouter: router,
        },
      },
    ];
    
    export default subapps;
    
    

    name,entry,container,activeRule

    在第上一篇已经介绍过了

    这里多了一个props,props意思是给子应用获取的对象,意味着有些东西,可以从主应用往下传递给子应用。这里我传递了两个值

    routeBasePath:子应用的路由地址前缀,在history模式下用于填写子应用的basepath,非常有用
    
    mainRouter:主应用的router,在history模式下应用间跳转非常有用
    

    这个东西我们还可以做点有意思的操作,比如:无星的微前端之旅(四)——qiankun线上服务代理到本地

    5.提供挂载节点#sub-apps

    我们在路由定义中,把所有子应用的components都匹配到一个View中。

    这个没什么写代码的意义,截个图直接掠过。

    无星的微前端之旅(三)——qiankun改造

    这个view提供一个dom节点用于后续挂载和启动

    <template>
      <div id="sub-apps" />
    </template>
    <script>
    import Actions from '@/micro/store';
    import {
      defineComponent, onMounted, onUnmounted,
    } from 'vue';
    import { start } from 'qiankun';
    
    export default defineComponent({
      name: 'SubApps',
      setup() {
        
        onMounted(async () => {
          console.log('Subapps页面加载');
          if (!window.qiankunStarted) {
            window.qiankunStarted = true;
            start();
          }
        });
        onUnmounted(() => {
          if (window.qiankunStarted) {
            window.qiankunStarted = false;
            Actions.offGlobalStateChange();
          }
        });
        return {
        };
      },
    });
    </script>
    <style lang="less">
    #sub-apps {
      height: 100%;
      >div:first-child {
        height: 100%;
      }
    }
    </style>
    
    

    如何在主应用的某个路由页面加载微应用

    这里有一个需要注意的点。不是js代码,而是css。

    主应用加载子应用的时候,会新增一个qiankun的div盒子,可能这个盒子会影响样式,导致撑不开。所以需要使用css选择器让qiankun注入的盒子加一些css进行改变以达到预期效果。

    无星的微前端之旅(三)——qiankun改造

    6.main.js修改

    改造前:

    // 默认生成的main.js
    import { createApp } from 'vue';
    import App from './App.vue';
    import router from './router';
    import store from './store';
    
    createApp(App).use(store).use(router).mount('#app');
    
    

    改造后:

    import { createApp } from 'vue';
    import App from './App.vue';
    import router from './router';
    import store from './store';
    // 注册
    import register from './micro/index';
    // 添加全局通信
    import '@/micro/store';
    
    createApp(App).use(store).use(router).mount('#app');
    // 注册
    register();
    

    到此为止,主应用改造完毕。


    子应用改造

    1.vue.config.js添加核心配置

    const packageName = require('./package.json').name;
    
    module.exports = {
    // 先写为/,后续会修改
      publicPath: '/',
    //   输出目录重命名为项目名称,方便后期部署
      outputDir: packageName,
    // 来自qiankun文档的修改
      configureWebpack: {
        output: {
          library: `${packageName}-[name]`,
          libraryTarget: 'umd',
          jsonpFunction: `webpackJsonp_${packageName}`,
        },
      },
      devServer: {
        open: true,
        // 建议添加端口,不同模块加载不同端口,方便开发制定加载
        port: 3001,
        // 必须添加,qiankun需要支持跨域
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
      },
    };
    
    

    2.添加public-path

    if (window.__POWERED_BY_QIANKUN__) {
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    

    它的作用,点击标题查看文档

    3.src文件夹下新建一个micro/store.js,用于应用间通信相关

    // 全局数据store
    import { reactive } from 'vue';
    
    let actions = null;
    const initialState = reactive({});
    let routeBasePath = '';
    function setActions(tmpActions) {
      // 如果有兴趣,还可以把这里的初始化和vuex关联起来
      actions = tmpActions;
      actions.onGlobalStateChange((newState, oldState) => {
        console.log('子应用监听', '变化前', oldState, '变化后', newState);
        Object.keys(newState).forEach((key) => {
          initialState[key] = newState[key];
        });
      }, true);
    }
    
    function setGlobalState(state) {
      return actions.setGlobalState(state);
    }
    function getActions() {
      return actions;
    }
    
    function getGlobalState(key) {
      return key ? initialState[key] : initialState;
    }
    // 基础数据
    function setRouteBasePath(path) {
      routeBasePath = path;
    }
    // 基础数据
    function getRouteBasePath() {
      return routeBasePath;
    }
    
    export default {
      setActions,
      getActions,
      setGlobalState,
      getGlobalState,
      setRouteBasePath,
      getRouteBasePath,
    };
    

    4.router改造

    import { createRouter, createWebHistory } from 'vue-router';
    import Actions from '@/micro/store';
    
    const routes = [
      {
        path: '/',
        name: 'Login',
        component: () => import('../views/Login/index.vue'),
      },
      {
        path: '/register',
        name: 'Register',
        component: () => import('../views/Register/index.vue'),
      },
      {
        path: '/config',
        name: 'Config',
        component: () => import('../views/Config/index.vue'),
      },
    ];
    
    const setupRouter = () => createRouter({
      // 获取来自主应用的前缀
      history: createWebHistory(Actions.getRouteBasePath()),
      routes,
    });
    
    export default setupRouter;
    

    5.main.js改造

    改造前:

    // 默认生成的main.js
    import { createApp } from 'vue';
    import App from './App.vue';
    import router from './router';
    import store from './store';
    
    createApp(App).use(store).use(router).mount('#app');
    
    

    改造后:

    // 注意哦,这一行引入不要忘记了,要加载最上面
    import './public-path';
    import { createApp } from 'vue';
    import Actions from '@/micro/store';
    
    import App from './App.vue';
    import store from './store';
    import setupRouter from './router';
    
    let instance = null;
    let router = null;
    function render(props = {}) {
      const { container, routeBasePath, mainRouter } = props;
      // 这里需要注意,往下注入的必须和子打包配置一致
      Actions.setRouteBasePath(routeBasePath || process.env.BASE_URL);
      router = setupRouter();
      instance = createApp(App);
      instance.use(router);
      instance.use(store);
    
      router.isReady().then(() => {
        instance.mount(container ? container.querySelector('#app') : '#app');
      });
    }
    
    if (!window.__POWERED_BY_QIANKUN__) {
      // 如果是非乾坤访问,意思是子应用单独访问
      render();
    }
    
    export async function bootstrap() {
      console.log('%c ', 'color: green;', 'vue3.0 app bootstraped');
    }
    
    export async function mount(props) {
      // 获取props中的全局通信
      Actions.setActions(props);
      // 只有从qiankun访问,才会到这个生命周期
      render(props);
    }
    
    export async function unmount() {
      // 这里千万不要忘记置空!!!!
      instance.unmount();
      instance._container.innerHTML = '';
      instance = null;
      router = null;
    }
    
    

    好了,子应用到此就改造完毕了。


    Hash

    如果是hash模式,那么需要变动的地方就比较多了

    1.主应用和子应用中vue.config.js全部修改为

    module.exports = {
      publicPath: './',
    }
    

    2.主应用和子应用中router修改为

    
    const setupRouter = () => createRouter({
      history: createWebHashHistory(),
      routes,
    });
    

    3.主应用subapps.js修改为

    import router from '@/router/index';
    // 此时在开发,测试环境,理论上已经挂载了subapps的入口
    const baseUrl = '#/';
    const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
    const subapps = [
      {
        name: 'sub-login',
        entry: process.env.VUE_APP_SUB_LOGIN,
        container: '#sub-apps',
        activeRule: getActiveRule(`${baseUrl}sub-login`),
        props: {
          routeBasePath: '/sub-login',
          mainRouter: router,
        },
      },
      {
        name: 'sub-user-manage',
        entry: process.env.VUE_APP_SUB_USER_MANAGE,
        container: '#sub-apps',
        activeRule: getActiveRule(`${baseUrl}sub-login`),
        props: {
          routeBasePath: '/sub-user-manage',
          mainRouter: router,
        },
      },
    ];
    
    export default subapps;
    
    

    修改完毕。

    至此,主应用和子应用均修改完毕。


    起源地下载网 » 无星的微前端之旅(三)——qiankun改造

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元