Implementing a CSS Sandbox for Qiankun: Shadow DOM and Scoped CSS Isolation
This article explains how to build a CSS sandbox for Qiankun micro‑frontends by using Shadow DOM for strict style isolation and a Scoped CSS approach for experimental isolation, providing step‑by‑step code examples, underlying principles, and a final Web Component implementation.
The article begins with a brief introduction, stating that it will demonstrate how to create a CSS sandbox for Qiankun, complementing a previous tutorial on a JavaScript sandbox.
Preparation
Three files are required: index.html (the entry HTML), shadowDOMIsolation.js (Shadow DOM sandbox implementation), and scopedCSSIsolation.js (Scoped CSS sandbox implementation). The source code is hosted in the mini-css-sandbox repository.
Shadow DOM Sandbox
Principle
Shadow DOM attaches a hidden, independent DOM tree to a regular DOM element (the shadow host), providing hard style isolation.
Implementation
The shadowDOMIsolation function trims the HTML string, creates a container div , extracts the root element, clears its content, attaches a shadow root (using attachShadow({mode:'open'}) or the legacy createShadowRoot ), injects the original inner HTML into the shadow root, and returns the element.
function shadowDOMIsolation(contentHtmlString) {
// Clean HTML
contentHtmlString = contentHtmlString.trim();
// Create container div
const containerElement = document.createElement('div');
containerElement.innerHTML = contentHtmlString;
// Get root div element
const appElement = containerElement.firstChild;
const { innerHTML } = appElement;
appElement.innerHTML = '';
let shadow;
if (appElement.attachShadow) {
// Modern API
shadow = appElement.attachShadow({ mode: 'open' });
} else {
// Legacy API
shadow = appElement.createShadowRoot();
}
// Populate shadow DOM
shadow.innerHTML = innerHTML;
return appElement;
}The resulting effect shows that external styles (e.g., a global red p style) do not affect the content inside the shadow DOM.
Scoped CSS Sandbox
Principle
Scoped CSS extracts the <style> text from a micro‑app and rewrites each selector by prefixing it with a unique container selector such as div[data-app-name="MyApp"] , ensuring the styles apply only within that container.
Implementation
The core functions are:
processCSS(appElement, stylesheetElement, appName) – creates a temporary disabled <style> element to obtain CSS rules, then rewrites them using rewrite .
ruleStyle(rule, prefix) – replaces the selector part of a CSS rule with the prefixed selector.
rewrite(rules, prefix) – iterates over CSS rules, handling STYLE rules (others like MEDIA and SUPPORTS are omitted for brevity).
function processCSS(appElement, stylesheetElement, appName) {
const prefix = `${appElement.tagName.toLowerCase()}[data-app-name="${appName}"]`;
const tempNode = document.createElement('style');
document.body.appendChild(tempNode);
tempNode.sheet.disabled = true;
if (stylesheetElement.textContent !== '') {
const textNode = document.createTextNode(stylesheetElement.textContent || '');
tempNode.appendChild(textNode);
const sheet = tempNode.sheet;
const rules = [...sheet?.cssRules ?? []];
stylesheetElement.textContent = this.rewrite(rules, prefix);
tempNode.removeChild(textNode);
}
}
function scopedCSSIsolation(appName, contentHtmlString) {
contentHtmlString = contentHtmlString.trim();
const containerElement = document.createElement('div');
containerElement.innerHTML = contentHtmlString;
const appElement = containerElement.firstChild;
appElement.setAttribute('data-app-name', appName);
const styleNodes = appElement.querySelectorAll('style') || [];
[...styleNodes].forEach((stylesheetElement) => {
processCSS(appElement, stylesheetElement, appName);
});
return appElement;
}A test case demonstrates that the outer text remains red while the inner text becomes blue after the scoped CSS transformation.
Turning It Into a Web Component
To avoid repetitive container handling, the article wraps the isolation logic into a custom element isolation-content . The component reads data-app-name and data-isolation-mode attributes, builds the inner HTML, applies either shadowDOMIsolation or scopedCSSIsolation based on the mode, clears its original content, and appends the isolated element.
class Isolation extends HTMLElement {
constructor() {
super();
const name = this.getAttribute('data-app-name');
const mode = this.getAttribute('data-isolation-mode');
const html = `
${this.innerHTML.trim()}
`;
const appElement = mode === 'shadowDOM' ? shadowDOMIsolation(html) : scopedCSSIsolation(name, html);
this.innerHTML = '';
this.appendChild(appElement);
}
}
customElements.define('isolation-content', Isolation);The final HTML includes the three script files ( scopedCSSIsolation.js , shadowDOMIsolation.js , and Isolation.js ) and demonstrates both isolation modes side by side.
Summary
Qiankun style isolation offers two methods: Shadow DOM isolation (hard isolation) and Scoped CSS isolation (selector prefixing).
Shadow DOM leverages the browser's native shadow tree to completely separate styles.
Scoped CSS rewrites <style> rules by adding a unique container selector, achieving isolation without a shadow tree.
Both techniques can be encapsulated into a reusable Web Component for cleaner micro‑frontend integration.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.