- A+
一、背景
‘熵增’问题一直是所有软件开发中都会遇到的问题,不管是前端还是后端都会遇到,老的系统在需求不断变更或者迭代,代码量会越来越大,最终都会形成一座‘屎山’,
今天主要讨论前端对于这种情况的解决方案。
目前前端的解决方案有比较古老的iframe,但是iframe是完全隔绝了应用,导致应用之间的通信也完全隔离,只能通过windows的postmessage等api进行通信,所以
这种方式不是很推荐。qiankun是阿里推出的一个前端微服务架构,所以目前就qiankun搭建一个demo项目
二、技术调研
- ? 简单
由于主应用微应用都能做到技术栈无关,qiankun 对于用户而言只是一个类似 jQuery 的库,你需要调用几个 qiankun 的 API 即可完成应用的微前端改造。同时由于 qiankun 的 HTML entry 及沙箱的设计,使得微应用的接入像使用 iframe 一样简单。
- ? 解耦/技术栈无关
微前端的核心目标是将巨石应用拆解成若干可以自治的松耦合微应用,而 qiankun 的诸多设计均是秉持这一原则,如 HTML entry、沙箱、应用间通信等。这样才能确保微应用真正具备 独立开发、独立运行 的能力。
三、搭建
1、主应用
由于qiankun对于应用技术栈完全开发,所以可以根据自己的实际情况自行选择,我这里用的是vue2
首先使用vuecli脚手架框架创建新项目
vue create parent
安装qiankun依赖,我这里使用的是yarn包管理工具,自己也可以替换成npm和pnpm
yarn add qiankun
然后在主应用里面注册子应用,子应用的创建后面会说
import { registerMicroApps, start } from "qiankun"; //注册微服务应用 registerMicroApps([ { name: "vueApp", entry: "//localhost:8080", container: "#micro-app-container", activeRule: "/app-vue", }, ]); //启动qiankun start();
这里先以vue作为子应用来说,后面会继续增加react项目子应用。
name是应用名称,起到区分微服务的作用,
entry是子应用的实际访问地址,
container是子应用的挂载dom节点,这个dom节点是放在主应用里边的,下面这是主应用app.vue截取的部分代码
<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" alt="前端微服务架构qiankun初体验" /> <div id="micro-app-container"></div> </div> </template>
activeRule是子应用的路由匹配规则,就如如果访问地址是ip:端口/app-vue,qiankun框架会去访问entry对应的子应用地址,将子应用渲染加载到container节点
(特别说明:这里的activeRule和子应用的publicPath是有区别的,而且这两个需要不一样,否则有可能是直接访问的子应用服务,而不是通过qiankun框架去加载)
到这里qiankun主应用基本就搭建好了,但是在本地开发环境下,由于子应用和主应用不是同一个端口会触发csp跨域拦截,导致子应用加载不了,在这里我们可以
利用devserve的代理在主应用里边将子应用代理转发出去,以上边为例,子应用的端口号是8080,主应用端口号是8081,在主应用devserve配置一下配置
devServer: { open: false, host: "0.0.0.0", port: 8081, https: false, // 跨域设置 proxy: { "/app-vue": { target: "http://localhost:8080", // 子应用运行的端口 changeOrigin: true, }, }, },
2、子应用
子应用是vuecli搭建的vue3项目,vue3相对vue2配置上会有点区别,以后还会介绍react子应用
首先也是通过脚手架命令创建项目,这里就不概述了,创建的时候选择vue3,选项
在src目录下创建publicPath.js文件,文件内容如下,主要作用用于qiankun 在微应用 bootstrap 之前注入一个运行时的 publicPath 变量,这个webpack的publicPath
是不一样的
if (window.__POWERED_BY_QIANKUN__) { __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
然后在main.js里边引入,以下是main.js的内容
import { createApp } from "vue"; import App from "./App.vue"; import store from "./store"; import routers from "./router"; import { createWebHistory, createRouter } from "vue-router"; import "./publicPath.js"; let router = null; let instance = null; let mountNode = null; //渲染函数 function render(props = {}) { const { container } = props; router = createRouter({ history: createWebHistory( window.__POWERED_BY_QIANKUN__ ? "/app-vue/" : "/" ), routes: routers, }); if (container) { //qiankun框架时避免和其他的dom冲突,限制查找范围 mountNode = container.querySelector("#app"); } else { mountNode = "#app"; } instance = createApp(App); instance.use(store).use(router).mount(mountNode); } // 独立运行时 if (!window.__POWERED_BY_QIANKUN__) { render(); } //微服务情况下,需要暴露出三个生命周期钩子供主应用调用 /** * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 */ export async function bootstrap() { console.log("[vue] vue app bootstraped"); } /** * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 */ export async function mount(props) { console.log("[vue] props from main framework", props); render(props); } export async function unmount() { instance.unmount(mountNode); instance = null; router = null; }
详细说明一下这个mian文件,除去一些是vue项目的通用地方。这里新增了一个render函数,主要作用是用来区分是通过qiankun框架访问的,还是直接访问子应用,
直接访问应用直接实例渲染到app节点,如果是通过qiankun访问的,需要限制一下dom的查找范围,因为有可能会与主应用的dom节点冲突,另外创建路由时选择history
路由,且要设置base,vue3是通过createWebHistory来创建的,接受一个参数作为base,如果通过qiankun框架访问,需要和主应用配置的activeRule保持一致,base的
作用主要是在实际路由的基础上在前面加上配置的base,比如子应用有个路由是/router1,加base之后就是/app-vue/router1,否则访问的就是/router1,导致qiankun
过滤不到activeRule规则而页面显示不了。最后就是暴露的三个方法boostrap、mount、unmount,具体说明在代码里有说明。
至此子应用大致配置完成,剩下最后的一点配置,在vue.config.js添加以下配置
publicPath: "/", lintOnSave: false, devServer: { headers: { "Access-Control-Allow-Origin": "*", }, open: false, host: "0.0.0.0", port: 8080, https: false, // 跨域设置 // proxy: {}, }, configureWebpack: { output: { library: `${name}-[name]`, libraryTarget: "umd", chunkLoadingGlobal: `webpackJsonp_${name}`, }, },
这里的pulicPath根据实际情况配置,如果是在nginx访问根目录下就设置 / 就行,如果在根目录的文件夹下则需要改成文件夹名,而在主应用的entry和子应用的路由base也要
做相应修改,这里暂时不展开讨论。添加headers配置是因为qiankun内部请求都是fetch来请求资源,所以子应用必须允许跨域,否则会出现资源访问不了的问题,最后的是
子应用打包配置,为了让主应用能正确识别微应用暴露出来的一些信息。
现在整个qiankun微服务就搭建完成了,后续会做react项目的集成,以及主应用、子应用之间的通信。