120.乾坤css隔离机制

1,251 阅读3分钟

乾坤解决样式隔离的几种方法:

  • 确保单实例场景子应用之间的样式隔离,但是无法确保主应用跟子应用、或者多实例场景的子应用样式隔离
start({sandbox : true})//沙箱隔离,

判断沙箱隔离的方法,发现直接设置sandbox=true是不行的,需要使用experimentalStyleIsolation这个属性配置。

export function isEnableScopedCSS(sandbox: FrameworkConfiguration['sandbox']) {
  if (typeof sandbox !== 'object') {
    return false;
  }
  if (sandbox.strictStyleIsolation) {
    return false;
  }
  return !!sandbox.experimentalStyleIsolation;
}
  • { strictStyleIsolation: true } 开启严格的样式隔离模式 ,shawod dom'
start({shadow:{ strictStyleIsolation: true }})

直观的可以看到,子应用的代码不是正常的dom,上面显示的shadow-root,先不管shadow-root是什么,接着往下看

image.png

  • experimentalStyleIsolation 给子应用加上自定义的data-xxx属性,相当于vue中我们通常使用的scoped,从生成的代码中可以看出来

image.png

在主应用中,通过配置sandbox做到样式隔离

start({ sandbox: { strictStyleIsolation: true } });

在start得时候配置得参数,但是只有等到start函数里面 frameworkStartedDefer.resolve() 之后才能往下执行. registerMicroApps只是准备好注册的应用配置信息,只有等调用了start方法之后,才会走registerApplication相关逻辑,registerApplicationsingle-spa中的一个主要的方法,乾坤实现微应用也主要用的是single-spa这个库

start 方法

export function start(opts: FrameworkConfiguration = {}) {
  frameworkConfiguration = { prefetch: true, singular: true, sandbox: true, ...opts };
  startSingleSpa({ urlRerouteOnly });
  started = true;
    //执行loadApp相关逻辑
  frameworkStartedDefer.resolve();
}

修改appid得代码是在single-spa中还是qiankun中得?->qiankun,在处理模板的时候使用shadow dom实现的

loadApp中使用严格样式隔离的代码

export async function loadApp<T extends ObjectType>(
  app: LoadableApp<T>,
  configuration: FrameworkConfiguration = {},
  lifeCycles?: FrameworkLifeCycles<T>,
): Promise<ParcelConfigObjectGetter> {
      const strictStyleIsolation = typeof sandbox === 'object' && !!sandbox.strictStyleIsolation;
      let initialAppWrapperElement: HTMLElement | null = createElement(
        appContent,
        strictStyleIsolation,
        scopedCSS,
        appInstanceId,
      );
}

createElement方法相关代码,我们在start方法中配置了strictStyleIsolation严格样式模式,因此会走到strictStyleIsolation判断相关逻辑里面,从这里面就可以看到创建shadow dom相关代码

function createElement(
  appContent: string,
  strictStyleIsolation: boolean,
  scopedCSS: boolean,
  appInstanceId: string,
): HTMLElement {
  const containerElement = document.createElement('div');
  containerElement.innerHTML = appContent;
  // appContent always wrapped with a singular div
  const appElement = containerElement.firstChild as HTMLElement;
     //绝对样式隔离相关代码
  if (strictStyleIsolation) {
      //使用shadow做样式隔离
      const { innerHTML } = appElement;
      appElement.innerHTML = '';
      let shadow: ShadowRoot;

      if (appElement.attachShadow) {
        shadow = appElement.attachShadow({ mode: 'open' });
      } else {
        // createShadowRoot was proposed in initial spec, which has then been deprecated
        shadow = (appElement as any).createShadowRoot();
      }
      shadow.innerHTML = innerHTML;

  }

 //...scopedCSS判断相关代码

  return appElement;
}

使用scopedCss做到样式隔离

前面入口出的逻辑和shadow大致相同,scopedCss代码也很好理解,就是先查找要添加的应用是否有相应的属性,没有的话给子应用容器添加一个属性。然后遍历每个style标签,在每个style标签代码前面增加属性选择符,通过属性选择符+样式来确保样式不被重写

  if (scopedCSS) {
    const attr = appElement.getAttribute(css.QiankunCSSRewriteAttr);
    if (!attr) {
        //这边attr生成的属性:data-qiankun :vue2App
      appElement.setAttribute(css.QiankunCSSRewriteAttr, appInstanceId);
    }

    const styleNodes = appElement.querySelectorAll('style') || [];
    forEach(styleNodes, (stylesheetElement: HTMLStyleElement) => {
      css.process(appElement!, stylesheetElement, appInstanceId);
    });
  }

css.process方法

export const process = (
  appWrapper: HTMLElement,
  stylesheetElement: HTMLStyleElement | HTMLLinkElement,
  appName: string,
): void => {
  // lazy singleton pattern
  if (!processor) {
    processor = new ScopedCSS();
  }
  const mountDOM = appWrapper;
  if (!mountDOM) {
    return;
  }
  const tag = (mountDOM.tagName || '').toLowerCase();

  if (tag && stylesheetElement.tagName === 'STYLE') {
      //生成前缀
    const prefix = `${tag}[${QiankunCSSRewriteAttr}="${appName}"]`;
    processor.process(stylesheetElement, prefix);
  }
};

什么是shadow dom?

可以将标记结构,样式和行为隐藏起来,和页面上其他代码相隔离。

shadow dom的一个demo:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <style>
    h1 {
      color: red;
    }
  </style>
  <body>
    <h1>hello 这个主页中的内容</h1>
    <div id="box"></div>
  </body>

  <script>
    window.onload = function () {
      let box = document.getElementById("box");
      //开启shadow dom
      box.attachShadow({ mode: "open" });
      let shadow = box.shadowRoot;
      var h1 = document.createElement("h1");
      h1.innerHTML = "world 这是shaow dom中的内容";
      var style = document.createElement("style");
      style.textContent = `h1{color:green}`;
      shadow.appendChild(style);
      shadow.appendChild(h1);
    };
  </script>
</html>

image.png