背景
公司做了两年多的项目,已经算是个巨石应用了。整个项目中包含了好几个相对独立的功能模块,之前都是独立分布在各个后台管理系统中,为了让用户使用方便,统一账号体系,把它们都揉合到了一个项目里。日积月累,已不堪重负(代码量,打包时间,运行时间,开发体验,维护成本),苦它久矣!现在有机会把项目中的几个功能模块提前出来做商业化,这正是一次重构的机会!
微前端框架选型
我们团队的主要技术栈就是vue,选择micro-app就是看中了它的简洁和侵入性弱,这样接入成本就低了很多,更喜欢它基于WebCompoent的实现原理。
这次vue3没有选择vite作为打包工具,主要还是对vite的微前端方案适配代价巨大,前方不知道还有没有什么坑,所以暂时不去踩坑。
pnpm 初始化项目
lerna已经停止更新维护了,很多知名的开源项目都选择了pnpm,我们也是紧跟时代潮流。
安装pnpm:
npm i pnpm -g
初始化项目:
pnpm init -y
新建一个pnpm-workspace.yaml文件:
packages:
- 'packages/**'
新建一个packages目录,在里面建立几个模块:
├── packages
| ├── base // 基座
| | ├── package.json
| ├── dataCenter // 数据中心
| | ├── package.json
| ├── share // 共享模块
| | ├── package.json
├── package.json
初始化vue项目
在packages/base目录下创建一个vue3 + ts的项目。因为我们技术栈都是vue,我们可以将公共使用的依赖拷贝到根目录下的package.json中,进行统一安装。
"dependencies": {
...
"@element-plus/icons-vue": "^2.0.1",
"axios": "^0.27.2",
"element-plus": "^2.2.2",
"pinia": "^2.0.14",
"vue": "^3.2.25",
"vue-router": "^4.0.12"
...
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^4.18.0",
"@typescript-eslint/parser": "^4.18.0",
...
"babel-loader": "^8.2.5",
...
}
在根目录下执行命令:
pnpm i -w // -w, --workspace-root 表示在根目录下安装
初始化公共模块
在 packages 下新建一个share目录:
├── share
| ├── components // 公共组件
| ├── style // 公共样式
| ├── utils // 公共方法
├── package.json
通过link机制实现package包之间的依赖引用:
pnpm i pnpm i [packageName]@workspace --filter [packageName] // 通过filter 过滤需要关联依赖的package
"dependencies": {
...
"@ultra/share": "workspace:^0.1.0", // 通过 workspace协议 为本地引用
...
},
我们可以在share/components下新建一个测试用的组件:
<template>
<div>
这是公共组件 {{msg}}
</div>
</template>
<script setup lang="ts">
import { defineProps } from 'vue';
const props = defineProps({
msg: {
type: String,
default: 'Hello World!'
}
});
</script>
// packages/base/scr/app.vue
<template>
<TestComponent/>
</template>
<script setup lang="ts">
import TestComponent from '@ultra/share/components/test.vue';
</script>
其他share目录下的模块,也用类似的方式引用:@ultra/share/style/index.less,@ultra/share/utils/index.ts。
micro-app 微前端实现
关于micro-app的安装配置内容,建议前往官方文档,查看对应框架的配置说明,手把手教学,简单易上手,侵入性低。官方文档!
实现基座
- 在
base项目中安装micro-app
pnpm i @micro-zoe/micro-app --filter @ultra/base
- 在入口里引入:
// main.ts
import microApp from '@micro-zoe/micro-app';
microApp.start();
createApp(App).use(router).mount('#app');
- 分配一个子路由:
const routes: Array<RouterRecordRaw> = [
...
{
// 👇 非严格匹配,/popularize/* 都指向 Popularize 页面
path: '/popularize/:page*',
name: 'Popularize',
component: () => import('../views/popularize.vue')
}
...
]
<template>
<div>
<!--
url: nginx 配置的 子项目路径
baseroute: nginx 配置的 主项目路径 + 项目中子项目的路由入口
-->
<micro-app
name='popularize'
:url='url'
baseroute='/micro/popularize'
@datachange='handleDataChange'></micro-app>
</div>
</template>
<script setup lang="ts">
const url = process.env.NODE_ENV === 'development' ? 'http://localhost:8888/popularize' : 'http://www.finget.xyz/popularize';
</script>
配置子应用
- 配置跨域支持:
// vue.config.js
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
}
}
- 设置基础路由:如果基座是history路由,子应用是hash路由,这一步可以省略
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import routes from './router'
const router = createRouter({
// 👇 __MICRO_APP_BASE_ROUTE__ 为micro-app传入的基础路由
history: createWebHistory(window.__MICRO_APP_BASE_ROUTE__ || process.env.BASE_URL),
routes,
})
- 设置 publicPath:
// publicPath.ts
// __MICRO_APP_ENVIRONMENT__和__MICRO_APP_PUBLIC_PATH__是由micro-app注入的全局变量
// @ts-ignore
if (window.__MICRO_APP_ENVIRONMENT__) {
// @ts-ignore
__webpack_public_path__ = window.__MICRO_APP_PUBLIC_PATH__
}
// main.ts
import './publicPath.ts';
运行项目
在根目录下的package.json中新增脚本:
"scripts": {
"serve": "pnpm run --parallel serve",
"build": "pnpm run --parallel build"
}
通过--parallel会执行所有package中的serve命令。更多pnpm 命令的配置请查看官方文档。
部署
官方强烈建议我们保持开发环境和线上环境路径(即webpack的publicPath)的一致性。
所以先考虑好上线部署的目录结构:
├── html
| ├── micro // 基座
| ├── popularize // 子应用
| ├── child1 // 子应用
在
vue.config.js中需要配置好对应的publicPath:
module.exports = {
publicPath: '/micro/'
}
主应用的路由中还得配置一下baseRoute:
const router = createRouter({
history: createWebHistory('/micro'),
routes
});
nginx配置:
location /popularize {
alias html/popularize;
add_header Access-Control-Allow-Origin *;
if ( $request_uri ~* ^.+.(js|css|jpg|png|gif|tif|dpg|jpeg|eot|svg|ttf|woff|json|mp4|rmvb|rm|wmv|avi|3gp)$ ){
add_header Cache-Control max-age=7776000;
add_header Access-Control-Allow-Origin *;
}
try_files $uri $uri/ /popularize/index.html;
index index.html index.htm;
}
location /micro {
alias html/micro;
try_files $uri $uri/ /micro/index.html;
index index.html index.htm;
}
线上体验地址: www.finget.xyz/micro/home (服务器配置低,访问有点慢!)