介绍
先介绍一下咱们的我的项目背景,该我的项目是从18年开始启动的两头搁置了很久导致我的项目架构比较落后并且前期持续业务开发的时候也没有继续对架构进行优化迭代而是间接进行了业务开发,导致前面有很多的问题比方启动慢
、代码耦合性强
、我的项目体量微小
导致开发效率升高之类的问题
问题形容
- 目前我的项目体量达到了
400
个路由 - 打包完的包大小为
60MB
左右 - 首次启动工夫为
3分钟
左右 - 热更新工夫为
10s
左右
过程
通过一段时间对我的项目架构的优化迭代,包含对路由架构的划分重构,解决了一部分在开发中的问题,然而对于我的项目体量微小导致耦合性强
和启动较慢
的问题还是没方法从根本上解决,所以咱们筹备采纳微前端
的计划来对我的项目进行具体的拆分,最初达到的一个目标解决代码之间耦合性
的问题、解决我的项目开发效率
的问题、划分分明每个子利用能够反对独自部署
打算
咱们我的项目外围是从场景+管控+领取生成的每一条线,之后的打算是依照这三局部划分子利用,所以采纳的大的框架计划就是这样
先定好指标和打算而后一步步向着这个方向缓缓搞
外围逻辑
- 场景是一个子利用咱们有比方酒店、火车等场景
- 管控是一个子利用他能够接管来自场景的参数渲染不同的管控页面
- 收银台是一个子利用提供场景领取能力
计划
咱们采纳的是当下比拟成熟的计划qinakun
,这个在社区曾经沉闷很久了并且有肯定的稳定性,开发接入文档和可能遇到的一些异样的问题社区都有很好的答复,接下来看看咱们的具体操作
梳理以后须要拆分子利用波及到的问题和依赖
- 应用的组件依赖
- 款式上的依赖
- 适配计划的依赖
- 路由的依赖划分
- 去除以后利用之外的代码 --- 瘦身操作
创立一个新的架构用来承载子利用
- 采纳react-scripts从新cra配置疾速构建
- 搭建环境用到的依赖比方须要用到的
ts、less、vw/vh适配计划
- 依据主我的项目用到的依赖去构建子利用的
package
尽量不要改变太大避免子项目启动失败
之后依据qiankun文档的提醒去革新主利用和子利用
- 主利用革新个别是在入口文件
import { registerMicroApps, start } from 'qiankun'registerMicroApps([ { name: 'webapp-qkrechargecenter', //对应的子利用package的name entry: '//localhost:3000/', //子利用域名 container: '#webapp-qkrechargecenter', //承载子利用的容器 activeRule: (location)=>{ return location.pathname.startsWith('/nav/normal') //渲染子利用的路由匹配 }, props: { //传递到子利用的组件或者参数等 PrivatePayment, Test1 } }])start()
- 在主利用中须要创立一个容器路由页面这个也就是咱们的场景入口,这外面渲染的就是咱们的子利用
import React, { useEffect } from 'react'import { start } from 'qiankun'const RechargeCenterPage: React.FC<any> = props => { useEffect(() => { return () =>{ console.log('卸载') } }, []) return <div id="webapp-qkrechargecenter"></div>}export default RechargeCenterPage
- 子利用架构革新
这里只会有针对微前端之间的交互进行革新,其余的配置就看大家本人的一个具体计划了
/** * 重写 react-scripts 默认配置 */ const appPackageJson = require('../package.json')module.exports = (config, env) => { config.output.library = `${appPackageJson.name}-[name]` config.output.libraryTarget = 'umd' config.output.jsonpFunction = `webpackJsonp_${appPackageJson.name}`, return config;};
- 子利用入口文件革新
import ReactDOM from "react-dom";import * as React from "react";import RouteComp from "./routes/routerList";function render(props) { const { container } = props; ReactDOM.render(<RouteComp {...props}/>, container ? container.querySelector('#root') : document.querySelector('#root'));}if (!window.__POWERED_BY_QIANKUN__) { render({});}/** * bootstrap 只会在微利用初始化的时候调用一次,下次微利用从新进入时会间接调用 mount 钩子,不会再反复触发 bootstrap。 * 通常咱们能够在这里做一些全局变量的初始化,比方不会在 unmount 阶段被销毁的利用级别的缓存等。 */export async function bootstrap() { console.log("react app bootstraped");}/** * 利用每次进入都会调用 mount 办法,通常咱们在这里触发利用的渲染办法 */export async function mount(props) { render(props);}/** * 利用每次 切出/卸载 会调用的办法,通常在这里咱们会卸载微利用的利用实例 */export async function unmount(props) { ReactDOM.unmountComponentAtNode( props.container ? props.container.querySelector("#root") : document.getElementById("root") );}/** * 可选生命周期钩子,仅应用 loadMicroApp 形式加载微利用时失效 */export async function update(props) { console.log("update props", props);}
- 子利用路由革新
这里我是采纳 react-router-config 间接渲染的路由如果大家有其余非凡配置能够自行配一下
import React from 'react'import { renderRoutes } from 'react-router-config'import { BrowserRouter as Router } from 'react-router-dom'import rechange from 'app/index/index'import paymentRechargePrivateView from 'app/paymentRechargePrivateView'const NoFound = (props) => { return <div> NoFound </div>}const routerList = [ { path: '/views/recharge/center', component: rechange, name: '充值核心', exact: true, }, { path: '/views/paymentRechargePrivateView', component: paymentRechargePrivateView, name: "充值核心个人消费收银台", exact: true, }, { component: NoFound, }]// 重点是basename须要匹配上主利用activeRule对应的门路const RouteComp = (props) => <Router basename={window.__POWERED_BY_QIANKUN__ ? '/nav/normal' : '/'}> {renderRoutes(routerList, props)}</Router>export default RouteComp
子利用配置跨域解决
/** * config-overrides.js * 重写 react-scripts 默认配置 */const configDev = require("./config/webpack.config.dev");const configProd = require("./config/webpack.config.prod");module.exports = { webpack: function override(config, env) { if (env == "development") { return configDev(config, env) } else { return configProd(config, env) } }, devServer: (configFunction) => { return function (proxy, allowedHost) { const config = configFunction(proxy, allowedHost); config.open = false; config.hot = false; config.headers = { 'Access-Control-Allow-Origin': '*', //设置容许跨域 }; return config; }; },};
整体的思路都是依照这种形式去做拆分,两头遇到了很多的问题咱们在上面分享一下,如果大家也有这种比拟历史悠久的我的项目拆分那大概率也会遇到
实现目标过程中的遇到的比拟麻烦
的问题
拆分实现的子利用外部路由跳转始终找不到门路或者始终向主利用路由去跳
- 呈现问题的起因是咱们之前老的路由name有一些本人的前缀规定
- 老的路由规定新的路由规定匹配的时候产生了异样,导致进行跳转的时候脱离了以后子利用的容器呈现了跳转白屏
解决办法是针对子利用外面跳转的门路须要在主利用外面也配置一个对应的路由指向的是同一个容器页面,相当于从新加载了子利用容器这样不会在跳转的时候产生页面跳转谬误导致白屏问题
//主利用//援用同一个页面RechargeCenter<Route path="/nav/normal/views/recharge/center" component={RechargeCenter} /><Route path="/nav/normal/views/paymentRechargePrivateView" component={RechargeCenter} />//子利用const routerList = [ { path: '/views/recharge/center', //该门路在主利用中注册 component: rechange, name: '充值核心', exact: true, }, { path: '/views/paymentRechargePrivateView', //该门路在主利用中注册 component: paymentRechargePrivateView, name: "充值核心个人消费收银台", exact: true, }, { component: NoFound, }]
子利用应用主利用传递下来的函数组件应用异样
异样图
<img width='1000' src='https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/30f27a5674264fdabb7b79c767c83918~tplv-k3u1fbpfcp-watermark.image' />
留神是只有函数组件传递到子利用中应用才有异样提醒,类组件是没有的,起因是以后应用的中央和传递下来的组件不是同一个实例,hooks组件须要在同一个实例下
- 解决办法
借助
webpack
的externals
,去应用同一份react,同时革新子利用和主利用//主利用externals:{ 'react':'React', 'react-dom':'ReactDOM',},
//子利用config.externals = { 'react':'React', 'react-dom':'ReactDOM',}
在主利用的入口文件引入react的cdn链接留神尽量和本地用的是同一个版本,避免兼容问题
<script src="https://unpkg.com/react@16.13.1/umd/react.development.js"></script> <script src="https://unpkg.com/react-dom@16.13.1/umd/react-dom.development.js"></script>
完结
以上是咱们我的项目拆分的整体思路和具体落实计划第一季,前面必定还会遇到更多的问题,所以不出意外还有第二季和第三季..... 谁家还有特地大的我的项目能够依照这种思路参考一下,依照业务线或者是比拟独立的模块去独自拆子利用
如果大家有更好的计划能够在评论区讨论一下~
最初~周末欢快
本文由mdnice多平台公布