手写微前端micro-app-页面渲染

我们可以使用循环递归的方式提取上面字符串资源中所有遍历到的link、style、script标签,提取静态资源地址并格式化标签。

在source.js文件中,添加extraSourceDom函数,用来提取link,script这种特殊标签

// 提取link的css链接
function extractSourceDom(parent, app) {
  // 将伪数组转换成数组
  const children = Array.from(parent.childNodes);
  // 递归每一个元素
  children.length && children.forEach(child => {
    extractSourceDom(child, app)
  })
  for (const dom of children) {
    // 如果是link标签
    if (dom instanceof HTMLLinkElement) {
      // 如果link标签有href属性,并且rel的属性===stylesheet,说明是css资源 
      const href = dom.getAttribute('href');
      if (dom.getAttribute('rel') === 'stylesheet' && href) {
        // 先将ref地址存放到缓存中
        app.source.links.set(href, {
          code: '',//具体的css代码内容,这个我们还需要远程获取 
        });
        // 由于link标签需要转换成style标签,所以需要删除link标签
        parent.removeChild(dom)
      }
    }else if(dom instanceof HTMLScriptElement){
      // 获取src属性
      const src = dom.getAttribute('src');
      //如果有src则证明是外联的js资源,还需要远程获取
      if(src){
        app.source.scripts.set(src,{
          code:'',
          isExternal:true
        })
      }else if(dom.textContent){
        // 内联js,随机名字当作缓存键名
        const randomName = Math.random().toString(36).substring(2,15)
        app.source.scripts.set(randomName,{
          code:dom.textContent,
          isExternal:false
        })
      }
      parent.removeChild(dom)
    }
  }
}

这里放入了map集合中,可以直接在上面的函数中,调用打印一下看看存放的值

export default function loadingHtml(app) {
  fetchSource(app.url).then(html => {
    html = html
      .replace(/<head[^>]*>[\s\S]*?<\/head>/i, (match) => {
        // 将head标签替换为micro-app-head,因为web页面只允许有一个head标签
        return match
          .replace(/<head/i, "<micro-app-head")
          .replace(/<\/head>/i, "</micro-app-head>");
      })
      .replace(/<body[^>]*>[\s\S]*?<\/body>/i, (match) => {
        // 将body标签替换为micro-app-body,防止与基座应用的body标签重复导致的问题。
        return match
          .replace(/<body/i, "<micro-app-body")
          .replace(/<\/body>/i, "</micro-app-body>");
      });

    // 将html字符串转化为DOM结构
    const htmlDom = document.createElement("div");
    htmlDom.innerHTML = html;
    // console.log("html:", htmlDom);
    //提取子应用的link或者script标签资源
    extractSourceDom(htmlDom, app)
    const microAppHead = htmlDom.querySelector('micro-app-head');
    if(app.source.links.size){
      fetchLinksFromHtml(app,microAppHead,htmlDom);
    }else{
      // 过滤link标签之后再加载到界面中
      app.onLoad(htmlDom)
    }
    // 如果有script
    if(app.source.scripts.size){
      fetchScriptFromHtml(app,htmlDom)
    }else{
      app.onLoad(htmlDom)
    }
  }).catch(e => {
    console.log('加载子应用远程报错', e);
  })
}
// 提取link中的内容,并生成style标签,把内容放进去
// style标签放入micro-app-header标签中
export function fetchLinksFromHtml(app,microAppHead,htmlDom){
  // linkEntries是二维数组
  const linkEntries = Array.from(app.source.links.entries());
  console.log(linkEntries,'linkEntries');
  // 声明远程获取的promise数组
  const fetchLinkPromise = [];
  for(const [href,source] of linkEntries){
    fetchLinkPromise.push(fetchSource(href));
  }
  Promise.all(fetchLinkPromise).then(res =>{
    for(let i = 0; i < res.length; i++){
      const code = res[i]
      // 将代码放入到缓存
      linkEntries[i][1].code = code
      // 创建style标签
      const link2Style = document.createElement('style');
      link2Style.textContent = code;
      microAppHead.appendChild(link2Style)
    }
    // 将处理好了的htmlDom挂载到micro-app
    app.onLoad(htmlDom)
  }).catch(e =>{
    console.error('远程加载css出错',e);
  })
}
// 提取script的内容并且直接执行
export function fetchScriptFromHtml(app, htmlDom) {
  // 将map转成数组
  const scriptEntries = Array.from(app.source.scripts.entries());
  // 声明远程获取的promise数组
  const fetchScriptPromise = [];
  for (let [url, info] of scriptEntries) {
    // 这里的url可能是相对地址,如果引用相对地址,就会到基座下查找js文件 
    if (!url.includes('https')) {
      url = `${app.url.endsWith('/') ? app.url.substring(0, app.url.length - 1) : app.url}${url}`;
    }
    // 内联的js直接resolve外联的就请求
    fetchScriptPromise.push(info.code ? Promise.resolve(info.code) : fetchSource(url));
  }
  Promise.all(fetchScriptPromise).then(res => {
    for (let i = 0; i < res.length; i++) {
      const code = res[i];
      scriptEntries[i][1].code = code;
    }
    app.onLoad(htmlDom)
  }).catch(e => {
    console.log('加载js资源出错', e);
  })
}

上面的代码中,把js字符串放入到eval中执行,使用了(0, eval)(info.code)这样的表达式,这其实就是一个立即执行的函数,然后将字符串info.code传给了eval。这样可以保证eval是在当前环境下执行,还是在全局环境下值。说到底,最重要的是保证如果传入字符串info.code中有this,保证this的作用域是指向全局的,因为上面的代码中,eval是放在一个函数中执行的。

效果图
在这里插入图片描述
source.js完整代码

import { fetchSource } from './utils.js';

export default function loadingHtml(app) {
  fetchSource(app.url).then(html => {
    html = html
      .replace(/<head[^>]*>[\s\S]*?<\/head>/i, (match) => {
        // 将head标签替换为micro-app-head,因为web页面只允许有一个head标签
        return match
          .replace(/<head/i, "<micro-app-head")
          .replace(/<\/head>/i, "</micro-app-head>");
      })
      .replace(/<body[^>]*>[\s\S]*?<\/body>/i, (match) => {
        // 将body标签替换为micro-app-body,防止与基座应用的body标签重复导致的问题。
        return match
          .replace(/<body/i, "<micro-app-body")
          .replace(/<\/body>/i, "</micro-app-body>");
      });

    // 将html字符串转化为DOM结构
    const htmlDom = document.createElement("div");
    htmlDom.innerHTML = html;
    // console.log("html:", htmlDom);
    //提取子应用的link或者script标签资源
    extractSourceDom(htmlDom, app)
    const microAppHead = htmlDom.querySelector('micro-app-head');
    if (app.source.links.size) {
      fetchLinksFromHtml(app, microAppHead, htmlDom);
    } else {
      // 过滤link标签之后再加载到界面中
      app.onLoad(htmlDom)
    }
    // 如果有script
    if (app.source.scripts.size) {
      fetchScriptFromHtml(app, htmlDom)
    } else {
      app.onLoad(htmlDom)
    }
  }).catch(e => {
    console.log('加载子应用远程报错', e);
  })
}
// 提取link的css链接
function extractSourceDom(parent, app) {
  // 将伪数组转换成数组
  const children = Array.from(parent.childNodes);
  // 递归每一个元素
  children.length && children.forEach(child => {
    extractSourceDom(child, app)
  })
  for (const dom of children) {
    // 如果是link标签
    if (dom instanceof HTMLLinkElement) {
      // 如果link标签有href属性,并且rel的属性===stylesheet,说明是css资源 
      const href = dom.getAttribute('href');
      if (dom.getAttribute('rel') === 'stylesheet' && href) {
        // 先将ref地址存放到缓存中
        app.source.links.set(href, {
          code: '',//具体的css代码内容,这个我们还需要远程获取 
        });
        // 由于link标签需要转换成style标签,所以需要删除link标签
        parent.removeChild(dom)
      }
    } else if (dom instanceof HTMLScriptElement) {
      // 获取src属性
      const src = dom.getAttribute('src');
      //如果有src则证明是外联的js资源,还需要远程获取
      if (src) {
        app.source.scripts.set(src, {
          code: '',
          isExternal: true
        })
      } else if (dom.textContent) {
        // 内联js,随机名字当作缓存键名
        const randomName = Math.random().toString(36).substring(2, 15)
        app.source.scripts.set(randomName, {
          code: dom.textContent,
          isExternal: false
        })
      }
      parent.removeChild(dom)
    }
  }
}
// 提取link中的内容,并生成style标签,把内容放进去
// style标签放入micro-app-header标签中
export function fetchLinksFromHtml(app, microAppHead, htmlDom) {
  // linkEntries是二维数组
  const linkEntries = Array.from(app.source.links.entries());
  console.log(linkEntries, 'linkEntries');
  // 声明远程获取的promise数组
  const fetchLinkPromise = [];
  for (const [href, source] of linkEntries) {
    fetchLinkPromise.push(fetchSource(href));
  }
  Promise.all(fetchLinkPromise).then(res => {
    for (let i = 0; i < res.length; i++) {
      const code = res[i]
      // 将代码放入到缓存
      linkEntries[i][1].code = code
      // 创建style标签
      const link2Style = document.createElement('style');
      link2Style.textContent = code;
      microAppHead.appendChild(link2Style)
    }
    // 将处理好了的htmlDom挂载到micro-app
    app.onLoad(htmlDom)
  }).catch(e => {
    console.error('远程加载css出错', e);
  })
}
// 提取script的内容并且直接执行
export function fetchScriptFromHtml(app, htmlDom) {
  // 将map转成数组
  const scriptEntries = Array.from(app.source.scripts.entries());
  // 声明远程获取的promise数组
  const fetchScriptPromise = [];
  for (let [url, info] of scriptEntries) {
    // 这里的url可能是相对地址,如果引用相对地址,就会到基座下查找js文件 
    if (!url.includes('https')) {
      url = `${app.url.endsWith('/') ? app.url.substring(0, app.url.length - 1) : app.url}${url}`;
    }
    // 内联的js直接resolve外联的就请求
    fetchScriptPromise.push(info.code ? Promise.resolve(info.code) : fetchSource(url));
  }
  Promise.all(fetchScriptPromise).then(res => {
    for (let i = 0; i < res.length; i++) {
      const code = res[i];
      scriptEntries[i][1].code = code;
    }
    app.onLoad(htmlDom)
  }).catch(e => {
    console.log('加载js资源出错', e);
  })
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/465769.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一些 AI 工具

AI 搜索&#xff1a;Phind&#xff0c;perplexity AI聊天大模型&#xff1a;chatgpt&#xff0c; kimi&#xff08;国内可用&#xff0c;支持上传文件&#xff09; AI 机器人&#xff1a;https://www.coze.com/ AI工具集&#xff1b;https://ai-bot.cn/#term-2 agent GPT&a…

【REST2SQL】13 用户角色功能权限设计

【REST2SQL】01RDB关系型数据库REST初设计 【REST2SQL】02 GO连接Oracle数据库 【REST2SQL】03 GO读取JSON文件 【REST2SQL】04 REST2SQL第一版Oracle版实现 【REST2SQL】05 GO 操作 达梦 数据库 【REST2SQL】06 GO 跨包接口重构代码 【REST2SQL】07 GO 操作 Mysql 数据库 【RE…

【每日一题】2024年3月汇编(上)

3.1【2369】检查数组是否存在有效划分 2369. 检查数组是否存在有效划分https://leetcode.cn/problems/check-if-there-is-a-valid-partition-for-the-array/ 1.这样的判断可以用动态规划来解决&#xff0c;用一个长度为(n1) 的数组来记录 是否存在有效划分&#xff0c;dp[i]…

【iOS】ARC学习

文章目录 前言一、autorelease实现二、苹果的实现三、内存管理的思考方式__strong修饰符取得非自己生成并持有的对象__strong 修饰符的变量之间可以相互赋值类的成员变量也可以使用strong修饰 __weak修饰符循环引用 __unsafe_unretained修饰符什么时候使用__unsafe_unretained …

webstorm 使用prettier格式化保存 导致代码缩进与gitlab代码不一致问题

问题 webstorm显示缩进正常 gitlab显示不正常 解决 .prettierrc.js module.exports {printWidth: 100,tabWidth: 2,useTabs: false, //设置为false 不使用tab作为缩进符semi: true,vueIndentScriptAndStyle: true,singleQuote: true,quoteProps: as-needed,bracketSpaci…

肖恩的投球游戏——前缀和

题目链接&#xff1a;1.肖恩的投球游戏 - 蓝桥云课 (lanqiao.cn) 前缀和&#xff1a; package lanqiao;import java.util.Arrays; import java.util.Scanner;/*** 2023/11/29* 前缀和问题*/ public class lanqiao3693_肖恩的投球游戏 {public static void main(String[] args) …

vue3+vite项目打包遇到的问题

一、项目打包出现空白页 vite.config.js中&#xff0c;添加base: ./ import { defineConfig } from vite import vue from vitejs/plugin-vueexport default defineConfig({base: ./, })router/index.js&#xff0c;将路由模式改成hash模式 import { createRouter, createWe…

性能分析调优模型

性能测试除了为获取性能指标外&#xff0c;更多是为了发现性能瓶颈和性能问题&#xff0c;然后针对性能问题和性能瓶颈进行分析和调优。在当今互联网高速发展的时代&#xff0c;结合传统软件系统模型以及互联网网站特征&#xff0c;性能调优的模型可以归纳总结为如图1-5-1所示的…

idea 的基本配置

一、安装目录介绍 其中&#xff1a;bin 目录下&#xff1a; 二、配置信息目录结构 这是 IDEA 的各种配置的保存目录。这个设置目录有一个特性&#xff0c;就是你删除掉整个目录之后&#xff0c;重新启动 IntelliJ IDEA 会再自动帮你生成一个全新的默认配置&#xff0c;所以很多…

力扣● 583. 两个字符串的删除操作 ● 72. 编辑距离 ● 编辑距离总结篇

● 583. 两个字符串的删除操作 注意审题&#xff1a; 给定两个单词 word1 和 word2 &#xff0c;返回使得 word1 和 word2 相同所需的最小步数。 每步 可以删除任意一个字符串中的一个字符。 删除最少的字符使两者相同&#xff0c;说明留下来的就是最大公共子序列。不要求…

01.Vue2入门

一、为什么要学习Vue 1.前端必备技能 2.岗位多&#xff0c;绝大互联网公司都在使用Vue 3.提高开发效率 4.高薪必备技能&#xff08;Vue2Vue3&#xff09; 二、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 **构建用户界面 ** 的 渐进式 …

多种双拼方案的实现

首发日期 2024-03-14, 以下为原文内容: 就像 GNU/Linux 用户, 虽然比例小, 却又分散为一堆不同的发行版. 双拼用户在拼音输入法之中的比例也很小, 同时也分为各种不同的双拼方案. 那么作为一个 双拼 输入法, 最重要的事情是什么呢 ? 嗯, 那当然是支持自定义双拼方案 ! 实际上…

网络协议与层次划分:探索计算机网络体系结构

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

AWTK slider_circle 控件发布

slider_circle 控件。 主要特色&#xff1a; 支持正向和反向支持设置滑块的半径支持背景线宽和颜色支持前景线宽和颜色支持设置是否显示值的文本支持设置起始角度和结束角度支持设置格式化值的格式字符串支持使用图片填充背景和前景 界面效果&#xff1a; 注意&#xff1a; …

【绘图案例-绘图的方式1 Objective-C语言】

一、接下来,我们来说这个,绘图的方式 1.新建一个项目,Name:04-绘图的方式, 方式:就是,我要同样画一条线,然后,用不同的代码,把它写出来,这就叫方式, 我们在storyboard里边,还拖一个UIView,这些步骤都一样, 我们来一个,宽= 300, 高 = 300 , 然后,再来一个水…

zabbix配置

1 下载zabbix 1 配置yum源 rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release- 5.0-1.el7.noarch.rpm yum clean all yum makecache fast 完成后会出现zabbix.repo文件 2安装zabbix服务 yum -y install zabbix-server-mysql zabbix-web-mysql z…

计算机网络——物理层(信道复用技术)

计算机网络——物理层&#xff08;信道复用技术&#xff09; 信道复用技术频分多址与时分多址 频分复用 FDM (Frequency Division Multiplexing)时分复用 TDM (Time Division Multiplexing)统计时分复用 STDM (Statistic TDM)波分复用码分复用 我们今天接着来看信道复用技术&am…

20W-50W厚膜无感电阻TO-220封装技术规格散热说明

EAK为设计工程师提供了一种开放式屏蔽基板器件&#xff0c;用于需要卓越热性能的应用&#xff0c;开发了一种额定功率高达 50W 的厚膜功率电阻器。该电阻器采用 TO-220 开放式屏蔽基板封装&#xff0c;并具有与基板粘合的绝缘锥形文丘里管&#xff0c;以实现最大的散热。 电阻器…

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:Counter)

计数器组件&#xff0c;提供相应的增加或者减少的计数操作。 说明&#xff1a; 该组件从API Version 7开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 子组件 可以包含子组件。 接口 Counter() 从API version 9开始&#xff0c;该接口…

Flask 专题

[CISCN2019 总决赛 Day1 Web3]Flask Message Board 查看session解密 但不知道密钥&#xff0c;题目说FLASK,那肯定就是找密钥,发现输入什么都没有显示&#xff0c;只有author那里有回显在版上&#xff0c;所以尝试sstl&#xff0c;{{config}}找到密钥 扫目录发现有admin进入…