前端微应用-乾坤(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无界的核心就是这个,等咱们分析无界的时候再细说。

相关推荐
xiaofeichaichai5 小时前
Webpack
前端·webpack·node.js
Thecozzy5 小时前
线上 Bug 排查与修复实录
架构
鹏大师运维5 小时前
为什么信创电脑装软件总提示“软件包架构不匹配”?
linux·运维·架构·国产化·麒麟·deb·统信uos
问心无愧05135 小时前
ctf show web入门111
android·前端·笔记
唐某人丶5 小时前
模型越来越强,我们还需要 Agent 工程吗?—— 从价值重估到 Harness 实践
前端·agent·ai编程
智码看视界5 小时前
现代Web开发基础:全栈工程师的起航点
前端·后端·c5全栈
JS菌6 小时前
手写一个 AI Agent 全栈项目:从沙箱执行到子智能体的完整实现
前端·人工智能·后端
excel7 小时前
HLS TS 文件损坏的元凶:Git 提交与拉取
前端
Aphasia3117 小时前
https连接传输流程
前端·面试