目录结构※
一个基础的 微应用 项目大致是这样的
|-- .env
|-- config-overrides.js
|-- package.json
|-- yarn.lock
|-- build
|-- public
|-- node_modules.zip
|-- src
|-- api
|-- assets
|-- components
|-- pages
|-- router
|-- store
|-- styles
|-- utils
|-- index.js
|-- index.less
根目录※
package.json 包含插件和插件集
.env 环境变量
比如
PORT=9999
WDS_SOCKET_PORT=9999
yarn.lock
依赖包版本锁定文件
config-overrides.js
配置文件,包含webpack功能与插件的配置
node_modules.zip
项目的依赖压缩包,解压即可使用
build目录※
执行 npm run build 后,产物默认会存放在这里
public目录※
此目录下所有文件会被 copy 到输出路径。
/src 目录
api目录 与后端接口交互文件夹
assets目录 静态文件存放文件夹
components目录 公共组件存放文件夹
pages目录 业务代码存放文件夹
router目录 工程路由定义文件夹
store目录 工程 全局状态管理 文件夹
styles目录 工程 全局样式 文件夹
utils目录
工程工具函数文件夹
index.js
工程 入口文件
index.less
工程整体 样式文件
#项目基础环境要求
NODE :12.22.10及以上
NPM :最新的即可
配置手册※
微应用打包工具 config-overrides.js 文件配置如下
const packageName = require('./package.json').name
const { override, fixBabelImports, addLessLoader, overrideDevServer, watchAll, addWebpackPlugin } = require('customize-cra')
module.exports = {
'webpack': override(
(config) => {
config.output.library = `${packageName}`
config.output.libraryTarget = 'umd'
config.output.publicPath = '/';
config.output.jsonpFunction = `webpackJsonp_${packageName}`
return config
},
addLessLoader({
lessOptions: {
javascriptEnabled: true,
modifyVars: { '@primary-color': '#3366ff' }
},
}),
),
'devServer': overrideDevServer(
(config) => {
config.headers = config.headers || {}
config.port = 9999;
config.headers['Access-Control-Allow-Origin'] = '*'
return config
},
watchAll()
)
}src/index.js 入口文件配置如下
import React from "react";
import ReactDOM from "react-dom";
import { ConfigProvider, Drawer, Modal, Button } from "antd";
import zhCN from "antd/lib/locale/zh_CN";
import { BrowserRouter, Route, HashRouter } from "react-router-dom";
import PubSub from 'pubsub-js'
import "./styles/curd.less"
import "./index.less";
class App extends React.Component {
state = {
flowModalShow: false,
modalData:{}
}
closeFlowModalShow = () => {
this.setState({ flowModalShow: false })
this.state.modalData.unmountSelf()
}
componentDidMount() {
PubSub.subscribe('modalData', (msg, data) => { // 订阅事件
if (data) {
// 手动加载应用打开弹窗
}
}
}
}
function render(props) {
ReactDOM.render(
<div className="developMaster">
<BrowserRouter basename='/base/develop'>
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
</BrowserRouter>
</div>,
document.getElementById("microRoot")
);
}
if (!window.__POWERED_BY_QIANKUN__) { // 判断是不是 微前端(qiankun) 环境
render();
}
function renderPatch(props) {
PubSub.publish('modalData', props) // 发送广播事件
}
/**
* bootstrap 只会在微应用初始化的时候调用一次,下次微应用重新进入时会直接调用 mount 钩子,不会再重复触发 bootstrap。
* 通常我们可以在这里做一些全局变量的初始化,比如不会在 unmount 阶段被销毁的应用级别的缓存等。
*/
export async function bootstrap(props) { }
/**
* 应用每次进入都会调用 mount 方法,通常我们在这里触发应用的渲染方法
*/
export async function mount(props) {
render(props);
}
/**
* 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
*/
export async function unmount(props) {
ReactDOM.unmountComponentAtNode(document.getElementById("microRoot"));
}
/**
* 可选生命周期钩子,仅使用 loadMicroApp 方式加载微应用时生效
*/
export async function update(props) {
renderPatch(props);
}启动项目※
// 解压 node_modules.zip
// 执行 npm start
// 访问 http://localhost:9999 或者将微应用注册到主应用直接访问主应用
// 打包 npm run build 打包代码 -> 部署上线 ( 参考部署文档 )
项目开发
在 src/index.js 入口文件中这样操作
import React from "react";
import ReactDOM from "react-dom";
import { ConfigProvider, Drawer, Modal, Button, message } from "antd"; // 引入 message 进行提示配置
import { BrowserRouter, Route, HashRouter } from "react-router-dom";
import zhCN from "antd/lib/locale/zh_CN";
import ResultsTheShop from './pages/ResultsTheShop' // 引入开发的组件
class App extends React.Component {
componentDidMount(){
message.config({
maxCount:1 // 设置 消息提示数量 最大为 1 个
})
}
render() {
return (
<div>
<Route path="/ResultsTheShop" component={ResultsTheShop} /> <!-- 在这里写自己的业务组件页面即可 -->
</div>
)
}
}
function render(props) {
ReactDOM.render(
<div className="developMaster">
<BrowserRouter basename='/base'>
<ConfigProvider locale={zhCN}>
<App />
</ConfigProvider>
</BrowserRouter>
</div>,
document.getElementById("microRoot")
);
}
if (!window.__POWERED_BY_QIANKUN__) {
render();
}在 src/api/api.js 文件里修改与后端交互的 网关地址 代码如下
import axios from "axios";
const publicIp = "http://27.196.10.30/" // 项目的网关地址更改为 业务网关地址
const instance = axios.create({ //创建axios实例,在这里可以设置请求的默认配置
timeout: 180000, // 设置超时时间180s
baseURL: publicIp //根据自己配置的反向代理去设置不同环境的baeUrl
})
// 文档中的统一设置post请求头。下面会说到post请求的几种'Content-Type'
instance.defaults.headers.post['Content-Type'] = 'application/json'
/** 添加请求拦截器 **/
instance.interceptors.request.use(config => {
config.headers['token'] = sessionStorage.getItem('token') || ''
// 在这里:可以根据业务需求可以在发送请求之前做些什么:例如我这个是导出文件的接口,因为返回的是二进制流,所以需要设置请求响应类型为blob,就可以在此处设置。
return config
}, error => {
// 对请求错误做些什么
return Promise.reject(error)
})
/** 添加响应拦截器 **/
instance.interceptors.response.use(response => {
hide()
if (response.code === '200') { // 响应结果里的statusText: ok是我与后台的约定,大家可以根据实际情况去做对应的判断
return Promise.resolve(response.data)
} else {
message.error('响应超时')
return Promise.reject(response.data.message)
}
}, error => {
return Promise.reject('请求超时, 请刷新重试')
})
/* 统一封装get请求 */
export const get = (url, params, config = {}) => {
return new Promise((resolve, reject) => {
instance({
method: 'get',
url,
params,
...config
}).then(response => {
resolve(response)
}).catch(error => {
reject(error)
})
})
}
/* 统一封装post请求 */
export const post = (url, data, config = {}) => {
return new Promise((resolve, reject) => {
instance({
method: 'post',
url,
data,
...config
}).then(response => {
resolve(response)
}).catch(error => {
reject(error)
})
})
}非 webpack 构建的微应用※
接入之前请确保你的项目里的图片、音视频等资源能正常加载,如果这些资源的地址都是完整路径(例如:http://27.196.10.156:18770/static/img/logo.png),则没问题。如果都是相对路径,需要先将这些资源上传到服务器,使用完整路径。
接入非常简单,只需要额外声明一个 script,用于 export 相对应的 lifecycles. 例如:
声明 entry 入口
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Purehtml Example</title>
</head>
<body>
<div>
Purehtml Example
</div>
</body>
<script src="http://绝对地址/entry.js" entry></script>
</html>在 entry js 里声明 lifecycles
const render = ($) => {
//可选择
return Promise.resolve();
};
((global) => {
global['purehtml'] = {
bootstrap: () => {
console.log('purehtml bootstrap');
return Promise.resolve();
},
mount: () => {
console.log('purehtml mount');
return render($);
},
unmount: () => {
console.log('purehtml unmount');
return Promise.resolve();
},
};
})(window);