qiankun微前端实践及常见问题
关注:搜索前端⼯具⼈ ; 收货更多的⼲货
⼀、介绍:
qiankun 项⽬实际搭建,及各种微应⽤流⾏框架技术(vue2 、vue3、react 、 umi2 、umi3)的配置;
初衷:⾃⼰当时摸索qiankun构建项⽬时,问题百出,特别是umi2 及 umi3,百度了⼏天才把热门框架都集合完毕;
⽬的:总结出的模板项⽬,便于⾃⼰后期重构项⽬技术选型及项⽬快速搭建;也为其他有需要的朋友提供⽰例及参考;
项⽬源码:已上传到 github/laijinxian/qiankun-template 如有对你有帮助,⿇烦 star 下
末尾的常见问题多数为⽬前开发中遇到的疑难点,特地整理出来;有其他问题欢迎留⾔交流
实际项⽬源码就没贴出来了,都是依据这个模板构建的;
后⾯看下好不好把实际项⽬源码抽离出来,上传到github; ⽬前⼦项⽬使⽤的是 vite2.0 + vue3 + ts 以
及 react + Umi3 + dva + ts
⼆、什么是微前端 qiankun 篇
推荐阅读
其实我个⼈更喜欢叫成前端微服务架构,感觉逼格更⾼点...
2.1 官⽅介绍:
微前端是⼀种多个团队通过独⽴发布功能的⽅式来共同构建现代化 web 应⽤的技术⼿段及⽅法策略 (有点⾼深...)
2.2 我的观点:
与技术栈⽆关、独⽴开发、独⽴部署、增量升级、独⽴运⾏;
拆分、细化、解耦你的巨⽆霸项⽬;提升开发及打包部署效率;
不在局限于⼀个项⽬只能使⽤⼀种技术,⼀个项⽬可以使⽤ N 种技术,扩展⾃⼰技术知识⾯;
对于⽐如⼤型 erp、OA 之类的系统, 微前端可以让你更加的得⼼应⼿的开发;
对于想重构公司辣眼睛的项⽬尤为合适;这也算我⼊⼿微前端的主要原因之⼀,下⾯会讲到;
顺应时代潮流,作为主流技术现在⾮常多的公司招聘⾯试基本都会问微前端,细化程度不⼀样;
三、为什么⽤qiankun,为什么选择qiankun
3.1 为什么⽤qiakun
⾃打进⼊公司,看到了现有的项⽬,我总结了⼏点
项⽬全都使⽤ Vue, ⼀直开发下去你会发现 React 忘得快差不多了;技术的局限性;
现有项⽬代码⼜臭⼜长,毫⽆规范;eslint、css预编译啥都没有;
2-3层 for 循环,var之类的,粘贴复制⽆⽤代码不删除到处可见;公共代码提取、接⼝统⼀处理、⼯具类编写不存在的;
⼀个项⽬同时出现 vue、jQuery两个⼤框架;运⾏项⽬、热编译、你可以先上趟厕所;
每期功能迭代,先要花⼤半天时间去熟悉这个代码、还真不敢乱改,有毒、谁改谁后悔的那种
想重构,奈何刚接⼿的时候项⽬已经很⼤了,并且不怎么熟悉业务,且不断的加功能;⼀时重构基本不可能;千万级别的⽤户量出问题了这锅背不动,时间也不允许后⾯需求排期不是很紧凑,正直qiankun微前端很⽕,就想着使⽤qiankun微前端⽅案重构;
思路如下:
⽬标把⼀个项⽬按照菜单划分,⼀个⼤菜单分为⼀个⼦服务(⼦项⽬)
刚开始原有项⽬全部划分为⼀个⼦服务,新加功能菜单划分为另⼀个⼦服务;这样既保证原有项⽬不变,新项⽬完全使⽤新的框架及开发风格规范;
时间充裕下情况下,慢慢把其他功能按照菜单划分成⼦服务,慢慢的最⼩粒度去重构项⽬
3.2 ⽬前微前端⽅案有:
iframe
single-spa
qiankun 基于 single-spa ⽅案实现, 更强⼤更易上⼿
推荐阅读 , ⽂章有详细介绍及常见问题
四、构建步骤
项⽬结构:
├── main-service // 主应⽤
└── sub-service // 微应⽤
└── sub-react // react ⼦应⽤
└── sub-umi2 // umi2 ⼦应⽤
└── sub-umi3 // umi3 ⼦应⽤
└── sub-vue2 // vue2 ⼦应⽤
└── sub-vue3 // vue3 ⼦应⽤
推荐阅读:
详细结构代码已上传github 请前往查看
4.1 项⽬结构组成
主应⽤:
vue2.x + vuec-li3 主要业务功能就是登录注册及菜单;官⽅推荐主应⽤尽可能的简单,不要涉及其他的业务功能微应⽤:
vue2.x + vue-cli3
vue3.x + vue-cli4 + typescript
react16
react16 + umi2 + dva
react16 + umi3 + dva
4.2 主应⽤配置
qiankun 只需要在主应⽤中引⼊,微应⽤不需要
yarn add qiankun # 或者 npm i qiankun -S
4.3 主应⽤ src 下注册微应⽤
主应⽤ src 下新建 qiankun/index.js
import {
registerMicroApps,
runAfterFirstMounted,
setDefaultMountApp,
start
} from "qiankun";
import store from '../store/index'
import { instance } from "../main";
import 'nprogress/nprogress.css'
/**
* Step1 初始化应⽤(可选)
*/
function loader(loading) {
if (instance && instance.$children) {
// instance.$children[0] 是App.vue,此时直接改动App.vue的isLoading
instance.$children[0].isLoading = loading;
}
}
/**
* Step2 注册⼦应⽤
*/
const microApps = [
{
name: 'sub-vue2',
developer: 'vue2.x',
entry: '//localhost:7788',
activeRule: '/sub-vue2',
},
{
name: 'sub-vue3',
developer: 'vue3.x',
entry: '//localhost:7799',
activeRule: '/sub-vue3'
},
{
name: 'sub-react',
developer: 'react16',
entry: '//localhost:7755',
activeRule: '/sub-react'
},
{
name: 'sub-umi2',
developer: 'umi2.x',
entry: '//localhost:7766',
activeRule: '/sub-umi2'
},
{
name: 'sub-umi3',
developer: 'umi3.x',
entry: '//localhost:7733',
activeRule: '/sub-umi3'
}
]
const apps = microApps.map(item => {
return {
...item,
loader, // 给⼦应⽤配置加上loader⽅法
container: '#subapp-container', // ⼦应⽤挂载的div
props: {
developer: item.developer, // 下发基础路由
routerBase: item.activeRule, // 下发基础路由
getGlobalState: GlobalState // 下发getGlobalState⽅法
}
}
})
registerMicroApps(apps, {
beforeLoad: app => {
console.log('before load app.name====>>>>>', app.name)
},
beforeMount: [
app => {
console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name)
}
],
afterMount: [
app => {
console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name)
}
],
afterUnmount: [
app => {
console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name)
}
]
})
/**
* Step3 设置默认进⼊的⼦应⽤
*/
setDefaultMountApp('/sub-vue2')
/**
* Step4 启动应⽤
*/
start();
runAfterFirstMounted(() => {
console.log("[MainApp] first app mounted");
});
export default apps
4.4 微应⽤导出⽣命周期钩⼦
各种框架配置推荐阅读
下⾯以 vue3.x 及 react umi3 为例; 其他微服务配置请前往 [github](github/laijinxian/qiankun-template) 源码查看⼦应⽤的名称最好与⽗应⽤在 qiankun/index.js 中配置的名称⼀致(这样可以直接使⽤package.json中的name作为output)vue3.x 微应⽤
⾸先 vue create sub-vue3 创建项⽬
修改 main.js 导出⽣命周期函数
// @ts-nocheck
import "./public-path";
import { createApp } from "vue";
import { createRouter, createWebHistory } from "vue-router";
import App from "./App.vue";
import routes from "./router";
import store from "./store";
let router = null;
let instance = null;
function render(props = {}) {
const { container } = props;
router = createRouter({
history: createWebHistory(window.__POWERED_BY_QIANKUN__ ? "/sub-vue3" : "/"),
routes
});
instance = createApp(App);
instance.use(router);
instance.use(store);
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap() {
console.log("%c ", "color: green;", "vue3.0 app bootstraped");
}
function storeTest(props) {
(value, prev) =>
console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true
);
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name
}
});
}
export async function mount(props) {
storeTest(props);
render(props);
}
export async function unmount() {
instance.unmount();
instance._container.innerHTML = "";
instance = null;
router = null;
}
新建 fig.js
const path = require('path');
const { name } = require('./package');
function resolve(dir) {
return path.join(__dirname, dir);
}
outputDir: 'dist',
assetsDir: 'static',
filenameHashing: true,
devServer: {
hot: true,
disableHostCheck: true,
port: '7799',
overlay: {
warnings: false,
errors: true,
},
clientLogLevel: "warning",
disableHostCheck: true,
compress: true,
headers: {
'Access-Control-Allow-Origin': '*',
},
historyApiFallback: true,
overlay: { warnings: false, errors: true }
},
/
/ ⾃定义webpack配置
configureWebpack: {
resolve: {
alias: {
'@': resolve('src'),
},
},
output: {
// 把⼦应⽤打包成 umd 库格式
library: `${name}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${name}`,
},
},
};
src 新建 public-path.js 并引⼊
/* eslint-disable @typescript-eslint/camelcase */
if ((window as any).__POWERED_BY_QIANKUN__) {
/* eslint-disable @typescript-eslint/camelcase */
__webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
react umi3 微应⽤
创建项⽬
推荐阅读:
$ mkdir myapp && cd myapp
$ yarn create umi
安装
$ npm install --save-dev @umijs/plugin-qiankun
$ yarn add @umijs/plugin-qiankun
修改 src/app.js 导出⽣命周期函数
import './public-path'
export const dva = {
config: {
jquery在项目里是干啥的
onError(err) {
err.preventDefault();
<(ssage);
},
},
};
export const qiankun = {
// 应⽤加载之前
async bootstrap(props) {
console.log('app1 bootstrap', props);
},
/
/ 应⽤ render 之前触发
async mount(props) {
console.log('app1 mount', props);
storeTest(props);
},
// 应⽤卸载之后触发
async unmount(props) {
console.log('app1 unmount', props);
},
};
function storeTest(props) {
(value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true,
);
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name,
},
});
}
修改 .umirc.js ⽂件引⼊ @umijs/plugin-qiankun 插件
// ref: /config/
export default {
mountElementId: 'sub-umi3',
base: `sub-umi3`, // ⼦应⽤的 base,默认为 package.json 中的 name 字段
treeShaking: true,
routes: [
{ exact: false, path: '/', component: '../layouts/index',
routes: [
{ exact: false, path: '/', component: '../pages/index' },
{ component: './404.js' }
],
}
],
plugins: [
['@umijs/plugin-qiankun', {
keepOriginalRoutes: true
}],
// ref: /plugin/umi-plugin-react.html
['umi-plugin-react', {
antd: true,
dva: true,
dynamicImport: { webpackChunkName: true },
title: 'react',
dll: false,
routes: {
exclude: [
/models\//,
/services\//,
/model\.(t|j)sx?$/,
/service\.(t|j)sx?$/,
/components\//,
]
,
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论