基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

写在最初

前端系列文章:

  • 根据 qiankun 的微前端最佳实践(万字长文) – 从 0 到 1 篇
  • 根据 qiankun 的微前端最佳实践(图文并茂) – 运用间通讯篇
  • 万字长文+图文并茂+全面解析微前端框架 qiankun 源码 – qiankun 篇

本系列其他文章计划一到两个月内完结,点个 重视 不走失。

计划如下:

  • 生命周期篇;
  • IE 兼容篇;
  • 出产环境部署篇;
  • 性能优化、缓存计划篇;

导言

大家好~

本文是根据 qiankun 的微前端最佳实践系列文章之 从 0 到 1 篇,本文将分享怎么运用 qiankun 怎么建立主运* ) E O : j用基座,然后接入不同技能栈) S O v D i $ a的微运用,完结微前端架构的从 0 到 1。

本教程选用 Vue 作为主运用基座,接入不B R M同技能栈的微运用。假如你不明白 Vue 也没关系,咱们在建立主运用基座的教程尽量不涉及 VueAPI,涉及到 API 的地方都会给出解说。

留意:qiankun 属于无侵入性的微前端框架,对主运用基座和微运用的技能栈都没有要求。

咱们在本教程中,接入了多技能栈 微运用主运用 最终作用图如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

构建主运用基座

咱们以 实战事例 – feature-inject-sub-apps 分支 (事例是以 Vue 为基座的主运用,接入多个微运用) 为例,来介绍一下怎么在 qiankun 中怎么接入不同技能栈的微运用。

咱们先运用 vue-cli 生成一个 Vue 的项目,初始化主运用。

vue-cli 是 Vue 官方提供的脚手架工具,用于快速建立一个 Vue 项目h 1 d & ^ Z。假如你想越过这一步,能够直接( B | clone 实战事例 – feature-inject-sub-apps 分支 的代码。

将一般的项目改形成 qiankun 主运用基座,需求进行三步操作:

  1. 创立微运用容器 – 用于承载微运用,烘托显现微运用;
  2. 注册微运用 – 设置微运用激活条件,微运用地址等等c f K X
  3. 发动 qiankun

创立t g q O U R } W 微运用容器

咱们先在主运用中创立{ 0 1 – ~ j 3 8微运用的承载容器,这个容器规矩了微运= H 2 c N t 6用的显现区域,微运用将在该容器内烘托并显现。

咱们先设置路由,路由文件规矩了主运用自身的路由匹配规矩,代码完结如下:

// micro-app-z } )main/src/routes/iF q - + 7 & 8 B ^ndex.ts
import Home from "@/pages/home/index.vueh u U $ m ; G V 6";
const routes = [
{
/**
* path: 途径为 /~ k 4 9 - @ e 时触发该路由规矩
* name: 路由的 name 为 Home
* component: 触发路由时加载 `H& - 9 3 e (ome` 组件
*/
path: "/",
na; Q Xme: "Home",
component: Home,
},
];
export default routes;
// micro-;  , xapp-main/src/main.ts
//...
import Vue from "vue";
import VueRoutex + 0r from "vue-router";
import routes from "./routes";
/**
* 注册路由实例
* 即将开端监听B y b | location 改变L P C l , ? ; a,触? 4 { # u X Z发路由规矩
*/
const router = new VueRouter({
mode:0 % W "history",
routes,
});
// 创立 Vue 实例
// 该实例将挂载/烘托在 id 为 main-app 的节点上
new Vue({
router,
render: (h) => hF ( P : 8 _ q(App),
}).$mounv p d & ; N 4 1 .tx 3 $("#2 e ( Imain-apr - P & q Mp");

从上面代码9 Q M . p C l能够看出,咱们设置了主运用的路由规矩5 c ^ , 3 M o l :,设置了 Home 主页的路由匹配规矩。

咱们现在来设置主运用的布局,咱们会有一个菜单和显现区域,代码完结如下:

// micro-am 1 N z o W , W hpp-main/sr@ n 5 k 1 ? gc/App.vue
//...
export default class App extends` W W 7 L I P Vue {
/**
*- * ? H u s V 菜单列表
* key: 仅有 Key 值
* title: 菜单标题
* path: 菜单对应的途径
*/
menus = [
{
key:x ? 1 d | q 3 q f"Home",
title:I r M "主页",
path: "/",
},
];
}

上面的代码是咱们对菜单装备的完结,咱们还需求完结基座和微运用的显现区域(如下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

咱们来剖析一下上面的代码:

  • 第 5 行:主运用菜单,用于烘托菜单;
  • 第 9 行:主运用烘托区。在触发主运用路由规矩时(由路由装备表的 $route.name 判别),将烘托主运用的组件;
  • 第 1E ( ;0 行:微运用烘D I k / L q i Z e托区。在未触发主运用路由规矩时(由路由装备表的 $route.name 判别4 / ; ; s 4 I 0 v),将烘托微运用节点;

从上面的剖析能够看出,咱们运用了在路由表装备的 name 字段进行判别,判别当时路由是否为主运用路由,最终决议烘托主运用组件或是微运用节点j g ~ L W u 6

由于篇幅原因,款式完结代码就不贴出来了,最终主运用的完结作用如下图所示:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

从上图能够看出,咱们主运用的组件和微运用是显现在同一片内容区域,根据路由规矩决议烘托规矩。

注册微运用

在构建好了主$ N _ T ; ~ [框架后,咱们需求运用 qiankunregisterMicroApps 办法注册微运用,代码完结如下:

/D { j I Z [ K J/ micro-app-main/src/micro/apps.ts
// 此刻咱们还没有微运用,所以 apps 为空
const apps = [];
export default apps;
// micro-app-main/src/micro/index.ts
// 一个进度条插件
import NProgress from "nprogress";
import "npB B |rogress/nprogress.d A , 3 k D U v gcss";
import { messageW O + Z - z _ ] C } from "ant-design-vue";
import {
register# ] + 7 i zMicroApps,
addGll 4 8 N d 3 1  NobalUncaughtErrorHandler,
start,
} from "qiankun";
// 微运用注册信息
import apps from "./apps";
/**
* 注册微运用
* 第一个参数 -? l K g O  x Q 微运用的注册信息
* 第二个参数 - 大/ * A  = q Y  N局生命周期钩子
*/
registerMicrY d ; f d !oApo V 6 { q z 4ps(apps, {
/; a 6 6 A X/ qiankun 生命周期钩子 - 微运用加载前
beforeLoad: (app: any) => {
// 加载微运用前,加载进度条
NProgress.start();
console.log("before load", a~ o m C E % ` Rpp.name);
return~ 9 Z Prom^ 5 7 v 8 , & v 3ise.resolve();
},
// qiankun 生命周期钩子 - 微运用挂载后
afterMount: (app: any) => {
// 加载微运用前,进度条加载完结
NProgress.done();
con- s 2 rsole.log("after mount", app.name);
return Promise.resolve(), L =;
},
});
/**
* 添加大局的未捕获 P [异常处理器
*/
addGlobalUncaugh) X utErrorHandler((event: Event | string) => {
console.error(event);
const { message: msg } = event as any;
// 加载失利时提示
if (msg && msg.includes("died in status LOADING_SOURCE_CODE")) {
message.error("微运用加载失利,请检查- # & * F运用是否可运转");
}
});
// 导出 qiankun 的发动函数
export default start;

从上面能够看出,咱们的微运用注册信息在 apps 数组中(此刻为空,咱们在后面接入微运用时会添加微运用注册信息),然后运用] O H Z { ) k ; qianu { m V BkunregisterMicroApps 办法注册微运用4 P {,最终导出了 start 函数,注册微运用的作业就完结啦!

发动主运用

咱们在注册好了微运用,导出 start 函数$ ? k 0 n X T #后,咱们需求在合适的地方调用 start 发动主运用。

咱们一般是在进口文件发动 qiankun 主运用,代码完结如下:

// micro-app-main/st { p :rc/main.ts
//...
imp1 H d r q U 2 K ^ort startQiankun from "./micro";
stao o 1 VrtQiankun();

最终,发动咱们的主运用,作用图如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

由于咱们还没有注册任何微运用,所以这儿的作用图和上面的作用图是相同的。

到这一步,咱们的主运用基座就创立好啦!

接入微运用z S , [ Q

+ t : U # Z们现在的主运用基座只3 h f e ; U 3有一个主页,现在咱们需求接入微运用。

qiankun 内部经过 import-entry-html( 9 w X G 加载微运用,要求微运用需求导出生命周期钩子函数(见下图)。

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

从上图能够看出,qiankun 内部会校[ P O v t e p k验微运用的生命周期钩子函@ ] J X ]数,假如微运用没有导出这三个生命周期钩子函数,则微运用会加载失利。

假如咱们运用了脚手架建立微运用的话,咱们能够经过 webpack 装备在进口文件处导出这三个生命周期钩子函数。假如没有运用脚手架的话,也能够直接在微z 3运用的 window 上挂载这三个生命周期钩子函数。

现在( f T C7 ^ . { : M们来接入咱们的各个技能栈微运用吧!

留意,下面的内容对相关技s T w K a e : @能栈 API 不会再有过多介绍啦,假如你要接入不同技能0 k : 0 4 P栈的微运用,最好要对该技能栈有一些基础了解。

接入 Vue 微运用

咱们以 实战事例 – fE l & [ : Z { i 0eature-injecti S | u-sub-apps 分支 为例,咱们在主运用的同级目录(micro-app-main 同级目录),运用 vu Z 6e-cli 先创立一个B G m r K Vue 的项目,在指令行运M : M W转如下指令:

vue create micro-app-vue

本文的) 3 F vue$ e 8 F (-cli 选项如下图所示,你也能够根据自X B A A己的喜爱挑选装备。

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

在新建/ . o项目完结后,咱们创立几个路由页面再加上一些款式,最终作用如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇
基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

注册微运用

在创立好了 Vue 微运用后,咱们能够开u ) 2 P P端咱们的接入作业了。首要咱们需求在主运用中注册该微运用的信息,代码完结如下:

// micro-app-main/src/micro/apm 1 c i / Pps.ts
const apps = [
/**
* name: 微运用称号 - 具有仅有性
* entry: 微运用进口 - 经过该地址加载微运用
* container: 微运用挂载节点 - 微运用加载完结后将挂载在该节点上
* activeRule: 微运用触发的路由规矩 - 触发路由规矩后将加载该微运用
*/
{
name: "VueMicroApp",
entry: "//lox ? @ H d ? V F pcalhost:10200",
container: "#frame",
activeRule: "/vue",
},
];
export default apps;

经过上面的代码,咱们就在主运用中注册了咱们的 Vue 微运用,进入 /vue 路由时将加载咱们的 Vue 微运用。

咱们在菜单装备处也参加 Vue 微运用的方便进口,代码完结如下:

// micro-app-mais ; x P C dn/src/App.vue
//...
export default class App extends Vue5 . } $ j { 9 {
/**
* 菜单列表
* key: 仅有 Key 值
* title: 菜单标题
* path: 菜单对应的途径
*/
menus = [
{
key: "Home",
title: "主页",
path: "/",
},
{
key: "VueMicroApz s F M j @ P W (p",
title: "Vue 主页",
path: "/vue",
},
{
key: "VueMicroAppListE L g 1 T l a",
title: "Vue 列表页",
path: "/vue/list",
},
];
}

菜单装备完结后,咱们的主运用基座作用图如O , A N w } .

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

装备微运用

在主运用注册好了微运用后,咱们还需求对微运用进行一系/ I r F j – c R i列的装备。首要V g K D . # ) d *,咱们在 Vue 的进口文件 main.js 中,导出 qiankun 主运用所需求的三个生命周期钩子函数,代码完结如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

从上图来剖析:

  • n ) m E r A W i 6 行webpack 默许的 publicPath"" 空字符串,会根据当时途径来加载资源。咱们在主运用中加载微运用时需求从头设置 publicPath,这样才干正确加载微运用的相关资源。(public-path.js 详细完结在后面)
  • 第 21 行:微运用的挂载函数,在主运用中9 I ; Y ? 3 : } 运转时将在 mouI 7 P ! C fnt 生命周期钩– 0 o子函数中调用,能够确保在沙箱内运转。
  • 第 38 行:微运用独立运转时,直接履行 render 函数挂载微运用。
  • 第 46 行:微运用导出的生命周期Y h c 5 6 G ` N 1钩子函数 – boo, 3 [ % ntstrap
  • 第 53 行:微运用导出的生命周期钩子函数 – mount
  • 第 61 行:微运用导出的生命周期钩子函数 – unm} . 2 ? r 1 `ount

完好代码完结如下:

//L B # a F micro-app-vue/src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// 动态设W } o E f F置 webpack publicPath,避免资源加载犯错
// esla w Kint-disable-next-line no-undef
__webpack_publ] | q V ; i z iic_path__ = windL C sow.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
// micro-app-vuem n f 8 7/src/main.js
import Vue fro{ X Nm "vue";
import VueRouter from "vue-router";
import Antd from "ant-design-vue+ / y 9 /";
import "ant-design-vue/dist/antd.css";
import "./public-path";
impog N Y C , @rt App from "./App.b b p Kvue";
impoL n $ k _rt routes from ".J d 8 g h } C t/routes";
Vue.use(VueRouter);
Vue.use(5 & Y ! : hAntd);
Vue.config.productionTip = false;
let instance = null;
letN h Z } B z 6 router = null;
/**
* 烘托函数
* 两种状况:主运用生命周期钩子中运转 / 微运用单独发动时运转
*/
function rendw | ~ X d N # G /er() {
// 在t k s b F e r % render 中创立 VueRouter,能够确保在卸载微i x a s运用时,移除 location 事情监听,避免事情污染
r8 P  K ! / Wouter = new V. q m DueRouter({
// 运转在主运用中时,添加路由命名空间 /vue
base: window.__POWERED_BY_QIANKUN__ ? "/vue" : "/r { h F",
mode: "history",
routes,
});
// 挂载运用
instance = new Vue({
router,
render: (h) => h(App),
}).$mount(V Y 6 I w *"#app");
}
// 独立运转时,直接挂载运用d K p Q j
if (!window.__POWQ o @ 7ERED_BY_QIANKUN__) {C x j D U
render();
}
/**! 4 b
* bootstrap 只会F h ; . 3 4 h U $在微运用初始化的时分调用一次,下次微运用从头进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 一般咱们能够在这儿做一些大局变q G Y m @ / 2量的初始化,比方不会在 unU 4 Gmount 阶段被毁掉的R * $ W _ T ( o d运用等级的缓存等。
*/
export6 : s D d } D M } async function bootstrap() {
console.log("VueMicroApp bootstraped");
}
/**
* 运用每次进入都会调用 mount 办法,一般2 f M 咱们在这儿触发运用的烘托办法
*/
export async function mount(R 3 S B g k 3 Qprops) {
console.log("VueMicroApp mount", props);
render(props);
}
/**
* 运用每次 切出/卸载 会调用的办法,一般在这儿咱们会卸载微运用的运用实例
*/
exp8 ? O E ] 5 Fort async A y + | 0 X Pc function unmount() {
console.log("VueMicroApp unmount");
instance.$destroy();
instance = null;
roN ] t k a L cuter = null;
}

在装备好了进口文件 main.js 后,咱们还需求装备 webpack,使 main.js 导出的生命周期钩子函数能够被 qiankun 辨认获取。

咱们直接装备 vuH + 4e.config.js 即可,代码完结如下3 a W C B P Q s

// micro-app-vue/vue.config.js
const path = require("path");
mod@ N @ A wule.exports = {
dev[ 2 & : BServer: {
// 监听端口
port: 10200,
// 封闭主机检查,使微6 V ( =  p运用能够被 fetch
disableHostCheck: true,
// 装备跨域恳求头,处理开发环境的跨域问题
headers: {
"Access-n i x tControl-AlloZ K 7 k ( @ W | tw-Origin": "*",
},
},
configureWebpack: {
resolve: {
alias: {
"@": path.resolve(__dirnamec Z ] C d 8, "src"),
},
},
output: {
// 微运用的包名,这儿与i f V Q主运用中注册的微运用称号共同
li_ ) ] ? u R  vbrary: "VueMicroApp",
// 将你的 library 露出为一切的模块界说下都可运转的办法
libraryTarget: "umd",
// 按需加载相关,设置为 webpackJsonp_VueMh c d $ w c ; dicroApp 即可
jsonpFunction: `webpackJsonp_VueMicroApp`,
},
},
};

咱们需求N C ~ A 4 D ` ! +重点重视一下 output 选项,当咱们把 libraryTarget 设置为 umd 后,咱们的 library 就露出为一切的模块界说下都可运转的办法了,主运用就能够获取到微运用的生命周期钩子函数了。

vue.config- T ? I.js 修正完结后,咱们从头发动 Vue 微运用,然o A a n ` } & !后翻开主运用基座 http://n n {localhost:9999。咱们点击左边菜单切换到微运用,此刻咱们的 Vue 微运用被正确加载啦!(见下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

此刻咱们翻开控制台,能够看到咱们所履行的生命周期钩子函数(见下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

到这儿,Vue 微运用就接入成功了!

接入 React 微运用

咱们以 实战事例 – feature-inject-sub-apps 分支 为例,咱们在主运用的同级目录(micro-app-main 同级目录),运用 cF j L ~ % 9reate-react-app 先创立一个 React 的项目,在指令行运转如下指令:

npx create-react- r -J 5 H app micro-app-react

在项目创[ & U g立完结后,咱们在根目录下添加 .env 文件,设置项目监听的端口,代码完结如下:

# micro-ay x Tpp-react/.env
POR7 n cT=10100
BROWSEY ] M 5R=none

然后,咱们创立几个路由页面再加上一些款式,最终作用如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇
基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

注册微运用

在创立好了 React 微运用 r + _ 7 w R / 后,咱们能够开端咱们的接入作业了。首要咱们需求在主运用中注册该微运用的信息,代码完结如下:

// micro-app-main/src/micro/apps.ts
const apps = [
/**
* name: 微运用称号 - 具有仅有性
* entry: 微运用进口 - 经过该地址加载微运用
* coG U J  B f , nntainer: 微运用= 0 D 5 ? , .挂载节点 - 微运用加载完) # 结后将挂载在该节点上
* activeRule: 微运用触发的路由规矩 - 触发路由规矩后将加载该微运用
*/
{
name: "ReactMicroApp",
enZ g B x q _ v *t1 m G ) $ L P 0ry: "//locA 2 d t  ralhost:10100",
container: "#frame" A q U g,
activeRule: "/react",
},
];
export default apps;

经过上面的代码,咱们就在主运用中注册了咱们的 React 微运用,进入 /reB | F e 5 8act 路由时将加载咱们的 React 微运用。

咱们在菜单装备处也参加 React 微运用的方便进口,代码完结如下:

// micro-app-mas 6 y = : 8 / #in/src/App.vue
//...
ed x a N P 5 i Axport default class App extends Vue {
/**
* 菜单列表
* keyP # !: 仅有 Key 值
* title: 菜单标题
* path: 菜单对应的途径
*/
menus = [
{
key: "Home",
tiA e k | w h _ ? ^tle: "主页",
path: "/",
},
{
key: "ReacR H U W o L Y !tMicroApp9 l N m m 1 D , w",
title: "React 主页",
path: "/react",
},
{
key: "ReactMicroAppList",
title: "React 列表页",
path: "/reaQ t Q N R t & } tct/c f Y rlist",
}o ! + A n i : s,
];
}

菜单装备完结后,咱们的主运用基座作用图如下

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

装备微运用

在主运用注册好了微运用后,咱们还需求对微运用进行一系列的装备。首要,咱们在 React 的进口V V j ; q & t文件 index.js 中,导出 qiankun 主运用所需求的三个生命周0 E T O Z 3期钩子函数,代码完结如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

从上图来剖析:

  • 第 5 行webp| w : 3 V , s Oack 默许的 publicPatN z /h"" 空字符串,会根据当时途B G 0径来加载资源。咱们在主运用中加载微运用时需求从头设置 pv f g W ^ublicPath,这样才干正确加载微7 W Z + / ) v l ju 7 – t用的相关资源。(public-path.js 详细完结在9 q K |后面)
  • 第 12 行:微运用的挂载函数,在主运用中运转时将在 mount 生命周期钩子函数中调用,能够确保在p [ 7 b沙箱内运转。
  • 第 17 行:微运用独立运转时,直接? ( 8 – | 7 m履行[ J b m E M render 函数挂载微运用。
  • 第 25 行:微运用导出的生命周期A o `钩子函数 – bootstrO | P T p L d Y Fap
  • 第 32 行:微运用导出) + h @ 9 ` L = ^的生命周期钩子函数 – mount
  • 第 40 行:微运用导出的生命周期钩子函数 – un[ % ( L j j 4 [ Dmount

完好代码完结如下:

// micro-app-react/src/public-path.js
if (window.__POWERED_BY_QIANKUN__) {
// 动态设置 webpack publicPath,避免资& o ` 源加载犯错
// eslint-disable-next-line no-undef
__webpack_public_path__ = wind} Y dow.__INJECTED_PUBLIC_N B H s U 6 APATH_BY_QI6 E sANKUN__;
}
// micro-app-react/sP 0 =rc/index.js
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./public-pag M Z S cth";
import App from "./App.jsx";
/**
* 烘托函数
* 两种状i } % D x ` T 6 u况:主运用生命周期钩子中运转 / 微运用单独发动时运转
*/
function render() {
ReactDOM.render(<Ap& T & Zp />, document.getElementById("root"));
}
// 独立运转时,直接挂载运用
if (!window.__w ^ i J YPOWERED_BY_QIANKUN__) {
render();
}
/**
* bootstrap 只会在微运用初始化的时分调用M a C k 0 C一次,下次微运用从头进入时会直接调用 mount 钩子,不会再重复触发 boo( * + E U _ v M Qtstrap1 s C t R y j B }。
* 一般咱们E c y b U 6能够在这儿做一些大局变量的初始化,比X E S - - @ # p J方不会在 unmount 阶段被毁掉的运用等级的缓存等。
*/
export async function bootstrap() {
console.. w { r L / / clog("ReactMicroApp bootstraped");
}
/**
* 运用每次进入都会调用 mount 办法,一般咱们在这儿触发运用的烘托办法
*/
export4 O k 3 H X d s async function mount(props) {
console.log("Reaf T ] C P , HctMicroApp mount", props);
render(props);
}
/**
* 运用每次 切出/卸载 会调用的办法,一般在这儿咱们会卸[ ; L } 6 B u d M载微运用的运用实例
*/P y l z d 1 8 N K
export async function unmount() {
console.log("ReactMicroApp unmount");
ReactDOM.unmountComponentAtNod# y : y e /e(document.getElementById("root"));
}

在装备好了进口文件 index.js 后,咱们还需求装备路由命名空间,以确保主运用能够正确加载微运用,代码完结如下:

// micro-app-react/src/App.jsx
const BASE_NAME = window.__POWERED_BY_QIANKUo [ ! b | hN__ ? "/react" : "";
const App = () =>{ X @ A A { F {
//...
return (
// 设置路由命名空间
<RouteG 5 4 D /r basename={BASE_NAME}>{/* ... */}&k I C u 7 Clt;/Router>
);
};

接下来,咱们还需求装备 webpack,使 index.js 导出的生命周期钩子函数能够被 qiankun 辨认获取。

咱们需求凭借B ( S } react-app-rewired 来帮助咱们修正 webpack 的装备,咱们直接装置该插件:

npm install react-app-rewired -D

react-app-rewiredu – O k Z c E O置完结后F . } c 4 Q,咱们还需求修正 pact S T ^ Kkage.jsonscripts 选项,修正为由 react-app-rewired| p / & 1 c 4 Y 5动运用,就像下面这样

// micro-app-react7 . P W [ ] E _ ]/package.json
//...
"scripts": {
"start": "react-app-r) o D = Yewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"ejeI 3 7 gct": "react-app-rewired eject"
}

react-app-rewiS E = gredm V u %备完结? Z + l d N G l后,咱们新建 config-overrides.js 文件来装备W V S webpack,代码完结如下:

const path = require("path");
module.exports = {
webpack: (config) => {
// 微运用的a a 6 K # G C 5包名,这儿与主运用中注; { R 7 Z ` M  1册的微运用称号共同
config.output.lii g } fbrary = `ReactM& f 0icroApp`;
// 将你的 library 露出为一切的模块界说下都可运转的办法
config.output.libraryTarget = "umd";
// 按需加载相关,设置为 webpackJsonp_VueMicroApp 即可
config.9 M ; R 3 %output.jsonpFunction = `webpackJsonp_ReactMiJ C Rcd T X 2 Z A { N aroApp`;
config.ro v Q ) J U Besolve.alias = {_ X ? h ^ a 9
...config.resolve.alias,
"@": path.resolveE w i 6 l w 2 L(__dirname, "src"),
};
return config;
},
de^ F } ] G P X o UvServer: function (configFunction) {
return fu% @ g r J a qnction (proxy, allowedHost) {@ S + ~
const config = configFunction(proxy, allowedHost);
// 封闭主机检查,使微运用能/ c ^ 8 f D H够被 fetch
config.disableH~ k ( % ( ,ostCheck = true;
// 装备跨域恳求头,处理开发环境的跨域问题
config.headers = {
"Access-C* J 9 ? Z 6 _ontrol-Allow-Origin": "*",
};
// 装备 history 形式
config.historyApiFallback = true;
return config;
};
},
};

咱们需求重点重视一下 outpe @ v Uut 选项,当咱们把 libraryTarget 设置为 umd 后,咱K m H 4 S ? G a X们的 library 就露出为一切的模块界说下都可运转的办法了,主运用就能够获取到微运用的生命周期钩子函数了。

config-over? } | Brides.js 修正完结后,咱们从头发动 React 微运用,然后翻开主运用基座 http://9 g Pl$ x N ) m m !ocalhost:9999。咱们点击左边菜单切换到微运用,此刻咱们的 React 微运用被正确加载啦!(见下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

此刻咱们翻开控制台,能够看到咱们所履行的生命周期钩子函数(见下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

到这儿,React 微运用就接入成功了!

接入 Angular 微运用

Angularqiankun 现在的兼容性并不太好,接入 Angular 微运用需求必定的耐心与技巧。

关于挑选 Angular 技能栈的前端开发来说,对这类状况应该驾轻就熟(没有办法)。

咱们以 实战事例 – feature-inject-sub-apps 分支 为例,咱们在主运用的同级目录(micro-app-main 同级目录),运用 @angular/cli 先创立一个 Angular 的项目,在指令行p N z A $ ) Y % w运转如下指令:

ng new micro-app-angulO u , t j A s W Dar

本文的 @angular/cli 选项如下图所示,你也能够根据自己的喜爱挑选装备。

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

然后,咱们a D A 5 X z { 1 4创立几个路由页面再加上一些款式,最终作用如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇
基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

注册微运用

在创立好了 Angular 微运用后,咱们能够开端咱们的接入作业了。首要咱们需求在主运) & = ; H用中注册该微运用的信息,代码完结如下:

// micro-app-main/src/micro/apps.ts
const apps = [
/**
* nai Q : B S ; b Yme: 微运用称号 - 具* } V X 2 / a D x有仅有性
* entry: 微运用进口 - 经过该地址加载微运用
* container: 微运用挂载节点} n C L ` m q - 微运用加载完结后将挂载在该节点上
* activeRu^ M L T j ` d N zle: 微运用触发的路由B ? k 3 y P K 5 w规矩 - 触发路由规矩后@ `  D *将加载该微运用
*/
{
name: "AngularMicroApp",
entry: "//localhost:10300",
container: "#frame",
activeRule:x , , 1 "/angu4 9 { = 4 8lar",
},
]t R 9;
export default apps;

经过上面的代码,咱们就在主运[ = 3 3 j G d G用中注册了咱们的 Angular 微运用,进入 /angular 路由时将加载咱们的 Angular 微运用。

咱们在菜单装备处也参加 Angular 微运用的方便进口,代码完结如下:

// micro| - G L ` 8-app-main/src/App.vue
//...
export default class App extends Vue {
/**
* 菜单列表
* key: 仅有 Key 值
* title: 菜单标题
* path: 菜单对应的途径
*/
mJ X ; @ / b ! N Nenus =7 r . M 7 F 6 [
{
key: "Home",
title: "主页",
path: "/V p T K 6 u 7",
},
{
key: "Angul0 e L %arMicroApp",
title: "Angular 主页",
path: . ; + 7 Y S U O T"/ang, A ; v U ;ular",
},
{
key: "AngularMicroAppList",
title: "Angular 列表页",
path: "/angular/l* b { ] k q $ o hist",
},
];
}Q * k [ 6 v K

菜单装备完结后,咱们的主运用基座作用图如下

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

最终咱们在主运用的进口文件,引进 zone.js,代码完结如下:

Angular9 h 8 x . ) ( | 5转依赖于 zon+ C M Ge.js

qiankun 根据 single-spa 完结,singl3 ] W q M y 2 r Te-spa 明确指出一个项目的 zone.js 只能存在一份实例,所以咱们在主运用注入 zone.js

// micro-P h 4 ! O ! K :app-main/src/main.js
// 为 Angular 微运用所2 R L  & ! k }做的 zone 包注入
import "zone.js/dist/zone";

装备微运用l _ / m ! h Z v

在主运用的作业完结后,咱们还需求对微运用进行一系列的装备。首要,咱Y k = M b p z们运用 single-spa-angular 生成一套装备,在指令行运转以下指令:u @ J [ B j & 0 J

# 装置 single-spa
yarn add single| 5 f } h t a-spa -S
# 添加 single-spa-angular
ng al x ( 3dd single-spa-angular

运转指令时,根据自己的需求挑选装备即可,本文装备如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

在生成 single-spa 装备后,咱们需求进行一些 qiankun 的接入装备。咱q [ M * * | n z m们在 Angular 微运用的进口文件 maiF y x f 1n.single-spa.ts 中,导出 qiankun 主运用所o 1 E L f { ` l需求的三个生命周期钩子b Q L : g M函数,代码完结如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

从上图来v t x N % |剖析:

  • 第 2u ; q a z B ~ I1 行:微运用独立运转时,直接履行挂载函数挂载微运用。
  • 第 46 行:微运用导出的生命周期钩子函数 – bootstrap
  • 第 50 行:微运用导出的生命周期钩子函数 – mount
  • 第 54 行:微运用导出的生命周期钩子函数 – unmount

完好代, V p ? d . 7 :码完结如下:

// micro-app-angular/src/main.single-spa.ts
import { enn w f A , D r HableProdMode, NgZone } from "@angular/coreP | R h r A";
import { platd g % YformBrowserDyy & L x  F gnamic } from "@angular/platform-browser-dynamic";
import { Router } from "_ ! O V I C @ W R@angular u q 8 O t @r/router";
import { Ani. ) @ +mationEngine as AnimationEngine } from "@angular/animations/browser";
import {
singleSpaAngular,
getSingleSpaExtraProviders,
} from "single-spa-angular";
import { AppModule } from "./app/app.module";
import { environment } from "./envm s r O }ironments/envir0 ( H 8 : H @ Y }onment";
import { singleSpa~ O =Propf R J QsSubject } from ~ B X $"./single-spa/single-5 o ) * $ j & dspa-props";
if (environment.production2 f 2 ` t 6 ! )) {
enableProdMode();
}
// 微运用单独发动时运转
if (!(window as any).__POWERED_BY_C _ . B ] 8 { tQIANKUN__) {
platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
} r = ! ] * b *
coC K I p W { -nst { bootstrap, mount, unmount } = singleSpaAngular(= 3 ` B{
bootstrapFunction: (singleSpaProps) =>u : ~ j 0  | / w {
singleSpaPropsSubject.I Q  U f !next(singleSpaProps);
return platformBrowserDynamic(getSingleSpaExtraProviders()).bootstrapModule(
AppMo r @dule
);
},
template:~ W = `  ( 1 G  "<a6 L ( [ - ! u L 2pp-rooD | L c 3t />",
Router,
Ngy E d Y V 2Zone,
AnimationEngine,
});
/** 主运用生命周期e V C钩子中运转 */
export {
/**
* bootstrap 只会在微运用初始化的时分调用一次,下次微运用从头进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 一般咱们能够在这儿做一些大局变量的初始化,比方不会在 unmountT : z D C 0 & 阶段被毁掉的运用等级的缓存等。
*/
bootstrap,
/**
* 运用每次进入都会调用 mount 办法,一般咱@ O + A  b e [ 8们在这儿触发运用的烘托办法
*/
mount,
/**
* 运用每次6 o Z ? } c 切出/卸载I | ; ! 0 Q 8 w 会调用的办法,一般在这儿咱们会卸载微运用的运用实例
*N a 7 o 0 [ A p ./
unmount,
};

在装备好了进口文件 main.single-spa.ts 后,咱们还需求装备 webpack,使 main.singlen 5 a [ a ^ y , .-spa.ts 导出的生命周期h c $ ; L 5 L p钩子函数能够被 qiankun 辨认获取。

咱们直接装备 extra-webpa% c Eck.config.js 即可,代码完结如下:

// micro-app-angular/extra-webpack.config.js
const singleSpaAngularWebpack = require("single-spa-angular/lib/we4 ? N * I z  )bpack")
.default;
const webpackMerge = require("webpack-merge");
module.expd G iorts = (anguB 6 glarWebpack7 } # Q V wConfig, options) => {
const singleSpaC J ) ?WebpackF 5 ; =Config = singleSpaAngularWebpack(
anguj + ] w G F r % :lA 0 R - i =arWebpackConfig,
options
);
const singleSpaConfig = {
output: {
// 微运用的包# l s & N 9 5 ]名,这儿与主运用中注册的微运用称4 N ^ k号共同
library: "AngularMicroApp",
// 将你的 library 露出为一切的模块界说下都可运转的办法
libn D u ~ t raryTarget: "umd",
},
};
conste f b ^ z r T mergedConfig = webpackMer_ o 3 q z P : Xge.smart(
singleSpaWebpackConfig,
singleSpaConfig
);
return mergedConfig;
};

咱们需求重点重视a M e O # $一下 output 选项,当咱们把 libraryTarget 设置$ e ` 1umd 后,咱们的 l# t l ? , / 8ibrary 就露出为一切g g @ (的模块界说下都可E ? p 6 3 $ D S S运转的办法了,主运用就能1 / W [ { =够获取到微运用的生命周期钩子函数了。

extra-webpack.! K i 2 t K aconfig.js 修正完结后,咱们还需& $ H d Y v求修正一下 package.json 中的发动指令,修正如下:

//X f s micro-app-angular/package.json
{
//...M D % Q z _ 7
"scrii h F  Opt": {
//...
//2 n A N D . --disable-host-check: 封闭主机检查,使微运用能够被 fetch
// --port: 监听端口
// --base-href: 站点的起始途径,与主运用中装1 ! n  p w a备的共同
"start": "ng serve --disable-host-check --port 10300 -? K w (-base-href /angular"
}
}

修正完结后,咱们从头发动 Angular 微运用a * B J,然后翻开主运用基座 http://localhost:9999。咱们b C 7 w Z j – a d点击左边菜单切换到微运用,此刻咱们的 Angular 微运用被正确加载啦!(见下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

到这儿,Angular 微运用就接入成功了!

接入 Jqu6 # E | G ( |ery、xxx..M ` X e ( G 0 (. 微运用

这儿的 Jquery、xxx... 微运用指的是没有运用脚手架,直接选用 html + css + js 三剑客开发的运用。

本事例运用* ! k #了一些高级 ES 语法,请运用谷歌浏览器运转检查作用。

咱们以 实战事例 – feature~ C I X E ^ k Z A-inject-suk X q sb-app+ 7 Q 2 E Ts 分支 为例,咱们在主运用的同级目录(micro-app-ml * Z B v - 7 E lain_ Q ` 7 j y e级目录),手动创立目录 micro-app-staticZ f } , V / H Z 9K + U P ; $

咱们运用 express 作为服务器加载静态 html,咱们先编l d H u P npackage.json,设置发动指令和相关依赖。

// micro-app-statie p ; Tc/package.json
{
"name": "micrH c i c N J 2 /o-app-jquery",
"version": "1.0.0",
"description": "",
"main": "indexY V /.js",
"scra z }ipts": {
"start": "nodemon index.js"
},
"author": "",
"license": G 6 s ("! A  i K sISC",
"dependencies": {
"express": "^4.17.1",
"cors": "^2.8.5"
},
"devDependencies": {
"5 D v % D P ! Xnodemon": "^2.0.2"
}
}

然后添加进口文件 index.js,代码完结如下:

// micro-app-static/index.js
const express = require("express");
const cors = requirn  l d 9 + # k [e("cors");
const app = express();
// 处理跨域问题
app.use(cors());
app~ 4 4 9 =.use('/', express.static('static'));
// 监听端口
app.listen(10400, () => {
console.log("server is listening in http://localhost:10400")
});

运用 npm install 装置v & ^ | D F ! # 9相关依赖后,咱们运用 npm start 发动运用。

咱们新建 static 文件夹,在文件夹内新增一个静态页面 index.html(代码在后面会贴出),加上一u l l 3些款式后,翻开浏览器,最终作用如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

注册微运用

在创立好了 Static 微运用后,咱们5 3 M D [ h $ G能够开端咱们的接入作业了N ] 9 A。首要咱们需求在主运用中注册该微运用的信息,代码完结如下:

// micro-app-main/src/micro/apps.ts
const apps = [
/**
* name: 微运 O | @ _ 5 ? b u用称号 - 具有仅有性
* e0 8 } Dntry3 _ A M % 7 U: 微运用进口 - 经过该地址加. [ i / U *载微运用
* container: 微运用挂载节点 - 微运6 g  ) ] u V + S用加载完结后将挂载~ m U P H o U在该节点上
* activeRule: 微运用触发的路由规矩 - 触发路由规矩后将加载该微运用
*/
{
name: "StaticMicroApp",
entry: "//local( 3 8 5host:10400",
container: "#frame",
activT 5 _ ? h . E $eRule: "/static"
},
];
export default apps;

经过上面的代码,咱们就在主运用中注册了咱们的 StaticO Y l = |运用,进入 /static 路由时将加载咱们的 Static 微运用。

咱们在菜单装备处也参加 Static 微运用的方便进口,代码完结如下:

// micro-app-main/src/App.vue
//...
export defaultc r J z . p s r  class App extends V4 ) 9 X * N A Q wue {
/**
* 菜单列表
* key: 仅有 Key 值
* title: 菜单标题
* path: 菜单对应的途径
*/
menus = [
{
key: | T Y y m R"Home",
title: "主页",
path: "/"
},
{
keyB p y W: "Stat@ Q ?icMicroApp",
title: "StaK [ _ K L Y ] 6 ktic 微运用",
path: "/static"
}
];
}

菜单装备完结后,咱们的主运用i U基座作用图如下

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

装备微运用

在主运用注册好了微运用后,咱们还需求直接@ D B q / 1写微运用 index.html 的代码即可,代码完结如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

从上图来剖析:

  • 第 70 行:微运用的挂载函数,在主运用中运转时将在 mount 生命周期钩子函数中调用Q d z A m w K D P,能够确保在沙箱内运转。
  • 第 77 行:微运用独立运转3 : =时,直接履行 render 函数挂载微运用。
  • 第 88 行:微运用注册的生命周期钩子函数 – bootstrap
  • 第 95 行:微运用注册的生命周期钩子函数 – mount
  • 第 102 行:微运用注册的生命周期钩子函数 – unmount

完好代码完结如下:

<!-- micro-app-static/static/indy j ( W N R Oex.html -->
<!DOCTYPE html>
<html lang= 8 g & }"en">
<head>g K X X 3 : . E
<meta charset="UTF-8" />
<meta name=, y : w Q # t"viewport"N ; F cS h t { W Z 7 oontent="width=device-width, initiaz I A 7 g a (l-scale=1.0" />
<mi 4 v ! w = P : #eta http-equiv=5 j A e l b 3 ] #"d W T 5 G Z (X-UA-Compatibl0 k pe" content="ie=edge" />
<!-- 引进 bootstrap -->
<link
href="https://cd5 1 Qn.bootcdn.net/ajax/libs/twitt% n oer-bootstrap/4.4.1/css/bootstrap.min.css"
rel="stylesheet"
/>
<title>Jquery Am m N  Tpp</5 V -title>
</head>
<bo~  X v R 5dy>
<section
id="jquerJ B [ L d Z 0 Ly-app-container"
style="padding:T  v r 2 N _ ` A 20px; color: bluec 5 c : u B J o;"
></sectis ? a w B 0ow B x 0 u Xn>
</body>3 E ^ O;
<!-- 引进 jquery -->
<s$ G O 0 4 c z Dcript src="https://cdn.bootcdn.ne; p A V O 2 ? w Jt/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
/**
* 恳[ [ Y  4 ` 4 V求接口数据,构建 HTML
*/
async function buildHTML() {
const result = await fetch("http://dev-api.jt-gmall.com/mall", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
// graphql 的查询风格
body: JSON.stringify({
quer{ u ` W q 7 C ]y: `{ vegetableList (page: 1, pageSize: 20) { page, pageSize, total, items { _id, name, poster, price } } }`,
}),
}).t8 s d p then((res) => res.jso[ ` H ^ 6 Mn());
const list = result.data.vegetableList.items;
const html = `<table class="table">
<thead>
<tr>
<th scope=b O Z q T f Q { G"col">菜名</th>
<th scope="col">图片R O w } ? f 0 &</th>
<th scope="col">报价</th>
</tr>
</the^ { R 7adr S R k v @ } d>
<^ w  1 ) @ N;tbody>
${list
.map(
(item) => `
<G  | 6 C Utr} x W C k>
<td>
<img src="https://juejin.V D D J O k T iim/post/5ebbdl g ^ W u2986fb9a0432f0fff86/${item.poster}"></img>
</td>
<td>${item.name}</td>
<td>¥ ${itp 2 s { | r Y +em.price}</td>
<A J f K /  I x w/tr>
`
)
.join("")}
&! + ! `lt;/tbody>
</table>`;
return html;
}
/**
* 烘托函数
* 两种状况:主运用生命周期钩子中运转 / 微运用单独发动时运转
*/
const render = async ($) => {
const html = awY  , eait buildHTML();
$("#jquery-app-container").ht` K P = $ml(html);
return Promise.resolve();
};
// 独立运转时,直接挂载运用
if (!window.__POWERED_BY_QIANKUN__) {
render($);
}
((global) => {
/**
* 注册 3 + | * # H微运用生命周期钩子函数# Z ( q b = - B D
* globaa ( { C z 7 6 % Al[appName] 中的 appName 与主运用中注册的g o j s微运用称t G r X p 9号共同
*/
global: w 5 m g C | d k["StaticMicroApp"] = {
/**
* bootstrap 只会在微运用初始化的时分调用一次,下次微运用从头进入时会直接调用 mount 钩子s _ % P , c @,不会再重复触发 bootstrap。
* 一般咱们能够在这儿做一些大局变量的初始化,比f 6 & U +方不会在 unmount 阶段被毁掉的运用等级的缓存等。
*/
bootstrap: () => {
console.log("MicroJqueryApp bootstrapedN e V V C f l i }");
retur[ ; M e ) in Promise.resolve();? s N 1
} g H M 1 g 3 B,
/**
* 运用每次进入都会调用 mount 办法,一般咱们在这儿触发运用的烘c ; F C托办法
*/
mount: () => {
console.log("MicroJqueryApp mount");
return render($);
},
/**
* 运用每次 切出/卸载 会调用的办法,一般在这儿咱们会卸载微运用的运3 c 3 X i用实例
*/
unmount: () => {
console.log("MicroJqueryApp unmount");
return Promise.resolve();
},
};
})(window);
</scri4 K l [pt>
</html>

在构建好了 Static 微运用后,咱们翻开主运用基座 http://localhost:9999。咱们点击左边菜单切换到微运用,此刻能够看到e i A F _ . E n _,咱们的 StaticC g p c =运用被正确加载啦!(见下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

此刻咱们翻开控制台,能& Y ! 0够看到咱们所履行的生命周期钩子函数(见Y c z L下图)

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

到这儿,Static 微运用就接入成功c O + O * u I Q了!

扩展阅读

假如在 Static 微运用的 html 中注入 SPA 路由功能的话,将演化成单页运用,只需求在主运用中注册一次。

假如是多个 html 的多页运用 – MPA,则需求在服务器(或反向代理服务器)U & l W # C中经过 refererG = t C回来对应的 html 文件,或y H W & W O Q y者在主运用中注册多个微运用(不推荐)。

小结

最终,咱们一切微运用都注册在主运用和主运用的菜单中,作用图如下:

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

从上图能够看出,咱们把不同技能] 4 A q uVue、React、Angular、Jquery... 的微运用都现已接入到主运用基座中啦!

最终一件事

假如您现已看到这儿了,希望您还是点个 再走吧~

Q + : f D ` L点赞 是对作者的最大鼓励,也能够让: n $更多人看到本篇文章!

跟着前端责任的日益添加,微前端架构可能成为未来的趋势,感兴趣的童鞋能够加我微信进群一同讨论微前端技能呀!

基于 qiankun 的微前端最佳实践(万字长文) - 从 0 到 1 篇

github 地址