├── .eslintignore ├── sub-vue ├── .env ├── .browserslistrc ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── views │ │ ├── About.vue │ │ └── Home.vue │ ├── store │ │ └── index.js │ ├── public-path.js │ ├── router │ │ └── index.js │ ├── main.js │ ├── App.vue │ └── components │ │ └── HelloWorld.vue ├── babel.config.js ├── .editorconfig ├── .gitignore ├── .eslintrc.js ├── README.md ├── vue.config.js └── package.json ├── common ├── .gitignore ├── .npmignore ├── src │ ├── api │ │ └── index.js │ ├── sdk │ │ └── index.js │ ├── store │ │ ├── index.js │ │ └── global-register.js │ └── index.js └── package.json ├── main ├── .browserslistrc ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── assets │ │ └── logo.png │ ├── micro-app.js │ ├── store.js │ ├── main.js │ └── App.vue ├── babel.config.js ├── .editorconfig ├── .env.development ├── .env.production ├── vue.config.js ├── .gitignore ├── .eslintrc.js ├── README.md └── package.json ├── .gitignore ├── sub-react ├── .env ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── index.html ├── src │ ├── public-path.js │ ├── setupTests.js │ ├── App.test.js │ ├── index.css │ ├── App.js │ ├── App.css │ ├── index.js │ ├── logo.svg │ └── serviceWorker.js ├── .gitignore ├── config-overrides.js ├── package.json └── README.md ├── sub-html ├── scripts │ └── build.sh ├── README.md ├── package.json ├── public │ └── index.html ├── index.html └── js │ └── main.js ├── .vscode └── settings.json ├── README.md ├── scripts └── bundle.sh ├── .editorconfig ├── package.json ├── React + qiankun -demo.md └── qiankun、micro-app、Garfish、wujie.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /examples 2 | -------------------------------------------------------------------------------- /sub-vue/.env: -------------------------------------------------------------------------------- 1 | VUE_APP_PORT=7777 2 | -------------------------------------------------------------------------------- /common/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /common/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /common/src/api/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /common/src/sdk/index.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | } 4 | -------------------------------------------------------------------------------- /main/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /sub-vue/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | dist 5 | .xml 6 | 7 | 8 | -------------------------------------------------------------------------------- /sub-react/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true 2 | PORT=7788 3 | PUBLIC_URL=/subapp/sub-react 4 | -------------------------------------------------------------------------------- /main/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/qiankun/HEAD/main/public/favicon.ico -------------------------------------------------------------------------------- /main/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/qiankun/HEAD/main/src/assets/logo.png -------------------------------------------------------------------------------- /sub-react/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /sub-vue/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/qiankun/HEAD/sub-vue/public/favicon.ico -------------------------------------------------------------------------------- /main/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /sub-react/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/qiankun/HEAD/sub-react/public/favicon.ico -------------------------------------------------------------------------------- /sub-react/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/qiankun/HEAD/sub-react/public/logo192.png -------------------------------------------------------------------------------- /sub-react/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/qiankun/HEAD/sub-react/public/logo512.png -------------------------------------------------------------------------------- /sub-vue/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nemo-crypto/qiankun/HEAD/sub-vue/src/assets/logo.png -------------------------------------------------------------------------------- /sub-vue/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /common/src/store/index.js: -------------------------------------------------------------------------------- 1 | import globalRegister from './global-register' 2 | export default { 3 | globalRegister 4 | } 5 | -------------------------------------------------------------------------------- /sub-vue/src/views/About.vue: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /main/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /sub-vue/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | trim_trailing_whitespace = true 5 | insert_final_newline = true 6 | -------------------------------------------------------------------------------- /main/.env.development: -------------------------------------------------------------------------------- 1 | VUE_APP_SUB_VUE=//localhost:7777/subapp/sub-vue/ 2 | VUE_APP_SUB_REACT=//localhost:7788/subapp/sub-react/ 3 | VUE_APP_SUB_HTML=//localhost:7799/ 4 | -------------------------------------------------------------------------------- /common/src/index.js: -------------------------------------------------------------------------------- 1 | import store from './store' 2 | import sdk from './sdk' 3 | import api from './api' 4 | 5 | export { 6 | store, 7 | sdk, 8 | api 9 | } 10 | -------------------------------------------------------------------------------- /sub-html/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 把当前项目对外输出的文件都放到dist目录 4 | 5 | rm -rf ./dist 6 | 7 | mkdir ./dist 8 | 9 | cp -r ./public/* ./dist/ 10 | 11 | cp -r ./js ./dist/js 12 | 13 | -------------------------------------------------------------------------------- /main/.env.production: -------------------------------------------------------------------------------- 1 | VUE_APP_SUB_VUE=//qiankun.nemo-crypto.com/subapp/sub-vue/ 2 | VUE_APP_SUB_REACT=//qiankun.nemo-crypto.com/subapp/sub-react/ 3 | VUE_APP_SUB_HTML=//qiankun.nemo-crypto.com/subapp/sub-html/ 4 | -------------------------------------------------------------------------------- /sub-react/src/public-path.js: -------------------------------------------------------------------------------- 1 | if (window.__POWERED_BY_QIANKUN__) { 2 | // eslint-disable-next-line 3 | __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 4 | //__webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 5 | } 6 | -------------------------------------------------------------------------------- /main/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transpileDependencies: ['common'], 3 | chainWebpack: config => { 4 | config.plugin('html') 5 | .tap((args) => { 6 | args[0].title = 'qiankun-example' 7 | return args 8 | }) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /sub-html/README.md: -------------------------------------------------------------------------------- 1 | # 原生HTML项目 2 | 3 | ## 启动 4 | ``` 5 | npm start 6 | ``` 7 | 本地服务通过 `http-server` 提供。 8 | 9 | ## 参考 10 | 官方例子(jQuery):[https://github.com/umijs/qiankun/tree/master/examples/purehtml](https://github.com/umijs/qiankun/tree/master/examples/purehtml) 11 | -------------------------------------------------------------------------------- /common/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "common", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "nemo-crypto", 10 | "license": "MIT" 11 | } 12 | -------------------------------------------------------------------------------- /sub-vue/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | 4 | Vue.use(Vuex) 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | testCount: 0, 9 | }, 10 | mutations: { 11 | }, 12 | actions: { 13 | }, 14 | modules: { 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /sub-react/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/extend-expect'; 6 | -------------------------------------------------------------------------------- /main/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /sub-react/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | 7 | const { getByText } = render(); 8 | const linkElement = getByText(/learn react/i); 9 | expect(linkElement).toBeInTheDocument(); 10 | }); 11 | -------------------------------------------------------------------------------- /sub-vue/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Editor directories and files 16 | .idea 17 | .vscode 18 | *.suo 19 | *.ntvs* 20 | *.njsproj 21 | *.sln 22 | *.sw? 23 | -------------------------------------------------------------------------------- /sub-vue/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 19 | -------------------------------------------------------------------------------- /main/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sub-react/.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 | -------------------------------------------------------------------------------- /main/README.md: -------------------------------------------------------------------------------- 1 | # main 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /sub-vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/standard' 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint' 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | "./main", 4 | "./sub-vue", 5 | "./sub-react", 6 | "./common" 7 | ], 8 | "eslint.enable": true, 9 | "editor.formatOnSave": false, 10 | "editor.codeActionsOnSave": { 11 | "source.fixAll.eslint": "explicit" 12 | }, 13 | "search.useIgnoreFiles": false, 14 | "search.exclude": { 15 | "**/dist": true 16 | }, 17 | "eslint.debug": true, 18 | } 19 | -------------------------------------------------------------------------------- /sub-react/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 | -------------------------------------------------------------------------------- /sub-vue/README.md: -------------------------------------------------------------------------------- 1 | # sub-vue 2 | 3 | ## Project setup 4 | 5 | ``` 6 | npm install 7 | ``` 8 | 9 | ### Compiles and hot-reloads for development 10 | 11 | ``` 12 | npm run serve 13 | ``` 14 | 15 | ### Compiles and minifies for production 16 | 17 | ``` 18 | npm run build 19 | ``` 20 | 21 | ### Lints and fixes files 22 | 23 | ``` 24 | npm run lint 25 | ``` 26 | 27 | ### Customize configuration 28 | 29 | See [Configuration Reference](https://cli.vuejs.org/config/). 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qiankun-example 2 | 3 | qiankun 实战 demo,父应用 vue,子应用使用 `react`, `vue` 和 `原生HTML`。 4 | 5 | [微前端qiankun从搭建到部署的实践](https://juejin.im/post/6875462470593904653) 6 | 7 | ## 开始 8 | 安装根目录工程依赖 9 | 10 | ``` 11 | npm i 12 | ``` 13 | 一键安装所有主子应用的依赖 14 | ``` 15 | npm run install 16 | ``` 17 | 18 | 一键启动所有所有应用 19 | ``` 20 | npm start 21 | ``` 22 | 23 | 通过 [http://localhost:8080/](http://localhost:8080/) 访问主应用。 24 | 25 | ## 发布 26 | 一键构建并打包所有主子应用 27 | ``` 28 | npm run build 29 | ``` 30 | 31 | 32 | -------------------------------------------------------------------------------- /scripts/bundle.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf ./dist 4 | 5 | mkdir ./dist 6 | mkdir ./dist/subapp 7 | 8 | # sub-react子应用 9 | cp -r ./sub-react/build/ ./dist/subapp/sub-react/ 10 | 11 | # sub-vue子应用 12 | cp -r ./sub-vue/dist/ ./dist/subapp/sub-vue/ 13 | 14 | # sub-html子应用 15 | cp -r ./sub-html/dist/ ./dist/subapp/sub-html/ 16 | 17 | # main基座 18 | cp -r ./main/dist/ ./dist/main/ 19 | 20 | # cd ./dist 21 | # zip -r mp$(date +%Y%m%d%H%M%S).zip * 22 | # cd .. 23 | echo 'bundle.sh execute success.' 24 | -------------------------------------------------------------------------------- /sub-html/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-html", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.html", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "cross-env PUBLIC_PATH=/ http-server ./ -p 7799 --cors", 9 | "build": "bash ./scripts/build.sh" 10 | }, 11 | "author": "nemo-crypto", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "cross-env": "^7.0.3", 15 | "http-server": "^13.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /sub-vue/src/public-path.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (window.__POWERED_BY_QIANKUN__) { 3 | if (process.env.NODE_ENV === 'development') { 4 | // eslint-disable-next-line 5 | __webpack_public_path__ = `//localhost:${process.env.VUE_APP_PORT}${process.env.BASE_URL}` 6 | return 7 | } 8 | // eslint-disable-next-line 9 | __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__ 10 | // __webpack_public_path__ = `${process.env.BASE_URL}/` 11 | } 12 | })() 13 | -------------------------------------------------------------------------------- /sub-vue/vue.config.js: -------------------------------------------------------------------------------- 1 | const { name } = require('../package.json') 2 | 3 | module.exports = { 4 | publicPath: '/subapp/sub-vue', 5 | transpileDependencies: ['common'], 6 | chainWebpack: config => config.resolve.symlinks(false), 7 | configureWebpack: { 8 | output: { 9 | // 把子应用打包成 umd 库格式 10 | library: `${name}-[name]`, 11 | libraryTarget: 'umd', 12 | jsonpFunction: `webpackJsonp_${name}` 13 | } 14 | }, 15 | devServer: { 16 | port: process.env.VUE_APP_PORT, 17 | headers: { 18 | 'Access-Control-Allow-Origin': '*' 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sub-vue/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import Home from '../views/Home.vue' 4 | import About from '../views/About.vue' 5 | 6 | Vue.use(VueRouter) 7 | 8 | const routes = [ 9 | { 10 | path: '/', 11 | name: 'Home', 12 | component: Home 13 | }, 14 | { 15 | path: '/about', 16 | name: 'About', 17 | // route level code-splitting 18 | // this generates a separate chunk (about.[hash].js) for this route 19 | // which is lazy-loaded when the route is visited. 20 | component: About 21 | } 22 | ] 23 | 24 | export default routes 25 | -------------------------------------------------------------------------------- /sub-react/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 | -------------------------------------------------------------------------------- /sub-html/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sub-html子应用生产环境 6 | 11 | 12 | 13 |
14 | 纯原生HTML子应用, 当前处于 独立运行 环境。 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sub-react/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from './logo.svg'; 3 | import './App.css'; 4 | 5 | function App() { 6 | return ( 7 |
8 |
9 | logo 10 |

11 | Edit src/App.js and save to reload. 12 |

13 | 19 | Learn React 20 | 21 |
22 |
23 | ); 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /main/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sub-html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | sub-html子应用开发环境 6 | 11 | 12 | 13 |
14 | 纯原生HTML子应用, 当前处于 独立运行 环境。 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /sub-vue/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: https://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # Matches multiple files with brace expansion notation 12 | # Set default charset 13 | [*.{js,py}] 14 | charset = utf-8 15 | 16 | # 4 space indentation 17 | [*.py] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | # Indentation override for all JS under lib directory 22 | [src/**.{js,vue}] 23 | indent_style = space 24 | indent_size = 2 25 | 26 | # Matches the exact files either package.json or .travis.yml 27 | [{package.json,vue.config.js}] 28 | indent_style = space 29 | indent_size = 2 30 | -------------------------------------------------------------------------------- /sub-react/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 | -------------------------------------------------------------------------------- /main/src/micro-app.js: -------------------------------------------------------------------------------- 1 | import store from './store' 2 | 3 | const microApps = [ 4 | { 5 | name: 'sub-vue', 6 | entry: process.env.VUE_APP_SUB_VUE, 7 | activeRule: '/sub-vue' 8 | }, 9 | { 10 | name: 'sub-react', 11 | entry: process.env.VUE_APP_SUB_REACT, 12 | activeRule: '/sub-react' 13 | }, 14 | { 15 | name: 'sub-html', 16 | entry: process.env.VUE_APP_SUB_HTML, 17 | activeRule: '/sub-html' 18 | } 19 | ] 20 | 21 | const apps = microApps.map(item => { 22 | return { 23 | ...item, 24 | container: '#subapp-viewport', // 子应用挂载的div 25 | props: { 26 | routerBase: item.activeRule, // 下发基础路由 27 | getGlobalState: store.getGlobalState // 下发getGlobalState方法 28 | } 29 | } 30 | }) 31 | 32 | export default apps 33 | -------------------------------------------------------------------------------- /main/src/store.js: -------------------------------------------------------------------------------- 1 | import { initGlobalState } from 'qiankun' 2 | import Vue from 'vue' 3 | 4 | // 父应用的初始state 5 | // Vue.observable是为了让initialState变成可响应:https://cn.vuejs.org/v2/api/#Vue-observable。 6 | const initialState = Vue.observable({ 7 | user: { 8 | name: 'zhangsan' 9 | } 10 | }) 11 | 12 | const actions = initGlobalState(initialState) 13 | 14 | actions.onGlobalStateChange((newState, prev) => { 15 | // state: 变更后的状态; prev 变更前的状态 16 | console.log('main change', JSON.stringify(newState), JSON.stringify(prev)) 17 | 18 | for (const key in newState) { 19 | initialState[key] = newState[key] 20 | } 21 | }) 22 | 23 | // 定义一个获取state的方法下发到子应用 24 | actions.getGlobalState = (key) => { 25 | // 有key,表示取globalState下的某个子级对象 26 | // 无key,表示取全部 27 | 28 | return key ? initialState[key] : initialState 29 | } 30 | 31 | export default actions 32 | -------------------------------------------------------------------------------- /sub-react/config-overrides.js: -------------------------------------------------------------------------------- 1 | const { name } = require('./package.json'); 2 | console.log(name) 3 | 4 | module.exports = { 5 | webpack: function override(config, env) { 6 | config.entry = config.entry.filter( 7 | (e) => !e.includes('webpackHotDevClient') 8 | ); 9 | 10 | config.output.library = `${name}-[name]`; 11 | config.output.libraryTarget = 'umd'; 12 | config.output.jsonpFunction = `webpackJsonp_${name}`; 13 | return config; 14 | }, 15 | devServer: (configFunction) => { 16 | return function (proxy, allowedHost) { 17 | const config = configFunction(proxy, allowedHost); 18 | config.open = false; 19 | config.hot = false; 20 | config.headers = { 21 | 'Access-Control-Allow-Origin': '*', 22 | }; 23 | // Return your customised Webpack Development Server config. 24 | return config; 25 | }; 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /sub-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^4.2.4", 7 | "@testing-library/react": "^9.3.2", 8 | "@testing-library/user-event": "^7.1.2", 9 | "react": "^16.13.1", 10 | "react-dom": "^16.13.1", 11 | "react-scripts": "3.4.3" 12 | }, 13 | "scripts": { 14 | "start": "react-app-rewired start", 15 | "build": "react-app-rewired build", 16 | "test": "react-app-rewired test --env=jsdom", 17 | "eject": "react-scripts eject" 18 | }, 19 | "eslintConfig": { 20 | "extends": "react-app" 21 | }, 22 | "browserslist": { 23 | "production": [ 24 | ">0.2%", 25 | "not dead", 26 | "not op_mini all" 27 | ], 28 | "development": [ 29 | "last 1 chrome version", 30 | "last 1 firefox version", 31 | "last 1 safari version" 32 | ] 33 | }, 34 | "devDependencies": { 35 | "react-app-rewired": "^2.1.6" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /main/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "start": "npm run serve" 10 | }, 11 | "dependencies": { 12 | "core-js": "^3.6.5", 13 | "nprogress": "^0.2.0", 14 | "qiankun": "^2.0.22", 15 | "vue": "^2.6.11" 16 | }, 17 | "devDependencies": { 18 | "@vue/cli-plugin-babel": "~4.5.0", 19 | "@vue/cli-plugin-eslint": "~4.5.0", 20 | "@vue/cli-service": "~4.5.0", 21 | "@vue/eslint-config-standard": "^5.1.2", 22 | "babel-eslint": "^10.1.0", 23 | "eslint": "^6.7.2", 24 | "eslint-plugin-import": "^2.20.2", 25 | "eslint-plugin-node": "^11.1.0", 26 | "eslint-plugin-promise": "^4.2.1", 27 | "eslint-plugin-standard": "^4.0.0", 28 | "eslint-plugin-vue": "^6.2.2", 29 | "sass": "^1.26.10", 30 | "sass-loader": "^10.0.2", 31 | "vue-template-compiler": "^2.6.11" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /sub-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sub-vue", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "vue-cli-service lint", 9 | "start": "npm run serve" 10 | }, 11 | "dependencies": { 12 | "common": "file:../common", 13 | "core-js": "^3.6.5", 14 | "vue": "^2.6.11", 15 | "vue-router": "^3.2.0", 16 | "vuex": "^3.4.0" 17 | }, 18 | "devDependencies": { 19 | "@vue/cli-plugin-babel": "~4.5.0", 20 | "@vue/cli-plugin-eslint": "~4.5.0", 21 | "@vue/cli-plugin-router": "~4.5.0", 22 | "@vue/cli-plugin-vuex": "~4.5.0", 23 | "@vue/cli-service": "~4.5.0", 24 | "@vue/eslint-config-standard": "^5.1.2", 25 | "babel-eslint": "^10.1.0", 26 | "eslint": "^6.7.2", 27 | "eslint-plugin-import": "^2.20.2", 28 | "eslint-plugin-node": "^11.1.0", 29 | "eslint-plugin-promise": "^4.2.1", 30 | "eslint-plugin-standard": "^4.0.0", 31 | "eslint-plugin-vue": "^6.2.2", 32 | "vue-template-compiler": "^2.6.11" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sub-html/js/main.js: -------------------------------------------------------------------------------- 1 | const render = (options) => { 2 | // options是基座下发的参数,可以保存到全局的状态管理或其他地方,用于后面与基座进行通信 3 | 4 | // 可通过 options.getGlobalState() 获取基座下发的数据 5 | // options.setGlobalState({user: {name: ''}}) 改变全局的数据 6 | // options.onGlobalStateChange 监听全局数据的变化 7 | 8 | 9 | document.querySelector('#current-env').innerHTML = 'qiankun' 10 | const globalState = options.getGlobalState() 11 | 12 | // 展示基座下发的状态 13 | const node = document.createElement('div') 14 | node.innerHTML = `基座下发的globalState: ${JSON.stringify(globalState)}打开独立运行环境` 15 | 16 | document.querySelector('.container').appendChild(node) 17 | 18 | return Promise.resolve(); 19 | }; 20 | 21 | (global => { 22 | global['prehtml'] = { 23 | bootstrap: () => { 24 | console.log('purehtml bootstrap'); 25 | return Promise.resolve(); 26 | }, 27 | mount: (options) => { 28 | console.log('purehtml mount', options); 29 | return render(options); 30 | }, 31 | unmount: () => { 32 | console.log('purehtml unmount'); 33 | return Promise.resolve(); 34 | }, 35 | }; 36 | })(window); 37 | -------------------------------------------------------------------------------- /main/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import { registerMicroApps, start, setDefaultMountApp } from 'qiankun' 4 | import microApps from './micro-app' 5 | import 'nprogress/nprogress.css' 6 | 7 | Vue.config.productionTip = false 8 | 9 | const instance = new Vue({ 10 | render: h => h(App) 11 | }).$mount('#app') 12 | 13 | // 定义loader方法,loading改变时,将变量赋值给App.vue的data中的isLoading 14 | function loader (loading) { 15 | if (instance && instance.$children) { 16 | // instance.$children[0] 是App.vue,此时直接改动App.vue的isLoading 17 | instance.$children[0].isLoading = loading 18 | } 19 | } 20 | 21 | // 给子应用配置加上loader方法 22 | const apps = microApps.map(item => { 23 | return { 24 | ...item, 25 | loader 26 | } 27 | }) 28 | 29 | registerMicroApps(apps, { 30 | beforeLoad: app => { 31 | console.log('before load app.name====>>>>>', app.name) 32 | }, 33 | beforeMount: [ 34 | app => { 35 | console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name) 36 | } 37 | ], 38 | afterMount: [ 39 | app => { 40 | console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name) 41 | } 42 | ], 43 | afterUnmount: [ 44 | app => { 45 | console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name) 46 | } 47 | ] 48 | }) 49 | setDefaultMountApp('/sub-vue') 50 | start() 51 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qiankun-example", 3 | "version": "0.0.1", 4 | "description": "qiankun demo", 5 | "main": "index.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "npm-run-all": "^4.1.5" 9 | }, 10 | "scripts": { 11 | "install": "npm-run-all --serial install:*", 12 | "install:main": "cd main && npm i", 13 | "install:sub-vue": "cd sub-vue && npm i", 14 | "install:sub-react": "cd sub-react && npm i", 15 | "install:sub-html": "cd sub-html && npm i", 16 | "start": "npm-run-all --parallel start:*", 17 | "start:sub-react": "cd sub-react && npm start", 18 | "start:sub-vue": "cd sub-vue && npm start", 19 | "start:main": "cd main && npm start", 20 | "start:sub-html": "cd sub-html && npm start", 21 | "build": "npm-run-all build:* && bash ./scripts/bundle.sh", 22 | "build:sub-react": "cd sub-react && npm run build", 23 | "build:sub-vue": "cd sub-vue && npm run build", 24 | "build:sub-html": "cd sub-html && npm run build", 25 | "build:main": "cd main && npm run build", 26 | "test": "echo \"Error: no test specified\" && exit 1" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git@nemo-crypto.github.com:nemo-crypto/qiankun-example.git" 31 | }, 32 | "keywords": [ 33 | "qiankun", 34 | "qiankun-example", 35 | "demo" 36 | ], 37 | "author": "nemo-crypto", 38 | "license": "MIT" 39 | } 40 | -------------------------------------------------------------------------------- /sub-vue/src/main.js: -------------------------------------------------------------------------------- 1 | import './public-path' 2 | import Vue from 'vue' 3 | import App from './App.vue' 4 | import routes from './router' 5 | import { store as commonStore } from 'common' 6 | import store from './store' 7 | import VueRouter from 'vue-router' 8 | 9 | Vue.config.productionTip = false 10 | let instance = null 11 | 12 | function render (props = {}) { 13 | const { container, routerBase } = props 14 | const router = new VueRouter({ 15 | base: window.__POWERED_BY_QIANKUN__ ? routerBase : process.env.BASE_URL, 16 | mode: 'history', 17 | routes 18 | }) 19 | 20 | instance = new Vue({ 21 | router, 22 | store, 23 | render: (h) => h(App) 24 | }).$mount(container ? container.querySelector('#app') : '#app') 25 | } 26 | 27 | if (!window.__POWERED_BY_QIANKUN__) { 28 | // 这里是子应用独立运行的环境,实现子应用的登录逻辑 29 | 30 | // 独立运行时,也注册一个名为global的store module 31 | commonStore.globalRegister(store) 32 | // 模拟登录后,存储用户信息到global module 33 | const userInfo = { name: '我是独立运行时名字叫张三' } // 假设登录后取到的用户信息 34 | store.commit('global/setGlobalState', { user: userInfo }) 35 | 36 | render() 37 | } 38 | 39 | export async function bootstrap () { 40 | console.log('[vue] vue app bootstraped') 41 | } 42 | 43 | export async function mount (props) { 44 | console.log('[vue] props from main framework', props) 45 | 46 | commonStore.globalRegister(store, props) 47 | 48 | render(props) 49 | } 50 | 51 | export async function unmount () { 52 | instance.$destroy() 53 | instance.$el.innerHTML = '' 54 | instance = null 55 | } 56 | -------------------------------------------------------------------------------- /common/src/store/global-register.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * 4 | * @param {vuex实例} store 5 | * @param {qiankun下发的props} props 6 | */ 7 | function registerGlobalModule (store, props = {}) { 8 | 9 | if (!store || !store.hasModule) { 10 | return; 11 | } 12 | 13 | // 获取初始化的state 14 | const initState = props.getGlobalState && props.getGlobalState() || { 15 | menu: [], 16 | user: {} 17 | }; 18 | 19 | // 将父应用的数据存储到子应用中,命名空间固定为global 20 | if (!store.hasModule('global')) { 21 | const globalModule = { 22 | namespaced: true, 23 | state: initState, 24 | actions: { 25 | // 子应用改变state并通知父应用 26 | setGlobalState ({ commit }, payload) { 27 | commit('setGlobalState', payload); 28 | commit('emitGlobalState', payload); 29 | }, 30 | // 初始化,只用于mount时同步父应用的数据 31 | initGlobalState ({ commit }, payload) { 32 | commit('setGlobalState', payload); 33 | }, 34 | }, 35 | mutations: { 36 | setGlobalState (state, payload) { 37 | // eslint-disable-next-line 38 | state = Object.assign(state, payload); 39 | }, 40 | // 通知父应用 41 | emitGlobalState (state) { 42 | if (props.setGlobalState) { 43 | props.setGlobalState(state); 44 | } 45 | }, 46 | }, 47 | }; 48 | store.registerModule('global', globalModule); 49 | } else { 50 | // 每次mount时,都同步一次父应用数据 51 | store.dispatch('global/initGlobalState', initState); 52 | } 53 | }; 54 | 55 | export default registerGlobalModule; 56 | -------------------------------------------------------------------------------- /sub-react/src/index.js: -------------------------------------------------------------------------------- 1 | import './public-path' 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import './index.css'; 5 | import App from './App'; 6 | import * as serviceWorker from './serviceWorker'; 7 | 8 | 9 | 10 | 11 | 12 | function render() { 13 | ReactDOM.render( 14 | , 15 | document.getElementById('root') 16 | ); 17 | } 18 | 19 | if (!window.__POWERED_BY_QIANKUN__) { 20 | render(); 21 | } 22 | /** 23 | * bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。 24 | * 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。 25 | */ 26 | export async function bootstrap() { 27 | console.log('react app bootstraped'); 28 | } 29 | /** 30 | * 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法 31 | */ 32 | export async function mount(props) { 33 | console.log('基座下发的能力:', props); 34 | 35 | // 可通过 props.getGlobalState() 获取基座下发的数据 36 | 37 | // props.setGlobalState({user: {name: ''}}) 改变全局的数据 38 | 39 | // props.onGlobalStateChange 监听全局数据的变化 40 | render(); 41 | } 42 | /** 43 | * 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例 44 | */ 45 | export async function unmount() { 46 | ReactDOM.unmountComponentAtNode(document.getElementById('root')); 47 | } 48 | /** 49 | * 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效 50 | */ 51 | export async function update(props) { 52 | console.log('update props', props); 53 | } 54 | 55 | // If you want your app to work offline and load faster, you can change 56 | // unregister() to register() below. Note this comes with some pitfalls. 57 | // Learn more about service workers: https://bit.ly/CRA-PWA 58 | serviceWorker.unregister(); 59 | -------------------------------------------------------------------------------- /sub-react/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 | -------------------------------------------------------------------------------- /sub-vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 59 | 60 | 90 | -------------------------------------------------------------------------------- /sub-vue/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 43 | 44 | 45 | 61 | -------------------------------------------------------------------------------- /sub-react/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /sub-react/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `yarn start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `yarn test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `yarn build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `yarn eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | 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. 35 | 36 | 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. 37 | 38 | 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. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | 46 | ### Code Splitting 47 | 48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting 49 | 50 | ### Analyzing the Bundle Size 51 | 52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size 53 | 54 | ### Making a Progressive Web App 55 | 56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app 57 | 58 | ### Advanced Configuration 59 | 60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration 61 | 62 | ### Deployment 63 | 64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment 65 | 66 | ### `yarn build` fails to minify 67 | 68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify 69 | -------------------------------------------------------------------------------- /main/src/App.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 93 | 94 | 133 | -------------------------------------------------------------------------------- /sub-react/src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then(response => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then(registration => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready 134 | .then(registration => { 135 | registration.unregister(); 136 | }) 137 | .catch(error => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /React + qiankun -demo.md: -------------------------------------------------------------------------------- 1 | 2 | 1. **基座应用搭建** 3 | 4 | ```bash 5 | # 创建主 应用 6 | npx create-react-app main-app 7 | cd main-app 8 | 9 | # 安装qiankun 10 | npm install qiankun 11 | ``` 12 | 13 | ```typescript:src/App.tsx 14 | import { useState, useEffect } from 'react'; 15 | import { registerMicroApps, start } from 'qiankun'; 16 | 17 | function App() { 18 | useEffect(() => { 19 | // 注册子应用 20 | registerMicroApps([ 21 | { 22 | name: 'sub-react', // 子应用名称 23 | entry: '//localhost:3001', // 子应用入口 24 | container: '#subapp-container', // 子应用容器 25 | activeRule: '/sub-react', // 激活规则 26 | }, 27 | // 可以注册多个子应用 28 | ]); 29 | 30 | // 启动 qiankun 31 | start(); 32 | }, []); 33 | 34 | return ( 35 |
36 |
主应用
37 | {/* 子应用容器 */} 38 |
39 |
40 | ); 41 | } 42 | 43 | export default App; 44 | ``` 45 | 46 | 2. **子应用配置** 47 | 48 | ```bash 49 | # 创建子应用 50 | npx create-react-app sub-react 51 | cd sub-react 52 | 53 | # 安装必要依赖 54 | npm install @rescripts/cli 55 | ``` 56 | 57 | ```javascript:config-overrides.js 58 | const { name } = require('./package.json'); 59 | 60 | module.exports = { 61 | webpack: (config) => { 62 | config.output.library = `${name}-[name]`; 63 | config.output.libraryTarget = 'umd'; 64 | config.output.chunkLoadingGlobal = `webpackJsonp_${name}`; 65 | config.output.globalObject = 'window'; 66 | 67 | return config; 68 | }, 69 | devServer: (_) => { 70 | const config = _; 71 | config.headers = { 72 | 'Access-Control-Allow-Origin': '*', 73 | }; 74 | config.historyApiFallback = true; 75 | config.hot = false; 76 | config.liveReload = false; 77 | return config; 78 | }, 79 | }; 80 | ``` 81 | 82 | 3. **子应用入口文件** 83 | 84 | ```typescript:src/index.tsx 85 | import './public-path'; 86 | import React from 'react'; 87 | import ReactDOM from 'react-dom'; 88 | import App from './App'; 89 | 90 | let root: any = null; 91 | 92 | function render(props: any) { 93 | const { container } = props; 94 | const targetContainer = container ? container.querySelector('#root') : document.querySelector('#root'); 95 | 96 | root = ReactDOM.createRoot(targetContainer); 97 | root.render(); 98 | } 99 | 100 | // 独立运行时 101 | if (!window.__POWERED_BY_QIANKUN__) { 102 | render({}); 103 | } 104 | 105 | // 导出生命周期钩子 106 | export async function bootstrap() { 107 | console.log('[react] sub-react bootstraped'); 108 | } 109 | 110 | export async function mount(props: any) { 111 | console.log('[react] props from main framework', props); 112 | render(props); 113 | } 114 | 115 | export async function unmount() { 116 | root.unmount(); 117 | } 118 | ``` 119 | 120 | 4. **公共路径配置** 121 | 122 | ```typescript:src/public-path.ts 123 | if (window.__POWERED_BY_QIANKUN__) { 124 | __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; 125 | } 126 | ``` 127 | 128 | 5. **路由配置** 129 | 130 | ```typescript:src/router/index.tsx 131 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 132 | 133 | export default function AppRouter() { 134 | return ( 135 | 136 | 137 | } /> 138 | } /> 139 | 140 | 141 | ); 142 | } 143 | ``` 144 | 6. **通信机制** 145 | 146 | ```typescript:src/utils/event.ts 147 | // 主应用 148 | import { initGlobalState } from 'qiankun'; 149 | 150 | const initialState = { 151 | user: { 152 | name: 'admin', 153 | }, 154 | }; 155 | 156 | const actions = initGlobalState(initialState); 157 | 158 | actions.onGlobalStateChange((state, prev) => { 159 | console.log('主应用: 变更前', prev); 160 | console.log('主应用: 变更后', state); 161 | }); 162 | 163 | export default actions; 164 | ``` 165 | 166 | ```typescript:src/App.tsx 167 | // 子应用 168 | function App(props: any) { 169 | const { onGlobalStateChange, setGlobalState } = props; 170 | 171 | useEffect(() => { 172 | onGlobalStateChange?.((state: any, prev: any) => { 173 | console.log('子应用: 变更前', prev); 174 | console.log('子应用: 变更后', state); 175 | }); 176 | }, []); 177 | 178 | return ( 179 |
180 | 183 |
184 | ); 185 | } 186 | ``` 187 | 188 | 7. **样式隔离** 189 | 190 | ```javascript:src/App.tsx 191 | // 主应用配置 192 | registerMicroApps([ 193 | { 194 | name: 'sub-react', 195 | entry: '//localhost:3001', 196 | container: '#subapp-container', 197 | activeRule: '/sub-react', 198 | props: { 199 | sandbox: { 200 | strictStyleIsolation: true, // 严格样式隔离 201 | experimentalStyleIsolation: true, // 实验性样式隔离 202 | }, 203 | }, 204 | }, 205 | ]); 206 | ``` 207 | 208 | 8. **性能优化** 209 | 210 | ```typescript:src/App.tsx 211 | // 预加载配置 212 | import { registerMicroApps, start, prefetchApps } from 'qiankun'; 213 | 214 | // 预加载配置 215 | prefetchApps([ 216 | { name: 'sub-react', entry: '//localhost:3001' }, 217 | ]); 218 | 219 | // 或者在注册时配置 220 | registerMicroApps([ 221 | { 222 | name: 'sub-react', 223 | entry: '//localhost:3001', 224 | container: '#subapp-container', 225 | activeRule: '/sub-react', 226 | props: { 227 | prefetch: true, // 开启预加载 228 | }, 229 | }, 230 | ]); 231 | ``` 232 | 233 | 9. **错误处理** 234 | 235 | ```typescript:src/App.tsx 236 | // 主应用错误处理 237 | registerMicroApps([ 238 | { 239 | name: 'sub-react', 240 | entry: '//localhost:3001', 241 | container: '#subapp-container', 242 | activeRule: '/sub-react', 243 | loader: (loading) => { 244 | console.log('loading', loading); 245 | }, 246 | props: { 247 | errorBoundary: (error) => { 248 | console.log('error', error); 249 | return
子应用加载失败
; 250 | }, 251 | }, 252 | }, 253 | ]); 254 | ``` 255 | 256 | 10. **部署配置** 257 | 258 | ```nginx:nginx.conf 259 | server { 260 | listen 80; 261 | server_name localhost; 262 | 263 | # 主应用 264 | location / { 265 | root /usr/share/nginx/html/main; 266 | index index.html index.htm; 267 | try_files $uri $uri/ /index.html; 268 | } 269 | 270 | # 子应用 271 | location /sub-react { 272 | root /usr/share/nginx/html/sub-react; 273 | index index.html index.htm; 274 | try_files $uri $uri/ /sub-react/index.html; 275 | } 276 | } 277 | ``` 278 | 279 | 注意事项: 280 | 1. **版本兼容** 281 | ```plaintext 282 | - 确保各个应用的依赖版本兼容 283 | - 注意React版本一致性 284 | - 检查qiankun版本更新 285 | ``` 286 | 287 | 288 | 2. **开发规范** 289 | ``` 290 | - 遵循微前端开发规范 291 | - 做好应用隔离 292 | - 规范通信机制 293 | ``` 294 | 295 | 3. **性能优化** 296 | ```plaintext 297 | - 合理使用预加载 298 | - 优化加载时机 299 | - 控制应用大小 300 | ``` 301 | 302 | 4. **调试技巧** 303 | ```plaintext 304 | - 使用开发者工具 305 | - 查看控制台输出 306 | - 监控应用状态 307 | ``` 308 | 309 | 5. **安全考虑** 310 | ```plaintext 311 | - 跨域配置 312 | - 权限控制 313 | - 数据安全 314 | ``` 315 | -------------------------------------------------------------------------------- /qiankun、micro-app、Garfish、wujie.md: -------------------------------------------------------------------------------- 1 | ### 一、qiankun (蚂蚁金服) 2 | ``` 3 | 4 | 1. 主应用配置 5 | 6 | ```javascript:src/main-app/src/main.js 7 | import { registerMicroApps, start } from 'qiankun'; 8 | 9 | // 注册子应用 10 | registerMicroApps([ 11 | { 12 | name: 'vue-app', // 单个子应用名称 13 | entry: '//localhost:8081', // 子应用入口 14 | container: '#vue-container', // 子应用容器 15 | activeRule: '/vue-app', // 激活规则 16 | }, 17 | { 18 | name: 'react-app', 19 | entry: '//localhost:8082', 20 | container: '#react-container', 21 | activeRule: '/react-app', 22 | } 23 | ]); 24 | 25 | // 启动 qiankun 26 | start({ 27 | prefetch: true, // 预加载 28 | sandbox: { 29 | strictStyleIsolation: true, // 严格的样式隔离 30 | } 31 | }); 32 | ``` 33 | 34 | 35 | 2. 主应用路由配置 36 | 37 | ```javascript:src/main-app/src/router/index.js 38 | import { createRouter, createWebHistory } from 'vue-router'; 39 | 40 | const router = createRouter({ 41 | history: createWebHistory(), 42 | routes: [ 43 | { 44 | path: '/', 45 | component: () => import('../views/Home.vue'), 46 | }, 47 | { 48 | path: '/vue-app/:pathMatch(.*)*', 49 | component: () => import('../views/MicroApp.vue'), 50 | }, 51 | { 52 | path: '/react-app/:pathMatch(.*)*', 53 | component: () => import('../views/MicroApp.vue'), 54 | } 55 | ] 56 | }); 57 | 58 | export default router; 59 | ``` 60 | 61 | 3. 主要应用容器组件 62 | 63 | ```vue:src/main-app/src/views/MicroApp.vue 64 | 70 | 71 | 76 | ``` 77 | 78 | 4. Vue项目中子应用配置 79 | 80 | ```javascript:src/vue-app/src/main.js 81 | import { createApp } from 'vue'; 82 | import App from './App.vue'; 83 | import router from './router'; 84 | import store from './store'; 85 | 86 | let instance = null; 87 | 88 | // 渲染函数 89 | function render(props = {}) { 90 | const { container } = props; 91 | instance = createApp(App); 92 | 93 | instance.use(router).use(store); 94 | instance.mount(container ? container.querySelector('#app') : '#app'); 95 | } 96 | 97 | // qiankun生命周期钩子函数 98 | export async function bootstrap() { 99 | console.log('vue app bootstraped'); 100 | } 101 | 102 | export async function mount(props) { 103 | console.log('vue app mounted', props); 104 | render(props); 105 | } 106 | 107 | export async function unmount() { 108 | console.log('vue app unmounted'); 109 | instance.unmount(); 110 | instance = null; 111 | } 112 | 113 | // 独立运行时 114 | if (!window.__POWERED_BY_QIANKUN__) { 115 | render(); 116 | } 117 | ``` 118 | 119 | 5. Vue项目子应用配置文件 120 | 121 | ```javascript:src/vue-app/vue.config.js 122 | const { defineConfig } = require('@vue/cli-service'); 123 | const packageName = require('./package.json').name; 124 | 125 | module.exports = defineConfig({ 126 | devServer: { 127 | port: 8081, // 启动端口8081 128 | headers: { 129 | 'Access-Control-Allow-Origin': '*', 130 | }, 131 | }, 132 | configureWebpack: { 133 | output: { 134 | library: `${packageName}-[name]`, 135 | libraryTarget: 'umd', 136 | chunkLoadingGlobal: `webpackJsonp_${packageName}`, 137 | }, 138 | }, 139 | }); 140 | ``` 141 | 142 | 6. React项目子应用配置 143 | 144 | ```javascript:src/react-app/src/index.js 145 | import React from 'react'; 146 | import ReactDOM from 'react-dom'; 147 | import App from './App'; 148 | 149 | function render(props = {}) { 150 | const { container } = props; 151 | ReactDOM.render( 152 | , 153 | container ? container.querySelector('#root') : document.querySelector('#root') 154 | ); 155 | } 156 | 157 | export async function bootstrap() { 158 | console.log('react app bootstraped'); 159 | } 160 | 161 | export async function mount(props) { 162 | console.log('react app mounted', props); 163 | render(props); 164 | } 165 | 166 | export async function unmount(props) { 167 | const { container } = props; 168 | ReactDOM.unmountComponentAtNode( 169 | container ? container.querySelector('#root') : document.querySelector('#root') 170 | ); 171 | } 172 | 173 | if (!window.__POWERED_BY_QIANKUN__) { 174 | render(); 175 | } 176 | ``` 177 | 178 | 7. React项目子应用配置文件 179 | 180 | ```javascript:src/react-app/config-overrides.js 181 | const { name } = require('./package.json'); 182 | 183 | module.exports = { 184 | webpack: (config) => { 185 | config.output.library = `${name}-[name]`; 186 | config.output.libraryTarget = 'umd'; 187 | config.output.chunkLoadingGlobal = `webpackJsonp_${name}`; 188 | config.output.globalObject = 'window'; 189 | 190 | return config; 191 | }, 192 | devServer: (configFunction) => { 193 | return function (proxy, allowedHost) { 194 | const config = configFunction(proxy, allowedHost); 195 | config.headers = { 196 | 'Access-Control-Allow-Origin': '*', 197 | }; 198 | return config; 199 | }; 200 | }, 201 | }; 202 | ``` 203 | 204 | 8. 公共依赖处理 205 | 206 | ```javascript:src/main-app/src/shared/index.js 207 | import store from '../store'; 208 | 209 | const shared = { 210 | store, 211 | utils: { 212 | // 公共工具函数及逻辑 213 | }, 214 | constants: { 215 | // 公共常量定义 216 | } 217 | }; 218 | 219 | export default shared; 220 | ``` 221 | 222 | 9. 通信机制实现 223 | 224 | ```javascript:src/main-app/src/utils/eventBus.js 225 | class EventBus { 226 | constructor() { 227 | this.events = {}; 228 | } 229 | 230 | on(event, callback) { 231 | if (!this.events[event]) { 232 | this.events[event] = []; 233 | } 234 | this.events[event].push(callback); 235 | } 236 | 237 | emit(event, data) { 238 | if (this.events[event]) { 239 | this.events[event].forEach(callback => callback(data)); 240 | } 241 | } 242 | 243 | off(event, callback) { 244 | if (this.events[event]) { 245 | this.events[event] = this.events[event].filter(cb => cb !== callback); 246 | } 247 | } 248 | } 249 | 250 | export default new EventBus(); 251 | ``` 252 | 253 | 10. 状态管理 254 | 255 | ```javascript:src/main-app/src/store/index.js 256 | import { createStore } from 'vuex'; 257 | 258 | export default createStore({ 259 | state: { 260 | globalData: {} 261 | }, 262 | mutations: { 263 | SET_GLOBAL_DATA(state, data) { 264 | state.globalData = data; 265 | } 266 | }, 267 | actions: { 268 | updateGlobalData({ commit }, data) { 269 | commit('SET_GLOBAL_DATA', data); 270 | } 271 | } 272 | }); 273 | ``` 274 | 275 | 11. 样式隔离 276 | 277 | ```javascript:src/main-app/src/utils/styleIsolation.js 278 | export function addStyleScope(styleStr, scopeName) { 279 | return styleStr.replace(/([^}]*){([^}]*)}/g, (match, selector, style) => { 280 | const newSelector = selector 281 | .split(',') 282 | .map(s => `${s.trim()}[data-qiankun="${scopeName}"]`) 283 | .join(','); 284 | return `${newSelector}{${style}}`; 285 | }); 286 | } 287 | ``` 288 | 289 | 12. 错误处理 290 | 291 | ```javascript:src/main-app/src/utils/errorHandler.js 292 | export function setupErrorHandler() { 293 | window.addEventListener('error', (event) => { 294 | console.error('Micro App Error:', event); 295 | // 错误上报逻辑 296 | }); 297 | 298 | window.addEventListener('unhandledrejection', (event) => { 299 | console.error('Unhandled Promise Rejection:', event); 300 | // 错误上报逻辑 301 | }); 302 | } 303 | ``` 304 | 305 | 13. 启动命令 306 | 307 | ```json:package.json 308 | { 309 | "scripts": { 310 | "start": "npm-run-all --parallel start:*", 311 | "start:main": "cd main-app && npm start", 312 | "start:vue": "cd vue-app && npm start", 313 | "start:react": "cd react-app && npm start", 314 | "build": "npm-run-all --parallel build:*", 315 | "build:main": "cd main-app && npm run build", 316 | "build:vue": "cd vue-app && npm run build", 317 | "build:react": "cd react-app && npm run build" 318 | } 319 | } 320 | ``` 321 | 322 | 特点: 323 | - 基于single-spa 324 | - 完善的沙箱机制 325 | - 样式隔离 326 | - 资源预加载 327 | - HTML Entry 方式 328 | 常见问题解决: 329 | - 跨域问题:确保微应用的 devServer 配置了正确的 CORS 头 330 | - 样式隔离:可以使用 experimentalStyleIsolation 配置 331 | - JS 沙箱:qiankun 默认启用了JS沙箱,可以通过配置调整 332 | start({ 333 | sandbox: { 334 | strictStyleIsolation: true, // 严格的样式隔离 335 | experimentalStyleIsolation: true, // 实验性的样式隔离 336 | } 337 | }); 338 | 339 | 确保微应用的打包配置正确,特别是 output.library 和 output.libraryTarget 340 | 微应用需要在自己的 HTML 入口文件增加 entry 配置 341 | 主应用和微应用的路由需要协调,避免冲突 342 | 建议使用 hash 或 history 路由模式 343 | 注意跨域问题的处理 344 | 345 | ### 二、micro-app (京东) 346 | ``` 347 | 348 | 352 | 353 | 354 | ``` 355 | 特点: 356 | - 使用Web Components 357 | - 零依赖 358 | - 简单易用 359 | - 不需要改造子应用 360 | - 天然隔离 361 | 362 | ### 三、wujie (腾讯) 363 | ``` 364 | // 主应用 365 | import { setupApp } from 'wujie'; 366 | setupApp({ 367 | name: 'app1', 368 | url: 'http://localhost:8080', 369 | exec: true, 370 | props: { data: 'shared' } 371 | }); 372 | // 组件使用 373 | 381 | ``` 382 | 特点: 383 | - 基于 Web Components 384 | - 使用 iframe 隔离 385 | - 预加载能力 386 | - 支持多个实例 387 | - 性能优秀 388 | ### 四、Garfish(字节跳动) 389 | ``` 390 | // 主应用 391 | import Garfish from 'garfish'; 392 | Garfish.run({ 393 | domGetter: '#container', 394 | apps: [{ 395 | name: 'app1', 396 | entry: 'http://localhost:8080', 397 | activeWhen: '/app1' 398 | }] 399 | }); 400 | // 子应用 401 | export function provider() { 402 | return { 403 | render() {}, 404 | destroy() {} 405 | }; 406 | } 407 | ``` 408 | 特点: 409 | - 轻量级 410 | - 性能好 411 | - 灵活的插件系统 412 | - 完善的沙箱机制 413 | 五、各框架对比 414 | 1. 隔离方案: 415 | - qiankun: Proxy 快照 416 | - micro-app: Web Components 417 | - wujie: iframe + Web Components 418 | - Garfish: VM 上下文 419 | 420 | 2. 通信方式: 421 | - qiankun: Props + 全局状态 422 | - micro-app: CustomEvent 423 | - wujie: Props + 通信对象 424 | - Garfish: Props + Channel 425 | 426 | 3. 子应用加载: 427 | - qiankun: HTML Entry 428 | - micro-app: HTML Entry 429 | - wujie: iframe fetch 430 | - Garfish: HTML Entry 431 | 432 | 4. 适用场景: 433 | - qiankun: 大型复杂应用 434 | - micro-app: 简单集成场景 435 | - wujie: 高性能要求场景 436 | - Garfish: 轻量级应用 437 | 438 | 6. 常见问题解决方案 439 | ``` 440 | // 1. 样式隔离 441 | // qiankun 442 | { 443 | sandbox: { 444 | experimentalStyleIsolation: true 445 | } 446 | } 447 | 448 | // micro-app 449 | 450 | 451 | // 2. JS 隔离 452 | // qiankun 453 | { 454 | sandbox: { 455 | strictIsolation: true 456 | } 457 | } 458 | 459 | // 3. 路由同步 460 | // 主应用 461 | window.history.pushState = new Proxy(window.history.pushState, { 462 | apply(target, thisBinding, args) { 463 | // 通知子应用 464 | return target.apply(thisBinding, args); 465 | } 466 | }); 467 | ``` 468 | - 最佳实践 469 | ``` 470 | // 1. 应用通信 471 | // 统一状态管理 472 | import { initGlobalState } from 'qiankun'; 473 | 474 | const actions = initGlobalState({ 475 | user: 'admin', 476 | theme: 'dark' 477 | }); 478 | 479 | // 2. 性能优化 480 | // 预加载 481 | { 482 | prefetch: true, 483 | prefetchApps: ['app1', 'app2'] 484 | } 485 | 486 | // 3. 错误处理 487 | { 488 | errorBoundary: (err) => { 489 | console.error(err); 490 | return

应用加载失败

; 491 | } 492 | } 493 | ``` 494 | 495 | ### 六、选择建议: 496 | 1. 如果是新项目,推荐使用 micro-app 或 wujie 497 | - 如果是复杂项目,推荐使用 qiankun 498 | - 如果对性能要求高,推荐使用 wujie 499 | - 如果需要轻量级方案,推荐使用 Garfish 500 | 注意事项: 501 | - 合理规划应用拆分粒度 502 | - 注意应用间通信的性能影响 503 | - 做好错误边界处理 504 | - 考虑应用预加载策略 505 | - 注意样式和 JS 隔离的完整性 506 | --------------------------------------------------------------------------------