微前端项目(qiankun+monorepo)
monorepo是什么?
monorepo 是monolithic repository的缩写,意为 "单一仓库"。
它是一个存放多个软件包的单一仓库,每个包有自己的 package.json 文件,通过 package 目录进行组织。
monorepo相比于multirepo的优势
- 统一的依赖管理。由于所有包都在同一个仓库,可以在仓库根目录统一安装依赖,然后进行 symlinks 链接,实现依赖复用。这避免了每个仓库都依赖相同模块的不同版本,节省空间与管理成本。
- 简单的跨包协作。在同一个仓库下开发,可以很容易的在不同包之间协作与重用代码。
- 一致的 Commit 历史。monorepo 下的所有包共享完全相同的提交历史和更改记录。这使得审查更改和依赖关系变得非常简单。
- 简化的工作流。由于一个仓库对应所有包,我们可以应用一致的 Git 分支模型、Issue 标签体系、Pull Request 流程与 CI 配置。这大大简化了多个仓库才需要的额外工作。
缺点:
但 monorepo 也有一定弊端,如仓库体积太大不易管理,而且并非所有的构建工具与包管理器都原生支持 monorepo 项目。
搭建monorepo项目
- 安装pnpm(monorepo需要特定的构建工具和包管理器)
- 初始化项目:新建一个文件夹,初始化一个默认的package.json (子项目共用的package.json)
- 根目录创建
pnpm-workspace.yaml文件。 这个文件可以帮助我们在安装公共依赖的情况下,也将packages下的项目所需要的依赖也同时进行安装(让子项目需要的特殊依赖也同时安装)(workspace协议) - 新建packages文件夹,来存放项目,进入package项目,初始化项目:(初始化一个子项目 pnpm create vite vue-demo1 --template vue-ts )
- 进入到子项目,将提取到公共依赖项的依赖删除。(修改子项目的package.json)。
- 在根目录执行pnpm i。
- 通过命令启动子项目。
注:全局安装依赖项: 如果想在根目录添加依赖,比如添加 element-plus需要在命令行后面加-w : (pnpm install element-plus -w)
项目结构目录
// pnpm-workspace.yaml
packages:
- 'packages/*'
// package.json
{
"name": "vue3-pnpm-monorepo",
"version": "1.0.0",
"private": true,
"scripts": {
"prepare": "husky install",
"dev:vue-demo1": "pnpm -C ./packages/vue-demo1 dev",
"dev:vue-demo2": "pnpm -C ./packages/vue-demo2 dev",
"dev:vue-demo3": "pnpm -C ./packages/vue-demo3 dev",
"lint:create": "eslint --init",
"lint": "eslint \"packages/**/*.{js,vue,ts}\" ",
"prettier-format": "prettier --config .prettierrc.js \"packages/**/*.{js,vue,ts}\" --write"
},
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"element-plus": "^2.3.4",
"eslint-config-prettier": "^8.8.0",
"qiankun": "^2.10.8",
"vue": "^3.2.47"
},
"devDependencies": {
"@commitlint/cli": "^17.6.3",
"@commitlint/config-conventional": "^17.6.3",
"@types/node": "^18.11.12",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint": "^8.40.0",
"eslint-plugin-vue": "^9.13.0",
"husky": "^8.0.3",
"lint-staged": "^13.2.2",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"shelljs": "^0.8.4",
"typescript": "^5.0.2",
"vite-plugin-eslint": "^1.8.1"
},
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"pnpm run lint",
"pnpm run prettier-format"
]
},
"license": "ISC"
}
// 子项目的package.json
{
"name": "vue-demo1",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build: pre": "vue-tsc && vite build --mode dev",
"build: pro": "vue-tsc && vite build --mode pro",
"preview": "vite preview"
},
"dependencies": {
"echarts": "^5.4.2",
"vite-plugin-qiankun": "^1.0.15",
"vue": "^3.2.47"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
"typescript": "^5.0.2",
"vite": "^4.3.2",
"vue-tsc": "^1.4.2"
}
}
微前端--使用路由控制微应用
主应用: vue3+vite(vue-base)
子应用1: vue3+vite(vue-demo1)
子应用2: vue3+vite(vue-demo2)
子应用3: angular+webpack(angularApp)
在主应用注册子应用
```
1 安装qiankun依赖: pnpm install qiankun
2 注册子应用:在src文件下新建qiankun文件夹,新建index.js文件
3 index.js文件内容
import { registerMicroApps, start } from 'qiankun';
registerMicroApps([
{
name: 'vue-app', // 必须与微应用注册名字相同
entry: '//localhost:9999', // 入口路径,开发时为微应用所启本地服务,上线时为微应用线上路径
container: '#container', // 微应用挂载的节点
activeRule: '/micro-vue',// 当访问路由为 /micro-vue 时加载微应用
props:{
msg: "我是第一个微应用" // 主应用向微应用传递参数
}
},
{
name: 'angular-app',
entry: '//localhost:9000',
container: '#container', // 微应用挂载的节点
activeRule: '/micro-angular',
},
]);
start({
prefetch: 'all', // 预加载
sandbox: {
experimentalStyleIsolation: true, // 开启沙箱模式,实验性方案
},
});
4 在主应用的main.ts里面引入这个文件: import "./qiankun"
import 'zone.js'; // zone.js要放在最上面,不然页面卡死
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import './assets/main.css'
import { createPinia } from 'pinia'
import router from './router'
import './qiankun'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus , { size: 'small', zIndex: 3000 })
app.use(createPinia())
app.use(router)
app.mount('#app')
5 在 App.vue里面挂载节点
<script setup></script>
<template>
<div>
<div id="containerVue" /> // 我是子应用
</div>
</template>
<style scoped></style>
```
微应用配置
微应用是vue3+vite(vue-demo1)
1、安装vite插件(qiankun 目前是不支持vite的。需要借助插件)
// pnpm install vite-plugin-qiankun
2、修改vite.config.js.
// import qiankun from 'vite-plugin-qiankun'
export default defineConfig({
plugins: [
vue(),
qiankun('vue-app', { // 微应用名字,与主应用注册的微应用名字保持一致
useDevMode: true
})
],
server: {
origin: 'http://localhost:9999', // 项目baseUrl,解决主应用中出现静态地址404问题
host: 'localhost',
port: 9999,
open: true,
},
})
3、修改main.ts入口文件
import { createApp } from 'vue'
import App from './App.vue'
import './style.css'
import { renderWithQiankun, qiankunWindow, QiankunProps } from 'vite-plugin-qiankun/dist/helper'
const initQianKun = () => {
renderWithQiankun({
// 当前应用在主应用中的生命周期
// 文档 https://qiankun.umijs.org/zh/guide/getting-started#
mount(props) {
render(props.container)
// 可以通过props读取主应用的参数:msg
// 监听主应用传值
props.onGlobalStateChange((res: { count: any }) => {
// console.log(res.count)
})
},
bootstrap() { },
unmount() { },
update: function (props: QiankunProps): void | Promise<void> {
throw new Error('Function not implemented.')
}
})
}
const render = (container?: HTMLElement | undefined) => {
// 如果是在主应用的环境下就挂载主应用的节点,否则挂载到本地
const appDom = container ? container : '#app'
createApp(App).mount(appDom)
}
// 判断当前应用是否在主应用中
qiankunWindow.__POWERED_BY_QIANKUN__ ? initQianKun() : render()
微应用是angular
1 新建 angular子项目 ng new name
2 新增 public-path.js文件.内容为
if (window.__POWERED_BY_QIANKUN__) {
// eslint-disable-next-line no-undef
__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__;
}
3 修改入口文件,src/main.ts 文件。
import './public-path';
import { enableProdMode, NgModuleRef } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
let app: void | NgModuleRef<AppModule>;
async function render() {
app = await platformBrowserDynamic()
.bootstrapModule(AppModule)
.catch((err) => console.error(err));
}
if (!(window as any).__POWERED_BY_QIANKUN__) {
render();
}
export async function bootstrap(props: Object) {
console.log(props);
}
export async function mount(props: Object) {
render();
}
export async function unmount(props: Object) {
console.log(props);
// @ts-ignore
app.destroy();
}
4 修改webpack打包配置,
// 需要安装@angular-builders/custom-webpack 插件
// 注意: 插件版本要和angular保持一致
// 在根目录新增custom-webpack.config.js内容为:
const appName = require('./package.json').name;
module.exports = {
devServer: {
headers: {
'Access-Control-Allow-Origin': '*',
},
},
output: {
library: `${appName}-[name]`,
libraryTarget: 'umd',
jsonpFunction: `webpackJsonp_${appName}`,
},
};
// 修改 angular.json中打包配置,加入我们的打包配置文件
- "builder": "@angular-devkit/build-angular:browser", // 删除
+ "builder": "@angular-builders/custom-webpack:browser",
"options": {
+ "customWebpackConfig": {
+ "path": "./custom-webpack.config.js"
+ }
}
- "builder": "@angular-devkit/build-angular:dev-server", // 删除
+ "builder": "@angular-builders/custom-webpack:dev-server",
5 解决zone.js
// 在父应用引入 zone.js,需要在 import qiankun 之前引入。(顶部引用,防止angular项目卡死)
// 将微应用的 src/polyfills.ts 里面的引入 zone.js 代码删掉
6 为了防止主应用或其他微应用也为 angular 时,建议给<app-root> 加上一个唯一的 id,比如说当前应用名称。
src/index.html :
+ <app-root id="angular9"></app-root>
src/app/app.component.ts :
+ selector: '#angular9 app-root',