一文读懂微前端

1 语雀文档

https://www.yuque.com/chanwj/vlkwxk/qvpv3kqws5hno3qt?singleDoc# 《微前端》
本文使用的参考文档均以链接方式粘贴于文章内,十分感谢~

2 项目github链接

如果你觉得本文档对你有用,恳请github仓库给个star~
https://github.com/OmegaChan/exampleCollection

3 软连接和硬连接

3.1 inode

inode (index node)是指在许多“类Unix文件系统”中的一种数据结构,用于描述文件系统对象(包括文件、目录、设备文件、socket、管道等)。每个inode保存了文件系统对象数据的属性和磁盘块(block)位置。文件系统对象属性包含了各种元数据(如:最后修改时间) ,也包含用户组(owner )和权限数据。
文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。
每一个文件都有对应的inode,里面包含了与该文件有关的一些信息。
我们打开一个一个文件时,系统首先找到文件名对应的 inode 号码,然后通过 inode 号码获取inode 信息,然后根据 inode 信息中的文件数据所在 block 读出数据。

3.2 软连接(soft link,也叫符号链接)

npm link 是一个全局的软链接
image.png
通过软链接创建的B文件会创建自己的inode,并且他的inode指向源文件的inode(注意:不是直接指向磁盘数据);
特点:

  • 1、访问:同一源文件通过软链接创建的不同文件指向源文件的inode,访问内容相同;
  • 2、修改:对源文件的修改会映射到所有软链接文件中;
  • 3、删除:删除源文件会一并删除源文件的inode,访问软链接文件报错No such file or directory;删除软链接文件不会影响到源文件;

命令:ln -s source target 创建软链接(target “指向” source);

3.3 硬连接

一般情况,一个文件名"唯一"对应一个 inode。但是对于硬链接机制, linux 允许多个文件都指向同一个 inode。
特点:

  • 1、访问:可以使用不同对文件名访问同样的内容;
  • 2、修改:对文件内容进行修改将映射到所有文件内容改变;
  • 3、删除:删除一个文件不影响另一个文件对访问(此时可以认为每个文件都是源文件,删除文件只是删除文件名不会删除其指向的inode);

命令:ln source target 来创建硬链接

3.4 区别

  • 使用 ln source target 建立硬链接;使用ln -s source target建立软链接;
  • 硬链接不会创建额外 inode,和源文件共用同一个 inode;软链接会创建额外一个文件和 inode,但是软链接文件inode指向源文件的 inode;
  • 建立硬链接时,source 必须存在且只能是文件;建立软链接时,source 可以不存在而且可以是目录;
  • 删除源文件不会影响硬链接文件的访问(因为inode还在);删除源文件会影响软链接文件的访问(因为指向的inode已经不存在了);
  • 对于已经建立的同名链接,不能再次建立,除非删掉或者使用 -f 参数;

https://juejin.cn/post/7013612655680159758
https://juejin.cn/post/7056581097429139463

4 npm、yarn、pnmp

4.1 npm

npm是围绕着语义版本控制(semver)的思想而设计的

  • 主版本号: 当API发生改变,并与之前的版本不兼容的时候
  • 次版本号: 当增加了功能,但是向后兼容的时候
  • 补丁版本号: 当做了向后兼容的缺陷修复的时候

npm使用一个名为package.json的文件,用户可以通过npm install --save命令把项目里所有的依赖项保存在这个文件里。
例如,运行npm install --save lodash会将以下几行添加到package.json文件中

"dependencies": {
    "lodash": "^4.17.4"
}

在版本号lodash之前有个^字符。这个字符告诉npm,安装主版本等于4的任意一个版本即可。所以如果我现在运行npm进行安装,npm将安装lodash的主版本为4的最新版,可能是 lodash@4.25.5
理论上,次版本号的变化并不会影响向后兼容性。因此,安装最新版的依赖库应该是能正常工作的,而且能引入自4.17.4版本以后的重要错误和安全方面的修复。
但是,另一方面,即使不同的开发人员使用了相同的package.json文件,在他们自己的机器上也可能会安装同一个库的不同种版本,这样就会存在潜在的难以调试的错误和“在我的电脑上…”的情形。
大多数npm库都严重依赖于其他npm库,这会导致嵌套依赖关系,并增加无法匹配相应版本的几率。
虽然可以通过npm config set save-exact true命令关闭在版本号前面使用的默认行为,但这个只会影响顶级依赖关系。由于每个依赖的库都有自己的package.json文件,而在它们自己的依赖关系前面可能会有符号,所以无法通过package.json文件为嵌套依赖的内容提供保证。
为了解决这个问题,npm提供了shrinkwrap命令。此命令将生成一个npm-shrinkwrap.json文件,为所有库和所有嵌套依赖的库记录确切的版本。
然而,即使存在npm-shrinkwrap.json这个文件,npm也只会锁定库的版本,而不是库的内容。即便npm现在也能阻止用户多次重复发布库的同一版本,但是npm管理员仍然具有强制更新某些库的权力。

npm-shrinkwrap.json与package-lock.json的区别

package-lock.json

npm5之后,在项目里会存在一个文件——package-lock.json,这是一个自动生成的文件,在我们修改pacakge.json或者node_modules时就会自动产生或更新了。这个东西不仅长得像npm-shrinkwrap.json,功能也跟它很像。

npm-shrinkwrap.json

该文件是通过运行npm shrinkwrap命令产生的,在npm5之前(不包括5),主要是用来精准控制安装指定版本的npm包。package.json通过写死当前依赖版本可以锁定当前依赖版本号,但是却无法控制依赖里面的依赖。
那么如何避免这种情况的发生呢,就是要确保安装的npm包版本一致,这时候,就是npm-shrinkwrap.json的作用了

image.png

  • 每次更新package.json或者node_modules时,如npm install新包,npm update, npm uninstall等操作,为保持协作人的资源一致,还是要手动运行npm shrinkwrap更新npm-shrinkwrap.json文件的。
  • 而且记得加入到版本控制中,不然就没意义了。
  • shrinkwrap计算时是根据当前依赖安装的目录结构生成的,如果你不能保证package.json文件定义的依赖与node_modules下已安装的依赖是匹配、无冗余的,建议在执行shrinkwrap命令前清理依赖并重新安装(rm -rf node_modules && npm install)或精简依赖(npm prune)
区别

npm版本

package-lock.json是npm5的新特性,也不向前兼容,如果npm版本是4或以下,那还是使用npm-shrinkwrap.json

npm处理机制
  • 这两个文件的优先级都比package.json高
  • 如果项目两个文件都存在,那么安装的依赖是依据npm-shrinkwrap.json来的,而忽略package-lock.json
  • 运行命令npm shrinkwrap后,如果项目里不存在package-lock.json,那么会新建一个npm-shrinkwrap.json文件,如果存在package-lock.json,那么会把package-lock.json重命名为npm-shrinkwrap.json
更新机制
  1. npm-shrinkwrap.json只会在运行npm shrinkwrap才会创建/更新
  2. package-lock.json会在修改pacakge.json或者node_modules时就会自动产生或更新了。
npm发包
  1. package-lock.json不会在发布包中出现,就算出现了,也会遭到npm的无视。
  2. npm-shrinkwrap.json可以在发布包中出现
  3. 建议库作者发布包时不要包含npm-shrinkwrap.json,因为这会阻止最终用户控制传递依赖性更新。(1. 把第三方依赖锁死了,最终用户在安装该包的时候,永远只能使用被锁死的包版本了 2.依赖的 npm 包不会被扁平化出来)

https://juejin.cn/post/6844903797668462605

npm2 vs npm3

npm2

npm 2会安装每一个包所依赖的所有依赖项。如果我们有这么一个项目,它依赖项目A,项目A依赖项目B,项目B依赖项目C,那么依赖树将如下所示,这个结构可能会很长。这对于基于Unix的操作系统来说只不过是一个小烦恼,但对于Windows来说却是个破坏性的东西,因为有很多程序无法处理超过260个字符的文件路径名。
image.png

npm3

npm 3采用了扁平依赖关系树来解决这个问题
image.png
同时,扁平依赖还带来了其他的问题,例如,上面提供了A,B,C三个测试包,假如我们再加一个D包,D包和C包一样都依赖于B包,不过D包依赖的B包版本是1.0.0,而C包依赖的B包版本则是1.0.1。
假如我们在npm或者yarn的项目中同时安装C包和D包,那么安装后的项目node_modules结构什么样呢
image.png
其实在安装依赖包时,npm/yarn会对所有依赖先进行一次排序,按照字典序kai_npm_test_c在kai_npm_test_d的前面,所以不管package.json中的顺序怎么写,被提取出来的都会是C包的依赖B包。

4.2 yarn

每个yarn安装都会生成一个类似于npm-shrinkwrap.json的yarn.lock文件,而且它是默认创建的。除了常规信息之外,yarn.lock文件还包含要安装的内容的校验和,以确保使用的库的版本相同。
像npm一样,yarn使用本地缓存。与npm不同的是,yarn无需互联网连接就能安装本地缓存的依赖项,它提供了离线模式。这个功能在2012年的npm项目中就被提出来过,但一直没有实现。

4.3 pnpm

pnpm 内部使用基于内容寻址的文件系统,来管理磁盘上依赖,减少依赖安装;node_modules/.pnmp为虚拟存储目录,该目录通过@来实现相同模块不同版本之间隔离和复用,由于它只会根据项目中的依赖生成,并不存在提升。
它采用了一种巧妙的方法,利用硬链接和符号链接来避免复制所有本地缓存源文件,这是yarn的最大的性能弱点之一。pnpm的依赖管理则用了另外的一种方式

image.png

可以看出node_modules下的目录非常简洁,我们这里只安装了C包,所以node_modules下就有了kai_npm_test_c文件夹,但是注意,这里其实是一个软链接(图中可以看到C包的右边有个弯曲的箭头),关于什么是软链接,简单说下,软链接(soft link),也叫符号链接(symbolic link),是指向另一个文件的特殊文件,可以简单理解为一个快捷方式。
关于 Node 的模块解析算法,Node在解析模块时,会忽略符号链接,直接解析其真正位置。
如下图,.pnpm下的目录结构,可以发现其目录结构是扁平的结构,并且其目录的名称都是包名加上@符号和版本号。

image.png

点开kai_npm_test_c@1.0.1,如下图,可以看到上面说到的C包软链接的真正位置是在kai_npm_test_c@1.0.1下的node_modules文件夹下的kai_npm_test_c文件夹。这里的kai_npm_test_c文件夹就是全局对应依赖的硬链接,而由于C包依赖B包,所以该node_modules文件夹下还有个kai_npm_test_b文件夹,这里是个软链接,真正位置在.pnpm下的kai_npm_test_b@1.0.1/node_modules/kai_npm_test_b。
同理,由于B包依赖A包,所以目录kai_npm_test_b@1.0.1/node_modules下也会有个软链接的kai_npm_test_a,其真正位置就是.pnpm下的kai_npm_test_a@1.0.1/node_modules/kai_npm_test_a

image.png

总的来说,项目根目录下的node_modules文件夹下的各个依赖文件夹都是软链接,而 .pnpm 文件夹下有所有依赖的扁平化结构,以依赖名加版本号命名目录名,其目录下的node_modules下有个相同依赖名的目录,是硬链接,除了相同依赖名的目录,如果该依赖还有其他的依赖,也会展示在同级下,是软链接,它们的真正位置也是扁平在.pnpm项目下的对应位置的硬链接。
提供了A,B,C三个测试包,假如我们再加一个D包,D包和C包一样都依赖于B包,不过D包依赖的B包版本是1.0.0,而C包依赖的B包版本则是1.0.1

image.png

可以发现.pnpm中有两个不同版本的B包,可以说pnpm的这种结构看上去更加的直观清晰。

4.4 安全性

C包依赖B包,B包依赖A包,假如我们在npm和yarn的项目中只安装C包,由于node_modules下是扁平化结构,所以其实我们在项目中可以正常引用A包。类似A包这种依赖也被称为幽灵依赖(Phantom dependencies)。

import { AName } from "kai_npm_test_a";
console.log(AName);

只装C包的情况下上面的代码可以正常执行。即使上线也可以正常运行。
但是这里面其实有潜在的风险,由于我们没有在package.json中声明直接使用的A包,所以我们并不知道B包中的A包依赖的版本控制是怎么样的,万一有天B包更新了,换了一个版本的A包或者干脆不用A包了,我们的代码可能会直接报错。
而在pnpm的项目中,由于其node_modules结构(如上面所说),在只装C包的情况下无法直接引用A包,直接引用会报错,只有真正在依赖项中的包才能访问。一定程度上保障了安全性。
https://juejin.cn/post/7098260404735836191
https://zhuanlan.zhihu.com/p/37653878

5 webcomponets

5.1 自定义元素

<user-card></user-card>

5.2 customElements.define()

使用浏览器原生的customElements.define()方法,告诉浏览器元素与这个类关联。

class UserCard extends HTMLElement {
  constructor() {
    super();
  }
}

window.customElements.define('user-card', UserCard);

5.3 自定义内容

class UserCard extends HTMLElement {
  constructor() {
    super();

    var image = document.createElement('img');
    image.src = 'https://semantic-ui.com/images/avatar2/large/kristy.png';
    image.classList.add('image');

    var container = document.createElement('div');
    container.classList.add('container');

    var name = document.createElement('p');
    name.classList.add('name');
    name.innerText = 'User Name';

    var email = document.createElement('p');
    email.classList.add('email');
    email.innerText = 'yourmail@some-email.com';

    var button = document.createElement('button');
    button.classList.add('button');
    button.innerText = 'Follow';

    container.append(name, email, button);
    this.append(image, container);
  }
}

上面代码最后一行,this.append()的this表示自定义元素实例。
完成这一步以后,自定义元素内部的 DOM 结构就已经生成了。

5.4 template标签

Web Components API 提供了标签,可以在它里面使用 HTML 定义 DOM。

<template id="userCardTemplate">
  <img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
  <div class="container">
    <p class="name">User Name</p>
    <p class="email">yourmail@some-email.com</p>
    <button class="button">Follow</button>
  </div>
</template>

改写一下自定义元素的类,为自定义元素加载。

class UserCard extends HTMLElement {
  constructor() {
    super();

    var templateElem = document.getElementById('userCardTemplate');
    var content = templateElem.content.cloneNode(true);
    this.appendChild(content);
  }
}  

获取节点以后,克隆了它的所有子元素,这是因为可能有多个自定义元素的实例,这个模板还要留给其他实例使用,所以不能直接移动它的子元素。
到这一步为止,完整的代码如下。

<body>
  <user-card></user-card>
  <template>...</template>

  <script>
    class UserCard extends HTMLElement {
      constructor() {
        super();

        var templateElem = document.getElementById('userCardTemplate');
        var content = templateElem.content.cloneNode(true);
        this.appendChild(content);
      }
    }
    window.customElements.define('user-card', UserCard);    
  </script>
</body>

5.5 添加样式

可以给它指定全局样式

user-card {
  /* ... */
}

但是,组件的样式应该与代码封装在一起,只对自定义元素生效,不影响外部的全局样式。所以,可以把样式写在里面。

<template id="userCardTemplate">
  <style>
   :host {
     display: flex;
     align-items: center;
     width: 450px;
     height: 180px;
     background-color: #d4d4d4;
     border: 1px solid #d5d5d5;
     box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.1);
     border-radius: 3px;
     overflow: hidden;
     padding: 10px;
     box-sizing: border-box;
     font-family: 'Poppins', sans-serif;
   }
   .image {
     flex: 0 0 auto;
     width: 160px;
     height: 160px;
     vertical-align: middle;
     border-radius: 5px;
   }
   .container {
     box-sizing: border-box;
     padding: 20px;
     height: 160px;
   }
   .container > .name {
     font-size: 20px;
     font-weight: 600;
     line-height: 1;
     margin: 0;
     margin-bottom: 5px;
   }
   .container > .email {
     font-size: 12px;
     opacity: 0.75;
     line-height: 1;
     margin: 0;
     margin-bottom: 15px;
   }
   .container > .button {
     padding: 10px 25px;
     font-size: 12px;
     border-radius: 5px;
     text-transform: uppercase;
   }
  </style>

  <img src="https://semantic-ui.com/images/avatar2/large/kristy.png" class="image">
  <div class="container">
    <p class="name">User Name</p>
    <p class="email">yourmail@some-email.com</p>
    <button class="button">Follow</button>
  </div>
</template>

样式里面的:host伪类,指代自定义元素本身。

5.6 自定义元素的参数

<user-card
  image="https://semantic-ui.com/images/avatar2/large/kristy.png"
  name="User Name"
  email="yourmail@some-email.com"
></user-card>

代码也相应改造。

<template id="userCardTemplate">
  <style>...</style>

  <img class="image">
  <div class="container">
    <p class="name"></p>
    <p class="email"></p>
    <button class="button">Follow John</button>
  </div>
</template>

最后,改一下类的代码,把参数加到自定义元素里面。

class UserCard extends HTMLElement {
  constructor() {
    super();

    var templateElem = document.getElementById('userCardTemplate');
    var content = templateElem.content.cloneNode(true);
    content.querySelector('img').setAttribute('src', this.getAttribute('image'));
    content.querySelector('.container>.name').innerText = this.getAttribute('name');
    content.querySelector('.container>.email').innerText = this.getAttribute('email');
    this.appendChild(content);
  }
}
window.customElements.define('user-card', UserCard); 

5.7 Shadow DOM

我们不希望用户能够看到的内部代码,Web Component 允许内部代码隐藏起来,这叫做 Shadow DOM,即这部分 DOM 默认与外部 DOM 隔离,内部任何代码都无法影响外部。
自定义元素的this.attachShadow()方法开启 Shadow DOM

class UserCard extends HTMLElement {
  constructor() {
    super();
    var shadow = this.attachShadow( { mode: 'closed' } );

    var templateElem = document.getElementById('userCardTemplate');
    var content = templateElem.content.cloneNode(true);
    content.querySelector('img').setAttribute('src', this.getAttribute('image'));
    content.querySelector('.container>.name').innerText = this.getAttribute('name');
    content.querySelector('.container>.email').innerText = this.getAttribute('email');

    shadow.appendChild(content);
  }
}
window.customElements.define('user-card', UserCard);

上面代码中,this.attachShadow()方法的参数{ mode: ‘closed’ },表示 Shadow DOM 是封闭的,不允许外部访问。

5.8 组件的扩展

交互,在类里面监听各种事件

this.$button = shadow.querySelector('button');
this.$button.addEventListener('click', () => {
  // do something
});

与网页代码放在一起,其实可以用脚本把注入网页。这样的话,JavaScript 脚本跟就能封装成一个 JS 文件,成为独立的组件文件。网页只要加载这个脚本,就能使用组件。
https://www.ruanyifeng.com/blog/2019/08/web_components.html

5.9 生命周期函数

image.png
image.png
https://zhuanlan.zhihu.com/p/445774159

6 monorepo

Monorepo 是一种项目代码管理方式,指单个仓库中管理多个项目,有助于简化代码共享、版本控制、构建和部署等方面的复杂性,并提供更好的可重用性和协作性。

6.1 Monorepo 演进

阶段一:单仓库巨石应用, 一个 Git 仓库维护着项目代码,随着迭代业务复杂度的提升,项目代码会变得越来越多,越来越复杂,大量代码构建效率也会降低,最终导致了单体巨石应用,这种代码管理方式称之为 Monolith。
阶段二:多仓库多模块应用,于是将项目拆解成多个业务模块,并在多个 Git 仓库管理,模块解耦,降低了巨石应用的复杂度,每个模块都可以独立编码、测试、发版,代码管理变得简化,构建效率也得以提升,这种代码管理方式称之为 MultiRepo。
阶段三:单仓库多模块应用,随着业务复杂度的提升,模块仓库越来越多,MultiRepo这种方式虽然从业务上解耦了,但增加了项目工程管理的难度,随着模块仓库达到一定数量级,会有几个问题:跨仓库代码难共享;分散在单仓库的模块依赖管理复杂(底层模块升级后,其他上层依赖需要及时更新,否则有问题);增加了构建耗时。于是将多个项目集成到一个仓库下,共享工程配置,同时又快捷地共享模块代码,成为趋势,这种代码管理方式称之为 MonoRepo。
image.png
https://www.modb.pro/db/626876
https://juejin.cn/post/7202441529150767162

7 微前端方案

image.png

  • 单个前端部分可独立开发、测试和部署;
  • 无需重新构建即可添加、移除或替换单个前端部分;
  • 不同的前端部分可使用不同的技术构建;
  • 解决iframe硬隔离的一些问题
  • 技术无关:主框架不限制子应用使用的技术栈,子应用有充分的自主权。

image.png

方案

7.1 iframe

浏览器原生,不从体验角度上来看几乎是最可靠的微前端方案了。主应用通过 iframe 来加载子应用,iframe 自带的样式、环境隔离机制使得它具备天然的沙盒机制,但也是由于它的隔离性导致其并不适合作为加载子应用的加载器,iframe 的特性不仅会导致用户体验的下降,也会在研发在日常工作中造成较多困扰。

特点
  1. 接入比较简单
  2. 隔离非常稳完美
不足
  1. dom割裂感严重,弹框只能在iframe,而且有滚动条
  2. 通讯非常麻烦,而且刷新iframe url状态丢失
  3. 前进后退按钮无效
7.2 Single-Spa

作为比较早的微前端框架,single-spa只是实现了加载器、路由托管。沙箱隔离并没有实现。

特性:
  1. 加载子应用:single-spa 是一个顶层的路由器。当一个路由被激活时,它下载并执行该路由的代码
  2. 生命周期:bootstrap, mount, unmount, unload
  3. JS Entry.
  4. 注册子应用时支持懒加载
  5. 通信:props: customProps
7.2.1 Single-Spa之qiankun 方案

qiankun 方案是基于 single-spa 的微前端方案。

特点
  1. html entry 的方式引入子应用,相比 js entry 极大的降低了应用改造的成本;
  2. 完备的沙箱方案,js 沙箱做了 SnapshotSandbox、LegacySandbox、ProxySandbox 三套渐进增强方案,css 沙箱做了 strictStyleIsolation、experimentalStyleIsolation 两套适用不同场景的方案;
  3. 做了静态资源预加载能力;
不足
  1. 适配成本比较高,工程化、生命周期、静态资源路径、路由等都要做一系列的适配工作;
  2. css 沙箱采用严格隔离会有各种问题,js 沙箱在某些场景下执行性能下降严重;
  3. 无法同时激活多个子应用,也不支持子应用保活;
  4. 无法支持 vite 等 esmodule 脚本运行;

底层原理 js沙箱使用的是proxy进行快照然后用用 with(window){} 包裹起来 with内的window其实就是proxy.window 我们声明变量 var name = ‘小满’ 实际这个变量挂到了proxy.window 并不是真正的window css沙箱原理 第一个就是shadowDom隔离 第二个类似于Vue的scoped [data-qiankun-426732]

7.3 webComponent

原理见上文

7.3.1 webcomponet之micro-app 方案

micro-app 是基于 webcomponent + qiankun sandbox 的微前端方案。
image.png

特点
  1. 使用 webcomponet 加载子应用相比 single-spa 这种注册监听方案更加优雅;
  2. 复用经过大量项目验证过 qiankun 的沙箱机制也使得框架更加可靠;
  3. 组件式的 api 更加符合使用习惯,支持子应用保活;
  4. 降低子应用改造的成本,提供静态资源预加载能力;
不足
  1. 接入成本较 qiankun 有所降低,但是路由依然存在依赖; (虚拟路由已解决)
  2. 多应用激活后无法保持各子应用的路由状态,刷新后全部丢失; (虚拟路由已解决)
  3. css 沙箱依然无法绝对的隔离,js 沙箱做全局变量查找缓存,性能有所优化;
  4. 支持 vite 运行,但必须使用 plugin 改造子应用,且 js 代码没办法做沙箱隔离;
  5. 对于不支持 webcompnent 的浏览器没有做降级处理;

底层原理 js隔离跟qiankun类似也是使用proxy + with,css隔离自定义前缀类似于scoped

7.3.2 webcomponet之无界方案

它是一种基于 Web Components + iframe 的全新微前端方案,继承iframe的优点,补足 iframe 的缺点,让 iframe 焕发新生。
Web Components 是一个浏览器原生支持的组件封装技术,可以有效隔离元素之间的样式,iframe 可以给子应用提供一个原生隔离的运行环境,相比自行构造的沙箱 iframe 提供了独立的 window、document、history、location,可以更好的和外部解耦

特点
  1. 接入简单只需要四五行代码
  2. 不需要针对vite额外处理
  3. 预加载
  4. 应用保活机制
不足
  1. 隔离js使用一个空的iframe进行隔离
  2. 子应用axios需要自行适配
  3. iframe沙箱的src设置了主应用的host,初始化iframe的时候需要等待iframe的location.orign从’about:blank’初始化为主应用的host,这个采用的计时器去等待的不是很优雅。

底层原理 使用shadowDom 隔离css,js使用空的iframe隔离,通讯使用的是proxy
https://juejin.cn/post/7212603829572911159?searchId=202311081100583F87C24635E52A8F02E6#heading-3
https://juejin.cn/post/7236021829000691771
https://juejin.cn/post/7161677082211123207#heading-20

7.4 EMP

EMP 方案是基于 webpack 5 module federation 的微前端方案。
image.png

特点
  1. webpack 联邦编译可以保证所有子应用依赖解耦;
  2. 应用间去中心化的调用、共享模块;
  3. 模块远程 ts 支持;
不足
  1. 对 webpack 强依赖,老旧项目不友好;
  2. 没有有效的 css 沙箱和 js 沙箱,需要靠用户自觉;
  3. 子应用保活、多应用激活无法实现;
  4. 主、子应用的路由可能发生冲突;

底层原理 这个东西有点类似于拆包,也可以叫模块共享,例如React有个模块可以共享给Vue项目用Vue2的组件可以共享给Vue3用。

使用

remote
  • 路由直接引入远程组件加载 const AReact = lazy(() => import(‘remote_app_a/AReact’));
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'remote-app-a',
      filename: 'remoteEntry.js',
      // 需要暴露的模块
      exposes: {
        './AReact': './src/App.tsx',
      },
      shared: ['react'],
    }),
  ],
  build: {
    target: 'esnext',
  },
});

host
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import federation from '@originjs/vite-plugin-federation';

export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'host-app',
      filename: 'remoteEntry.js',
      remotes: {
        remote_app_a: "http://127.0.0.1:4174/assets/remoteEntry.js",
      },
      shared: ['react'],
    }),
  ],
  build: {
    target: 'esnext',
  },
});

8 无界实例

npm i pnpm -g
pnpm create vite main --template vue-ts

新建子应用文件夹web

cd web
pnpm create vite react-demo --template react-ts
pnpm create vite vue-demo --template vue-ts

回到根目录

cd ..
pnpm init

根目录新建pnpm-workspace.yaml
配置

packages:
  # all packages in direct subdirs of packages/
  - 'main'
  # all packages in subdirs of components/
  - 'web/**'
pnpm i

分别启动主应用、子应用即可

cd main
pnpm i wujie-vue3

在主应用main.ts中

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import WujieVue from 'wujie-vue3'

createApp(App).use(WujieVue).mount('#app')

引入子应用

<template>
  <div>主应用 start</div>
  <div>
    <a href="https://vitejs.dev" target="_blank">
      <img src="/vite.svg" class="logo" alt="Vite logo" />
    </a>
    <a href="https://vuejs.org/" target="_blank">
      <img src="./assets/vue.svg" class="logo vue" alt="Vue logo" />
    </a>
  </div>
  <HelloWorld msg="Vite + Vue" />
  <div>主应用 end</div>
  <div>
    子应用
  </div>
  <WujieVue name="react" url="http://localhost:5174/"></WujieVue>
  <WujieVue name="vue" url="http://localhost:5175/"></WujieVue>
</template>

效果:
image.png

最后:
如果你觉得本文档对你有用,恳请github仓库给个star~
https://github.com/OmegaChan/exampleCollection

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

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

相关文章

231112-中文错别字识别与纠正问题的大模型与小模型调研

A. 引言 当前&#xff0c;以ChatGPT为代表的大语言模型&#xff08;Large Language Models, LLMs&#xff09;正引领着新一轮工业革命。ChatGPT最开始的研究领域隶属于NLP的一个子问题&#xff0c;其输入是text&#xff0c;输出也是text。在从文本输入到文本输出的诸多应用场景…

No177.精选前端面试题,享受每天的挑战和学习

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云课上架的前后端实战课程《Vue.js 和 Egg.js 开发企业级健康管理项目》、《带你从入…

PgSQL技术内幕-Analyze做的那些事-pg_statistic系统表

PgSQL的优化器为一个查询生成一个执行效率相对较高的物理执行计划树。执行效率的高低依赖于代价估算。比如估算查询返回的记录条数、记录宽度等&#xff0c;就可以计算出IO开销&#xff1b;也可以根据要执行的物理操作估算出CPU代价。那么估算依赖的信息来源哪呢&#xff1f;系…

代理模式-静态动态代理-jdk动态代理-cglib动态代理

代理模式 静态代理 动态代理&#xff1a;jdk动态代理 cglib动态代理 注意 &#xff1a;下面的代码截图 要配合文字去看 我对代码的每一步都做了解释 所以需要配合图片观看提取吗1111https://pan.baidu.com/s/1OxQSwbQ--t5Zvmwzjh1T0A?pwd1111 这里直接把项目文件 及代码 …

【Seata源码学习 】 AT模式 第一阶段 @GlobalTransaction的扫描

1. SeataAutoConfiguration 自动配置类的加载 基于SpringBoot的starter机制&#xff0c;在应用上下文启动时&#xff0c;会加载SeataAutoConfiguration自动配置类 # Auto Configure org.springframework.boot.autoconfigure.EnableAutoConfigurationio.seata.spring.boot.aut…

外中断的应用

前言 软件基础操作参考这篇博客&#xff1a; LED数码管的静态显示与动态显示&#xff08;KeilProteus&#xff09;-CSDN博客https://blog.csdn.net/weixin_64066303/article/details/134101256?spm1001.2014.3001.5501实验一&#xff1a;P1口上接8个LED灯&#xff0c;在外部…

cesium如何实现区域下钻

首先&#xff0c;这里讲一下数据源&#xff0c;数据源是拷贝的DataV.GroAtlas里的数据&#xff0c;这里整合了一下之前发的区域高亮的代码来实现的&#xff0c;单击左键使得区域高亮&#xff0c;每次点击都移除上一次点击的模块&#xff0c;双击左键&#xff0c;实现区域下钻并…

C++ 对象的拷贝、赋值、清理和移动(MyString类)

MyString类 MyString.h #ifndef MYSTRING_H #define MYSTRING_H#include <iostream> using namespace std;class MyString {private:char* str nullptr;unsigned int MaxSize 0;public:MyString(); /*默认构造函数*/MyString(unsigned int n); /*有…

SpringBoot系列-2 自动装配

背景&#xff1a; Spring提供了IOC机制&#xff0c;基于此我们可以通过XML或者注解配置&#xff0c;将三方件注册到IOC中。问题是每个三方件都需要经过手动导入依赖、配置属性、注册IOC&#xff0c;比较繁琐。 基于"约定优于配置"原则的自动装配机制为该问题提供了一…

recycleView(二)Grid,中间有间距,left,right,top,bottom没有间距

1.作用 1.效果图 item的top&#xff0c;bottom&#xff0c;right&#xff0c;left都是0 2.代码 1.关键代码 // 设置RecycleView的item间的间距&#xff0c;上下间距为20排序&#xff0c;左右间距为20排序binding.rv.addItemDecoration(object : RecyclerView.ItemDecorat…

算法——滑动窗口

什么是窗口&#xff1f;就是符合题目要求的区域内的数据&#xff0c;将每次符合数据的窗口内的数据记录下来&#xff0c;然后将窗口后移&#xff0c;寻找其他符合要求的数据&#xff0c;每次进入窗口和退出窗口都需要一定的要求 一、 LCR 008. 长度最小的子数组 - 力扣&#…

数据结构—二叉树的模拟实现(c语言)

目录 一.前言 二.模拟实现链式结构的二叉树 2.1二叉树的底层结构 2.2通过前序遍历的数组"ABD##E#H##CF##G##"构建二叉树 2.3二叉树的销毁 2.4二叉树查找值为x的节点 2.5二叉树节点个数 2.6二叉树叶子节点个数 2.7二叉树第k层节点个数 三.二叉树的遍历 3.1…

Keras实现图注意力模型GAT

简介&#xff1a;本文实现了一个GAT图注意力机制的网络层&#xff0c;可以在Keras中像调用Dense网络层、Input网络层一样直接搭积木进行网络组合。 一&#xff0c;基本展示 如下图所示&#xff0c;我们输入邻接矩阵和节点特征矩阵之后&#xff0c;可以直接调用myGraphAttention…

C语言之pthread_once实例总结(八十三)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

史上最全最新Ubuntu20.04安装教程(图文)

总的来说&#xff0c;安装Ubantu包含以下三个步骤&#xff1a; 一、安装虚拟机 二、Ubuntu镜像下载 三、虚拟机配置 一、安装虚拟机 选择安装VMware Workstation&#xff0c;登录其官网下载安装包&#xff0c;链接如下&#xff1a; 下载 VMware Workstation Pro​www.vmwa…

设计模式之--原型模式(深浅拷贝)

原型模式 缘起 某天&#xff0c;小明的Leader找到小明:“小明啊&#xff0c;如果有个发简历的需求&#xff0c;就是有个简历的模板&#xff0c;然后打印很多份&#xff0c;要去一份一份展示出来&#xff0c;用编程怎么实现呢&#xff1f;” 小明一听&#xff0c;脑袋里就有了…

ARM64 linux并发与同步之内存屏障

1.2 内存屏障 1.2.1 概念理解 原理部分比较苦涩难懂&#xff0c;我们先不过多详细介绍这部分的由来和经过&#xff0c;接下来着重讲解什么用途和实现&#xff1b; ARM64架构中提供了3条内存屏障指令。 数据存储屏障(Data Memory Barrier, DMB)指令。数据同步屏障(Data Synch…

劲松HPV防治诊疗中心发布:HPV感染全面防治解决方案

在当今社会&#xff0c;HPV(人乳头瘤病毒)感染问题已成为广大公众关注的焦点。作为一种高度传染性的病毒&#xff0c;HPV感染不仅可能导致生殖器疣&#xff0c;还可能引发各种癌症。面对这一严重威胁&#xff0c;劲松HPV防治诊疗中心以其专业的医疗团队、正规的治疗流程和全方位…

Python基础入门例程51-NP51 列表的最大与最小(循环语句)

最近的博文&#xff1a; Python基础入门例程50-NP50 程序员节&#xff08;循环语句&#xff09;-CSDN博客 Python基础入门例程49-NP49 字符列表的长度-CSDN博客 Python基础入门例程48-NP48 验证登录名与密码&#xff08;条件语句&#xff09;-CSDN博客 目录 最近的博文&…

函数极限求解方法归纳

1、连续函数直接代入值&#xff08;加减不可以部分代入值&#xff09; 例题1 配凑构造等价无穷小 等价无穷小 注意&#xff1a;不要在加减中部分使用等价无穷小&#xff0c;可以利用拆极限的方式求&#xff0c;拆出来的每一部分都要有极限&#xff0c;如果有一部分没有极限就是…