前言
基于架构的调整,前端开始转为微前端。经过调研,决定使用qiankun微服务框架来使用,本文将介绍VUE3+TS+qiankun的实践经过。微服务架构的优势之一在于可以结合不同技术栈的节点,基于技术栈的考虑,此处用的都是vue3。
源码:https://github.com/Kuari/Blog/tree/master/Examples/microFrontend
环境
- vue 3.0.0
- TypeScript 4.1.5
- vue router 4.0.0
- @vue/cli 4.5.15
- qiankun 2.6.3
实践
架构
如上图所示,微服务架构将会由多个节点构成,首先由一个主节点site_base
连接所有子节点,子节点可以不断拓展。
主节点
主节点源码可见于https://github.com/Kuari/Blog/tree/master/Examples/microFrontend/site_base
创建主节点,选择vue3+ts
1
2
|
vue create site_base
cd site_base
|
安装qiankun
在src/App.vue
中添加路由和渲染节点
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<template>
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<!-- 新增site1路由 -->
<router-link to="/site1">Site1</router-link> |
<!-- 新增site2路由 -->
<router-link to="/site2">Site2</router-link>
</div>
<router-view/>
<!-- 新增site1渲染节点 -->
<div id="site1" />
<!-- 新增site2渲染节点 -->
<div id="site2" />
</template>
|
在src/main.ts
中引入子节点配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import { registerMicroApps, start } from 'qiankun'
const apps: any[] = [
{
name: 'site1', // 应用的名字
entry: 'http://localhost:9001/', // 默认加载这个html,解析里面的js动态的执行(子应用必须支持跨域,内部使用的是 fetch)
container: '#site1', // 要渲染到的节点id,对应上一步中src/App.vue中的渲染节点
activeRule: '/site1' // 访问子节点路由
},
{
name: 'site2',
entry: 'http://localhost:9002/',
container: '#site2',
activeRule: '/site2'
}
]
registerMicroApps(apps) // 注册应用
start() // 开启应用
createApp(App).use(store).use(router).mount('#app')
|
子节点
子节点源码可见于https://github.com/Kuari/Blog/tree/master/Examples/microFrontend/site_1
此处以site1
为例,site2
同理。
创建子节点,选择vue3+ts
1
2
|
vue create site_1
cd site_1
|
编辑src/App.vue
1
2
3
|
<template>
<router-view />
</template>
|
编辑src/views/Home.vue
,修改其内容,写一点标识性的文本
1
2
3
|
<template>
<div>Hello, Site1!</div>
</template>
|
创建文件src/pulic-path.ts
,第一行的注视一定要加,避免eslint对于变量的报错
1
2
3
4
|
/* eslint-disable camelcase */
if ((window as any).__POWERED_BY_QIANKUN__) {
__webpack_public_path__ = (window as any).__INJECTED_PUBLIC_PATH_BY_QIANKUN__
}
|
编辑src/router/index.ts
,此处直接返回routes
,而不是router
,并且修改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import { RouteRecordRaw } from 'vue-router'
const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
}
]
// 直接返回routes,由其它地方处理创建路由
export default routes
|
编辑src/main.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
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: any = null
let history: any = null
function render (props = {}) {
const { container } = props
// 当为微服务主节点情况下访问,会设置二级路径,而直接访问时没有二级路径,此处需要根据实际情况修改
history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/site1' : '/')
router = createRouter({
history,
routes
})
instance = createApp(App)
instance.use(router)
instance.use(store)
instance.mount(container ? container.querySelector('#app') : '#app')
}
if (!window.__POWERED_BY_QIANKUN__) {
render()
}
export const bootstrap = async (): Promise<void> => {
console.log('%c ', 'color: green ', 'vue3.0 app bootstraped')
}
const storeTest = (props: any): void => {
props.onGlobalStateChange &&
props.onGlobalStateChange(
(value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev),
true
)
props.setGlobalState &&
props.setGlobalState({
ignore: props.name,
user: {
name: props.name
}
})
}
export const mount = async (props: any): Promise<void> => {
storeTest(props)
render(props)
instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange
instance.config.globalProperties.$setGlobalState = props.setGlobalState
}
export const unmount = async (): Promise<void> => {
instance.unmount()
instance._container.innerHTML = ''
instance = null
router = null
history.destroy()
}
|
创建文件vue.config.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
const path = require('path')
const { name } = require('./package')
function resolve (dir) {
return path.join(__dirname, dir)
}
const port = 9001
module.exports = {
outputDir: 'dist',
assetsDir: 'static',
filenameHashing: true,
devServer: {
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}`
}
}
}
|
验证
主节点和子节点分别独立运行,但是子节点的地址需要跟主节点配置中子节点对应的地址相同。
在主节点上点击子节点的路由,即可在主节点上访问子节点的页面了!
主节点优化
主节点除了如上配置,可以进行两项优化:
- 模块化子节点配置
- 添加过渡状态,当加载子节点时窗口顶部出现加载进度条
优化后主节点源码可见于https://github.com/Kuari/Blog/tree/master/Examples/microFrontend/site_base_optimize
模块化子节点配置
创建文件夹src/childNodes
,然后创建文件src/childNodes/apps.ts
1
2
3
4
5
6
7
8
9
10
|
const apps: any[] = [
{
name: 'site1', // 应用的名字
entry: 'http://localhost:9001/', // 默认加载这个html,解析里面的js动态的执行(子应用必须支持跨域,内部使用的是 fetch)
container: '#site1', // 要渲染到的节点id
activeRule: '/site1' // 访问子节点路由
}
]
export default apps
|
创建文件src/childNodes/index.ts
1
2
3
4
5
6
|
import { registerMicroApps, start } from 'qiankun'
import apps from './apps'
registerMicroApps(apps)
export default start
|
编辑src/main.ts
1
2
3
4
5
6
7
8
9
|
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import start from './childNodes'
start() // 开启应用
createApp(App).use(store).use(router).mount('#app')
|
过渡效果
此处的过渡效果采用NProgress
库,先来安装一波
编辑src/childNodes/index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import { addGlobalUncaughtErrorHandler, registerMicroApps, start } from 'qiankun'
import apps from './apps'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
registerMicroApps(apps, {
// qiankun 生命周期钩子 - 子节点加载前
beforeLoad: (app: any) => {
NProgress.start() // 开始进度条
return Promise.resolve()
},
// qiankun 生命周期钩子 - 子节点挂载后
afterMount: (app: any) => {
NProgress.done() // 进度条结束
return Promise.resolve()
}
})
export default start
|
结语
qiankun
框架确实挺不错的,配置也并不是复杂,但是唯一想吐槽的一点是对于ts的支持感觉不太好/狗头,或许是我写得不够好吧,后面会持续优化使用。
参考文档