• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

hql7/wl-micro-frontends: Micro front end practical project tutorial. 微前端项目 ...

原作者: [db:作者] 来自: 网络 收藏 邀请

开源软件名称:

hql7/wl-micro-frontends

开源软件地址:

https://github.com/hql7/wl-micro-frontends

开源编程语言:

Vue 50.1%

开源软件介绍:

wl-micro-frontends [wl-qiankun]

本项目采用 vue + qiankun 实践微前端落地。
同时 qiankun 是一个开放式微前端架构,支持当前三大前端框架甚至 jq 等其他项目无缝接入。
此项目为了尽可能的简单易上手,以及方便文章讲解,大部分逻辑都在主应用和子应用的 main.js 来回施展,
实际项目应用可不要如此粗暴,要有优雅的架构设计。

另有微前端进阶实战项目:基于 vue3.0-beta 及 qiankun2.0 极速尝鲜!wl-mfe
部署教程:使用各种姿势舒服的部署微前端项目

在线地址

项目启动

npm run cinit
npm run init
下载依赖,因为是批量下载所有应用下的依赖,推荐cinit节省下载时间

npm run serve
运行项目,同样,批量运行所有应用会耗时较久,浏览器页面自动打开后请稍家等待,然后刷新即可

npm run build
打包项目,打包所有应用

微前端 qiankun

微前端是什么、为什么要做微前端、qiankun 是什么这些笔者将不再叙述。(文末有彩蛋~)
传送门:可能是你见过最完善的微前端解决方案 & qiankun
下面直接进入实战教程。

实战教程目录详解

鉴于 qiankun 文档只有寥寥十几行,这里做一个尽量详细的实战示例描述:

  • 微前端主应用与子应用如何构建
  • 主应用与子应用通信(静态,无法监测到值变化)
  • 主、子,各应用间动态通信(动态,各应用间实时监听,同步数据)
  • 主应用资源下发至子应用
  • 微前端鉴权方案
    • 一:异步注册(主应用异步获取子应用注册表并将子应用对应的路由下发至子应用)
    • 二:异步路由(使用应用间通信,通知子应用路由数据,子应用在内部 addRoutes 异步插入路由)
  • 各应用间路由基础管理
  • 公共资源处理

微前端主应用与子应用如何构建

构建主应用

  1. 创建一个主项目工程目录
  2. npm install qiankun 下载微前端方案依赖
  3. 改造主项目:

    在 main.js 中

// 导入qiankun内置函数
import {
  registerMicroApps, // 注册子应用
  runAfterFirstMounted, // 第一个子应用装载完毕
  setDefaultMountApp, // 设置默认装载子应用
  start, // 启动
} from 'qiankun';

let app = null;
/**
 * 渲染函数
 * appContent 子应用html
 * loading 如果主应用设置loading效果,可不要
 */
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;
  }
}

/**
 * 路由监听
 * @param {*} routerPrefix 前缀
 */
function genActiveRule(routerPrefix) {
  return (location) => location.pathname.startsWith(routerPrefix);
}

// 调用渲染主应用
render();

// 注册子应用
registerMicroApps(
  [
    {
      name: 'vue-aaa',
      entry: '//localhost:7771',
      render,
      activeRule: genActiveRule('/aaa'),
    },
    {
      name: 'vue-bbb',
      entry: '//localhost:7772',
      render,
      activeRule: genActiveRule('/bbb'),
    },
  ],
  {
    beforeLoad: [
      (app) => {
        console.log('before load', app);
      },
    ], // 挂载前回调
    beforeMount: [
      (app) => {
        console.log('before mount', app);
      },
    ], // 挂载后回调
    afterUnmount: [
      (app) => {
        console.log('after unload', app);
      },
    ], // 卸载后回调
  }
);

// 设置默认子应用,参数与注册子应用时genActiveRule("/aaa")函数内的参数一致
setDefaultMountApp('/aaa');

// 第一个子应用加载完毕回调
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>

在 vue.config.js 中

主应用可以不配置 vue.config.js 或者为了方便找到主应用运行端口可以只设置一个 port

module.exports = {
  devServer: {
    port: 3333,
  },
};

更多的设置:

const port = 7770; // dev port
module.exports = {
  // publicPath: './',
  devServer: {
    // host: '0.0.0.0',
    hot: true,
    disableHostCheck: true,
    port,
    overlay: {
      warnings: false,
      errors: true,
    },
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
};

注意这里,vue 项目中,主应用设置publicPath: './'会造成子应用配置的 publicPath 失效进而刷新页面报错的问题。 一说可以在子应用 vue.config.js 里直接配置 publicPath 解决。

构建子应用

  1. 在主应用的同级目录下创建一个子项目
  2. 改造子项目

    在 main.js 中

import Vue from "vue";
import VueRouter from "vue-router";
import App from "./App.vue";
import "./public-path";
import routes from "./router";

Vue.config.productionTip = false;

// 声明变量管理vue及路由实例
let router = null;
let instance = null;

// 导出子应用生命周期 挂载前
export async function bootstrap(props) {
    console.log(props)
}

// 导出子应用生命周期 挂载前 挂载后
**注意,实例化路由时,判断当运行在qiankun环境时,路由要添加前缀,前缀与主应用注册子应用函数genActiveRule("/aaa")内的参数一致**
export async function mount(props) {
  router = new VueRouter({
    base: window.__POWERED_BY_QIANKUN__ ? "/aaa" : "/",
    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 环境时,路由要设置 base 参数,参数值与主应用注册子应用函数 genActiveRule("/aaa")内的参数一致。

**注意:**上面小小的修改了 router.js 导出的内容 & 引入了一个叫public-path的文件。

在 router.js 中(可不修改,自行处理)

const routes = [
  {
    path: '/',
    name: 'home',
    component: Home,
  },
  {
    path: '/about',
    name: 'a-about',
    component: () => import('../views/About.vue'),
  },
];

export default routes;

不再导出 rouer 实例而是导出路由数据

在 public-path 文件内(可直接在 vue.config.js 内配置 publicPath)

if (window.__POWERED_BY_QIANKUN__) {
  // eslint-disable-next-line no-undef
  __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}

在 vue.comfig.js 中

const path = require('path');
const { name } = require('./package');

function resolve(dir) {
  return path.join(__dirname, dir);
}

const port = 7771; // dev port

module.exports = {
  outputDir: 'dist',
  assetsDir: 'static',
  filenameHashing: true,
  devServer: {
    // host: '0.0.0.0',
    hot: true,
    disableHostCheck: true,
    port,
    overlay: {
      warnings: false,
      errors: true,
    },
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  },
  // 自定义webpack配置
  configureWebpack: {
    resolve: {
      alias: {
        '@': resolve('src'),
      },
    },
    output: {
      // 把子应用打包成 umd 库格式
      library: `${name}-[name]`,
      libraryTarget: 'umd',
      jsonpFunction: `webpackJsonp_${name}`,
    },
  },
};

注意 output 必须按照规定格式要求配置;

经过上述改造,一个简易的微前端环境就草草建成了,是不是很简单,你是不是已经跃跃欲试了?

直接上手跑代码 --> wl-micro-frontends(wl-qiankun)

当然,一个基础的微前端架子建成后,我们还有一些无法绕过的问题要处理,下面将其中部分逐一讲解。

主应用与子应用通信(静态,无法监测到值变化)

父子应用通信

在上述所建微前端应用中,父子间的通信是极其普遍且无法绕过的需求,而 qiankun 在这方面当然有所考虑。

在上述构建项目步骤中,有一步是在主应用 main.js 注册子应用:

registerMicroApps([
  {
    name: 'app1',
    entry: '//localhost:7771',
    render,
    activeRule: genActiveRule('/app1'),
    props: 'mg', // 传递给子应用
  },
]);

在上述构建项目步骤中,有一步是子应用导出生命周期:

export async function bootstrap(props) {
  console.log(props);
}

其中 props 参数即为传递给子应用的数据,其内容你可以自由定制。 子应用在 main.js 导出的生命周期函数中均可接收到所传 props 数据:

qiankun 对于 props 的应用类似于 react 框架的父子组件通信,传入 data 数据供自组件使用,传入 fn 函数给子组件触发向上回调。

按照这个思路我们将主应用的 main.js 和子应用的 main.js 都改造一番(此改造会在后续主应用下发子应用资源章节重构):

改造后的主应用 main.js

// ...
// 定义传入子应用的数据
let msg = {
  data: {
    auth: false,
  },
  fns: [
    function LOGOUT_(data) {
      alert('父应用返回信息:' + data);
    },
  ],
};

// 注册子应用
registerMicroApps([
  {
    name: 'app1',
    entry: '//localhost:7771',
    render,
    activeRule: genActiveRule('/app1'),
    props: msg, // 将定义好的数据传递给子应用
  },
]);
//...

在注册应用时将定义好的msg通过props参数传递给子应用。 现实使用场景中可能会依赖于某些后台返回数据比如权限路由信息,你也可以等到请求完成再调用注册。

改造后的子应用 main.js

// ...
export async function bootstrap(props = {}) {
  Array.isArray(props.fns) &&
    props.fns.map((i) => {
      Vue.prototype[i.name] = i[i.name];
    });
}
// ...

我们这里在 bootstrap 函数里将接收到的 props 参数内的函数挂在 vue 原型上方便使用,你也可以在其他导出的生命周期函数内得到 props 并按照你的设想去处理。

主、子应用间动态通信(动态,各应用间实时监听,同步数据)

经过上述处理后我们仍会遇到一个问题:即需要改变状态的数据如果通信,上面只是在注册子应用时传递了一次数据,而数据改变后又不能再注册一遍子应用。这个时候就需要应用间动态通信了,比如处理主应用登陆后的用户身份改变,或者 token 之类。 这个问题是考虑微前端架构的人都会遇到,当然 qiankun 官方也在日程上规划的有官方通信机制,但是鉴于一次又一次的时间推迟及维护人手短缺,这里本文作者使用rxjs来作为应用间通信的方案。

因为只使用 rxjs 解决应用间通信的需求,因此处理十分简单

  1. 先在主应用下载并引入 rxjs;并创建我们的‘呼机’
import { Subject } from 'rxjs'; // 按需引入减少依赖包大小
const pager = new Subject();
export default pager;
  1. 然后在主应用 main.js 引入并注册呼机,以及将呼机下发给子应用
import pager from './util/pager'; // 导入应用间通信介质:呼机

pager.subscribe((v) => {
  // 在主应用注册呼机监听器,这里可以监听到其他应用的广播
  console.log(`监听到子应用${v.from}发来消息:`, v);
  store.dispatch('app/setToken', v.token); // 这里处理主应用监听到改变后的逻辑
});

let msg = {
  // 结合下章主应用下发资源给子应用,将pager作为一个模块传入子应用
  data: store.getters, // 从主应用仓库读出的数据
  components: LibraryUi, // 从主应用读出的组件库
  utils: LibraryJs, // 从主应用读出的工具类库
  emitFnc: childEmit, // 从主应用下发emit函数来收集子应用反馈
  pager, // 从主应用下发应用间通信呼机
};
registerMicroApps(
  // 注册子应用
  [
    {
      name: 'subapp-ui',
      entry: '//localhost:6651',
      render,
      activeRule: genActiveRule('/ui'),
      props: msg, // 将上面数据传递给子应用
    },
  ]
);
  1. 在子应用中注册呼机
export async function bootstrap({ components, utils, emitFnc, pager }) {
  Vue.use(components); // 注册主应用下发的组件
  Vue.prototype.$mainUtils = utils; // 把工具函数挂载在vue $mainUtils对象
  Object.keys(emitFnc).forEach((i) => {
    // 把mainEmit函数一一挂载
    Vue.prototype[i] = emitFnc[i];
  });

  pager.subscribe((v) => {
    // 在子应用注册呼机监听器,这里可以监听到其他应用的广播
    console.log(`监听到子应用${v.from}发来消息:`, v);
    // store.dispatch('app/setToken', v.token)   // 在子应用中监听到其他应用广播的消息后处理逻辑
  });
  Vue.prototype.$pager = pager; // 将呼机挂载在vue实例
}
  1. 在各应用中使用呼机动态传递信息
methods: {  // 在某个应用里调用.next方法更新数据,并传播给其他应用
  callParentChange() {
    this.myMsg = "但若不见你,阳光也无趣" 
                       
                    
                    

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
热门推荐
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap