前端微应用-乾坤(qiankun)原理分析-沙箱隔离(css)

为什么需要沙箱隔离?沙箱sandbox 又是什么概念呢?

沙箱(sandbox)是前端开发中的一个概念,它指的是在某个特定的环境中,对代码进行隔离,防止代码相互影响。在 Web 开发中,沙箱通常指的是在某个特定的 DOM 节点中,对代码进行隔离,防止代码相互影响。

比如网络安全 中的沙箱 是指,一些资源文件在沙箱环境中进行分析,避免是恶意脚本被执行了影响整个系统瘫痪。

Q: 为什么乾坤需要沙箱隔离呢?我之前开发多页应用的时候也没用到该技术啊?只是配置了下nginx转发到对应的HTML就行了啊?

A: 因为乾坤是容器(主应用) ,以及子应用 的模式,载体主应用 是一个完整的HTML,子应用仅作为HTML中的一部分。如下图所示:

如果这种模式下,子应用里面有样式对body、html、:root这种全局样式在会照成影响的,以及如果子应用中重写了window某个方法 ,那么岂不是对全局都污染了。

如果你说你们比较规范 不会有以上这些问题 ,但是你不能保证你引入的第三方插件库有没有这种操作,所以才需要沙箱隔离。

Q: 沙箱隔离是运行时 ,还是编译时 ,会不会影响我的性能?

A: 沙箱隔离是运行时 的,多少会影响 点性能,如果你是后台管理 那么可以忽略不计,如果你是做3d或者2d图谱流程图等里面需要频繁用到检测节点使用Math计算的话(因为节点碰撞可能是要千万级的使用window上的Math方法),那么可能需要你关闭沙箱隔离

Q: 那我应该什么场景下关闭沙箱,需要我自己测试下性能吗?

A: 如果你频繁需要用到window上的API,比如1s调用10万次,那么你必须要去关闭沙箱。经测试1s内调用1万次基本无感的。正常是不会影响你开发的。

Q: 我怎么关闭沙箱?或者我能用什么方式解决这个问题呢?

A: start({sandbox: false })就可以关闭了。比如你知道频繁调用了Math方法,那么你把Math方法cloneDeep一份再去使用就不会有这个问题了。

Q: 我能只关闭单独某个微应用的沙箱吗?

A: 暂时看应该是不行的!

在乾坤中针对css,js的window分别做了沙箱隔离,咱们接下来会一个个的拆分,看看它是怎么做到的。

css 沙箱隔离

乾坤(qiankun) 默认是没有对css沙箱隔离的。但是它提供了一个API可以帮咱们开启css隔离,其实就是把全局的body、html、:root进行了替换,如果你自己设置一个scope避免这些全局样式同样相当于隔离了。start({ sandbox: {experimentalStyleIsolation: true} });

在上一篇中咱们可以看到子应用的htmllink标签外部引入的css被插件import-html-entry替换成了style标签。

比如是<link rel="stylesheet" href="./test.css" />

css 复制代码
/* ./test.css */
a {
 color: red;
}

会被转成<style>a{color: red;}</style>

转换后的HTML是怎么操作css的呢?通过以下代码

ts 复制代码
function createElement(
  appContent: string, // 1. 替换后的HTML
  scopedCSS: boolean,
  appName: string,
): HTMLElement {
  const containerElement = document.createElement('div');
  containerElement.innerHTML = appContent;
  // appContent always wrapped with a singular div
  const appElement = containerElement.firstChild as HTMLElement;

  if (scopedCSS) {
    const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);
    if (!attr) {
      appElement.setAttribute(css.QiankunCSSRewriteAttr, appName); // 2. 这里对父元素增加了隔离属性data-qiankun="app-vue-history" appName
    }

    const styleNodes = appElement.querySelectorAll('style') || []; // 3. 获取所有style标签
    forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {
      css.process(appElement!, stylesheetElement, appName);  // 4. 这里是替换的
    });
  }
  return appElement;
}

具体执行流程如下图所示:

乾坤(qiankun)process替换部分是怎么做的呢?如下代码:

ts 复制代码
  // eslint-disable-next-line class-methods-use-this
 const  ruleStyle = (rule: CSSStyleRule, prefix: string) => {
    const rootSelectorRE = /((?:[^\w\-.#]|^)(body|html|:root))/gm;
    const rootCombinationRE = /(html[^\w{[]+)/gm;

    const selector = rule.selectorText.trim();

    let { cssText } = rule;
    // handle html { ... }
    // handle body { ... }
    // handle :root { ... }
    if (selector === 'html' || selector === 'body' || selector === ':root') {
      return cssText.replace(rootSelectorRE, prefix);
    }

    // handle html body { ... }
    // handle html > body { ... }
    if (rootCombinationRE.test(rule.selectorText)) {
      const siblingSelectorRE = /(html[^\w{]+)(\+|~)/gm;

      // since html + body is a non-standard rule for html
      // transformer will ignore it
      if (!siblingSelectorRE.test(rule.selectorText)) {
        cssText = cssText.replace(rootCombinationRE, '');
      }
    }

    // handle grouping selector, a,span,p,div { ... }
    cssText = cssText.replace(/^[\s\S]+{/, (selectors) =>
      selectors.replace(/(^|,\n?)([^,]+)/g, (item, p, s) => {
        // handle div,body,span { ... }
        if (rootSelectorRE.test(item)) {
          return item.replace(rootSelectorRE, (m) => {
            // do not discard valid previous character, such as body,html or *:not(:root)
            const whitePrevChars = [',', '('];

            if (m && whitePrevChars.includes(m[0])) {
              return `${m[0]}${prefix}`;
            }

            // replace root selector with prefix
            return prefix;
          });
        }

        return `${p}${prefix} ${s.replace(/^ */, '')}`;
      }),
    );

    return cssText;
  }
    1. 处理 html { ... } 、 body { ... } 、 :root { ... }
    1. 处理 html body { ... } 、html > body { ... }
    1. 处理 body下 div,body,span { ... }

然后拼接成需要用的css,在通过style.textContent设置style中的样式`。

css 沙箱总结

Q: 乾坤(qiankun) css 沙箱有没有必要?,看起来好像也没啥特殊的不就加个前缀,并且它默认也没有开启啊?

A: 从本次源码中分析,如果咱们开启start({ sandbox: {experimentalStyleIsolation: true} });后才会增加前缀,并且咱们了解到了它的作用替换规则 ,其实就是对html、body、:root进行了替换。并且css经过子应用的卸载 并不会留下痕迹(影响别的应用) 。不开启其实也行的

Q: 乾坤(qiankun) css 沙箱那岂不是个笑话?

A: 如果你的子应用没有全局样式,确实用不到这个API,但是也算是给我们敲了个警钟,避免使用这些全局样式。

设置start({ sandbox: {strictStyleIsolation: true} });的话会启用 shadowDOM无界的核心就是这个,等咱们分析无界的时候再细说。

相关推荐
wuyijysx11 分钟前
JavaScript grammar
前端·javascript
溪饱鱼33 分钟前
第6章: SEO与交互指标
服务器·前端·microsoft
咔_44 分钟前
LinkedList详解(源码分析)
前端
逍遥德1 小时前
CSS可以继承的样式汇总
前端·css·ui
邪恶的贝利亚1 小时前
《Docker 入门与进阶:架构剖析、隔离原理及安装实操》
docker·容器·架构
读心悦1 小时前
CSS3 选择器完全指南:从基础到高级的元素定位技术
前端·css·css3
_龙衣2 小时前
将 swagger 接口导入 apifox 查看及调试
前端·javascript·css·vue.js·css3
进取星辰3 小时前
25、Tailwind:魔法速记术——React 19 样式新思路
前端·react.js·前端框架
x-cmd4 小时前
[250512] Node.js 24 发布:ClangCL 构建,升级 V8 引擎、集成 npm 11
前端·javascript·windows·npm·node.js
夏之小星星4 小时前
el-tree结合checkbox实现数据回显
前端·javascript·vue.js