精华内容
下载资源
问答
  • 微前端乾坤使用过程中的坑 乾坤在启动子应用的时候默认开启沙箱模式{sandbox: true},这样的情况下,乾坤节点下会生成一个 shadow dom,shadow dom 内的样式与外部样式是没有关联的,这样就会给子应用内的样式带来一...

    微前端乾坤使用过程中的坑

    乾坤在启动子应用的时候默认开启沙箱模式{sandbox: true},这样的情况下,乾坤节点下会生成一个 shadow dom,shadow dom 内的样式与外部样式是没有关联的,这样就会给子应用内的样式带来一系列问题。这其中很多问题并不是乾坤造成的,而是 shadow dom 本身的特性导致的,乾坤还是不错的(不背锅)。随时补充

    1.iconffont 字体在子应用无法加载

    原因:shadow dom 是不支持@font-face 的,所以当引入 iconfont 的时候,尽管可以引入样式,但由于字体文件是不存在的,所以相对应的图标也无法展示。

    相关链接:@font-face doesn’t work with Shadow DOM?Icon Fonts in Shadow DOM

    方案:

    1. 把字体文件放在主应用加载
    2. 使用通用的字体文件

    2.dom的查询方法找不到指定的元素

    原因:shadow dom 内的元素是被隔离的元素,故 document下查询的方法例如,querySelector、getElementsById 等是获取不到 shadow dom 内元素的。

    方案:代理 document 下各个查询元素的方法,使用子应用传入的容器,即外面的 shadow dom一层查询。具体使用可以参考乾坤的这个方法 initGlobalState

    3.组件库动态创建的元素无法使用自己的样式

    原因:有些对话框或提示窗是通过document.body.appendChild添加的,所以 shadow dom 内引入的 CSS 是无法作用到外面元素的。

    方案:代理document.body.appendChild方法,即把新加的元素添加到 shadow dom容器下,而不是最外面的 body节点下。

    补充: 类似的问题都可以往这个方向靠,看是不是shadow dom节点或者dom方法的问题。

    4.第三方引入的 JS 不生效

    原因:有些 JS 文件本身是个立即执行函数,或者会动态的创建 scipt 标签,但是所有获取资源的请求是被乾坤劫持处理,所以都不会正常执行,也不会在 window 下面挂载相应的变量,自然在取值调用的时候也不存在这个变量。

    方案:参考乾坤的 issue,子应用向body添加script标签失败

    5.webpack-dev-server 代理访问的接口 cookie 丢失

    原因:在主应用的端口下请求子应用的端口,存在跨域,axios 默认情况下跨域是不携带 cookie 的,假如把 axios 的 withCredential设置为 true(表示跨域携带 cookie),那么子应用需要设置跨域访问头Access-Control-Allow-Origin(在 devServer 下配置 header)为指定的域名,但不能设置为*,这时候同时存在主应用和子应用端口发出的请求,而跨域访问头只能设置一个地址,就导致无法代理指定服务器接口。

    方案:子应用接口请求的端口使用主应用接口请求的端口,使用主应用的配置代理请求

    // 主应用
    
    devServer: {
        ...
    	port: 9600
        proxy: {
    		// 代理配置
        }
    }
    
    // 子应用
    devServer: {
        ...
    	port: 9600, // 使用主应用的页面访问端口
    }
    
    展开全文
  • qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。 公司有一个门户网站需要嵌入其他系统,使用iframe会有很多的兼容性问题,如果使用qiankun框架,不仅...

    介绍

    qiankun 是一个基于 single-spa 的微前端实现库,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

    公司有一个门户网站需要嵌入其他系统,使用iframe会有很多的兼容性问题,如果使用qiankun框架,不仅可以绕过这些iframe的坑,还可以进行很多的“骚操作”。

    快速上手

    qiankun官网的快速上手很简单,不是因为他们懒得写,而是引入qiankun的确是非常简单。

    只需要在主应用中引入qiankun,微服务做相应的配置,就可以,下面就介绍我在项目中如何引入。

    主应用

    • 安装

      npm install -S qiankun
      
    • 在main.js入口文件

      import { registerMicroApps, start } from 'qiankun';
      const getActiveRule = (hash) => (location) => location.hash.startsWith(hash);
       registerMicroApps([
        {
          name: 'TPP', 
          entry: 'http://localhost:7000/',
      	// 主应用挂载的节点
          container: '#TPP',
          // 主应用使用的hash模式
          activeRule: getActiveRule('#/TPP'),
        },
        {
          name: 'PTP', 
          entry: 'http://localhost:7001/',
      	// 主应用挂载的节点
          container: '#PTP',
          // 主应用使用的hash模式
          activeRule: getActiveRule('#/PTP'),
        },
      ]);
      start();
      

    微服务配置( vue/cli3创建应用)

    仅针对开发环境

    • 微服务入口文件

    qiankun会在微应用注入一个全局变量,用于针对此变量来做一些配置

    if (window.__POWERED_BY_QIANKUN__) {
    // 如果是正常访问,则为undefined,如果是qiankun访问,则为true
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    function render(props = {}) {
    	/** 
    	props 参数
    	container:  主应用挂载微应用的节点
    	mountParcel: ƒ ()
    	name: "TPP" 微应用name
    	onGlobalStateChange: ƒ onGlobalStateChange(callback, fireImmediately)
    	setGlobalState: ƒ setGlobalState()
    	singleSpa
    	*/
      const { container } = props;
      const mountPath = container ? container.querySelector('#TPPAPP') : '#TPPAPP';
    
      window.sfopenpc = new Vue({
        render: (h) => h(App),
        store,
        router,
      }).$mount(mountPath);
    }
    
    // 如果正常访问,则传入空对象,用于正常的配置
    if (!window.__POWERED_BY_QIANKUN__) {
      render({});
    }
    // qiankun的生命周期回调
    export async function bootstrap() {
      console.log('[vue] vue app bootstraped');
    }
    export async function mount(props) {
      console.log('[vue] props from main framework', props);
      // 如果qiankun框架引入,则会在页码加载完成之后调用此方法
      render(props);
    }
    export async function unmount() {
      window.sfopenpc.$destroy();
      window.sfopenpc.$el.innerHTML = '';
      window.sfopenpc = null;
      // router = null;
    }
    
    
    • webpack配置(开发环境)
      由于是不同端口,所以会出现跨域的问题,所以需要在代理服务器进行配置headers,支持微服务的Access-Control-Allow-Origin
    const { name } = require('./package.json');
    module.exports = {
      configureWebpack: {
        devServer: {
          headers: {
            // 本地服务器接受跨域
            'Access-Control-Allow-Origin': '*',
          },
        },
      },
      output: {
        library: `${name}-[name]`,
        libraryTarget: 'umd', // 把微应用打包成 umd 库格式,不配置的话,主应用导入会报错
        jsonpFunction: `webpackJsonp_${name}`,
      },
    }
    

    效果

    在这里插入图片描述
    点击上面的菜单栏进行跳转
    在这里插入图片描述
    已经成功引入。

    其他问题

    主应用与微应用跳转问题

    如果主应用和微应用同时使用hash模式的话,如果不做特殊处理的话,就会出现跳转异常。
    我这里简单写了一个简单的中间件,原理是在微应用的入口文件新增一个前缀处理,使得如果是qiankun框架访问的时候,自动添加路由跳转前缀的同时,点击时也会自动重定向对应的前缀。

    微应用配置

    • 中间件QiankunRouter
    import Router from 'vue-router';
    
    function handleRouter(router, prefix = '') {
      if (router.path) {
        if (router.path === '/') {
          router.path = `${prefix}`;
        } else {
          router.path = `${prefix}${router.path}`;
        }
      }
      if (router.children && router.children.length) {
        for (const r of router.children) {
          handleRouter(r, prefix);
        }
      }
    }
    
    // eslint-disable-next-line no-unused-vars
    function decorate(router, { isQiankun, prefix }) {
      if (isQiankun) {
        for (const r of router) {
          handleRouter(r, prefix);
        }
      }
    
      return router;
    }
    export default class QiankunRouter extends Router {
      constructor(props) {
        decorate(props.routes, {
          isQiankun: props.isQiankun,
          prefix: props.prefix,
        });
        super(props);
        this.isQiankun = !!props.isQiankun;
        this.prefix = props.prefix || '';
        if (this.isQiankun) {
        // 注册跳转路由前置,跳转时自动新增前缀
          this.qiankunbeForeEach();
        }
      }
    
      // eslint-disable-next-line no-unused-vars
      qiankunbeForeEach() {
        super.beforeEach((to, from, next) => {
          if (this.isQiankun && !to.path.includes('/microApp')) {
            if (to.path === '/') {
              next({
                path: this.prefix,
              });
            } else {
              next({
                path: `${this.prefix}${to.path}`,
              });
            }
          }
          next();
        });
      }
    }
    
    
    • 使用QiankunRouter替换Vue-router
    /* eslint-disable no-unused-vars */
    import Vue from 'vue';
    // import router from 'Vue-router'
    import QiankunRouter from '../qiankun/decorateRouter';
    // 路由信息
    const routes = [{
    	path:'/',
    	component: () => import('./qiankun/index') 
    }]
    
    Vue.use(QiankunRouter);
    const router = new QiankunRouter({
      mode: 'hash',
      routes: vuexHoc(routes),
      // eslint-disable-next-line no-underscore-dangle
      isQiankun: !!window.__POWERED_BY_QIANKUN__,
      // 路由跳转前缀
      prefix: '/microApp/TPP',
    });
    
    export default router;
    

    主应用配置修改

    只需要修改路由信息和qiankun主路由检测路径即可

    • qiankun主路由检测路径
    const qiankunConfig = [{
        name: 'TPP',
        // 访问微应用的跳转首页路径 新增#/microApp/TPP,匹配的是原来的/
        entry: 'http://localhost:7001/#/microApp/TPP',
        container: '#microApp',
        // 主应用hash配置前缀
        activeRule: getActiveRule('#/microApp/TPP'),
      }]
    
    • 路由信息
      {
      	// 给微应用配置一个固定前缀
        path: '/microApp/*',
        name: '微应用',
        component: () => import('../views/microApp.vue'),
      },
    

    微应用的弹出新窗口

    弹出新窗口是可以使用相对路径,浏览器会自动获取当前的路由信息,但是如果使用主应用打开的时候打开新窗口就会异常了。
    解决方案有如下:

    1. 封装一个跳转窗口的方法,统一使用该方法进行跳转
    2. 如果使用的是<a>标签跳转新窗口的话,就不能使用相应路径,而改用URL的绝对路径替换。

    js文件的预加载问题

    在开发环境中,跳转路由的时候,会出现一个明显的“白屏时间”,这可能是因为请求多次导致加载时间变长,目前暂时还没有确定的解决方案,可能需要在主应用中加一个loading优化显示。

    展开全文
  • 微前端——乾坤qiankun Demo

    千次阅读 热门讨论 2020-12-05 16:08:36
    微前端——qiankun(乾坤)实例 一、什么是微前端 微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。微前端的核心在于拆,拆完后在合! 二、为什么使用微前端 不同团队间开发同...

    微前端——qiankun(乾坤)实例

    一、什么是微前端

    微前端就是将不同的功能按照不同的维度拆分成多个子应用。通过主应用来加载这些子应用。微前端的核心在于,拆完后在

    二、为什么使用微前端

    1. 不同团队间开发同一个应用技术栈不同
    2. 希望每个团队都可以独立开发,独立部署
    3. 项目中还需要老的应用代码

    我们可以将一个应用划分成若干个子应用,将子应用打包成一个个的 lib 。当路径切换 时加载不同的子应用。这样每个子应用都是独立的,技术栈也不用做限制了!从而解决了前端协同开发问题。

    三、qiankun框架

    **文档地址:**https://qiankun.umijs.org/zh

    2018 年 Single-SPA 诞生了, single-spa 是一个用于前端微服务化的 JavaScript 前端解决方案 ( 本身没有处理样式隔离, js 执行隔离 ) 实现了路由劫持和应用加载。

    2019 年 qiankun 基于 Single-SPA, 提供了更加开箱即用的 API ( single-spa + sandbox + import-html-entry ) 做到了,技术栈无关、并且接入简单(像 i frame 一样简单)。

    四、qiankun框架实例

    这里我们打算建立三个项目进行实操,一个Vue项目充当主应用,另一个Vue和React应用充当子应用

    1、创建三个应用

    1)创建基座

    vue create qiankun-base
    

    2)创建子应用1

    vue create qiankun-vue
    

    3)创建子应用2

    cnpm install -g create-react-app
    create-react-app qiankun-react
    
    • 三个项目

      基座:qiankun-base 子应用:qiankun-vue、qiankun-react

    2、项目配置(主要)

    1)基座qiankun-base配置

    ​ 项目创建好后我们首先进行主应用qiankun-base的配置,进入man.js文件进行配置, 在main.js中加入以下代码,要注意的是,entry这项配置是我们两个子项目的域名和端口,我们必须确保两字子项目运行在这两个端口上面,container就是我们的容器名,就是我们子应用挂载的节点,相当于Vue项目里面的app节点,activeRule就是我们的激活路径,根据路径来显示不同的子应用。

    • 引入qiankun插件
    yarn add qiankun 或者 npm i qiankun -S
    
    • main.js配置
    // 引入qiankun
    import { registerMicroApps, start } from 'qiankun';
    
    const apps = [
      {
        name: 'vueApp', // 应用的名字
        entry: '//localhost:8081', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
        container: '#vue', // 容器名(此项目页面中定义的容器id,用于把对应的子应用放到此容器中)
        activeRule: '/vue', // 激活的路径
        props: { a: 1 }	// 传递的值(可选)
      },
      {
        name: 'reactApp',
        entry: '//localhost:20000', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
        container: '#react',
        activeRule: '/react',
      }
    ]
    registerMicroApps(apps); // 注册应用
    start({
      prefetch: false // 取消预加载
    });// 开启
    
    • 配置完之后我们去到qiankun-base的app.vue文件进行主应用的页面编写,这里我安装了element-ui来进行页面美化
    npm i element-ui -S
    

    在main.js中引入element-ui:

    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    
    Vue.use(ElementUI);
    
    • 修改app.vue的组件代码
    <template>
      <div id="app">
        <el-menu :router="true" mode="horizontal">
          <!--基座中可以放自己的路由-->
          <el-menu-item index="/">Home</el-menu-item>
          <el-menu-item index="/about">About</el-menu-item>
          <!--引用其他子应用-->
          <el-menu-item index="/vue">vue应用</el-menu-item>
          <el-menu-item index="/react">react应用</el-menu-item>
        </el-menu>
        <router-view></router-view>
        <div id="vue"></div>
        <div id="react"></div>
      </div>
    </template>
    
    • router.js代码
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home.vue'
    
    Vue.use(VueRouter)
    
    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',
        // base: process.env.BASE_URL,
        base: '',
        routes
    })
    
    export default router
    

    2)子应用qiankun-vue配置

    • main.js配置
    import Vue from 'vue'
    import App from './App.vue'
    import router from './router'
    
    // Vue.config.productionTip = false
    
    let instance = null
    function render(props) {
      instance = new Vue({
        router,
        render: h => h(App)
      }).$mount('#qkApp'); // 这里是挂载到自己的html中  基座会拿到这个挂载后的html 将其插入进去
    }
    
    if (window.__POWERED_BY_QIANKUN__) { // 动态添加publicPath
      __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    if (!window.__POWERED_BY_QIANKUN__) { // 默认独立运行
      render();
    }
    
    // 父应用加载子应用,子应用必须暴露三个接口:bootstrap、mount、unmount
    // 子组件的协议就ok了
    export async function bootstrap(props) {
    
    };
    
    export async function mount(props) {
      render(props)
    }
    
    export async function unmount(props) {
      instance.$destroy();
    }
    
    • router.js配置
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    import Home from '../views/Home.vue'
    
    Vue.use(VueRouter)
    
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: Home
      },
      {
        path: '/about',
        name: 'About',
        component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
      }
    ]
    
    const router = new VueRouter({
      mode: 'history',
      base: '/vue',
      routes
    })
    
    export default router
    
    • Vue.config.js配置

    在子应用的根目录下面新建一个Vue.config.js文件

    module.exports = {
        lintOnSave: false,  // 关闭eslint检测
        devServer: {
            port: 8080,//这里的端口是必须和父应用配置的子应用端口一致
            headers: {
                //因为qiankun内部请求都是fetch来请求资源,所以子应用必须允许跨域
                'Access-Control-Allow-Origin': '*'
            }
        },
        configureWebpack: {
            output: {
                //资源打包路径
                library: 'vueApp',
                libraryTarget: 'umd'
            }
        }
    }
    

    3)子应用qiankun-react配置

    • src目录下index.js文件
    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './App';
    
    function render(){
      ReactDOM.render(
        <React.StrictMode>
          <App />
        </React.StrictMode>,
        document.getElementById('root')
      );
    }
    
    if(!window.__POWERED_BY_QIANKUN__){
      render();
    }
    
    export async function bootstrap(){
     
    }
    export async function mount() {
      render()
    }
    export async function unmount(){
      ReactDOM.unmountComponentAtNode( document.getElementById('root'));  // 卸载节点
    }
    
    • config-overrides.js配置

    先引入react-app-rewired,在修改package.json启动命令

    npm install react-app-rewired
    

    修改package.json启动命令

    "scripts": {
        "start": "react-app-rewired start",
        "build": "react-app-rewired build",
        "test": "react-app-rewired test",
        "eject": "react-app-rewired eject"
      },
    

    再进行dev以及打包的配置,根目录下创建config-overrides.js

    module.exports = {
        webpack: (config) => {
            config.output.library = 'reactApp';
            config.output.libraryTarget = 'umd';
            config.output.publicPath = 'http://localhost:20000/';	// 此应用自己的端口号
            return config;
        },
        devServer: (configFunction) => {
            return function (proxy, allowedHost) {
                const config = configFunction(proxy, allowedHost);
                config.headers = {
                    "Access-Control-Allow-Origin": '*'
                }
                return config
            }
        }
    }
    

    3、注意点

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

    react + react-router 技术栈的主应用:只需要让子应用的 activeRule 包含主应用的这个路由即可。

    vue + vue-router 技术栈的主应用:

    例如:主应用需要在login页面登录,登录成功后跳转到main后台管理界面,在main管理界面下可以显示子应用。

    修改主应用router.js:

    // 如果这个路由有其他子路由,需要另外注册一个路由,任然使用这个组件即可。
    // 本案例就是有子路由,所以需要才后面重新定义main页面的路由
    const routes = [
        {
            path: '/',
            name: 'Login',
            component: Login
        },
        {
            path: '/main',
            name: 'Main',
            component: Main,
            children: [
                {
                    path: '/home',
                    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')
                }
            ]
        },
        {
            path: '/main/*',
            name: 'Main',
            component: Main,
        }
    ]
    

    修改主应用main.js的文件:

    // 子应用的 activeRule 需要包含主应用的这个路由 path
    const apps = [
      {
        name: 'vueApp', // 应用的名字
        entry: '//localhost:8081', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
        container: '#vue', // 容器名
        activeRule: '/main/vue', // 激活的路径
        props: { a: 1 }
      },
      {
        name: 'reactApp',
        entry: '//localhost:20000', // 默认会加载这个html 解析里面的js 动态的执行 (子应用必须支持跨域)fetch
        container: '#react',
        activeRule: '/main/react',
      }
    ]
    registerMicroApps(apps); // 注册应用
    

    修改主应用main.vue页面代码:

    // 在 Main.vue 这个组件的 mounted 周期调用 start 函数,注意不要重复调用。
    <template>
      <div class="main-content">
        <el-menu :router="true" mode="horizontal">
          <!--基座中可以放自己的路由-->
          <el-menu-item index="/home">Home</el-menu-item>
          <el-menu-item index="/about">About</el-menu-item>
          <!--引用其他子应用-->
          <el-menu-item index="/main/vue">vue应用</el-menu-item>
          <el-menu-item index="/main/react">react应用</el-menu-item>
        </el-menu>
        <router-view></router-view>
        <div id="vue"></div>
        <div id="react"></div>
      </div>
    </template>
    
    <script>
    import { start } from "qiankun";
    
    export default {
      name: "Main",
      mounted() {
        if (!window.qiankunStarted) {
          window.qiankunStarted = true;
          start();
        }
      },
    };
    </script>
    
    展开全文
  • 微前端框架乾坤配置记录

    千次阅读 2020-12-08 23:50:45
    主应用 1. 路由包装为history模式 // src/main.js import routes from './routes'; const router = new VueRouter({ mode: 'history', ...2. 引入乾坤微前端主应用配置 // src/mico/index.js import {

    主应用

    1. 路由包装为history模式

    // src/main.js
    import routes from './routes';
    const router = new VueRouter({
      mode: 'history',
      routes
    });
    
    new Vue({
      router,
      store,
      render: (h) => h(App)
    }).$mount('#app');
    

    2. 引入乾坤微前端主应用配置

    // src/mico/index.js
    
    import { registerMicroApps, addGlobalUncaughtErrorHandler, start } from 'qiankun';
    
    import apps from './apps';
    
    registerMicroApps(apps,
    {
      beforeLoad: [
        app => {
          console.log('[LifeCycle] before load %c%s', 'color: green;', app.name);
        },
      ],
      beforeMount: [
        app => {
          console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name);
        },
      ],
      afterUnmount: [
        app => {
          console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name);
        },
      ],
    },);
    
    addGlobalUncaughtErrorHandler((event) => {
      console.log(event);
      const { msg } = event;
      if (msg && msg.includes('died in status LOADING_SOURCE_CODE')) {
        console.log('微应用加载失败,请检查应用是否可运行');
      }
    });
    
    export default start;
    

    3. 配置子应用列表

    // /src/micfo/apps.js
    
    const apps = [
        {
            name: 'planResource',
            entry: '//localhost:8083',
            container: '#iframe',
            activeRule: '/plan'
        },
        {
            name: 'configration',
            entry: '//localhost:8081',
            container: '#iframe',
            activeRule: '/configure'
        }
    ];
    
    export default apps;
    

    4. 主应用实例化后启动微应用监听

    // /src/main.js
    import startQiankui from './micro';
    
    new Vue({
      router,
      store,
      render: (h) => h(App)
    }).$mount('#app');
    
    startQiankui();
    

    子应用

    1. Vue应用接入

    1.1 修改webpack配置

    // webpack.config.js || vue.config.js
    
    devServer: 
    	port: 8083 // 端口要写死,因为主应用中配置子应用列表时用的是写死的端口配置
    	disableHostCheck: false // 关闭主机检查,保证子应用可以被主应用fetch到
    	headers: {
    		'Access-Control-Allow-Origin': '*', // 配置跨域请求头,解决开发环境跨域问题
    	}
    	publicPath: process.env.NODE_ENV === 'production' ? '/newplanning/' : '//localhost:8083',  // 开发环境设置为当前固定访问地址
    }
    configureWebpack: {
    	 output: { 
                library: 'planResource', // 名称要和主应用中配置的子应用列表中的name字段相同
                libraryTarget: 'umd', // 把子应用打包成 umd 库格式
                jsonpFunction: `webpackJsonp_planResource` //  此处后缀也要和名称保持一致
            }
    }
    

    1.2 增加 public-path配置文件并在入口文件引入

    //  /src/micro/public-path.js
    if (window.__POWERED_BY_QIANKUN__) {
        // eslint-disable-next-line no-undef
        __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
    }
    
    //  /src/main.js
    
    import '@micro/public-path'
    

    1.3 路由实例化配置

    // src/router/index.js
    import Vue from 'vue';
    import Router from 'vue-router';
    import routes from './routers.js';
    
    Vue.use(Router);
    
    export default new Router({
        base: window.__POWERED_BY_QIANKUN__ ? '/plan' : '/', // 如果作为乾坤的子应用则使用主应用中配置的路由前缀,如果是独立运行则使用根目录
        mode: 'hash', 
        routes // 路由定义数组
    });
    

    1.4 Vue实例化包装和生命周期钩子导出

    //  /src/main.js
    import './public-path';
    import router from './router';
    let instance = null;
    
    // 重新包装render方法
    function render(props = {}) {
        const { container } = props;
        const renderContainer = container ? container.querySelector('#app') : '#app'; // 如果是作为子应用渲染,则渲染到主应用中配置的DOM节点中
    
        instance = new Vue({
            router,
            store,
            render: h => h(App)
        }).$mount(renderContainer);
    }
    
    // 独立运行时直接渲染
    if (!window.__POWERED_BY_QIANKUN__) {
        render();
    }
    
    export async function bootstrap() {
        console.log('[vue] vue app bootstraped');
    }
    
    export async function mount(props) {
        console.log('[vue] props from main framework', props);
        render(props); // 作为子应用渲染时将主应用传入的配置作为参数去渲染
    }
    
    export async function unmount() {
        instance.$destroy();
        instance.$el.innerHTML = '';
        instance = null;
        router = null;
    }
    

    1.5 将代理信息复制到主应用

    2. angularjs 应用接入

    2.1 引入 HTML entry

    // index.html
    <body>
      <!-- ... >
    	<script src="//localhost:8080/lib/micro/entry.js" entry></script>
    </body>
    

    2.2 入口配置

    // /src/lib/micro/entry.js
    
    ((global) => {
        global['myAngularApp'] = {
            bootstrap: () => {
                console.log('myAngularApp bootstrap');
                return Promise.resolve();
            },
            mount: (props) => {
                console.log('myAngularApp mount', props);
                return render(props);
            },
            unmount: () => {
                console.log('myAngularApp unmount');
                return Promise.resolve();
            },
        };
    })(window);
    
    /**
     * [render 渲染函数]
     *
     * @param   {[type]}  props  [props description]
     *
     * @return  {[type]}         [return description]
     */
    function render(props = {}) {
        if (props && props.container) {
            console.log('乾坤渲染');
            window.myActions = {
                onGlobalStateChange: function(...args) {
                    props.onGlobalStateChange(...args);
                },
                setGlobalState: function(...args) {
                    props.setGlobalState(...args);
                }
            };
            window.myActions.onGlobalStateChange(state => {
                const { token } = state;
                console.log(token);
                // 未登录 - 返回主页
                if (!token) {
                    console.log('未检测到登录信息!');
                    window.location.href = '/';
                }
                localStorage.setItem('myAngularApp-token', token);
            }, true);
        } else {
            angular.element(document).ready(function () {
                angular.bootstrap(document, ['app']);
                return Promise.resolve();
            });
        }
    }
    
    if (!window.__POWERED_BY_QIANKUN__) { 
        render();
    }
    
    

    2.3 路由前缀

    需要注意的是,对于angularjs,路由一定要改造为统一的前缀,这样子应用不同路由之间的跳转才好被主路由统一获取到。

    2.4 模板路径问题

    在angularjs中,我们常常会写templateUrl属性,大多数时候我们会写模板的相对地址,但是这样就造成在主应用中对应的相对路径位置是找不到模板文件的,所以路径需要加一个统一的前缀参数,根据是否运行在主应用内来设定不同前缀。

    通信

    1.主应用

    1.1 初始化全局跨应用通信方法

    // /src/mico/actions
    
    import { initGlobalState } from 'qiankun';
    const initState = {};
    
    const actions = initGlobalState(initState);
      
    export default actions;
    

    1.2 在需要设置跨应用全局状态的组件内:

    <script>
    import store from '@/store'
    import httpService from '@/core/httpService'
    import actions from '@/micro/actions'
    
    export default {
      name: 'App',
      created() {
        if (!store.getters.token) {
            httpService.getToken().then(data => {
                store.commit('SET_TOKEN', data.data.token);
                actions.setGlobalState({ token: data.data.token });
            });
        }
      }
    }
    </script>
    

    2. 子应用

    2.1 全局actions定义

    //  /src/micro/actions.js
    
    function emptyAction() {
        // 警告:提示当前使用的是空 Action
        console.warn('Current execute action is empty!');
      }
      class Actions {
        // 默认值为空 Action
        actions = {
          onGlobalStateChange: emptyAction,
          setGlobalState: emptyAction
        };
        /**
         * 设置 actions
         */
        setActions(actions) {
          this.actions = actions;
        }
        /**
         * 映射
         */
        onGlobalStateChange(...args) {
          return this.actions.onGlobalStateChange(...args);
        }
        /**
         * 映射
         */
        setGlobalState(...args) {
          return this.actions.setGlobalState(...args);
        }
      }
      const actions = new Actions();
      export default actions;
    
    

    2.2 在应用渲染前获取从主应用上的通信方法并注入到actions里

    // /src/main.js
    
    import actions from '@/micro/actions';
    
    function render(props = {}) {
        if (props) {
            actions.setActions(props);
            actions.onGlobalStateChange(state => {
                const { token } = state;
                console.log(token);
                // 未登录 - 返回主页
                if (!token) {
                  this.$message.error('未检测到登录信息!');
                  return this.$router.push('/');
                }
                store.commit('SET_TOKEN', token);
              }, true);
        }
        const { container } = props;
        const renderContainer = container ? container.querySelector('#app') : '#app';
    
        instance = new Vue({
            router,
            store,
            render: h => h(App)
        }).$mount(renderContainer);
    }
    
    export async function mount(props) {
        console.log('[vue] props from main framework', props);
        // 注册应用间通信
        render(props);
    }
    
    
    展开全文

空空如也

空空如也

1 2 3 4 5
收藏数 86
精华内容 34
关键字:

微前端乾坤