zl程序教程

您现在的位置是:首页 >  前端

当前栏目

微前端解读

前端 解读
2023-09-14 09:13:38 时间

微前端
目标
高质量:代码对标一线互联网大厂
全流程:子应用 -> 主应用 -> 服务端 -> 发布平台
前端架构
初始
无架构, 前端代码内嵌到后端应用中
而后端采用的是mvc架构, 将视图层、数据层和控制层做分离, 缺点是重度依赖开发环境, 代码混淆严重
前后端分离架构
将前端代码从后端环境中分离出来(Ajax促进了前后端分离架构的发展)多页面架构, 但是缺点是前端缺乏独立的部署能力, 整体依赖后端环境
单页面架构
各种打包、构建工具应运而生, 诞生了多元化前端开发方式, 使得前端开发可以脱离整体的后端环境
打包:gulp, webpack, vite
框架:vue, react
UI库:antd, element
优势:切换页面无刷新浏览器, 用户体验好; 组件化开发方式, 极大地提升了代码的复用率
缺点:不利于SEO优化, 首次渲染会出现较长时间的白屏
大前端时代
后端框架:express, koa
包管理工具:npm, yarn
node版本管理工具:nvm
微前端架构
与技术栈无关
主框架不限制接入应用的技术栈, 微应用具备完全自主权
独立开发, 独立部署
增量升级
是一种非常好的实施渐进式重构的手段和策略
微应用仓库独立, 前后端可独立开发, 主框架自动完成同步更新
独立运行时, 每一个微应用之间状态隔离, 运行时状态不共享
劣势:接入难度较高; 移动端少, 管理端多
软件设计原则与分层
单一职责原则
概念:永远不应该有多于一个原因来改变某个类
理解:对于一个类而言, 应该仅有一个引起它变化的原因
应用:如果一个类拥有了两种职责, 那就可以将这个类分成两个类
开放封闭原则
概念:软件实体扩展应该是开放的, 但对于修改应该是封闭的
理解:对扩展开放, 对修改封闭, 可以去扩展类, 但不要去修改类
应用:当需求有改动, 尽量用继承或者组合的形式来扩展类的功能, 而不是直接修改类的代码
里氏替换原则
理解:父类一定是可以被子类替换的
最少知识原则
概念:只与你最直接的对象交流
理解:低耦合, 高内聚
应用:做系统设计时, 尽量减小依赖关系
接口隔离原则
概念:一个类与另一个类之间的依赖性, 应该依赖于尽可能小的接口
理解:不要对外暴露没有实际意义的接口, 用户不应该依赖他不需要的接口
应用:当需要对外暴露接口时, 如果是非必要对外提供, 尽量删除
依赖倒置原则
概念:高层模块不应该依赖于低层模块, 它们应该依赖于抽象, 抽象不应该依赖于细节, 细节应该依赖于抽象
理解:应该面向接口编程, 不应该面向实现类编程
架构种类
系统级架构
应用在整个系统内, 如与后台服务如何通信, 与第三方系统如何集成
设计前端首要条件:了解前端系统与其它系统之间的关系(业务关系和协作机制)
设计后端:只需要规定与后台数据传递的机制(api设计规则, oauth的token机制, 数据传递cookie)
应用级架构
可以看做是系统级架构的细化
模块级架构
这部分内容是我们在开始业务编码之前的设计工作, 可以叫做迭代
代码级架构
规范与原则
代码质量以及改善
规范而非默契
微前端
在一个系统内微前端应用是应用间的架构方案
在多个应用之间, 微前端则是一种系统间的架构方案
微前端是将多个前端应用以某种形式结合在一起进行应用
目的
解决单体应用在一个较长的时间跨度下, 由于参与的人员、团队的增多、变迁, 从一个普通应用演变成一个巨应用后所带来的不可维护问题
单实例
同一个时刻, 只有一个子应用被展示, 子应用具备一个完整的应用生命周期
多实例
通常基于url的变化做子应用的切换
同一时刻可展示多个子应用
实现方式
1、iframe
优势:技术成熟, 支持页面嵌入, 天然支持运行沙箱隔离、独立运行
劣势:页面之间可以是不同域名的, 需要对应设计通讯机制, 如何监听、传参等内容
2、web component
优势:支持自定义元素; 支持shadow dom, 并可通过关联进行控制; 支持模板和插槽的功能, 引入自定义组件内容
劣势:需要重写当前的项目; 生态系统不完善, 兼容性问题比较大; 设计架构过于复杂
3、自研微前端框架
路由分发; 主应用控制路由匹配和子应用加载, 共享依赖加载
项目架构设计
主应用
注册子应用
加载、渲染子应用
路由匹配(activeWhen, rules - 由框架判断)
获取数据(公共依赖, 通过数据做鉴权处理)
通信(父子通信, 子父通信)
子应用
渲染
监听通信(主应用传递过来的数据)
微前端框架
子应用的注册
有开始的内容(应用加载完成)
路由更新判断
匹配对应的子应用
加载子应用的内容
完成所有依赖项的执行
将子应用渲染在固定的容器内
公共事件管理
异常捕获和报错
全局状态管理内容
沙箱隔离
通信机制
理解微前端
对比微服务
对于微服务来说, 模块分开解耦基本就完事了, 但是微前端不一样, 前端应用在运行时却是一个整体, 需要整合, 甚至还需要交互、通信
什么是微前端
是一种由独立交付的多个前端应用组成整体的架构风格
更加具体的来说呢, 微前端将前端应用分解成一些更小、更简单的能够独立开发、测试、部署的小块, 而在用户看来仍然是内聚的单个产品
框架与方案对比
single-spa - 代码简洁
qiankun - 基于single-spa封装的, 上手快, 文档比较丰富, 最优选
icestark - 阿里封装的框架
主应用创建
// 创建文件夹
mkdir qiankun-example
// 进入文件夹
cd qiankun-example
// 初始化
npm init
// 查看是否安装vue-cli
vue --version
// 如果没有, 则先全局安装
npm/yarn install/add -g @vue/cli
// 创建主应用
vue create main
修改外层package.json启动命令
“scripts”: {
“start:main”: "cd main && npm run serve"
}
可以通过npm run start:main来启动主应用
vue子应用创建
vue create sub-vue
修改外层package.json启动命令
“scripts”: {
“start:sub-vue”: "cd sub-vue && npm run serve"
}
可以通过npm run start:sub-vue来启动主应用
react子应用创建
// 首先需要安装脚手架create-react-app
npm install -g create-react-app
// 创建react子应用
create-react-app sub-react
修改外层package.json启动命令
“scripts”: {
“start:sub-react”: “cd sub-react && npm run start”
}
可以通过npm run start:sub-react来启动主应用
html子应用创建
mkdir sub-html
cd sub-html
npm init
文件目录结构
|-js
|–main.js
index.html
package.json
配置sub-html的package.json
cnpm install -D cross-env http-server
“main”: “index.html”,
“scripts”: {
“start”: "cross-env PUBLIC_PATH=/ http-server ./ -p 7800 --cors"
},
“devDependencies”: {
“cross-env”: “^7.0.3”,
“http-server”: "^14.1.0"
}
修改外层package.json启动命令
“scripts”: {
“start:sub-html”: "cd sub-html && npm run start"
}
可以通过npm run start:sub-html来启动主应用
主应用注册微应用
安装qiankun
cnpm install qiankun -S
配置.env文件 VUE_APP_SUB_VUE=//localhost:7777/subapp/sub-vue VUE_APP_SUB_REACT=//localhost:7778/subapp/sub-react VUE_APP_SUB_HTML=//localhost:7779/subapp/sub-html
注册启动微应用
import { registerMicroApps, start, setDefaultMountApp } from ‘qiankun’
import microApps from ‘./micro-app’ 
// 注册微应用(子应用)
registerMicroApps(microApps, {
// 监听它的一个注册的一个生命周期
beforeLoad: app => {},
beforeMount: [
app => {}
],
afterMount: [
app => {}
],
afterUnmount: [
app => {}
]
})
// 启动
start()
// 设置默认访问的路由
setDefaultMountApp(’/sub-vue’)
// microApps.js
const microApps = [
{
name: ‘sub-vue’, // 子应用的名称
entry: process.env.VUE_APP_SUB_VUE, // 入口, 通过环境配置文件获取
activeRule: ‘/sub-vue’, // 在子应用里面使用怎么样的路由规则去访问
container: ‘#subapp-viewport’, // 子应用的内容在主应用的哪个盒子里显示出来
props: {
// 做数据的交互
routerBase: ‘/sub-vue’,
getGlobalState: ‘’
}
},
{
name: ‘sub-react’,
entry: process.env.VUE_APP_SUB_REACT,
activeRule: ‘/sub-react’,
container: ‘#subapp-viewport’,
props: {
// 做数据的交互
routerBase: ‘/sub-react’,
getGlobalState: ‘’
}
},
{
name: ‘sub-html’,
entry: process.env.VUE_APP_SUB_HTML,
activeRule: ‘/sub-html’,
container: ‘#subapp-viewport’,
props: {
// 做数据的交互
routerBase: ‘/sub-html’,
getGlobalState: ‘‘
}
}
]
export default microApps
vue子应用开发配置
配置vue.config.js
const { name } = require(’./package.json’) 
module.exports = {
publicPath: ‘/subapp/sub-vue’,
configureWebpack: {
output: {
library: ${name}-[name],
libraryTarget: ‘umd’,
jsonpFunction: webpackJsonp_${name}
}
},
devServer: {
port: process.env.VUE_APP_PORT,
headers: {
‘Access-Control-Allow-Origin’: ‘'
}
}
}
修改main.js
import './public-path’
import Vue from 'vue’
import App from './App.vue’
import routes from './router’
import store from './store’
import VueRouter from ‘vue-router’
Vue.config.productionTip = false
Vue.use(VueRouter)
// 渲染页面
let instance = null // vue
function render (props = {}) {
const { container, routerBase } = props
const router = new VueRouter({
mode: 'history’
base: window.POWERED_BY_QIANKUN ? routerBase : process.env.BASE_URL,
routes
})
instance = new Vue({
router,
store,
render: h => h(App)
}).KaTeX parse error: Expected 'EOF', got '#' at position 44: …querySelector('#̲app') : '#app')…destroy()
instance.KaTeX parse error: Expected 'EOF', got '}' at position 42: …ance = null }̲ 重新修改的路由文件 /…{process.env.VUE_APP_PORT}KaTeX parse error: Expected 'EOF', got '}' at position 47: … return }̲ // esli…{process.env.BASE_URL}/} })() react子应用开发配置 安装依赖, 修改package.json cnpm install react-app-rewired -D "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build" } // 注意react的版本, 太高版本改造的过于大, 建议使用16 "dependencies": { "@testing-library/jest-dom": "^5.16.2", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-scripts": "5.0.0", "web-vitals": "^2.1.4" }, "devDependencies": { "react-app-rewired": "^2.1.11" } 建立config-overrides.js配置文件修改webpack配置 const { name } = require('./package.json') module.exports = { webpack: (config) => { config.output.publicPath =//localhost: p r o c e s s . e n v . P O R T {process.env.PORT} process.env.PORT{process.env.PUBLIC_URL}config.output.library =KaTeX parse error: Expected group after '_' at position 114: …= `webpackJsonp_̲{name}`
return config
},
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
}
}
}
修改入口文件
import './public-path’
import React from ‘react’;
import ReactDOM from 'react-dom’
import ‘./index.css’;
import App from ‘./App’;
import reportWebVitals from ‘./reportWebVitals’;
function render () {
ReactDOM.render(
<React.StrictMode>
<App /
</React.StrictMode>,
document.getElementById(‘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) {
    console.log(‘基座下发的能力:’, props
    render()
    }
    /
    *
  • 应用每次 切出/卸载 会调用的方法,通常在这里我们会卸载微应用的应用实例
    */
    export async function unmount() { ReactDOM.unmountComponentAtNode(document.getElementById(‘root’))
    }
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    // public-path.js
    if (window.POWERED_BY_QIANKUN) {
    // eslint-disable-next-line
    webpack_public_path = window.INJECTED_PUBLIC_PATH_BY_QIANKUN;
    //webpack_public_path = window.INJECTED_PUBLIC_PATH_BY_QIANKUN;
    }
    html子应用开发配置
    // ./js/main.js
    const render = (options)=> {
    document.querySelector(’#current-env’).innerHTML = ‘qiankun’
    return Promise.resolve()
    }
    (win => {
    win[‘pureHtml’] = {
    bootstrap: () => {
    console.log(‘purehtml bootstrap’)
    return Promise.resolve()
    },
    mount: (options) => {
    console.log(‘purehtml mount’, options)
    return render(options)
    },
    unmount: () => {
    console.log(‘purehtml unmount’)
    return Promise.resolve()
    }
    }
    })(window)
     html子应用  
 纯原生HTML子应用, 当前处于 独立运行 环境。
  项目打包服务配置 安装依赖  cnpm install npm-run-all -D "scripts": { "start": "npm-run-all --parallel start:*", "start:main": "cd main && npm run start", "start:sub-vue": "cd sub-vue && npm run start", "start:sub-react": "cd sub-react && npm run start", "start:sub-html": "cd sub-html && npm run start", "build": "npm-run-all build:* && bash ./scripts/bundle.sh", "build:sub-react": "cd sub-react && npm run build", "build:sub-vue": "cd sub-vue && npm run build", "build:sub-html": "cd sub-html && npm run build",  "build:main": "cd main && npm run build" }, scripts/bundle.sh #!/bin/bash rm -rf ./dist mkdir ./dist mkdir ./dist/subapp # sub-react子应用 cp -r ./sub-react/build/ ./dist/subapp/sub-react/  # sub-vue子应用 cp -r ./sub-vue/dist/ ./dist/subapp/sub-vue/ # sub-html子应用 cp -r ./sub-html/dist/ ./dist/subapp/sub-html/ # main基座 cp -r ./main/dist/ ./dist/main  # cd ./dist # zip -r mp$(date +%Y%m%d%H%M%S).zip * # cd .. echo 'bundle.sh execute success.