This website requires JavaScript.
en
og

qiankun类微前端内核解密(css沙箱隔离机制)

前言

qiankun模式是企业级后台迁移微前端的一个标杆模式,稍微有点技术的企业都会选择基于qiankun技术来自定义一套符合自身的微前端技术体系,所以这里面技术大部分是相通的,我司也不例外,在迁移微前端架构中有种种疑问,现在我们一起解密这个黑盒

qiankun类微前端内核实现主要功能

  1. JS沙箱:子应用之间互不影响,包括全局变量、事件等等
  2. CSS隔离:子应用之间样式互不影响
  3. Config Entry:配置每个子应用的JS和CSS
  4. 按需加载:切换到相应页面才加载对应资源
  5. 公共依赖加载:大部分子应用都需要用到的资源如何处理
  6. 预加载:空闲时间加载子应用资源
  7. 父子应用通信:子应用如何触发父级应用方法、父级应用如何调子级方法

单纯论微前端有多种方式,上面列表微前端核心功能也有多种实现方式,为了高效专注解密,本主题仅仅解密类qiankun微前端内核处理方式

CSS沙箱

shadowDOM

Web components 的一个重要属性是封装——可以将标记结构、样式和行为隐藏起来,并与页面上的其他代码相隔离,保证不同的部分不会混在一起,可使代码更加干净、整洁。其中,Shadow DOM 接口是关键所在,它可以将一个隐藏的、独立的 DOM 附加到一个元素上。

这里官方文档写的还是挺好的推荐直接看文档shadowDOM

简易示例

var shadowEl = document.querySelector(".shadow");
var shadow = shadowEl.attachShadow({mode: 'open'});
var link = document.createElement("a");
link.href = 'https://baidu.com';
link.innerHTML='百度'
shadow.appendChild(link);

特点

  1. 对主文档的 JavaScript 选择器隐身,比如 querySelector
  2. 只使用 shadow tree 内部的样式,不使用主文档的样式

scopedCSS

核心原理:利用css属性选择器层叠类似vue scoped的方式做样式隔离,被隔离的应用的样式表会被特定规则改写成如下模式

div .react15-lib{
    color: rgb(129, 143, 247);
}
div[data-qiankun="react15"] .react15-lib {
    color: rgb(129, 143, 247);
}

那么qiankun具体是如何实现的呢?它巧妙的利用CSSRule改写来实现上述功能。没用过这个功能的可能一脸懵逼,下面举个常用示例,可以找个网页打开F12测试下

var style=document.createElement('style')
var textnode=document.createTextNode('#id{color:red}')
style.appendChild(textnode)
document.body.appendChild(style)
var styleList=document.querySelectorAll('style')
var current=styleList[styleList.length-1]
console.log(current.sheet)

不想试的可以直接看结果 undefined

我们可以通过这个接口便捷实现css的编辑

源码步骤如下

undefined

上图得知往createElement传了四个参数appContent子应用入口文件,strictStyleIsolation是否开启严格的风格隔离,scopedCSS是否开启scopedCss隔离,appName子应用名称

export function isEnableScopedCSS(sandbox: FrameworkConfiguration['sandbox']) {
  if (typeof sandbox !== 'object') {
    return false;
  }

  if (sandbox.strictStyleIsolation) {
    return false;
  }

  return !!sandbox.experimentalStyleIsolation;
}

通过上述源代码得知,传入的css隔离配置,shadowDOM的css隔离优先于scopedCSS,关键函数createElement

``` ts function createElement( appContent: string, strictStyleIsolation: boolean, scopedCSS: boolean, appName: string, ): HTMLElement { const containerElement = document.createElement('div'); containerElement.innerHTML = appContent; // appContent always wrapped with a singular div const appElement = containerElement.firstChild