├── src ├── routers │ └── index.ts ├── html-entry.js └── index.tsx ├── .gitignore ├── .vscode └── settings.json ├── child-app0 ├── src │ ├── list.tsx │ ├── item.tsx │ ├── entry.tsx │ └── index.tsx ├── dist │ ├── index.html │ ├── main.js.LICENSE.txt │ └── main.js ├── .babelrc ├── public │ └── index.html ├── package.json └── webpack.config.js ├── child-app1 ├── src │ ├── list.tsx │ ├── item.tsx │ ├── entry.tsx │ └── index.tsx ├── public │ └── index.html ├── .babelrc ├── package.json └── webpack.config.js ├── child-app.config.js ├── dist └── index.html ├── public └── index.html ├── .babelrc ├── webpack.config.js ├── package.json └── README.md /src/routers/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "git.ignoreLimitWarning": true 3 | } -------------------------------------------------------------------------------- /child-app0/src/list.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const List = () => { 4 | return
List
5 | } 6 | 7 | export default List; 8 | 9 | -------------------------------------------------------------------------------- /child-app1/src/list.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const List = () => { 4 | return
List
5 | } 6 | 7 | export default List; 8 | 9 | -------------------------------------------------------------------------------- /child-app0/dist/index.html: -------------------------------------------------------------------------------- 1 | Document
-------------------------------------------------------------------------------- /child-app0/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-react", 4 | "@babel/preset-typescript" 5 | ], 6 | "plugins": [ 7 | [ 8 | "@babel/plugin-transform-runtime", 9 | { 10 | "regenerator": true, 11 | "corejs": 3 12 | } 13 | ] 14 | ] 15 | } -------------------------------------------------------------------------------- /child-app1/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 |
11 | 12 | -------------------------------------------------------------------------------- /child-app.config.js: -------------------------------------------------------------------------------- 1 | export const apps = [ 2 | { 3 | name: 'app0', 4 | entry: '//localhost:7000', 5 | container: '#micro-app', 6 | activeRule: '/app0', 7 | }, 8 | { 9 | name: 'app1', 10 | entry: '//localhost:3000', 11 | container: '#micro-app', 12 | activeRule: '/app1', 13 | }, 14 | ] -------------------------------------------------------------------------------- /src/html-entry.js: -------------------------------------------------------------------------------- 1 | 2 | import importHTML from 'import-html-entry'; 3 | 4 | importHTML('//localhost:7700') 5 | .then(res => { 6 | console.log(res.template); 7 | 8 | res.execScripts().then(exports => { 9 | const mount = exports; 10 | console.log(mount, 'exports==='); 11 | // console.log(exports, 'exports==='); 12 | }) 13 | }); -------------------------------------------------------------------------------- /child-app1/src/item.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | 3 | const Item = () => { 4 | const [count, setCount] = useState(0); 5 | 6 | function handleClick() { 7 | setCount(count + 1); 8 | } 9 | 10 | return ( 11 | <> 12 |
click me to amend state in app0-item
13 |
Item State: {count}
14 | 15 | ) 16 | } 17 | 18 | export default Item; 19 | 20 | -------------------------------------------------------------------------------- /child-app0/src/item.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from 'react'; 2 | 3 | const Item = () => { 4 | const [count, setCount] = useState(0); 5 | 6 | function handleClick() { 7 | setCount(count + 1); 8 | } 9 | 10 | return ( 11 | <> 12 |
click me to amend state in app0-item
13 |
Item State: {count}
14 | 15 | ) 16 | } 17 | 18 | export default Item; 19 | 20 | -------------------------------------------------------------------------------- /dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /child-app0/src/entry.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useHistory} from 'react-router-dom'; 3 | 4 | const List = () => { 5 | const history = useHistory(); 6 | 7 | function handleClick(e: React.MouseEvent) { 8 | const targetRoute = e.currentTarget.getAttribute('data-target-route'); 9 | history.push(targetRoute); 10 | } 11 | 12 | return ( 13 | <> 14 |
点我切换到app0的list路由
15 |
点我切换到app0的item路由
16 | 17 | ) 18 | } 19 | 20 | export default List; -------------------------------------------------------------------------------- /child-app1/src/entry.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {useHistory} from 'react-router-dom'; 3 | 4 | const List = () => { 5 | const history = useHistory(); 6 | 7 | function handleClick(e: React.MouseEvent) { 8 | const targetRoute = e.currentTarget.getAttribute('data-target-route'); 9 | history.push(targetRoute); 10 | } 11 | 12 | return ( 13 | <> 14 |
点我切换到app1的list路由
15 |
点我切换到app1的item路由
16 | 17 | ) 18 | } 19 | 20 | export default List; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false, // 对ES6的模块文件不做转化,以便使用tree shaking、sideEffects等 7 | "useBuiltIns": "entry", // browserslist环境不支持的所有垫片都导入 8 | // https://babeljs.io/docs/en/babel-preset-env#usebuiltins 9 | // https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md 10 | "corejs": { 11 | "version": 3, // 使用core-js@3 12 | "proposals": true 13 | } 14 | } 15 | ], 16 | "@babel/preset-react", 17 | "@babel/preset-typescript" 18 | ], 19 | "plugins": [] 20 | } -------------------------------------------------------------------------------- /child-app1/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "modules": false, // 对ES6的模块文件不做转化,以便使用tree shaking、sideEffects等 7 | "useBuiltIns": "entry", // browserslist环境不支持的所有垫片都导入 8 | // https://babeljs.io/docs/en/babel-preset-env#usebuiltins 9 | // https://github.com/zloirock/core-js/blob/master/docs/2019-03-19-core-js-3-babel-and-a-look-into-the-future.md 10 | "corejs": { 11 | "version": 3, // 使用core-js@3 12 | "proposals": true 13 | } 14 | } 15 | ], 16 | "@babel/preset-react", 17 | "@babel/preset-typescript" 18 | ], 19 | "plugins": [] 20 | } -------------------------------------------------------------------------------- /child-app0/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 |
14 | 17 | 18 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | entry: "./src/index", 7 | // entry: "./src/html-entry", 8 | mode: "development", 9 | devServer: { 10 | // contentBase: path.join(__dirname, "app0"), 11 | port: 3001, 12 | historyApiFallback: true, 13 | }, 14 | output: { 15 | publicPath: "/", 16 | }, 17 | resolve: { 18 | extensions: ['.ts', '.tsx', '.js', '.jsx'], 19 | }, 20 | module: { 21 | rules: [ 22 | { 23 | test: /\.(ts|tsx)}?$/, 24 | loader: "babel-loader", 25 | exclude: /node_modules/, 26 | }, 27 | ], 28 | }, 29 | plugins: [ 30 | new HtmlWebpackPlugin({ 31 | template: "./public/index.html", 32 | }), 33 | // new BundleAnalyzerPlugin() 34 | ], 35 | externals: { 36 | 'react': 'React', 37 | 'react-dom': 'ReactDOM', 38 | 'react-router-dom': 'ReactRouterDOM' 39 | } 40 | }; -------------------------------------------------------------------------------- /child-app1/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "child-app0", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve", 8 | "build": "webpack --watch" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@babel/runtime-corejs3": "^7.13.17", 15 | "qiankun": "^2.4.0", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2", 18 | "react-router-cache-route": "^1.11.0", 19 | "react-router-dom": "^5.2.0" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.13.16", 23 | "@babel/plugin-transform-runtime": "^7.13.15", 24 | "@babel/preset-env": "^7.13.15", 25 | "@babel/preset-react": "^7.13.13", 26 | "@babel/preset-typescript": "^7.13.0", 27 | "@types/node": "^14.14.41", 28 | "@types/react-dom": "^17.0.3", 29 | "@types/react-router-dom": "^5.1.7", 30 | "babel-loader": "^8.2.2", 31 | "html-webpack-plugin": "^5.3.1", 32 | "webpack": "^5.35.1", 33 | "webpack-cli": "^4.6.0", 34 | "webpack-dev-server": "^3.11.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /child-app1/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {BrowserRouter, Route} from 'react-router-dom'; 4 | import CacheRoute, {CacheSwitch} from 'react-router-cache-route'; 5 | 6 | import List from './list'; 7 | import Item from './item'; 8 | import Entry from './entry'; 9 | 10 | const App = () => ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ); 21 | 22 | interface IQiankunProps { 23 | container: HTMLElement; 24 | } 25 | 26 | // ReactDOM.render(, document.querySelector('#root')); 27 | 28 | export async function bootstrap() { 29 | console.log('react app bootstraped'); 30 | } 31 | 32 | export async function mount(props: IQiankunProps) { 33 | ReactDOM.render(, props.container.querySelector('#root')); 34 | } 35 | 36 | export async function unmount(params: IQiankunProps) { 37 | // do nothing 38 | } 39 | 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "micro-frontend-playground", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack-cli serve", 8 | "build": "webpack --mode development" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@babel/runtime-corejs3": "^7.13.17", 15 | "qiankun": "^2.4.0", 16 | "react": "^17.0.2", 17 | "react-dom": "^17.0.2", 18 | "react-router-cache-route": "^1.11.0", 19 | "react-router-dom": "^5.2.0" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.13.16", 23 | "@babel/plugin-transform-runtime": "^7.13.15", 24 | "@babel/preset-env": "^7.13.15", 25 | "@babel/preset-react": "^7.13.13", 26 | "@babel/preset-typescript": "^7.13.0", 27 | "@types/node": "^14.14.41", 28 | "@types/react-dom": "^17.0.3", 29 | "@types/react-router-dom": "^5.1.7", 30 | "babel-loader": "^8.2.2", 31 | "html-webpack-plugin": "^5.3.1", 32 | "webpack": "^5.35.0", 33 | "webpack-cli": "^4.6.0", 34 | "webpack-dev-server": "^3.11.2" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /child-app0/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "child-app0", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "webpack serve", 8 | "build": "webpack --watch" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "@babel/runtime": "^7.13.17", 15 | "@babel/runtime-corejs3": "^7.13.17", 16 | "qiankun": "^2.4.0", 17 | "react": "^17.0.2", 18 | "react-dom": "^17.0.2", 19 | "react-router-cache-route": "^1.11.0", 20 | "react-router-dom": "^5.2.0" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.13.16", 24 | "@babel/plugin-transform-runtime": "^7.13.15", 25 | "@babel/preset-env": "^7.13.15", 26 | "@babel/preset-react": "^7.13.13", 27 | "@babel/preset-typescript": "^7.13.0", 28 | "@types/node": "^14.14.41", 29 | "@types/react-dom": "^17.0.3", 30 | "@types/react-router-dom": "^5.1.7", 31 | "babel-loader": "^8.2.2", 32 | "html-webpack-plugin": "^5.3.1", 33 | "webpack": "^5.35.1", 34 | "webpack-cli": "^4.6.0", 35 | "webpack-dev-server": "^3.11.2" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /child-app0/dist/main.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /** @license React v0.20.2 8 | * scheduler.production.min.js 9 | * 10 | * Copyright (c) Facebook, Inc. and its affiliates. 11 | * 12 | * This source code is licensed under the MIT license found in the 13 | * LICENSE file in the root directory of this source tree. 14 | */ 15 | 16 | /** @license React v16.13.1 17 | * react-is.production.min.js 18 | * 19 | * Copyright (c) Facebook, Inc. and its affiliates. 20 | * 21 | * This source code is licensed under the MIT license found in the 22 | * LICENSE file in the root directory of this source tree. 23 | */ 24 | 25 | /** @license React v17.0.2 26 | * react-dom.production.min.js 27 | * 28 | * Copyright (c) Facebook, Inc. and its affiliates. 29 | * 30 | * This source code is licensed under the MIT license found in the 31 | * LICENSE file in the root directory of this source tree. 32 | */ 33 | 34 | /** @license React v17.0.2 35 | * react.production.min.js 36 | * 37 | * Copyright (c) Facebook, Inc. and its affiliates. 38 | * 39 | * This source code is licensed under the MIT license found in the 40 | * LICENSE file in the root directory of this source tree. 41 | */ 42 | -------------------------------------------------------------------------------- /child-app0/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | entry: "./src/index", 7 | mode: "development", 8 | devServer: { 9 | contentBase: path.join(__dirname, "dist"), 10 | port: 7700, 11 | historyApiFallback: true, 12 | injectClient: false, 13 | headers: { 14 | 'Access-Control-Allow-Origin': '*', 15 | }, 16 | }, 17 | output: { 18 | publicPath: "/", 19 | library: 'app0', 20 | libraryTarget: 'umd', 21 | chunkLoadingGlobal: 'app0GlobalFunc', 22 | }, 23 | resolve: { 24 | extensions: ['.ts', '.tsx', '.js', '.jsx'], 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(ts|tsx)}?$/, 30 | loader: "babel-loader", 31 | exclude: /node_modules/, 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new HtmlWebpackPlugin({ 37 | template: "./public/index.html", 38 | }), 39 | // new BundleAnalyzerPlugin() 40 | ], 41 | externals: { 42 | 'react': 'React', 43 | 'react-dom': 'ReactDOM', 44 | 'react-router-dom': 'ReactRouterDOM' 45 | } 46 | }; -------------------------------------------------------------------------------- /child-app1/webpack.config.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | // const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 3 | const path = require("path"); 4 | 5 | module.exports = { 6 | entry: "./src/index", 7 | mode: "development", 8 | devServer: { 9 | contentBase: path.join(__dirname, "dist"), 10 | port: 7701, 11 | historyApiFallback: true, 12 | injectClient: false, 13 | headers: { 14 | 'Access-Control-Allow-Origin': '*', 15 | }, 16 | }, 17 | output: { 18 | publicPath: "auto", 19 | library: 'app1', 20 | libraryTarget: 'umd', 21 | chunkLoadingGlobal: 'app1GlobalFunc', 22 | }, 23 | resolve: { 24 | extensions: ['.ts', '.tsx', '.js', '.jsx'], 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(ts|tsx)}?$/, 30 | loader: "babel-loader", 31 | exclude: /node_modules/, 32 | }, 33 | ], 34 | }, 35 | plugins: [ 36 | new HtmlWebpackPlugin({ 37 | template: "./public/index.html", 38 | }), 39 | // new BundleAnalyzerPlugin() 40 | ], 41 | externals: { 42 | 'react': 'React', 43 | 'react-dom': 'ReactDOM', 44 | 'react-router-dom': 'ReactRouterDOM' 45 | } 46 | }; -------------------------------------------------------------------------------- /child-app0/src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useEffect} from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {BrowserRouter, useLocation, useHistory} from 'react-router-dom'; 4 | import CacheRoute, {CacheSwitch} from 'react-router-cache-route'; 5 | 6 | import List from './list'; 7 | import Item from './item'; 8 | import Entry from './entry'; 9 | 10 | const App = () => { 11 | // console.log('app render'); 12 | 13 | const {pathname} = useLocation(); 14 | const history = useHistory(); 15 | 16 | useEffect(() => { 17 | if (pathname === '/') { 18 | history.push('/item'); 19 | } 20 | }, [pathname]); 21 | 22 | return ( 23 | <> 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | 33 | const App1 = () => ( 34 |
App1
35 | ) 36 | 37 | interface IQiankunProps { 38 | container: HTMLElement; 39 | } 40 | 41 | // ReactDOM.render(, document.querySelector('#root')); 42 | 43 | export async function bootstrap() { 44 | console.log('react app bootstraped'); 45 | } 46 | 47 | export async function mount(props: IQiankunProps) { 48 | // console.log('app0 mount', props.container); 49 | ReactDOM.render( 50 | 51 | 52 | , 53 | props.container.querySelector('#root') 54 | ); 55 | } 56 | 57 | export async function unmount(params: IQiankunProps) { 58 | console.log('app unmount'); 59 | } 60 | 61 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useRef, useEffect} from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {registerMicroApps, start, loadMicroApp} from 'qiankun'; 4 | import {BrowserRouter} from 'react-router-dom'; 5 | import {useHistory, useLocation, Redirect, Link} from 'react-router-dom'; 6 | 7 | const apps = [ 8 | {name: 'app0', activeRule: '/app0', entry: '//localhost:7700', container: '#micro-app'}, 9 | {name: 'app1', activeRule: '/app1', entry: '//localhost:7701', container: '#micro-app'} 10 | ]; 11 | 12 | const Aside = () => { 13 | const {pathname} = useLocation(); 14 | const activeAppPath = pathname.match(/(\w+)/) ? pathname.match(/(\w+)/)[0] : null; 15 | 16 | useEffect(() => { 17 | if (activeAppPath) { 18 | const aciveApp = apps.filter(app => app.name === activeAppPath)[0]; 19 | loadMicroApp(aciveApp); 20 | } 21 | }, [activeAppPath]); 22 | 23 | // useEffect(() => { 24 | // registerMicroApps(apps); 25 | // start(); 26 | // }, []); 27 | 28 | return ( 29 | <> 30 |
菜单栏
31 |
32 | {apps.map(app => ({app.name}))} 33 |
34 | 35 | ) 36 | }; 37 | 38 | const Content = () => { 39 | return ( 40 | <> 41 | 挂载点: 42 |
43 | 44 | ) 45 | } 46 | 47 | const App = () => { 48 | return ( 49 | 50 | 51 |