共计 13903 个字符,预计需要花费 35 分钟才能阅读完成。
🚀 基于 qiankun 微前端实战 + 部署粗略笔记
因业务须要,以下文字纯集体 qiankun 实战学习笔记,不谈原理只记操作过程,内容不免有纰漏局部,敬请不吝赐教批评指正。
✨ 指标场景
📝 准备知识点
- 已对 qiankun 微前端有了初步意识;
- 相熟 react、vue;
- 理解 github、docker、jenkins、nginx;
🔧 技术栈
🍔 基座
- 应用 create-react-app 初始化我的项目;
- 装置
"qiankun": "^2.4.0"
; - 代码地址:react-app-qiankun-main;
- 独立仓库,独立部署,独立域名:https://qiankun.xiaoqiang.tech;
🍟 react 子利用
- 应用 create-react-app 初始化我的项目;
- 装置
"react-app-rewired": "^2.1.8"
、"react-router-dom": "^5.2.0"
; - 代码地址:react-app-qiankun-sub;
- 独立仓库,独立部署,独立域名:https://react.xiaoqiang.tech;
🌮 vue 子利用
- 应用 vue-cli 初始化我的项目,对应
"vue": "^3.0.0"
; - 装置
"vue-router": "^4.0.0-beta.11"
; - 代码地址:vue-cli-qiankun-sub;
- 独立仓库,独立部署,独立域名:https://vue.xiaoqiang.tech;
🚴♂️ 疾速上手
🍔 基座
-
1. 初始化我的项目
npm init react-app react-app-qiankun-main
-
2. 装置
qiankun
yarn add qiankun # 或者 npm i qiankun -S
- 3. 目录构造
react-app-qiankun-main
├── .env.local // 本地环境
├── .env.development.local // 测试环境
├── .env.production.local // 生产环境
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── components
│ └── Loading.jsx
├── store
│ └── store.js // 主利用的全局状态
├── apps.js // 子利用配置
├── App.css
├── App.js // 基座布局,挂载子利用
├── App.test.js
├── index.css
├── index.js // 主利用中注册微利用
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
🍔 基座(开撸代码)
-
新增 3 个.env 文件,次要配置不同环境的对应的域名
- .env/.env.development.local(此处暂未辨别本地和测试的域名,所有环境变量值都保持一致)
REACT_APP_SUB_REACT=//localhost:2233/react REACT_APP_SUB_VUE=//localhost:3344/vue PORT=1122
- .env.production.local(生产环境)
REACT_APP_SUB_REACT = https://react.xiaoqiang.tech REACT_APP_SUB_VUE = https://vue.xiaoqiang.tech
-
批改
index.html
挂载 dom 的默认 id,避免与子利用 id 抵触// 默认 root => main-root <div id="main-root"></div>
-
新增 store/store.js,配置主利用的全局状态
import {initGlobalState} from 'qiankun'; const initialState = { user: {name: 'qiankun'} }; const actions = initGlobalState(initialState); actions.onGlobalStateChange((state, prev) => {for(const key in state) {initialState[key] = state[key]; } }) // 非官方 api,https://github.com/umijs/qiankun/pull/729 actions.getGlobalState = (key) => {return key ? initialState[key] : initialState; } export default actions;
-
批改 src/App.js,次要实现基座页面布局及减少挂载子利用的 dom(id=”subapp-viewport”)
function App(props) { // ... 省略,具体可见源码 return ( <> <div className="mainapp"> {/* 标题栏 */} <header className="mainapp-header"> <ul className="mainapp-header-sidemenu"> {/* 侧边栏 省略,具体可见源码 */} </ul> </header> <div className="mainapp-main"> {/* 子利用 */} <main id="subapp-viewport"></main> </div> </div> </> ); }
-
减少 apps.js,子利用的配置
import store from './store/store' const microApps = [ { name: 'react', entry: process.env.REACT_APP_SUB_REACT, activeRule: '/react', container: '#subapp-viewport', }, { name: 'vue', entry: process.env.REACT_APP_SUB_VUE, activeRule: '/vue', container: '#subapp-viewport', }, ] const apps = microApps.map(item => { return { ...item, props: { routerBase: item.activeRule, getGlobalState: store.getGlobalState, } } }) export default apps
-
批改 src/index.js,主利用中注册微(子)利用
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import {registerMicroApps, start, setDefaultMountApp} from 'qiankun'; import apps from './apps' function render({appContent, loading}) {const container = document.getElementById('main-root'); ReactDOM.render( <React.StrictMode> <App loading={loading} content={appContent} /> </React.StrictMode>, container, ) } const loader = loading => render({loading}); render({loading: true}); const microApps = apps.map((app => ({ ...app, loader, }))) registerMicroApps(microApps, { beforeLoad: app => {console.log('before load app.name=====>>>>>', app.name) }, beforeMount: [ app => {console.log('[LifeCycle] before mount %c%s', 'color: green;', app.name) } ], afterMount: [ app => {console.log('[LifeCycle] after mount %c%s', 'color: green;', app.name) } ], afterUnmount: [ app => {console.log('[LifeCycle] after unmount %c%s', 'color: green;', app.name) } ] }) setDefaultMountApp('/react') start();
-
本地启动
npm start
🍟 react 子利用
-
1. 初始化我的项目
npm init react-app react-app-qiankun-sub
-
2. 装置
react-app-rewired
、react-router-dom
npm i react-app-rewired --save-dev npm i react-router-dom --save
- 3. 目录构造
react-app-qiankun-sub
├── .env // 本地环境
├── config-overrides.js // 笼罩 create-react-app 的 webpack 配置
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── components
│ └── LibVersion.jsx
├── pages
│ └── Home.jsx
├── public-path.js // __webpack_public_path__
├── App.css
├── App.js // 子利用布局
├── App.test.js
├── index.css
├── index.js // 子利用入口,挂载 dom 导出相应的生命周期钩子
├── logo.svg
├── reportWebVitals.js
└── setupTests.js
🍟 react 子利用(开撸代码)
-
新增 1 个.env 文件,次要配置本地环境
此处 PORT 须要和基座
REACT_APP_SUB_REACT
端口保持一致PORT=2233
-
批改
index.html
挂载 dom 的默认 id,避免与基座及其他子利用 id 抵触// 默认 root => sub-react-root <div id="sub-react-root"></div>
-
新增 src/public-path.js,__webpack_public_path__
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
- 批改 src/App.js,次要实现子利用页面布局(略,见源码)
-
批改 src/index.js,微(子)利用导出相应的生命周期钩子
import './public-path'; import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; function getSubRootContainer(container) {return container ? container.querySelector('#sub-react-root') : document.querySelector('#sub-react-root'); } function render(props) {const { container} = props; ReactDOM.render( <React.StrictMode> <App store={{...props}} /> </React.StrictMode>, getSubRootContainer(container), ); } function storeTest(props) {props.onGlobalStateChange((value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true); props.setGlobalState({ ignore: props.name, user: {name: props.name,}, }); } if (!window.__POWERED_BY_QIANKUN__) {render({}); } export async function bootstrap() {console.log('react app bootstraped'); } export async function mount(props) {console.log('props from main framework', props); storeTest(props); render(props); } export async function unmount(props) {const { container} = props; ReactDOM.unmountComponentAtNode(getSubRootContainer(container)); }
-
减少 config-overrides.js,笼罩 create-react-app 的 webpack 配置
const {name} = require('./package'); module.exports = { webpack: config => {config.output.library = `${name}-[name]`; config.output.libraryTarget = 'umd'; config.output.jsonpFunction = `webpackJsonp_${name}`; return config; }, devServer: (configFunction) => {return (proxy, allowedHost) => {const config = configFunction(proxy, allowedHost); config.historyApiFallback = true; config.open = false; config.hot = false; config.watchContentBase = false; config.liveReload = false; config.headers = {'Access-Control-Allow-Origin': '*',}; return config; } } }
-
批改
package.json
"scripts": { - "start": "react-scripts start", + "start": "react-app-rewired start", - "build": "react-scripts build", + "build": "react-app-rewired build", - "test": "react-scripts test", + "test": "react-app-rewired test", "eject": "react-scripts eject" },
-
本地启动
npm start
🌮 vue 子利用
-
1. 初始化我的项目
npm install -g @vue/cli-service-global vue create vue-cli-qiankun-sub
-
2. 装置
vue-router
npm i vue-router --save
- 3. 目录构造
vue-cli-qiankun-sub
├── .env // 本地环境
├── vue.config.js // vue 可选的配置文件
├── babel.config.js
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── manifest.json
└── src
├── components
│ └── HelloWorld.vue
├── router
│ └── index.js
├── views
│ └── Home.vue
├── public-path.js // __webpack_public_path__
├── App.vue // 子利用布局
└── main.js // 子利用入口,挂载 dom 导出相应的生命周期钩子
🌮 vue 子利用(开撸代码)
-
新增 1 个.env 文件,次要配置本地环境
此处 PORT 须要和基座
REACT_APP_SUB_VUE
端口保持一致PORT=3344
-
批改
index.html
挂载 dom 的默认 id,避免与基座及其他子利用 id 抵触// 默认 root => sub-vue-root <div id="sub-vue-root"></div>
-
新增 src/public-path.js,__webpack_public_path__
if (window.__POWERED_BY_QIANKUN__) { // eslint-disable-next-line no-undef __webpack_public_path__ = window.__INJECTED_PUBLIC_PATH_BY_QIANKUN__; }
- 批改 src/App.vue,次要实现子利用页面布局(略,见源码)
-
批改 src/mian.js,微(子)利用导出相应的生命周期钩子
import './public-path'; import {createApp} from 'vue'; import {createRouter, createWebHistory} from 'vue-router'; import App from './App.vue'; import routes from './router'; import store from './store'; let router = null; let instance = null; let history = null; function render(props = {}) {const { container} = props; history = createWebHistory(window.__POWERED_BY_QIANKUN__ ? '/vue' : '/'); router = createRouter({ history, routes, }); instance = createApp(App); instance.use(router); instance.use(store); instance.mount(container ? container.querySelector('#sub-vue-root') : '#sub-vue-root'); } if (!window.__POWERED_BY_QIANKUN__) {render(); } export async function bootstrap() {console.log('%c', 'color: green;', 'vue3.0 app bootstraped'); } function storeTest(props) { props.onGlobalStateChange && props.onGlobalStateChange((value, prev) => console.log(`[onGlobalStateChange - ${props.name}]:`, value, prev), true, ); props.setGlobalState && props.setGlobalState({ ignore: props.name, user: {name: props.name,}, }); } export async function mount(props) {storeTest(props); render(props); instance.config.globalProperties.$onGlobalStateChange = props.onGlobalStateChange; instance.config.globalProperties.$setGlobalState = props.setGlobalState; } export async function unmount() {instance.unmount(); instance._container.innerHTML = ''; instance = null; router = null; history.destroy();}
-
减少 vue.config.js 配置文件
const path = require('path'); const {name} = require('./package'); function resolve(dir) {return path.join(__dirname, dir); } const port = process.env.PORT; module.exports = { outputDir: 'dist', assetsDir: 'static', filenameHashing: true, devServer: { hot: true, disableHostCheck: true, port, overlay: { warnings: false, errors: true, }, headers: {'Access-Control-Allow-Origin': '*',}, }, // 自定义 webpack 配置 configureWebpack: { resolve: { alias: {'@': resolve('src'), }, }, output: { // 把子利用打包成 umd 库格局 library: `${name}-[name]`, libraryTarget: 'umd', jsonpFunction: `webpackJsonp_${name}`, }, }, };
-
批改
package.json
"scripts": {+ "start": "vue-cli-service serve",},
-
本地启动
npm start
🏄 预览
以上操作完后,能够间接通过基座预览,子利用也可独立预览
基座预览
http://localhost:1122/
react 子利用预览
http://localhost:2233/
vue 子利用预览
http://localhost:3344/
🔨 部署
备选计划
- 1. 单域名部署;
// 基座:https://qiankun.xiaoqiang.tech
// react 子利用:https://qiankun.xiaoqiang.tech/react
// vue 子利用:https://qiankun.xiaoqiang.tech/vue
// 编译后服务器存储目录
react-app-qiankun
├── main
│ └── index.html
├── react
│ └── index.html
└── vue
└── index.html
- 2. 多域名独立部署;(当篇笔记抉择了多域名部署)
// 基座:https://qiankun.xiaoqiang.tech
// 编译后服务器我的项目独立存储目录
react-app-qiankun-main
└── index.html
// react 子利用:https://react.xiaoqiang.tech
// 编译后服务器我的项目独立存储目录
react-app-qiankun-sub
└── index.html
// vue 子利用:https://vue.xiaoqiang.tech
// 编译后服务器我的项目独立存储目录
vue-cli-qiankun-sub
└── index.html
部署(以下只初略记录部署过程,过于简陋)
- 前提:已购云服务器,并已装置 docker、nginx、jenkins、3 个独立域名及 ssl 证书
- 本地编码,github 存储代码,别离新建 3 个公开代码库
// 基座:react-app-qiankun-main 存储到 https://github.com/niexq/react-app-qiankun-main
// react 子利用:react-app-qiankun-sub 存储到 https://github.com/niexq/react-app-qiankun-sub
// vue 子利用:vue-cli-qiankun-sub 存储到 https://github.com/niexq/vue-cli-qiankun-sub
- github,jenkins 继续集成
// 具体配置步骤略
// github webHooks 设置
// jenkins 构建局部执行 shell
BUILD_ID=dontKillMe
cd /var/jenkins_home/workspace/react-app-qiankun-main
npm install
npm run build
rm -rf /srv/www/react-app-qiankun-main
cp -rf /var/jenkins_home/workspace/react-app-qiankun-main/build /srv/www/react-app-qiankun-main/
- 应用 nginx 代理
nginx.conf
user root;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {worker_connections 1024;}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local]"$request"''$status $body_bytes_sent "$http_referer" ''"$http_user_agent""$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
#include /etc/nginx/conf.d/*.conf;
server {
listen 443;
server_name qiankun.xiaoqiang.tech; # 你的域名
ssl on;
root www/react-app-qiankun-main; # 前台文件寄存文件夹,可改成别的
index index.html index.htm; # 下面配置的文件夹外面的 index.html
ssl_certificate cert/5543142_qiankun.xiaoqiang.tech.pem; #将 domain name.pem 替换成您证书的文件名。ssl_certificate_key cert/5543142_qiankun.xiaoqiang.tech.key; #将 domain name.key 替换成您证书的密钥文件名。ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
# 用于配合 browserHistory 应用
try_files $uri $uri/ /index.html;
# root /srv/www/react-app-qiankun-main;
# index index.html index.htm;
}
}
server {
listen 443;
server_name react.xiaoqiang.tech; # 你的域名
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {return 204;}
ssl on;
root www/react-app-qiankun-sub; # 前台文件寄存文件夹,可改成别的
index index.html index.htm; # 下面配置的文件夹外面的 index.html
ssl_certificate cert/4325684_react.xiaoqiang.tech.pem; #将 domain name.pem 替换成您证书的文件名。ssl_certificate_key cert/4325684_react.xiaoqiang.tech.key; #将 domain name.key 替换成您证书的密钥文件名。ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
# 用于配合 browserHistory 应用
try_files $uri $uri/ /index.html;
# root /srv/www/react-app-qiankun-sub;
# index index.html index.htm;
}
}
server {
listen 443;
server_name vue.xiaoqiang.tech; # 你的域名
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
if ($request_method = 'OPTIONS') {return 204;}
ssl on;
root www/vue-cli-qiankun-sub; # 前台文件寄存文件夹,可改成别的
index index.html index.htm; # 下面配置的文件夹外面的 index.html
ssl_certificate cert/5556275_vue.xiaoqiang.tech.pem; #将 domain name.pem 替换成您证书的文件名。ssl_certificate_key cert/5556275_vue.xiaoqiang.tech.key; #将 domain name.key 替换成您证书的密钥文件名。ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
# 用于配合 browserHistory 应用
try_files $uri $uri/ /index.html;
# root /srv/www/vue-cli-qiankun-sub;
# index index.html index.htm;
}
}
}
docker 运行 nginx 命令,重点关注-v 挂载目录
docker run --name nginx -p 80:80 -p 443:443 -v /root/nginx/config/nginx.conf:/etc/nginx/nginx.conf -v /root/nginx/cert:/etc/nginx/cert -v /root/nginx/logs:/var/log/nginx -v /srv/www/react-app-qiankun-main:/etc/nginx/www/react-app-qiankun-main -v /srv/www/react-app-qiankun-sub:/etc/nginx/www/react-app-qiankun-sub -v /srv/www/vue-cli-qiankun-sub:/etc/nginx/www/vue-cli-qiankun-sub --restart=always -d nginx:stable
🌴 总结
没有总结,遇到的问题太多,笔记总结的太杂,前期再整顿分享
线上预览地址:https://qiankun.xiaoqiang.tech
子利用线上也可独立预览
react 子利用预览:https://react.xiaoqiang.tech
vue 子利用预览:https://vue.xiaoqiang.tech
源码地址:https://github.com/niexq/reac…
🧩 参考链接
qiankun
qiankun-example
qiankun 微前端计划实际及总结
💯 跳过上述繁琐步骤是否可行
智慧的抉择
🏆 写在最初
能保持到最初的都是壮士,感激浏览
,欢送 star
激励