三、如何手动实现一个微前端框架雏形

如何手动实现一个微前端框架雏形

一、了解微前端

1. 什么是微前端

为了解决一整块儿庞大的前端服务所带来的变更和拓展方面的限制,将整体前端服务拆分成一些更小、更简单的,能够独立开发、测试部署的小块儿。但是在整体表现上还是一个整体的产品的服务称为微前端。

2. 为什么要学习微前端

2.1 关键优势

每个分模块的规模更小,更利于维护

松散各个模块之间的耦合。

可以实现增量升级,避免在重构的时候影响整体逻辑

技术栈无关,可以在每个子模块之间选取合适的技术栈进行开发

独立开发独立部署

2.2 为什么要学

在重构项目的时候,总会有各种各样的问题,如

项目技术栈落后,重构时混用多种技术栈,导致项目技术栈混杂。

各个模块之间耦合严重,动一处,可能影响整体项目运转

因为各种历史问题不得不做出各种妥协,或者是添加各种兼容条件限制。

当你也有以上问题的时候,不妨考虑使用微前端,不仅可以拜托繁重的历史包袱,让你可以进行轻松重构,而且不会出现重构不彻底的情况,可以根据需求的实际情况进行重构工作,而不是基于项目历史债务的问题进行考虑。

这是需要学习微前端一个很重要的前提,如果你的项目没有任何历史包袱,或者说项目是从零开始的,这样就不推荐你引入微前端这个东西,这样或许不能达到预期的目的,或许只会加重自己的开发负担。

3. 手写一个框架可以给我们带来什么

可以从框架作者的角度去考虑,为什么框架的架构要这么设计,从中学习作者的设计思想,对于模型概念的理解。

二、微前端实现方式对比

1.iframe

优势:

天生支持沙箱隔离、独立运行,这是最大的优势。不用做任何沙箱的处理。

劣势:

无法预加载缓存 iframe

无法共享基础库

事件通信限制较多

快捷键劫持

事件无法冒泡到顶层

跳转路径无法保持统一

登录状态无法共享

iframe 加载失败,主应用无法感知

性能问题难以计算

2.基于 SPA 的微前端架构

优势

可以规避 iframe 现存的问题点

可缓存和预加载

共享登录状态

主应用感知加载状态

快捷键劫持

通信设计

共享基础库

劣势:

实现难度较高。需要实现以下几项内容

路由系统

沙箱隔离

样式隔离

通信

html 加载和 js 解析能力

调试开发能力

三、项目介绍

主要用到koa、vue2、vue3、react15、react16

应用:主应用、子应用、后端服务和发布应用

主应用-选定vue3技术栈

vue2子应用:实现新能源页面

vue3子应用:首页、选车

react15子应用:资讯、视频、视频详情

react16子应用:新车、排行、登录

服务端接口:koa实现

发布应用:express

service

npm install koa-generator -g
koa -v
Koa2 service
// 监听修改文件自动重启
npm install supervisor —save-dev
处理跨域问题
npm install koa2-cors —save-dev

build>run.js快速启动所有的应用

四、子应用接入微前端

子应用接入微前端-vue2

// 1. vue2 > vue.config.js,子应用设置允许跨域,主应用需要获取子应用内容,防止资源拦截
  devServer: {
   
    contentBase: path.join(__dirname, 'dist'), // contentBase必须要配置
    hot: false,
    disableHostCheck: true,
    port,
    headers: {
   
      'Access-Control-Allow-Origin': '*', // 本地服务的跨域内容
    },
  },
    // 自定义webpack配置
  configureWebpack: {
   
    resolve: {
   
      alias: {
   
        '@': resolve('src'),
      },
    },
    output: {
   
      // library配置vue2后浏览器可以通过window.vue2获取到打包的内容,之后在微前端框架里也会用到这个信息,这个配置成子应用的名称
      library: `${
     packageName}`,
      // 把子应用打包成 umd 库格式 commonjs 浏览器,node环境
      libraryTarget: 'umd',
    },
  },


2. //vue2 > main.js  配置如果不是微前端环境下才执行render函数,微前端环境下需要根据微前端生命周期触发函数,暴露一组生命周期,window.vue2里会有这几个生命周期内容,后续会在微前端框架里使用,生命周期何时执行,需要在微前端框架里进行控制
  const render = () => {
   
  new Vue({
   
    router,
    render: h => h(App)
  }).$mount('#app-vue')
}

if (!window.__MICRO_WEB__) {
   
  render()
}
// 一般不做处理,特殊情况,例如加载之前需要做参数的处理再处理
export async function bootstrap() {
   
  console.log('bootstrap');
}
// 调用render方法,render方法执行成功之后就可以得到我们的整体vue2项目的vue实例,用实例就可以做一些其他东西
export async function mount() {
   
  render()
}
// 处理卸载上的事,如撤销监听事件,撤销这个容器显示的所有内容
export async function unmount(ctx) {
   
  const {
    container } = ctx
  if (container) {
   
    document.querySelector(container).innerHTML = ''
  }
}

子应用接入微前端 - vue3

配置与vue2同理

子应用接入微前端 - react15

  output: {
   
    path: path.resolve(__dirname, 'dist'),
    filename: 'react15.js',
    library: 'react15',
    libraryTarget: 'umd',
    umdNamedDefine: true,
    publicPath: 'http://localhost:9002/'
  },
  devServer: {
   
    // 配置允许跨域
    headers: {
    'Access-Control-Allow-Origin': '*' },
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    port: 9002,
    historyApiFallback: true,
    hot: true,
  }


  const render = () => {
   
  ReactDOM.render((
    <BasicMap />
  ), document.getElementById('app-react'))
}

if (!window.__MICRO_WEB__) {
   
  render()
}

export const bootstrap = () => {
   
  console.log('bootstrap')
}

export const mount = () => {
   
  render()
}

export const unmount = () => {
   
  console.log('卸载')
  // 可以通过index.html里的根结点根元素下直接置空,或者将传入的容器内容置空
}

子应用接入微前端 - react16

webpack打包回打包成
(function(){……})()
配置library之后会打包成
var react16 = (function(){……})()
当前变量内容是存在全局的

五、微前端框架开发

主框架—子应用注册

// main > src > components > MainNav.vue导航跳转时要跳转对应的链接,用useRouter, useRoute


// main > src > router > index.js,outer路由里配置react15、react16、vue2、vue3对应的组件都设置成app.vue
const routes = [
  {
   
    path: '/',
    component: () => import('../App.vue'),
  },
  {
   
    path: '/react15',
    component: () => import('../App.vue'),
  },
  {
   
    path: '/react16',
    component: () => import('../App.vue'),
  },
  {
   
    path: '/vue2',
    component: () => import('../App.vue'),
  },
  {
   
    path: '/vue3',
    component: () => import('../App.vue'),
  },
];


// 1. main>src>main.js
import {
    subNavList } from './store/sub'
import {
    registerApp } from './util'
registerApp(subNavList)



// 2. main > src > store > sub.js抛出子应用列表
export const subNavList = [
  {
   
    name: 'react15',// 唯一标识
    entry: '//localhost:9002/', // 去哪个入口获取到子应用的文件
    container: '#micro-container', // 子应用渲染容器container
    activeRule: '/react15', // 激活规则,子应用激活的路由
  },
  {
   
    name: 'react16',
    entry: '//localhost:9003/',
    container: '#micro-container',
    activeRule: '/react16',
  },
  {
   
    name: 'vue2',
    entry: '//localhost:9004/',
    container: '#micro-container',
    activeRule: '/vue2',
  },
  {
   
    name: 'vue3',
    entry: '//localhost:9005/',
    container: '#micro-container',
    activeRule: '/vue3',
  },
];



// 3.main > src > util > index.js里registerApp注册子应用并调用registerMicroApps注册到微前端框架里
import {
    registerMicroApps } from '../../micro'
export const registerApp = (list) => {
   
  // 注册到微前端框架里
  registerMicroApps(list)
}

// 4.main > micro > start.js有了微前端框架的第一个方法
import {
    setList } from './const/subApps'
export const registerMicroApps = (appList) => {
   
  setList(appList)
}

// 5.main > micro>const>subApps.js在微前端框架里定义了一个subApps去统一一管理子应用列表
let list = []

export const getList = () => list

export const setList = appList => list = appList // 通过setList将子应用列表注册到list上,之后在整体微前端框架运行期间都可以通过getList去获取

微前端框架 - 路由拦截

// 1. main > micro > start.js
import {
    rewriteRouter } from './router/rewriteRouter'
// 实现路由拦截
rewriteRouter()



// 2.main > micro  > router > rewriteRouter重写路由跳转
  1. main > micro > start.js调用rewriteRouter路由拦截
import {
    rewriteRouter } from './router/rewriteRouter'
// 实现路由拦截
rewriteRouter()

//2. main > micro  > router > rewriteRouter重写路由跳转,将window.history.pushState与replaceState同时做了更换,用patchRouter做了新的函数,有两个参数,第一个是原生的事件,第二个是起的事件名称
import {
    patchRouter } from '../utils'
import {
    turnApp } from './routerHandle'
// 重写window的路由跳转
export const rewriteRouter = () => {
   
  window.history.pushState = patchRouter(window.history.pushState, 'micro_push')
  window.history.replaceState = patchRouter(window.history.replaceState, 'micro_replace')
  window.addEventListener('micro_push', turnApp)
  window.addEventListener('micro_replace', turnApp)
  // 监听返回事件
  window.onpopstate = async function () {
   
    await turnApp()
  }
}


// 3.main > micro  > utils > index.js
// 给当前的路由跳转打补丁
export const patchRouter = (globalEvent, eventName) => {
   
  return function () {
   
    const e = new Event(eventName)  // new Event创建新的事件
    globalEvent.apply(this, arguments) // globalEvent传递过来的原生事件来代替当前return函数的执行,return函数是被  window.history.replaceState的下面window.addEventListener('micro_replace', turnApp)监听所调用的,this指向的就是监听的函数,将所有的参数传递过去
    window.dispatchEvent(e)// window.dispatchEvent触发刚创建的事件
    // 这样就实现了路由的拦截,所有能触发pushState和replaceState的地方我们都可以监听到
  }
}



// 4.main > micro  > router > routerHandle.js
export const turnApp = async () => {
   
  console.log('路由切换了')
}

微前端框架-获取首个子应用

// main > src > util > index.js
import {
    registerMicroApps } from '../../micro'
export const registerApp = (list) => {
   
  // 注册到微前端框架里
  registerMicroApps(list)
  // 开启微前端框架
  start()
}


// main > micro > start.js里写start函数
import {
    setList, getList } from './const/subApps'
import {
    currentApp } from './utils'
export const start = () => {
   
  // 首先验证当前子应用列表是否为空
  const apps = getList()
  if (!apps.length) {
   
    // 子应用列表为空
    throw Error('子应用列表为空, 请正确注册')
  }
  // 有子应用的内容, 查找到符合当前路由的子应用
  const app = currentApp()
  const {
    pathname, hash } = window.location
  if (app) {
   
    const url = pathname + hash
    window
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值