├── .gitignore ├── .npmrc ├── README.md ├── package.json ├── packages ├── main │ ├── .env │ ├── .eslintrc.js │ ├── .gitignore │ ├── .husky │ │ ├── commit-msg │ │ └── pre-commit │ ├── .lintstagedrc │ ├── .npmrc │ ├── .prettierignore │ ├── .prettierrc │ ├── .stylelintrc.js │ ├── .umirc.ts │ ├── README.md │ ├── config │ │ └── routes.tsx │ ├── mock │ │ └── userAPI.ts │ ├── package.json │ ├── src │ │ ├── access.ts │ │ ├── app.css │ │ ├── app.tsx │ │ ├── assets │ │ │ └── .gitkeep │ │ ├── components │ │ │ ├── CustomErrorBoundary │ │ │ │ └── index.tsx │ │ │ └── Guide │ │ │ │ ├── Guide.less │ │ │ │ ├── Guide.tsx │ │ │ │ └── index.ts │ │ ├── constants │ │ │ └── index.ts │ │ ├── hooks │ │ │ └── useBrowserHistory.ts │ │ ├── models │ │ │ └── global.ts │ │ ├── pages │ │ │ ├── Access │ │ │ │ └── index.tsx │ │ │ ├── Exception404 │ │ │ │ └── index.tsx │ │ │ ├── Home │ │ │ │ ├── index.less │ │ │ │ └── index.tsx │ │ │ ├── Theme │ │ │ │ ├── index.module.less │ │ │ │ └── index.tsx │ │ │ ├── global.ts │ │ │ └── load │ │ │ │ └── index.tsx │ │ ├── services │ │ │ └── demo │ │ │ │ ├── UserController.ts │ │ │ │ ├── index.ts │ │ │ │ └── typings.d.ts │ │ └── utils │ │ │ ├── format.ts │ │ │ └── helper.ts │ ├── tsconfig.json │ └── typings.d.ts ├── sub-app-1 │ ├── .editorconfig │ ├── .env │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc │ ├── .umirc.ts │ ├── README.md │ ├── mock │ │ └── .gitkeep │ ├── package.json │ ├── src │ │ ├── app.css │ │ ├── app.ts │ │ ├── layouts │ │ │ ├── index.less │ │ │ └── index.tsx │ │ ├── pages │ │ │ ├── index.less │ │ │ ├── index.tsx │ │ │ ├── one │ │ │ │ └── index.tsx │ │ │ ├── theme │ │ │ │ ├── index.js │ │ │ │ └── index.module.less │ │ │ └── two │ │ │ │ └── index.tsx │ │ └── utils │ │ │ └── qiankunConfig.ts │ ├── tsconfig.json │ └── typings.d.ts ├── sub-app-2 │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── craco.config.js │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── robots.txt │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── public-path.js │ │ ├── reportWebVitals.js │ │ └── setupTests.js └── sub-app-3 │ ├── .env │ ├── .gitignore │ ├── README.md │ ├── craco.config.js │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt │ └── src │ ├── App.js │ ├── App.less │ ├── App.test.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── pages │ ├── 404 │ │ └── index.js │ ├── one │ │ └── index.js │ ├── theme │ │ ├── index.js │ │ └── index.module.less │ ├── three │ │ └── index.js │ └── two │ │ └── index.js │ ├── public-path.js │ ├── reportWebVitals.js │ └── setupTests.js ├── pnpm-lock.yaml └── pnpm-workspace.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # misc 7 | .DS_Store 8 | 9 | # ide 10 | .idea 11 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qiankun+umi4+react+antd 微前端构建指南 2 | 3 | ### 前言 4 | 随时umi4的到来, 我把qiankun学习计划也提上了日程, 在系统的学习一周后, 下面通过umi4+qiankun+react 展开说说我的学习成果吧~ 5 | 6 | ### 项目结构 7 | - 主应用(基于umi4构建) 8 | - 微应用(基于umi3构建) 9 | - 微应用2(基于react-create-app构建) 10 | - 微应用3(react-create-app + router构建) 11 | 12 | ### 主要完成以下功能: 13 | 14 | #### 挂载 15 | - 基于qiankun提供的loadMicroApp方法 16 | - 基于umi-plugin-qiankun提供的MicroApp , MicroAppWithMemoHistory 组件 17 | - 基于umi-plugin-qiankun提供的路由声明形式绑定挂载 18 | - 微应用嵌套微应用挂载 19 | 20 | #### 通信 21 | - 基于qiankun-apps注册时的props属性透传 22 | - 基于qiankun提供的全局状态-initGlobalState 23 | - 基于umi-plugin-qiankun提供的hooks-useQiankunStateForSlave 24 | - umi微应用使用hooks-useModel, hoc-connectMaster方式获取内容 25 | 26 | #### 样式隔离 27 | - 基于qiankun提供的sandbox-experimentalStyleIsolation实现样式隔离 28 | - 基于css-loader添加前缀 29 | 30 | #### 错误处理 31 | - 基于qiankun提供的autoCaptureError属性开启组件异常捕获 32 | - 基于qiankun提供的errorBoundary属性实现自定义异常页面 33 | - 基于qiankun提供的addGlobalUncaughtErrorHandler全局异常捕获 34 | 35 | ### 环境准备 36 | 37 | clone 38 | ``` 39 | https://github.com/xoptimal/qiankun-demo.git 40 | ``` 41 | 项目使用pnpm管理依赖, 请务先安装pnpm 42 | ``` 43 | npm install -g pnpm 44 | ``` 45 | 安装依赖 46 | ``` 47 | pnpm i 48 | ``` 49 | 启动项目 50 | ``` 51 | pnpm dev 52 | 53 | http://localhost:5000 54 | ``` 55 | 56 | ### 预览 57 | 主应用 58 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112337882.png) 59 | 微应用页面形式挂载 60 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112338315.png) 61 | 微应用路由形式挂载 62 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112338386.png) 63 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112339050.png) 64 | 应用间通信 65 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112339696.png) 66 | 微应用嵌套微应用挂载 67 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112340643.png) 68 | 微应用嵌套微应用路由形式挂载 69 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112340385.png) 70 | 样式隔离 71 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112341501.png) 72 | 微应用路由形式加载自定义错误页面 73 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112343939.png) 74 | 微应用组件形式加载自定义错误页面 75 | ![](https://raw.githubusercontent.com/xoptimal/images/main/img/202208112344590.png) 76 | 77 | ### 参考链接 78 | * [qiankun官网](https://qiankun.umijs.org/) 79 | * [umi4-微前端配置指引](https://umijs.org/docs/max/micro-frontend) 80 | * [umi3-微前端配置指引](https://v3.umijs.org/zh-CN/plugins/plugin-qiankun) 81 | * [在 create-react-app 中使用 - Ant Design](https://ant-design.gitee.io/docs/react/use-with-create-react-app-cn) 82 | 83 | ### 最后 84 | 85 | 欢迎交流, 如果本项目对你有帮助的话, 欢迎━(*`∀´*)ノ亻! ⭐⭐⭐ ~ 86 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qiankun-demo-monorepo", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "author": "", 7 | "license": "ISC", 8 | "scripts": { 9 | "dev": "pnpm -r --parallel --filter=./packages/* run dev", 10 | "dev:main": "pnpm --filter=./packages/main run dev", 11 | "dev:sub1": "pnpm --filter=./packages/sub-app-1 run dev", 12 | "dev:sub2": "pnpm --filter=./packages/sub-app-2 run dev", 13 | "dev:sub3": "pnpm --filter=./packages/sub-app-3 run dev" 14 | }, 15 | "devDependencies": { 16 | "pnpm": "^7.8.0" 17 | }, 18 | "packageManager": "pnpm@7.8.0" 19 | } 20 | -------------------------------------------------------------------------------- /packages/main/.env: -------------------------------------------------------------------------------- 1 | PORT=5000 2 | BROWSER=true 3 | -------------------------------------------------------------------------------- /packages/main/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@umijs/max/eslint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/main/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.env.local 3 | /.umirc.local.ts 4 | /config/config.local.ts 5 | /src/.umi 6 | /src/.umi-production 7 | /.umi 8 | /.umi-production 9 | /.umi-test 10 | /dist 11 | /.mfsu 12 | -------------------------------------------------------------------------------- /packages/main/.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install max verify-commit $1 5 | -------------------------------------------------------------------------------- /packages/main/.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no-install lint-staged --quiet 5 | -------------------------------------------------------------------------------- /packages/main/.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{md,json}": [ 3 | "prettier --cache --write" 4 | ], 5 | "*.{js,jsx}": [ 6 | "max lint --fix --eslint-only", 7 | "prettier --cache --write" 8 | ], 9 | "*.{css,less}": [ 10 | "max lint --fix --stylelint-only", 11 | "prettier --cache --write" 12 | ], 13 | "*.ts?(x)": [ 14 | "max lint --fix --eslint-only", 15 | "prettier --cache --parser=typescript --write" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/main/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /packages/main/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .umi 3 | .umi-production 4 | -------------------------------------------------------------------------------- /packages/main/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "proseWrap": "never", 6 | "overrides": [{ "files": ".prettierrc", "options": { "parser": "json" } }], 7 | "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-packagejson"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/main/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: require.resolve('@umijs/max/stylelint'), 3 | }; 4 | -------------------------------------------------------------------------------- /packages/main/.umirc.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from '@umijs/max'; 2 | import routes from "./config/routes"; 3 | 4 | export default defineConfig({ 5 | antd: { 6 | configProvider: { 7 | prefixCls: 'mainAnt', 8 | }, 9 | }, 10 | access: {}, 11 | model: {}, 12 | initialState: {}, 13 | request: {}, 14 | layout: { 15 | title: '@umijs/max', 16 | }, 17 | lessLoader: { 18 | modifyVars: { 19 | '@ant-prefix': 'mainAnt', 20 | "primary-color": "#004FD9" 21 | }, 22 | javascriptEnabled: true, 23 | }, 24 | routes, 25 | npmClient: 'pnpm', 26 | qiankun: { 27 | master: { 28 | prefetch: false 29 | } 30 | }, 31 | mfsu: false 32 | }); 33 | 34 | -------------------------------------------------------------------------------- /packages/main/README.md: -------------------------------------------------------------------------------- 1 | # umi 2 | 3 |

4 | Version 5 | Downloads 6 | build status 7 | License 8 |

9 | 10 | A framework in react community ✨ 11 | 12 | > Please consider following this project's author, [sorrycc](https://github.com/sorrycc), and consider starring the project to show your ❤️ and support. 13 | 14 | ### [🚀 Read the launch post →](https://umijs.org/blog/umi-4-rc) 15 | 16 | ### [📚 Learn Umi →](https://umijs.org/) 17 | 18 | ## Contribution 19 | 20 | See [Contributing Guide](https://umijs.org/docs/introduce/contributing). 21 | 22 | ### Core Maintainers 23 | 24 | Core Maintainres are community members who have contributed a significant amount of time and energy to the project through issues, bug fixes, implementing enhancements/features. 25 | 26 | * [sorrycc](https://github.com/sorrycc) 27 | * [xiaohuoni](https://github.com/xiaohuoni) 28 | 29 | ### Maintainers 30 | 31 | Maintainers are community members who have 10 or more PRs merged in umi, or have spend lot's of time on umi community or issues. 32 | 33 | * [PeachScript](https://github.com/PeachScript) 34 | * [YdreamW](https://github.com/YdreamW) 35 | * [yuaanlin](https://github.com/yuaanlin) 36 | * [fz6m](https://github.com/fz6m) 37 | * [stormslowly](https://github.com/stormslowly) 38 | * [xierenyuan](https://github.com/xierenyuan) 39 | * [siyi98](https://github.com/siyi98) 40 | * [txp1035](https://github.com/txp1035) 41 | 42 | ### Contributors 43 | 44 | Contributors are community members who have 1 or more PR merged in umi. Contributors can contact me[[sorrycc](https://github.com/sorrycc)] to join the Contributor Group. 45 | 46 | 47 | 48 | ## Community 49 | 50 | * [交流和反馈群](https://fb.umijs.org/) 51 | 52 | ## LICENSE 53 | 54 | [MIT](./LICENSE) 55 | -------------------------------------------------------------------------------- /packages/main/config/routes.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default [ 4 | { 5 | path: '/', 6 | redirect: '/home', 7 | }, 8 | { 9 | path: '/home', 10 | name: 'home', 11 | component: '/Home', 12 | icon: 'HomeOutlined' 13 | }, 14 | { 15 | name: 'react-micro-app', 16 | path: '/sub-app-2', 17 | component: '/load', 18 | icon: 'SmileOutlined', 19 | }, 20 | { 21 | name: 'react-router-micro-app', 22 | path: '/sub-app-3', 23 | microApp: 'sub-app-3', 24 | icon: 'SmileOutlined', 25 | routes: [ 26 | { 27 | name: '嵌套路由1', 28 | path: '/sub-app-3/one', 29 | }, 30 | { 31 | name: '嵌套路由2', 32 | path: '/sub-app-3/three', 33 | }, 34 | { 35 | path: '/sub-app-3', 36 | redirect: '/sub-app-3/one', 37 | }, 38 | ] 39 | }, 40 | { 41 | name: 'umi3-micro-app', 42 | path: '/sub-app-1', 43 | layout: true, 44 | microApp: 'sub-app-1', 45 | icon: 'SmileOutlined', 46 | routes: [ 47 | { 48 | name: '应用间通信', 49 | path: '/sub-app-1/one', 50 | }, 51 | { 52 | name: '应用间嵌套', 53 | path: '/sub-app-1/two', 54 | }, 55 | { 56 | name: '应用间通信', 57 | path: '/sub-app-1/sub-app-3', 58 | routes: [ 59 | { 60 | name: '嵌套路由1', 61 | path: '/sub-app-1/sub-app-3/one', 62 | }, 63 | { 64 | name: '嵌套路由2', 65 | path: '/sub-app-1/sub-app-3/three', 66 | }, 67 | ] 68 | } 69 | ] 70 | }, 71 | { 72 | path: '/theme', 73 | name: 'theme', 74 | component: '/Theme', 75 | icon: 'SmileOutlined' 76 | }, 77 | { 78 | path: '/404', 79 | component: '/Exception404', 80 | }, 81 | ] 82 | -------------------------------------------------------------------------------- /packages/main/mock/userAPI.ts: -------------------------------------------------------------------------------- 1 | const users = [ 2 | { name: 'Umi', nickName: 'U', gender: 'MALE' }, 3 | { name: 'Fish', nickName: 'B', gender: 'FEMALE' }, 4 | ]; 5 | 6 | export default { 7 | 'GET /api/v1/queryUserList': (req: any, res: any) => { 8 | res.json({ 9 | success: true, 10 | data: { list: users }, 11 | errorCode: 0, 12 | }); 13 | }, 14 | 'PUT /api/v1/user/': (req: any, res: any) => { 15 | res.json({ 16 | success: true, 17 | errorCode: 0, 18 | }); 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "author": "", 4 | "scripts": { 5 | "dev": "max dev", 6 | "build": "max build", 7 | "format": "prettier --cache --write .", 8 | "prepare": "husky install", 9 | "postinstall": "max setup", 10 | "setup": "max setup", 11 | "start": "npm run dev" 12 | }, 13 | "dependencies": { 14 | "@ant-design/icons": "^4.7.0", 15 | "@ant-design/pro-components": "^1.1.3", 16 | "@umijs/max": "^4.0.9", 17 | "antd": "^4.20.7", 18 | "qiankun": "^2.7.4" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.0.0", 22 | "@types/react-dom": "^18.0.0", 23 | "husky": "^8.0.1", 24 | "lint-staged": "^13.0.3", 25 | "prettier": "^2.7.1", 26 | "prettier-plugin-organize-imports": "^2", 27 | "prettier-plugin-packagejson": "^2", 28 | "typescript": "^4.1.2" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/main/src/access.ts: -------------------------------------------------------------------------------- 1 | export default (initialState: API.UserInfo) => { 2 | // 在这里按照初始化数据定义项目中的权限,统一管理 3 | // 参考文档 https://next.umijs.org/docs/max/access 4 | const canSeeAdmin = !!( 5 | initialState && initialState.name !== 'dontHaveAccess' 6 | ); 7 | return { 8 | canSeeAdmin, 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/main/src/app.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .link { 4 | color: #2572E6; 5 | } 6 | -------------------------------------------------------------------------------- /packages/main/src/app.tsx: -------------------------------------------------------------------------------- 1 | // 运行时配置 2 | import {addGlobalUncaughtErrorHandler, initGlobalState, MicroAppStateActions} from "qiankun"; 3 | import React, {useState} from "react"; 4 | import {notification} from 'antd'; 5 | 6 | import './app.css' 7 | import CustomErrorBoundary from "@/components/CustomErrorBoundary"; 8 | import {getMicroAppRouteComponent} from "@@/plugin-qiankun-master/getMicroAppRouteComponent"; 9 | import {customFetch} from "@/utils/helper"; 10 | 11 | // 全局初始化数据配置,用于 Layout 用户信息和权限初始化 12 | // 更多信息见文档:https://next.umijs.org/docs/api/runtime-config#getinitialstate 13 | export async function getInitialState(): Promise<{ name: string }> { 14 | return {name: '@umijs/max'}; 15 | } 16 | 17 | export const layout = () => { 18 | 19 | const [openKeys, setOpenKeys] = useState([]) 20 | 21 | return { 22 | logo: 'https://img.alicdn.com/tfs/TB1YHEpwUT1gK0jSZFhXXaAtVXa-28-27.svg', 23 | menu: { 24 | locale: false, 25 | }, 26 | menuProps: { 27 | openKeys 28 | }, 29 | onOpenChange: setOpenKeys, 30 | rightContentRender: false, 31 | token: { 32 | sider: { 33 | menuBackgroundColor: '#004FD9', 34 | menuTextColor: 'rgba(255,255,255,0.85)', 35 | subMenuSelectedTextColor: '#fff', 36 | menuTextColorSecondary: 'rgba(255,255,255,0.65)', 37 | menuSelectedTextColor: '#fff', 38 | menuTitleTextColor: 'rgba(255,255,255,0.95)', 39 | menuItemHoverBgColor: 'rgba(0,0,0,0.06)', 40 | menuItemCollapsedHoverBgColor: 'rgba(0,0,0,0.06)', 41 | menuItemSelectedBgColor: 'rgba(0,0,0,0.15)', 42 | menuItemCollapsedSelectedBgColor: 'rgba(0,0,0,0.15)', 43 | menuItemDividerColor: 'rgba(255,255,255,0.15)', 44 | collapsedButtonBgColor: '#fff', 45 | collapsedButtonTextColor: 'rgba(0,0,0,0.45)', 46 | collapsedButtonHoverTextColor: 'rgba(0,0,0,0.65)', 47 | menuSubArrowColor: 'rgba(255,255,255,0.15)', 48 | }, 49 | appListIconTextColor: 'rgba(255,255,255,0.85)', 50 | appListIconHoverTextColor: 'rgba(255,255,255,0.95)', 51 | appListIconHoverBgColor: 'rgba(0,0,0,0.06)', 52 | }, 53 | }; 54 | }; 55 | 56 | const callback = (data: { name: string, message: string }) => { 57 | notification.open({ 58 | type: 'info', 59 | message: `来自${data.name}的Reply`, 60 | description: data.message, 61 | onClick: () => { 62 | console.log('Notification Clicked!'); 63 | }, 64 | }); 65 | } 66 | 67 | export function patchClientRoutes({routes}: any) { 68 | routes[0].children.forEach((item: any, index: number) => { 69 | if (item.microApp) { 70 | console.log("item", item) 71 | routes[0].children[index].element = getMicroAppRouteComponent({ 72 | appName: item.microApp, 73 | base: item.microAppProps?.base || '/', 74 | routePath: item.path, 75 | masterHistoryType: item.microAppProps?.hisory || 'browser', 76 | routeProps: { 77 | errorBoundary: (error: any) => 78 | } 79 | })() 80 | } 81 | }) 82 | } 83 | 84 | const state = { 85 | slogan: 'Hello MicroFrontend from qiankun-initGlobalState', 86 | callback 87 | } 88 | 89 | // 初始化 state 90 | const actions: MicroAppStateActions = initGlobalState(state); 91 | 92 | // 监听数据变化 93 | actions.onGlobalStateChange((state, prev) => { 94 | // update 95 | //actions.setGlobalState(state); 96 | }); 97 | 98 | // 取消监听 99 | // actions.offGlobalStateChange(); 100 | 101 | // export function patchClientRoutes({ routes }) { 102 | // console.log('routes', routes) 103 | // } 104 | 105 | export const qiankun: any = { 106 | apps: [ 107 | { 108 | name: 'sub-app-1', 109 | entry: '//localhost:5001', 110 | activeRule: '/sub-app-1', 111 | container: '#micro-app-1', 112 | props: { 113 | autoCaptureError: true, 114 | base: '/sub-app-1', 115 | defaultProps: { 116 | slogan: 'Hello MicroFrontend from qiankun-apps-props', 117 | callback 118 | } 119 | }, 120 | }, 121 | { 122 | name: 'sub-app-2', 123 | entry: '//localhost:5002', 124 | activeRule: '/sub-app-2', 125 | container: '#micro-app-2', 126 | sandbox: { 127 | experimentalStyleIsolation: true 128 | }, 129 | }, 130 | { 131 | name: 'sub-app-3', 132 | entry: '//localhost:5003', 133 | activeRule: '/sub-app-3', 134 | container: '#micro-app-3', 135 | sandbox: { 136 | experimentalStyleIsolation: true 137 | }, 138 | }, 139 | ], 140 | lifeCycles: { 141 | afterMount: (props: any) => { 142 | // 这里需要做一次set才能保证微应用能触发到change, 以便能拿到state 143 | actions.setGlobalState(state); 144 | }, 145 | beforeLoad: (props: any) => { 146 | }, 147 | beforeMount: (props: any) => { 148 | }, 149 | beforeUnmount: (props: any) => { 150 | }, 151 | }, 152 | fetch: customFetch 153 | }; 154 | 155 | // umi下自带的父子通信方式 156 | export function useQiankunStateForSlave() { 157 | return { 158 | slogan: 'Hello MicroFrontend from umi-useQiankunStateForSlave', 159 | callback 160 | }; 161 | } 162 | 163 | // 捕获全局微应用错误 164 | addGlobalUncaughtErrorHandler((event) => { 165 | // 这里会频繁触发, 注意使用 166 | }) 167 | -------------------------------------------------------------------------------- /packages/main/src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/main/src/assets/.gitkeep -------------------------------------------------------------------------------- /packages/main/src/components/CustomErrorBoundary/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Button, Result} from "antd"; 3 | import {history} from '@umijs/max'; 4 | 5 | export default function CustomErrorBoundary(props: { error?: any }) { 6 | 7 | const handleClick = () => { 8 | history.push('/home') 9 | } 10 | 11 | let status: any = 500, title = "500", subTitle = "Sorry, something went wrong."; 12 | 13 | return ( 14 | Back Home} 19 | /> 20 | ) 21 | 22 | } 23 | -------------------------------------------------------------------------------- /packages/main/src/components/Guide/Guide.less: -------------------------------------------------------------------------------- 1 | .title { 2 | margin: 0 auto; 3 | font-weight: 200; 4 | } 5 | -------------------------------------------------------------------------------- /packages/main/src/components/Guide/Guide.tsx: -------------------------------------------------------------------------------- 1 | import { Layout, Row, Typography } from 'antd'; 2 | import React from 'react'; 3 | import styles from './Guide.less'; 4 | 5 | interface Props { 6 | name: string; 7 | } 8 | 9 | // 脚手架示例组件 10 | const Guide: React.FC = (props) => { 11 | const { name } = props; 12 | return ( 13 | 14 | 15 | 16 | 欢迎使用 {name} ! 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | export default Guide; 24 | -------------------------------------------------------------------------------- /packages/main/src/components/Guide/index.ts: -------------------------------------------------------------------------------- 1 | import Guide from './Guide'; 2 | export default Guide; 3 | -------------------------------------------------------------------------------- /packages/main/src/constants/index.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_NAME = 'Umi Max'; 2 | -------------------------------------------------------------------------------- /packages/main/src/hooks/useBrowserHistory.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | import { history } from '@umijs/max'; 3 | 4 | // 处理子应用push操作,父应用的菜单高亮不正确BUG 5 | export function useBrowserHistory() { 6 | useEffect(() => { 7 | const handlePopState = (event: any) => { 8 | const { href } = event.target.location; // eg: http://localhost:1200/#/bps/producMange/imageAlbum?a=b 9 | // const pathNameWithSearch = href.split('#')[1]; // eg: /bps/producMange/imageAlbum?a=b 10 | // const pathname = pathNameWithSearch.split('?')[0]; 11 | // console.group('---handlePopState---'); 12 | // 13 | // console.log('event=>pathname-->', pathname); 14 | // console.log('event=>pathNameWithSearch-->', pathNameWithSearch); 15 | // console.groupEnd(); 16 | 17 | console.log('history.pathname-->', history.location.pathname); 18 | console.log('href-->', href); 19 | 20 | // 21 | // // 如果2个不一样,说明是子应用自己push的路由 22 | // if (history.location.pathname !== pathname) { 23 | // history.replace(pathNameWithSearch); 24 | // } 25 | }; 26 | window.addEventListener('popstate', handlePopState); 27 | return () => { 28 | window.removeEventListener('popstate', handlePopState); 29 | }; 30 | }, []); 31 | } 32 | -------------------------------------------------------------------------------- /packages/main/src/models/global.ts: -------------------------------------------------------------------------------- 1 | // 全局共享数据示例 2 | import { DEFAULT_NAME } from '@/constants'; 3 | import { useState } from 'react'; 4 | 5 | const useUser = () => { 6 | const [name, setName] = useState(DEFAULT_NAME); 7 | return { 8 | name, 9 | setName, 10 | }; 11 | }; 12 | 13 | export default useUser; 14 | -------------------------------------------------------------------------------- /packages/main/src/pages/Access/index.tsx: -------------------------------------------------------------------------------- 1 | import {PageContainer} from '@ant-design/pro-components'; 2 | import {Access, MicroAppWithMemoHistory, useAccess} from '@umijs/max'; 3 | import {Button} from 'antd'; 4 | 5 | const AccessPage: React.FC = () => { 6 | const access = useAccess(); 7 | return ( 8 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | }; 21 | 22 | export default AccessPage; 23 | -------------------------------------------------------------------------------- /packages/main/src/pages/Exception404/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import CustomErrorBoundary from "@/components/CustomErrorBoundary"; 3 | 4 | export default function () { 5 | 6 | return 7 | } 8 | -------------------------------------------------------------------------------- /packages/main/src/pages/Home/index.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/main/src/pages/Home/index.less -------------------------------------------------------------------------------- /packages/main/src/pages/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import Guide from '@/components/Guide'; 2 | import { trim } from '@/utils/format'; 3 | import { PageContainer } from '@ant-design/pro-components'; 4 | import { useModel } from '@umijs/max'; 5 | import styles from './index.less'; 6 | import React from "react"; 7 | 8 | const HomePage: React.FC = () => { 9 | const { name } = useModel('global'); 10 | return ( 11 | 12 |
13 | 14 |
15 |
16 | ); 17 | }; 18 | 19 | export default HomePage; 20 | -------------------------------------------------------------------------------- /packages/main/src/pages/Theme/index.module.less: -------------------------------------------------------------------------------- 1 | 2 | 3 | .link { 4 | color: #2572E6; 5 | } 6 | -------------------------------------------------------------------------------- /packages/main/src/pages/Theme/index.tsx: -------------------------------------------------------------------------------- 1 | import {Button, Divider, Space} from "antd"; 2 | import {PageContainer} from "@ant-design/pro-components"; 3 | import {MicroApp} from "@umijs/max"; 4 | import React from "react"; 5 | import CustomErrorBoundary from "@/components/CustomErrorBoundary"; 6 | import styles from './index.module.less' 7 | 8 | const defaultParams = { 9 | base: '/', 10 | url: '/theme', 11 | settings: { 12 | sandbox: { 13 | experimentalStyleIsolation: true 14 | } 15 | }, 16 | // 自动捕获错误, 吊起ant 17 | // autoCaptureError: true 18 | // 自定义异常页面 19 | errorBoundary: (error: any) => 20 | } 21 | 22 | const text = ` 23 | // CSS Modules to index.module.less 24 | .link { color: red } 25 | 我是Link 26 | 27 | // 内联样式 28 | 我是Link 29 | 30 | // 外联样式 to src/app.css 31 | 我是Link 32 | 33 | // 默认样式 34 | 我是Link 35 | ` 36 | 37 | export default function Theme() { 38 | 39 | return ( 40 | 44 |
{text}
45 | 46 | 47 |

主应用(main)

48 |
49 |

antd-组件库样式

50 | 51 | 52 | 53 | 54 | 55 |
56 |

CSS Modules

57 | 我是Link 58 |
59 |
60 |

内联样式

61 | 我是Link 62 |
63 |
64 |

外联样式

65 | 我是Link 66 |
67 |
68 |

默认

69 | 我是Link 70 |
71 |
72 |
73 | 74 |

微应用(sub-app-1)

75 | 76 | 77 |

微应用(sub-app-3)

78 | 79 | 80 |
81 | ) 82 | } 83 | -------------------------------------------------------------------------------- /packages/main/src/pages/global.ts: -------------------------------------------------------------------------------- 1 | 2 | console.log("============global") 3 | -------------------------------------------------------------------------------- /packages/main/src/pages/load/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useRef, useState} from "react"; 2 | import {PageContainer} from '@ant-design/pro-components'; 3 | import {MicroApp} from '@umijs/max'; 4 | import {loadMicroApp} from "qiankun"; 5 | import {customFetch} from "@/utils/helper"; 6 | import {Divider, Modal, Typography} from "antd"; 7 | 8 | const {Title, Text, Paragraph} = Typography; 9 | 10 | let microApp: any = null; 11 | 12 | const LoadMicroApp: React.FC = () => { 13 | 14 | const [replyMessage, setReplyMessage] = useState('') 15 | 16 | const containerRef = useRef(null) 17 | 18 | const handleOk = (type: number) => { 19 | if (type === 2) { 20 | setReplyMessage("you're welcome") 21 | } else { 22 | microApp.update({ 23 | base: '/', 24 | replyMessage: "you're welcome", 25 | message: 'hello sub-app-3, load as loadMicroApp function', 26 | callback: (message: string) => callback(message, 3) 27 | }) 28 | } 29 | } 30 | 31 | const callback = (message: string, type: number) => { 32 | Modal.success({ 33 | title: `来自微应用sub-app-${type}的信息`, 34 | content: message, 35 | okText: "reply you're welcome", 36 | onOk: () => handleOk(type) 37 | }); 38 | } 39 | 40 | useEffect(() => { 41 | if (containerRef.current) { 42 | microApp = loadMicroApp({ 43 | name: 'sub-app-3', 44 | entry: '//localhost:5003', 45 | container: containerRef.current, 46 | props: { 47 | base: '/', 48 | message: 'hello sub-app-3, load as loadMicroApp function', 49 | callback: (message: string) => callback(message, 3) 50 | }, 51 | }, { 52 | // @ts-ignore 53 | fetch: customFetch 54 | }); 55 | } 56 | return () => { 57 | microApp?.unmount() 58 | microApp = null 59 | } 60 | }, [containerRef.current]) 61 | 62 | return ( 63 | 67 | 68 |
    69 |
  • 70 | 微应用(sub-app-2) 71 | 基于umi-MicroApp组件 72 |
    73 | callback(message, 2)}/> 78 |
    79 | 80 |
  • 81 |
  • 82 | 路由微应用(sub-app-3) 83 | 基于qiankun-loadMicroApp方法 84 |
    85 |
  • 86 |
87 |
88 | 89 |
90 | ); 91 | }; 92 | 93 | export default LoadMicroApp; 94 | -------------------------------------------------------------------------------- /packages/main/src/services/demo/UserController.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // 该文件由 OneAPI 自动生成,请勿手动修改! 3 | import { request } from '@umijs/max'; 4 | 5 | /** 此处后端没有提供注释 GET /api/v1/queryUserList */ 6 | export async function queryUserList( 7 | params: { 8 | // query 9 | /** keyword */ 10 | keyword?: string; 11 | /** current */ 12 | current?: number; 13 | /** pageSize */ 14 | pageSize?: number; 15 | }, 16 | options?: { [key: string]: any }, 17 | ) { 18 | return request('/api/v1/queryUserList', { 19 | method: 'GET', 20 | params: { 21 | ...params, 22 | }, 23 | ...(options || {}), 24 | }); 25 | } 26 | 27 | /** 此处后端没有提供注释 POST /api/v1/user */ 28 | export async function addUser( 29 | body?: API.UserInfoVO, 30 | options?: { [key: string]: any }, 31 | ) { 32 | return request('/api/v1/user', { 33 | method: 'POST', 34 | headers: { 35 | 'Content-Type': 'application/json', 36 | }, 37 | data: body, 38 | ...(options || {}), 39 | }); 40 | } 41 | 42 | /** 此处后端没有提供注释 GET /api/v1/user/${param0} */ 43 | export async function getUserDetail( 44 | params: { 45 | // path 46 | /** userId */ 47 | userId?: string; 48 | }, 49 | options?: { [key: string]: any }, 50 | ) { 51 | const { userId: param0 } = params; 52 | return request(`/api/v1/user/${param0}`, { 53 | method: 'GET', 54 | params: { ...params }, 55 | ...(options || {}), 56 | }); 57 | } 58 | 59 | /** 此处后端没有提供注释 PUT /api/v1/user/${param0} */ 60 | export async function modifyUser( 61 | params: { 62 | // path 63 | /** userId */ 64 | userId?: string; 65 | }, 66 | body?: API.UserInfoVO, 67 | options?: { [key: string]: any }, 68 | ) { 69 | const { userId: param0 } = params; 70 | return request(`/api/v1/user/${param0}`, { 71 | method: 'PUT', 72 | headers: { 73 | 'Content-Type': 'application/json', 74 | }, 75 | params: { ...params }, 76 | data: body, 77 | ...(options || {}), 78 | }); 79 | } 80 | 81 | /** 此处后端没有提供注释 DELETE /api/v1/user/${param0} */ 82 | export async function deleteUser( 83 | params: { 84 | // path 85 | /** userId */ 86 | userId?: string; 87 | }, 88 | options?: { [key: string]: any }, 89 | ) { 90 | const { userId: param0 } = params; 91 | return request(`/api/v1/user/${param0}`, { 92 | method: 'DELETE', 93 | params: { ...params }, 94 | ...(options || {}), 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /packages/main/src/services/demo/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // 该文件由 OneAPI 自动生成,请勿手动修改! 3 | 4 | import * as UserController from './UserController'; 5 | export default { 6 | UserController, 7 | }; 8 | -------------------------------------------------------------------------------- /packages/main/src/services/demo/typings.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | // 该文件由 OneAPI 自动生成,请勿手动修改! 3 | 4 | declare namespace API { 5 | interface PageInfo { 6 | /** 7 | 1 */ 8 | current?: number; 9 | pageSize?: number; 10 | total?: number; 11 | list?: Array>; 12 | } 13 | 14 | interface PageInfo_UserInfo_ { 15 | /** 16 | 1 */ 17 | current?: number; 18 | pageSize?: number; 19 | total?: number; 20 | list?: Array; 21 | } 22 | 23 | interface Result { 24 | success?: boolean; 25 | errorMessage?: string; 26 | data?: Record; 27 | } 28 | 29 | interface Result_PageInfo_UserInfo__ { 30 | success?: boolean; 31 | errorMessage?: string; 32 | data?: PageInfo_UserInfo_; 33 | } 34 | 35 | interface Result_UserInfo_ { 36 | success?: boolean; 37 | errorMessage?: string; 38 | data?: UserInfo; 39 | } 40 | 41 | interface Result_string_ { 42 | success?: boolean; 43 | errorMessage?: string; 44 | data?: string; 45 | } 46 | 47 | type UserGenderEnum = 'MALE' | 'FEMALE'; 48 | 49 | interface UserInfo { 50 | id?: string; 51 | name?: string; 52 | /** nick */ 53 | nickName?: string; 54 | /** email */ 55 | email?: string; 56 | gender?: UserGenderEnum; 57 | } 58 | 59 | interface UserInfoVO { 60 | name?: string; 61 | /** nick */ 62 | nickName?: string; 63 | /** email */ 64 | email?: string; 65 | } 66 | 67 | type definitions_0 = null; 68 | } 69 | -------------------------------------------------------------------------------- /packages/main/src/utils/format.ts: -------------------------------------------------------------------------------- 1 | // 示例方法,没有实际意义 2 | export function trim(str: string) { 3 | return str.trim(); 4 | } 5 | -------------------------------------------------------------------------------- /packages/main/src/utils/helper.ts: -------------------------------------------------------------------------------- 1 | import {history} from "@umijs/max"; 2 | 3 | 4 | export async function customFetch(url: string, ...args: any) { 5 | // 拦截子应用加载资源失败的情况处理 6 | try { 7 | return await window.fetch(url, ...args); 8 | } catch (e) { 9 | // history.push('/404', {message: '微应用加载失败,请检查应用是否可运行'}) 10 | } 11 | return { 12 | async text() { 13 | return ''; 14 | }, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/main/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./src/.umi/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /packages/main/typings.d.ts: -------------------------------------------------------------------------------- 1 | import '@umijs/max/typings'; 2 | -------------------------------------------------------------------------------- /packages/sub-app-1/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /packages/sub-app-1/.env: -------------------------------------------------------------------------------- 1 | PORT=5001 2 | BROWSER=none 3 | -------------------------------------------------------------------------------- /packages/sub-app-1/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /npm-debug.log* 6 | /yarn-error.log 7 | /yarn.lock 8 | /package-lock.json 9 | 10 | # production 11 | /dist 12 | 13 | # misc 14 | .DS_Store 15 | 16 | # umi 17 | /src/.umi 18 | /src/.umi-production 19 | /src/.umi-test 20 | /.env.local 21 | -------------------------------------------------------------------------------- /packages/sub-app-1/.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.html 5 | package.json 6 | .umi 7 | .umi-production 8 | .umi-test 9 | -------------------------------------------------------------------------------- /packages/sub-app-1/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 80, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/sub-app-1/.umirc.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'umi'; 2 | 3 | export default defineConfig({ 4 | nodeModulesTransform: { 5 | type: 'none', 6 | }, 7 | theme: { 8 | "primary-color": "#ea1244" 9 | }, 10 | mfsu: false, 11 | antd: {}, 12 | routes: [ 13 | { 14 | path: '/theme', 15 | name: 'theme', 16 | component: '@/pages/theme', 17 | }, 18 | { 19 | path: '/', 20 | component: '@/layouts/index', 21 | routes: [ 22 | { 23 | path: '/one', 24 | name: 'one', 25 | component: '@/pages/one', 26 | }, 27 | { 28 | path: '/two', 29 | name: 'two', 30 | component: '@/pages/two', 31 | }, 32 | { 33 | name: 'micro-3', 34 | path: '/sub-app-3', 35 | microApp: 'sub-app-3', 36 | }, 37 | { 38 | path: '/', 39 | redirect: '/one' 40 | }, 41 | ] 42 | }, 43 | 44 | ], 45 | fastRefresh: {}, 46 | qiankun: { 47 | master: { 48 | // 注册子应用信息 49 | apps: [ 50 | { 51 | name: 'sub-app-2', 52 | entry: '//localhost:5002', 53 | }, 54 | { 55 | name: 'sub-app-3', 56 | entry: '//localhost:5003', 57 | }, 58 | ], 59 | }, 60 | slave: {}, //微应用必须配置 61 | }, 62 | runtimeHistory: {}, // 开始运行时history功能 63 | mountElementId: 'micro-app-1', // 容器ID 64 | base: '/', // umi微应用独立访问需要配置这个参数, 否则默认获取package.name作为base 65 | // publicPath: `/${packageName}/`, 66 | // outputPath: `./dist/${packageName}`, 67 | // hash: true, 68 | }); 69 | -------------------------------------------------------------------------------- /packages/sub-app-1/README.md: -------------------------------------------------------------------------------- 1 | # umi project 2 | 3 | ## Getting Started 4 | 5 | Install dependencies, 6 | 7 | ```bash 8 | $ yarn 9 | ``` 10 | 11 | Start the dev server, 12 | 13 | ```bash 14 | $ yarn start 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/sub-app-1/mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/sub-app-1/mock/.gitkeep -------------------------------------------------------------------------------- /packages/sub-app-1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-app-1", 3 | "scripts": { 4 | "dev": "umi dev", 5 | "build": "umi build", 6 | "postinstall": "umi generate tmp", 7 | "prettier": "prettier --write '**/*.{js,jsx,tsx,ts,less,md,json}'", 8 | "test": "umi-test", 9 | "test:coverage": "umi-test --coverage" 10 | }, 11 | "gitHooks": { 12 | "pre-commit": "lint-staged" 13 | }, 14 | "lint-staged": { 15 | "*.{js,jsx,less,md,json}": [ 16 | "prettier --write" 17 | ], 18 | "*.ts?(x)": [ 19 | "prettier --parser=typescript --write" 20 | ] 21 | }, 22 | "dependencies": { 23 | "@ant-design/icons": "^4.7.0", 24 | "@umijs/plugin-antd": "^0.15.0", 25 | "@umijs/plugin-initial-state": "^2.4.0", 26 | "@umijs/plugin-model": "^2.6.2", 27 | "antd": "^4.22.3", 28 | "lodash": "^4.17.21", 29 | "path-to-regexp": "^6.2.1", 30 | "qiankun": "^2.7.4", 31 | "react": "17.x", 32 | "react-dom": "17.x", 33 | "umi": "3.x" 34 | }, 35 | "devDependencies": { 36 | "@types/react": "^17.0.0", 37 | "@types/react-dom": "^17.0.0", 38 | "@umijs/plugin-qiankun": "^2.39.2", 39 | "@umijs/test": "^3.5.32", 40 | "lint-staged": "^10.0.7", 41 | "prettier": "^2.2.0", 42 | "typescript": "^4.1.2", 43 | "yorkie": "^2.0.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/app.css: -------------------------------------------------------------------------------- 1 | 2 | .link { 3 | color: #ea1244; 4 | } 5 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/app.ts: -------------------------------------------------------------------------------- 1 | import './app.css'; 2 | import config from "@/utils/qiankunConfig"; 3 | 4 | export const qiankun = config.qiankun 5 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/layouts/index.less: -------------------------------------------------------------------------------- 1 | #components-layout-demo-top-side .logo { 2 | float: left; 3 | width: 120px; 4 | height: 31px; 5 | margin: 16px 24px 16px 0; 6 | background: rgba(255, 255, 255, 0.3); 7 | } 8 | 9 | .ant-row-rtl #components-layout-demo-top-side .logo { 10 | float: right; 11 | margin: 16px 0 16px 24px; 12 | } 13 | 14 | .site-layout-background { 15 | background: #fff; 16 | } 17 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/layouts/index.tsx: -------------------------------------------------------------------------------- 1 | import {Descriptions, Layout, Menu, Typography} from "antd"; 2 | import React, {useEffect, useState} from "react"; 3 | import {useHistory} from "umi"; 4 | import './index.less' 5 | 6 | const {Sider, Content} = Layout 7 | const {Title} = Typography; 8 | 9 | export default function BasicLayout(props: React.PropsWithChildren) { 10 | 11 | const menus = [ 12 | { 13 | key: '/one', 14 | label: '应用间通信', 15 | }, 16 | { 17 | key: '/two', 18 | label: '应用间嵌套', 19 | }, 20 | { 21 | key: '/sub-app-3', 22 | label: '路由微应用嵌套', 23 | children: [ 24 | { 25 | key: '/sub-app-3/one', 26 | label: '嵌套路由1', 27 | }, 28 | { 29 | key: '/sub-app-3/three', 30 | label: '嵌套路由2', 31 | } 32 | ] 33 | }, 34 | ] 35 | 36 | const history = useHistory() 37 | 38 | const [openKeys, setOpenKeys] = useState([]) 39 | 40 | useEffect(() => { 41 | const arr = history.location.pathname.split('/') 42 | if (arr.length > 1) { 43 | setOpenKeys(arr.slice(1, arr.length - 1).map(path => '/' + path)) 44 | } 45 | }, []) 46 | 47 | return ( 48 | 49 | 50 | { 56 | history.push(event.key) 57 | }} 58 | onOpenChange={setOpenKeys} 59 | items={menus} 60 | /> 61 | 62 | 63 | 64 | 当前微应用 65 | 66 | sub-app-1 67 | React 68 | 基于umi3 + @umijs/plugin-qiankun 69 | {history.location.pathname} 70 | 71 | 72 | {props.children} 73 | 74 | 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/pages/index.less: -------------------------------------------------------------------------------- 1 | .title { 2 | background: rgb(121, 242, 157); 3 | } 4 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import {useHistory} from "umi"; 2 | 3 | export default function IndexPage(props: any) { 4 | 5 | const history = useHistory() 6 | 7 | const handleJump = (path: string) => { 8 | history.push(path) 9 | } 10 | 11 | return ( 12 |
13 |
14 | 16 | 18 |
19 | {props.children} 20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/pages/one/index.tsx: -------------------------------------------------------------------------------- 1 | import {connectMaster, useHistory, useModel} from "umi"; 2 | import {Button, Descriptions, Divider, Typography} from "antd"; 3 | import {useEffect, useState} from "react"; 4 | import qiankunConfig from "@/utils/qiankunConfig"; 5 | 6 | const {Title, Text} = Typography; 7 | 8 | function Record(props: any) { 9 | const handleClick = () => { 10 | props?.callback({name: 'sub-app-1-one', message: '我收到你的你的slogan啦~'}) 11 | } 12 | 13 | return <> 14 | 来自主应用消息: {props.slogan} 15 | 16 | 17 | } 18 | 19 | const Sub = connectMaster(Record) 20 | 21 | export default function One(props: any) { 22 | 23 | const globalProps = useModel('@@qiankunStateFromMaster'); 24 | 25 | const [qiankun_props] = useState(qiankunConfig.getGlobalProps()) 26 | const [qiankun_state, setQiankenState] = useState(qiankunConfig.getGlobalState()) 27 | 28 | useEffect(() => { 29 | qiankunConfig.addListener((state: any) => { 30 | setQiankenState(state) 31 | }) 32 | }, []) 33 | 34 | return ( 35 | 36 | 主应用通过路由声明实现微应用加载及通信 37 | 应用间通信 38 |
    39 |
  • 40 | 基于qiankun-apps的透传 41 | 42 | 43 |
  • 44 |
  • 45 | 基于qiankun-initGlobalState 46 | 47 | 48 |
  • 49 |
  • 50 | 基于umi-useModel 51 | 52 | 53 |
  • 54 |
  • 55 | 基于umi-connectMaster 56 | 57 |
  • 58 |
59 | 60 | 需要注意的是, umi通信方式仅能在主子都是umi上使用 61 | 62 |
63 | 64 | ) 65 | 66 | } 67 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/pages/theme/index.js: -------------------------------------------------------------------------------- 1 | import {Button, Space} from "antd"; 2 | import styles from './index.module.less' 3 | 4 | export default function Theme() { 5 | 6 | return ( 7 |
8 |

antd-组件库样式

9 | 10 | 11 | 12 | 13 | 14 |
15 |

CSS Modules

16 | 我是Link 17 |
18 |
19 |

内联样式

20 | 我是Link 21 |
22 |
23 |

外联样式

24 | 我是Link 25 |
26 |
27 |

默认

28 | 我是Link 29 |
30 |
31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/pages/theme/index.module.less: -------------------------------------------------------------------------------- 1 | 2 | 3 | .link { 4 | color: #ea1244; 5 | } 6 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/pages/two/index.tsx: -------------------------------------------------------------------------------- 1 | import {MicroApp, MicroAppWithMemoHistory, useHistory} from "umi"; 2 | import {Divider, Modal, Typography} from "antd"; 3 | import {useState} from "react"; 4 | 5 | const {Title, Text, Paragraph} = Typography; 6 | 7 | export default function Two() { 8 | 9 | const [replyMessage, setReplyMessage] = useState('') 10 | const [replyMessage2, setReplyMessage2] = useState('') 11 | 12 | const handleOk = (type: number) => { 13 | if (type === 2) { 14 | setReplyMessage("you're welcome") 15 | } else { 16 | setReplyMessage2("you're welcome") 17 | } 18 | } 19 | 20 | const callback = (message: string, type: number) => { 21 | Modal.success({ 22 | title: `来自微应用sub-app-${type}的信息`, 23 | content: message, 24 | okText: "reply you're welcome", 25 | onOk: () => handleOk(type) 26 | }); 27 | } 28 | 29 | const history = useHistory() 30 | 31 | return ( 32 |
33 | 34 | 应用间嵌套 35 |
    36 |
  • 37 | 微应用(sub-app-2) 38 | 基于umi-MicroApp组件 39 |
    40 | callback(message, 2)}/> 44 |
    45 | 46 |
  • 47 |
  • 48 | 路由微应用(sub-app-3) 49 | 基于umi-MicroAppWithMemoHistory组件 50 | 51 | 加载微应用对应的路由页面, 需要配置base, 以及访问的路径 52 | 53 |
    54 | callback(message, 3)}/> 59 |
    60 | 需要注意的是url配置是无效的, 只会根据当前主应用下的路径是寻找微应用对应的页面 61 |
  • 62 |
63 | 64 |
65 |
66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /packages/sub-app-1/src/utils/qiankunConfig.ts: -------------------------------------------------------------------------------- 1 | import {history, setCreateHistoryOptions} from "umi"; 2 | 3 | let qiankun_state: any; 4 | let qiankun_props: any; 5 | 6 | let listener: Function; 7 | 8 | export async function customFetch(url: string, ...args: any) { 9 | // 拦截子应用加载资源失败的情况处理 10 | try { 11 | return await window.fetch(url, ...args); 12 | } catch (e) { 13 | history.push('/404', {message: '微应用加载失败,请检查应用是否可运行'}) 14 | } 15 | return { 16 | async text() { 17 | return ''; 18 | }, 19 | } 20 | } 21 | 22 | const qiankun = { 23 | 24 | master: { 25 | // 注册子应用信息 26 | apps: [ 27 | { 28 | name: 'sub-app-2', 29 | entry: '//localhost:5002', 30 | }, 31 | { 32 | name: 'sub-app-3', 33 | entry: '//localhost:5003', 34 | }, 35 | ], 36 | prefetch: false, 37 | }, 38 | 39 | // 应用加载之前 40 | async bootstrap(props: any) { 41 | setCreateHistoryOptions({basename: props?.base || '/'}) 42 | }, 43 | 44 | // 应用 render 之前触发 45 | async mount(props: any) { 46 | // 监听qiankun initState 47 | props.onGlobalStateChange((state: any, prev: any) => { 48 | qiankun_state = state 49 | // 简单实现个订阅 50 | listener?.(state, prev) 51 | }) 52 | qiankun_props = props 53 | }, 54 | 55 | // 应用卸载之后触发 56 | async unmount(props: any) { 57 | console.log('app1 unmount', props); 58 | }, 59 | 60 | } 61 | 62 | function getGlobalProps() { 63 | return qiankun_props 64 | } 65 | 66 | function getGlobalState() { 67 | return qiankun_state 68 | } 69 | 70 | 71 | function addListener(func: Function) { 72 | listener = func 73 | } 74 | 75 | export default { 76 | qiankun, 77 | getGlobalState, 78 | getGlobalProps, 79 | addListener 80 | } 81 | -------------------------------------------------------------------------------- /packages/sub-app-1/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "moduleResolution": "node", 6 | "resolveJsonModule": true, 7 | "importHelpers": true, 8 | "jsx": "react-jsx", 9 | "esModuleInterop": true, 10 | "sourceMap": true, 11 | "baseUrl": "./", 12 | "strict": true, 13 | "paths": { 14 | "@/*": ["src/*"], 15 | "@@/*": ["src/.umi/*"] 16 | }, 17 | "allowSyntheticDefaultImports": true 18 | }, 19 | "include": [ 20 | "mock/**/*", 21 | "src/**/*", 22 | "config/**/*", 23 | ".umirc.ts", 24 | "typings.d.ts" 25 | ], 26 | "exclude": [ 27 | "node_modules", 28 | "lib", 29 | "es", 30 | "dist", 31 | "typings", 32 | "**/__test__", 33 | "test", 34 | "docs", 35 | "tests" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /packages/sub-app-1/typings.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.css'; 2 | declare module '*.less'; 3 | declare module '*.png'; 4 | declare module '*.svg' { 5 | export function ReactComponent( 6 | props: React.SVGProps, 7 | ): React.ReactElement; 8 | const url: string; 9 | export default url; 10 | } 11 | -------------------------------------------------------------------------------- /packages/sub-app-2/.env: -------------------------------------------------------------------------------- 1 | PORT=5002 2 | BROWSER=none 3 | -------------------------------------------------------------------------------- /packages/sub-app-2/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/sub-app-2/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /packages/sub-app-2/craco.config.js: -------------------------------------------------------------------------------- 1 | const {name} = require('./package.json'); 2 | const CracoAntDesignPlugin = require("craco-antd"); 3 | 4 | module.exports = { 5 | plugins: [{ 6 | plugin: CracoAntDesignPlugin, 7 | options: { 8 | customizeTheme: { 9 | "@primary-color": "#1DA57A", 10 | }, 11 | } 12 | }], 13 | webpack: { 14 | configure: (config, {env, paths}) => { 15 | config.output.library = `${name}-[name]`; 16 | config.output.libraryTarget = 'umd'; 17 | config.output.chunkLoadingGlobal = `webpackJsonp_${name}`; 18 | config.output.globalObject = 'window'; 19 | return config 20 | } 21 | }, 22 | devServer: (devServerConfig, { env, paths, proxy, allowedHost }) => { 23 | const config = {...devServerConfig} 24 | config.historyApiFallback = true; 25 | config.hot = false; 26 | // config.watchContentBase = false; 27 | config.liveReload = false; 28 | return config; 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /packages/sub-app-2/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-app-2", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "antd": "^4.22.3", 10 | "babel-plugin-import": "^1.13.5", 11 | "craco-antd": "^2.0.0", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "web-vitals": "^2.1.4" 15 | }, 16 | "devDependencies": { 17 | "@craco/craco": "^6.4.5", 18 | "react-scripts": "^5.0.1" 19 | }, 20 | "scripts": { 21 | "dev": "craco start", 22 | "build": "craco build", 23 | "test": "craco test", 24 | "eject": "craco eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/sub-app-2/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/sub-app-2/public/favicon.ico -------------------------------------------------------------------------------- /packages/sub-app-2/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/sub-app-2/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/sub-app-2/public/logo192.png -------------------------------------------------------------------------------- /packages/sub-app-2/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/sub-app-2/public/logo512.png -------------------------------------------------------------------------------- /packages/sub-app-2/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/sub-app-2/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/App.js: -------------------------------------------------------------------------------- 1 | function App(props) { 2 | 3 | const {message, replyMessage, callback} = props 4 | 5 | const handleClick = () => { 6 | callback('nice to meet you~') 7 | } 8 | return ( 9 |
10 |

sub-app-1: {message}

11 | sub-app-2: nice to meet you 12 | 13 | {replyMessage &&

sub-app-1: {replyMessage}

} 14 |
15 | ) 16 | } 17 | 18 | export default App; 19 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/index.js: -------------------------------------------------------------------------------- 1 | import './public-path'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import App from './App'; 5 | 6 | const rootId = '#micro-app-2' 7 | 8 | function render(props) { 9 | const {container} = props; 10 | ReactDOM.render(, container ? container.querySelector(rootId) : document.querySelector(rootId)); 11 | } 12 | 13 | if (!window.__POWERED_BY_QIANKUN__) { 14 | render({}); 15 | } 16 | 17 | /** 18 | * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 19 | * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 20 | */ 21 | export async function bootstrap(props) { 22 | } 23 | 24 | /** 25 | * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 26 | */ 27 | export async function mount(props) { 28 | render(props); 29 | } 30 | 31 | /** 32 | * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 33 | */ 34 | export async function unmount(props) { 35 | const {container} = props; 36 | ReactDOM.unmountComponentAtNode(container ? container.querySelector(rootId) : document.querySelector(rootId)); 37 | } 38 | 39 | /** 40 | * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 41 | */ 42 | export async function update(props) { 43 | render(props) 44 | } 45 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/public-path.js: -------------------------------------------------------------------------------- 1 | if (window.__POWERED_BY_QIANKUN__) { 2 | // eslint-disable-next-line no-undef 3 | __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 4 | } 5 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /packages/sub-app-2/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /packages/sub-app-3/.env: -------------------------------------------------------------------------------- 1 | PORT=5003 2 | BROWSER=none 3 | -------------------------------------------------------------------------------- /packages/sub-app-3/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/sub-app-3/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 13 | 14 | The page will reload when you make changes.\ 15 | You may also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!** 35 | 36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own. 39 | 40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `npm run build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /packages/sub-app-3/craco.config.js: -------------------------------------------------------------------------------- 1 | const {name} = require('./package.json'); 2 | const CracoAntDesignPlugin = require("craco-antd"); 3 | 4 | module.exports = { 5 | plugins: [{ 6 | plugin: CracoAntDesignPlugin, 7 | options: { 8 | customizeTheme: { 9 | "@primary-color": "#1DA57A", 10 | }, 11 | } 12 | }], 13 | webpack: { 14 | configure: (config, {env, paths}) => { 15 | config.output.library = `${name}-[name]`; 16 | config.output.libraryTarget = 'umd'; 17 | config.output.chunkLoadingGlobal = `webpackJsonp_${name}`; 18 | config.output.globalObject = 'window'; 19 | return config 20 | } 21 | }, 22 | devServer: (devServerConfig, { env, paths, proxy, allowedHost }) => { 23 | const config = {...devServerConfig} 24 | config.historyApiFallback = true; 25 | config.hot = false; 26 | // config.watchContentBase = false; 27 | config.liveReload = false; 28 | return config; 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /packages/sub-app-3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-app-3", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.4", 7 | "@testing-library/react": "^13.3.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "antd": "^4.22.3", 10 | "babel-plugin-import": "^1.13.5", 11 | "craco-antd": "^2.0.0", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-router-dom": "^6.3.0", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "devDependencies": { 18 | "@craco/craco": "^6.4.5", 19 | "react-scripts": "^5.0.1" 20 | }, 21 | "scripts": { 22 | "dev": "craco start", 23 | "build": "craco build", 24 | "test": "craco test", 25 | "eject": "craco eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/sub-app-3/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/sub-app-3/public/favicon.ico -------------------------------------------------------------------------------- /packages/sub-app-3/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/sub-app-3/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/sub-app-3/public/logo192.png -------------------------------------------------------------------------------- /packages/sub-app-3/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xoptimal/qiankun-demo/343568f12490d33967acff0636bfac9fe25de293/packages/sub-app-3/public/logo512.png -------------------------------------------------------------------------------- /packages/sub-app-3/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/sub-app-3/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/App.js: -------------------------------------------------------------------------------- 1 | import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom"; 2 | import One from "./pages/one"; 3 | import Two from "./pages/two"; 4 | import Three from "./pages/three"; 5 | import Error404 from "./pages/404"; 6 | import Theme from "./pages/theme"; 7 | 8 | function App(props) { 9 | const {base = '/sub-app-3'} = props 10 | 11 | return ( 12 | 13 | 14 | }/> 15 | }/> 16 | 17 | }/> 18 | }/> 19 | }/> 20 | }/> 21 | }/> 22 | 23 | 24 | ); 25 | } 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/App.less: -------------------------------------------------------------------------------- 1 | 2 | .link { 3 | color: #1DA57A 4 | } 5 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/App.test.js: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import App from './App'; 3 | 4 | test('renders learn react link', () => { 5 | render(); 6 | const linkElement = screen.getByText(/learn react/i); 7 | expect(linkElement).toBeInTheDocument(); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/index.js: -------------------------------------------------------------------------------- 1 | import './public-path'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import App from './App'; 5 | 6 | const rootId = "#micro-app-3" 7 | 8 | function render(props) { 9 | const {container} = props; 10 | ReactDOM.render(, container ? container.querySelector(rootId) : document.querySelector(rootId)); 11 | } 12 | 13 | if (!window.__POWERED_BY_QIANKUN__) { 14 | render({}); 15 | } 16 | 17 | export async function bootstrap(props) { 18 | } 19 | 20 | export async function mount(props) { 21 | render(props); 22 | } 23 | 24 | export async function unmount(props) { 25 | const {container} = props; 26 | ReactDOM.unmountComponentAtNode(container ? container.querySelector(rootId) : document.querySelector(rootId)); 27 | } 28 | 29 | export async function update(props) { 30 | render(props) 31 | } 32 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/pages/404/index.js: -------------------------------------------------------------------------------- 1 | 2 | export default function Error404() { 3 | return
404
4 | } 5 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/pages/one/index.js: -------------------------------------------------------------------------------- 1 | export default function One(props) { 2 | 3 | console.log("props",props) 4 | 5 | return ( 6 |
7 | 加载成功, 当前是sub-app-3/one 路由页面内容 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/pages/theme/index.js: -------------------------------------------------------------------------------- 1 | import {Button, Space} from "antd"; 2 | import styles from "./index.module.less"; 3 | 4 | export default function Theme() { 5 | 6 | return ( 7 |
8 |

antd-组件库样式

9 | 10 | 11 | 12 | 13 | 14 |
15 |

CSS Modules

16 | 我是Link 17 |
18 |
19 |

内联样式

20 | 我是Link 21 |
22 |
23 |

外联样式

24 | 我是Link 25 |
26 |
27 |

默认

28 | 我是Link 29 |
30 |
31 |
32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/pages/theme/index.module.less: -------------------------------------------------------------------------------- 1 | 2 | .link { 3 | color: #1DA57A; 4 | } 5 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/pages/three/index.js: -------------------------------------------------------------------------------- 1 | import {useNavigate} from "react-router-dom"; 2 | 3 | export default function Three(props) { 4 | 5 | const navigate = useNavigate() 6 | 7 | function handleClick() { 8 | navigate('/one') 9 | } 10 | 11 | return ( 12 |
13 |

加载成功, 当前是sub-app-3/three 路由页面内容

14 | 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/pages/two/index.js: -------------------------------------------------------------------------------- 1 | export default function Two(props) { 2 | const {message, replyMessage, callback} = props 3 | 4 | const handleClick = () => { 5 | callback('nice to meet you~') 6 | } 7 | return ( 8 |
9 |

sub-app-1: {message}

10 | sub-app-3: nice to meet you 11 | 12 | {replyMessage &&

sub-app-1: {replyMessage}

} 13 |
14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/public-path.js: -------------------------------------------------------------------------------- 1 | if (window.__POWERED_BY_QIANKUN__) { 2 | // eslint-disable-next-line no-undef 3 | __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 4 | } 5 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /packages/sub-app-3/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' 3 | --------------------------------------------------------------------------------