Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >深度:从零编写一个微前端框架

深度:从零编写一个微前端框架

作者头像
Peter谭金杰
发布于 2020-05-18 09:28:45
发布于 2020-05-18 09:28:45
1.4K00
代码可运行
举报
运行总次数:0
代码可运行

对于微前端,最近好像很火,之前我公众号也发过比较多微前端框架文章

那么现在我们需要手写一个微前端框架,首先得让大家知道什么是微前端,现在微前端模式分很多种,但是大都是一个基座+多个子应用模式,根据子应用注册的规则,去展示子应用。

这是目前的微前端框架基座加载模式的原理,基于single-spa封装了一层,我看有不少公司是用Vue做加载器(有天然的keep-alive),还有用angular和web components技术融合的


首先项目基座搭建,这里使用parcel

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mkdir pangu 
yarn init 
//输入一系列信息
yarn add parcel@next

然后新建一个index.html文件,作为基座

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

新建一个index.js文件,作为基座加载配置文件

新建src文件夹,作为pangu框架的源码文件夹,

新建example案例文件夹

现在项目结构长这样


既然是手写,就不依赖其他任何第三方库

我们首先需要重写hashchange popstate这两个事件,因为微前端的基座,需要监听这两个事件根据注册规则去加载不同的子应用,而且它的实现必须在React、vue子应用路由组件切换之前,单页面的路由源码原理实现,其实也是靠这两个事件实现,之前我写过一篇单页面实现原理的文章,不熟悉的可以去看看

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://segmentfault.com/a/1190000019936510
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const HIJACK_EVENTS_NAME = /^(hashchange|popstate)$/i;
const EVENTS_POOL = {
  hashchange: [],
  popstate: [],
};

window.addEventListener('hashchange', loadApps);
window.addEventListener('popstate', loadApps);

const originalAddEventListener = window.addEventListener;
const originalRemoveEventListener = window.removeEventListener;
window.addEventListener = function (eventName, handler) {
  if (
    eventName &&
    HIJACK_EVENTS_NAME.test(eventName) &&
    typeof handler === 'function'
  ) {
    EVENTS_POOL[eventName].indexOf(handler) === -1 &&
      EVENTS_POOL[eventName].push(handler);
  }
  return originalAddEventListener.apply(this, arguments);
};
window.removeEventListener = function (eventName, handler) {
  if (eventName && HIJACK_EVENTS_NAME.test(eventName)) {
    let eventsList = EVENTS_POOL[eventName];
    eventsList.indexOf(handler) > -1 &&
      (EVENTS_POOL[eventName] = eventsList.filter((fn) => fn !== handler));
  }
  return originalRemoveEventListener.apply(this, arguments);
};

function mockPopStateEvent(state) {
  return new PopStateEvent('popstate', { state });
}

// 拦截history的方法,因为pushState和replaceState方法并不会触发onpopstate事件,所以我们即便在onpopstate时执行了reroute方法,也要在这里执行下reroute方法。
const originalPushState = window.history.pushState;
const originalReplaceState = window.history.replaceState;
window.history.pushState = function (state, title, url) {
  let result = originalPushState.apply(this, arguments);
  reroute(mockPopStateEvent(state));
  return result;
};
window.history.replaceState = function (state, title, url) {
  let result = originalReplaceState.apply(this, arguments);
  reroute(mockPopStateEvent(state));
  return result;
};

// 再执行完load、mount、unmout操作后,执行此函数,就可以保证微前端的逻辑总是第一个执行。然后App中的Vue或React相关Router就可以收到Location的事件了。
export function callCapturedEvents(eventArgs) {
  if (!eventArgs) {
    return;
  }
  if (!Array.isArray(eventArgs)) {
    eventArgs = [eventArgs];
  }
  let name = eventArgs[0].type;
  if (!HIJACK_EVENTS_NAME.test(name)) {
    return;
  }
  EVENTS_POOL[name].forEach((handler) => handler.apply(window, eventArgs));
}

上面代码很简单,创建两个队列,使用数组实现

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const EVENTS_POOL = {
  hashchange: [],
  popstate: [],
};

如果检测到是hashchange popstate两种事件,而且它们对应的回调函数不存在队列中时候,那么就放入队列中。(相当于redux中间件原理)

然后每次监听到路由变化,调用reroute函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
function reroute() {
  invoke([], arguments);
}

这样每次路由切换,最先知道变化的是基座,等基座同步执行完(阻塞)后,就可以由子应用的vue-Rourer或者react-router-dom等库去接管实现单页面逻辑了。


那,路由变化,怎么加载子应用呢?

像一些微前端框架会用import-html之类的这些库,我们还是手写吧

逻辑大概是这样,一共四个端口,nginx反向代理命中基座服务器监听的端口(用户必须首先访问到根据域名),然后去不同子应用下的服务器拉取静态资源然后加载。


提示:所有子应用加载后,只是在基座的一个div标签中加载,实现原理跟ReactDom.render()这个源码一样,可参考我之前的文章

原创:从零实现一个简单版React (附源码)


那么我们先编写一个registrApp方法,接受一个entry参数,然后去根据url变化加载子应用(传入的第二个参数activeRule

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 *
 * @param {string} entry
 * @param {string} function
 */
const Apps = [] //子应用队列
function registryApp(entry,activeRule) {
    Apps.push({
        entry,
        activeRule
    })
}

注册完了之后,就要找到需要加载的app

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export async function loadApp() {
  const shouldMountApp = Apps.filter(shouldBeActive);
  console.log(shouldMountApp, 'shouldMountApp');
  //   const res = await axios.get(shouldMountApp.entry);
  fetch(shouldMountApp.entry)
    .then(function (response) {
      return response.json();
    })
    .then(function (myJson) {
      console.log(myJson, 'myJson');
    });
}

shouldBeActive根据传入的规则去判断是否需要此时挂载:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export function shouldBeActive(app){
    return app.activeRule(window.location)
}

此时的res数据,就是我们通过get请求获取到的子应用相关数据,现在我们新增subapp1和subapp2文件夹,模拟部署的子应用,我们把它用静态资源服务器跑起来

subapp1.js作为subapp1的静态资源服务器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const express = require('express');

subapp2.js作为subapp2的静态资源服务器

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const express = require('express');
const app = express();
const { resolve } = require('path');
app.use(express.static(resolve(__dirname, '../subapp1')));

app.listen(8889, (err) => {
  !err && console.log('8889端口成功');
});

现在文件目录长这样:

基座index.html运行在1234端口,subapp1部署在8889端口,subapp2部署在8890端口,这样我们从基座去拉取资源时候,就会跨域,所以静态资源服务器、webpack热更新服务器等服务器,都要加上cors头,允许跨域。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const express = require('express');
const app = express();
const { resolve } = require('path');
//设置跨域访问
app.all('*', function (req, res, next) {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Headers', 'X-Requested-With');
  res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS');
  res.header('X-Powered-By', ' 3.2.1');
  res.header('Content-Type', 'application/json;charset=utf-8');
  next();
});
app.use(express.static(resolve(__dirname, '../subapp1')));

app.listen(8889, (err) => {
  !err && console.log('8889端口成功');
});

⚠️:如果是dev模式,记得在webpack的热更新服务器中配置允许跨域,如果你对webpack不是很熟悉,可以看我之前的文章:

万字硬核 从零实现webpack热更新HMR

原创:如何自己实现一个简单的webpack构建工具 【附源码】


这里我使用nodemon启用静态资源服务器,简单为主,如果你没有下载,可以:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
npm i nodemon -g 
或
yarn add nodemon global 

这样我们先访问下8889,8890端口,看是否能访问到。

访问8889和8890都可以访问到对应的资源,成功


正式开启启用我们的微前端框架pangu.封装start方法,启用需要挂载的APP。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export function start(){
    loadApp()
}

注册子应用subapp1,subapp2,并且手动启用微前端

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { registryApp, start } from './src/index';
registryApp('localhost:8889', (location) => location.pathname === '/subapp1');
registryApp('localhost:8890', (location) => location.pathname === '/subapp2');
start()

修改index.html文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <div>
        <h1>基座</h1>
        <div class="subapp">
            <div>
                <a href="/subapp1">子应用1</a>
            </div>
            <div>
                <a href="/subapp2">子应用2</a>
            </div>
        </div>
        <div id="subApp"></div>
    </div>
</body>
<script src="./index.js"></script>

</html>

ok,运行代码,发现挂了,为什么会挂呢?因为那边返回的是html文件,我这里用的fetch请求,JSON解析不了

那么我们去看看别人的微前端和第三方库的源码吧,例如import-html-entry这个库

由于之前我解析过qiankun这个微前端框架源码,我这里就不做过度讲解,它们是对fetch做了一个text()。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export async function loadApp() {
  const shouldMountApp = Apps.filter(shouldBeActive);
  console.log(shouldMountApp, 'shouldMountApp');
  //   const res = await axios.get(shouldMountApp.entry);
  fetch(shouldMountApp.entry)
    .then(function (response) {
      return response.text();
    })
    .then(function (myJson) {
      console.log(myJson, 'myJson');
    });
}

然后我们已经可以得到拉取回来的html文件了(此时是一个字符串)

由于现实的项目,一般这个html文件会包含js和css的引入标签,也就是我们目前的单页面项目,类似下面这样:

于是我们需要把脚本、样式、html文件分离出来。用一个对象存储

本想照搬某个微前端框架源码的,但是觉得它写得也就那样,今天又主要讲原理,还是自己写一个能跑的把,毕竟html的文件都回来了,数据处理也不难

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export async function loadApp() {
  const shouldMountApp = Apps.filter(shouldBeActive);
  console.log(shouldMountApp, 'shouldMountApp');
  //   const res = await axios.get(shouldMountApp.entry);
  fetch(shouldMountApp[0].entry)
    .then(function (response) {
      return response.text();
    })
    .then(function (text) {
      const dom = document.createElement('div');
      dom.innerHTML = text;
      console.log(dom, 'dom');
    });
}

先改造下,打印下DOM

发现已经能拿到dom节点了,那么我先处理下,让它展示在基座中

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
export async function loadApp() {
  const shouldMountApp = Apps.filter(shouldBeActive);
  console.log(shouldMountApp, 'shouldMountApp');
  //   const res = await axios.get(shouldMountApp.entry);
  fetch(shouldMountApp[0].entry)
    .then(function (response) {
      return response.text();
    })
    .then(function (text) {
      const dom = document.createElement('div');
      dom.innerHTML = text;
      const content = dom.querySelector('h1');
      const subapp = document.querySelector('#subApp-content');
      subapp && subapp.appendChild(content);
    });
}

此时,我们已经可以加载不同的子应用了。

乞丐版的微前端框架就完成了,后面会逐步完善所有功能,向主流的微前端框架靠拢,并且完美支持IE11.记住它叫:pangu

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
实现微前端的十种方式 【第二种】
实现微前端的十种方式 【二】 实现微前端,我想了一想,大概有十种方式 想学习微前端的小伙伴,可以看我之前对微前端源码解析、加载方式、以及我开源的微前端框架chunchao源码 简单的文章,通俗易懂,感觉不错记得点个在看和关注哦 目前主流的微前端实现方式(基座加载式) 以基座为入口,配置不同的子应用入口地址,达到实现微前端的效果 目前微前端开源的框架:chunchao、qiankun,其中chunchao仅仅200行代码就实现了,是一个非常值得定制开发的微前端雏形框架 微前端基座模式配置 加载示意图 如何
Peter谭金杰
2020/07/08
6890
微前端框架chunchao(春潮)开源啦
写在开头 为了让大家更能理解微前端的工作模式,微前端的最佳实践应该还需要探索 乞丐版微前端框架chunchao源码开源,仅仅为了让大家学习微前端的工作模式而已,实际项目中,我们有使用Paas模式,web components,git submodule等模式都可以实现微前端,当然业内肯定有独特的、优于这些模式的微前端实现 正式开始 推荐你先看我之前的几篇文章,这样才能更好的阅读本文 如果你有什么问题想跟我交流,可以加入我们的专业微前端交流群/技术交流群 往期我的原创推荐: 深度:从零编写一个微前端框架 微前
Peter谭金杰
2020/06/16
7990
微前端框架 之 single-spa 从入门到精通
https://juejin.im/post/6862661545592111111
coder_koala
2020/09/08
3.3K0
微前端框架 之 single-spa 从入门到精通
【微前端】singleSpa&importHTMLEntry(流程图)源码解析
我们需要在基座中注册子应用,比如下面代码中,我们注册了对应的映射路径 path 以及对应的加载的方法
曾高飞
2025/05/22
680
初探 MicroApp,一个极致简洁的微前端框架
在微前端的领域里,相信大家都听说过阿里的 qiankun。我自己在上几个月也一直用它来做一些实践:
写代码的海怪
2022/03/30
1.7K0
初探 MicroApp,一个极致简洁的微前端框架
微前端08 : single-spa中的reroute函数
在开始本文之前,我们将微前端07 : 对single-spa的路由管理及微应用状态管理的分析中的微应用状态切换流程图放到这里,方便大家阅读本文的时候进行回顾:
杨艺韬
2022/09/27
4750
微前端08 : single-spa中的reroute函数
深入浅出微前端
在微前端出现之前,一个系统的前端开发模式基本都是单仓库,包含了所有的功能、代码...
Careteen
2022/02/14
3.4K0
深入浅出微前端
微前端06 : single-spa的注册机制
从整体上看,registerApplication一共做了4件比较重要的事情。首先,是对参数进行处理,对应代码片段1中的关键点1,参数处理函数sanitizeArguments有几十行代码,具体怎么处理的,逻辑相对简单,这里就不描述了。对参数的合理处理,给用户提供了更多的灵活性,可以通过不同形式来传递参数,然后将不同格式的参数处理成统一格式。同时,对参数进行了校验。这种写法很常见,在我们日常编程中可以借鉴。其次,是将微应用保存到数组apps中,apps是一个全局变量,会存放所有的注册过的微应用。这个数组很重要,微应用的各种状态都保存在这里,实际上single-spa的核心工作就是对apps中保存的微应用进行管理和控制。再次,是调用ensureJQuerySupport函数对JQuery的某些监听事件进行拦截,下文中进行详述。最后,是调用reroute函数,主要是加载微应用,下文中会进行详述。
杨艺韬
2022/09/27
5250
微前端07 : 对single-spa的路由管理及微应用状态管理的分析
从流程图中,关于路由管理的初始化,single-spa做了4件事情。我们同时看看代码:
杨艺韬
2022/09/27
1.5K0
微前端07 : 对single-spa的路由管理及微应用状态管理的分析
从 Prompt 来看微前端路由劫持原理
在结合微前端框架 icestark 使用时,跳转到同一微应用的其他路由,会产生异常的效果:Prompt 弹窗了两次。
coder_koala
2021/08/26
1.1K0
从零到一实现企业级微前端框架,保姆级教学
•如何进行路由劫持•如何渲染子应用•如何实现 JS 沙箱及样式隔离•提升体验性的功能
winty
2021/11/05
7630
从零到一实现企业级微前端框架,保姆级教学
微前端框架是怎么导入加载子应用的 【3000字精读】
微前端似乎是最近一个很火的话题,我们也即将使用在生产环境中,接下来会更新一系列微前端源码分析、手写微前端文章
Peter谭金杰
2020/05/09
2.5K0
微前端框架是怎么导入加载子应用的 【3000字精读】
微前端概述
Tech 导读 本文由浅到深地对微前端进行了概括性介绍,读者可以了解到微前端的概念、微前端的特点与价值、微前端的实现方案、一个微前端框架应具备的功能,以及微前端的适用场景。读者可以多关注下本文提到的各个开源的优秀的微前端实现方案,通过对比及借鉴来实现一套适合自身业务的微前端方案。 01 微前端是什么 传统的分而治之的策略已经无法应对现代 Web 应用的复杂性,因此衍生出了微前端这样一种新的架构模式,与后端微服务相同,它同样是延续了分而治之的设计模式,不过却以全新的方法来实现。微前端是一种由独立交付的多个前
京东技术
2022/03/17
1.7K0
微前端02 : 乾坤的微应用加载流程分析(从微应用的注册到loadApp方法内部实现)
我们在微前端01 : 乾坤的Js隔离机制原理剖析(快照沙箱、两种代理沙箱)一文中提到过,乾坤建立在single-spa的基础上,相对于single-spa,乾坤主要完成了两件事,微应用的加载和资源隔离。本文主要探讨乾坤中微应用的加载过程。
杨艺韬
2022/09/27
3K0
微前端02 : 乾坤的微应用加载流程分析(从微应用的注册到loadApp方法内部实现)
【微前端】10分钟学会乾坤大挪移
今天刚刚学习了一个微前端框架——乾坤,正着热乎劲,写一篇入门博客。这篇文章不会讨论太多的原理和实现,只是一个入门写 Hello World 的教程。
童欧巴
2021/08/20
1.3K0
【微前端】10分钟学会乾坤大挪移
微前端qiankun从搭建到部署的实践总结
最近负责的新项目用到了qiankun,写篇文章分享下实战中遇到的一些问题和思考。
coder_koala
2021/09/22
2.4K0
微前端qiankun从搭建到部署的实践总结
2022年了你必须要学会搭建微前端项目及部署方式
一、微前端简介 微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用可以独立运行、独立开发、独立部署。 微前端的好处 应用自治。只需要遵循统一的接口规范或者框架,以便于系统集成到一起,相互之间是不存在依赖关系的。 单一职责。每个前端应用可以只关注于自己所需要完成的功能。 技术栈无关。你可以使用 Angular 的同时,又可以使用 React 和 Vue。 微前端的缺点 应用的拆分基础依赖于基础设施的构建
前端进阶之旅
2022/01/06
2.5K0
2022年了你必须要学会搭建微前端项目及部署方式
微前端究竟是什么?微前端核心技术揭秘!
导语 | 微前端是将Web应用由单一的单体应用转变为多个小型前端应用聚合为一的一种手段。本文从微前端的基础理论出发,对其核心技术进行阐述,最后结合项目进行简单的应用实践。 一、微前端背景 (一)什么是微前端 微前端提供了一种技术:可以将多个独立的Web应用聚合到一起,提供统一的访问入口。一个微前端应用给用户的感观就是一个完整的应用,但是在技术角度上是由一个个独立的应用组合通过某种方式组合而成的。 为了防止概念有点抽象,可以看一个具体的例子:上图是一个微前端的demo,主应用中有导航栏,footer组件以及
腾讯云开发者
2022/02/24
2.7K0
【微前端】qiankun 到底是个什么鬼
在上一篇文章【微前端】single-spa 到底是个什么鬼[1] 聊到了 single-spa 这个框架仅仅实现了子应用的生命周期的调度以及 url 变化的监听。微前端的一个特点都没有实现,严格来说算不上微前端框架。
写代码的海怪
2022/03/30
1.8K0
手把手教你写一个简易的微前端框架
最近看了几个微前端框架的源码(single-spa、qiankun、micro-app),感觉收获良多。所以打算造一个迷你版的轮子,来加深自己对所学知识的了解。
谭光志
2022/03/24
2.7K0
手把手教你写一个简易的微前端框架
推荐阅读
相关推荐
实现微前端的十种方式 【第二种】
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验