Style Isolation โ
Style isolation is one of the most critical aspects of micro-frontend architecture. When multiple applications run in the same browser context, CSS conflicts can cause visual inconsistencies and broken layouts. This guide covers various strategies to achieve effective style isolation in qiankun applications.
๐ฏ Understanding the Problem โ
CSS Conflicts in Micro-Frontends โ
When multiple micro applications are loaded into the same page, they share the same global CSS namespace. This can lead to:
- Style Overriding: Later-loaded applications overriding earlier styles
- Selector Conflicts: Same class names causing unintended styling
- Global Pollution: Micro apps affecting main application styles
- Layout Breaks: Unexpected layout changes due to conflicting styles
Example of a CSS Conflict โ
/* Main Application */
.button {
background: blue;
padding: 10px;
}
/* Micro Application */
.button {
background: red; /* This will override main app styles! */
border: none;
}๐ก๏ธ qiankun's Built-in Style Isolation โ
qiankun provides several built-in style isolation mechanisms that you can enable through configuration.
Strict Style Isolation โ
The most robust isolation method using Shadow DOM:
import { start } from 'qiankun';
start({
sandbox: {
strictStyleIsolation: true
}
});How it works:
- Creates a Shadow DOM for each micro application
- Completely isolates styles between applications
- Prevents any style leakage
Pros:
- Complete style isolation
- No CSS conflicts possible
- Easy to implement
Cons:
- Some third-party libraries may not work properly
- Debugging can be more complex
- Performance overhead for large applications
Experimental Style Isolation โ
A less intrusive approach using CSS scoping:
import { start } from 'qiankun';
start({
sandbox: {
experimentalStyleIsolation: true
}
});How it works:
- Adds unique prefixes to CSS selectors
- Scopes styles to micro application containers
- Maintains DOM structure
Pros:
- Better third-party library compatibility
- Easier debugging
- Less performance overhead
Cons:
- Not as robust as strict isolation
- Some edge cases may still cause conflicts
๐จ CSS-in-JS Solutions โ
CSS-in-JS libraries provide natural style isolation by generating unique class names.
Styled Components โ
// Micro Application using Styled Components
import styled from 'styled-components';
const Button = styled.button`
background: ${props => props.primary ? 'blue' : 'grey'};
padding: 10px 20px;
border: none;
border-radius: 4px;
&:hover {
opacity: 0.8;
}
`;
function MyComponent() {
return (
<div>
<Button primary>Primary Button</Button>
<Button>Secondary Button</Button>
</div>
);
}Emotion โ
/** @jsxImportSource @emotion/react */
import { css } from '@emotion/react';
const buttonStyle = css`
background: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
&:hover {
background: darkblue;
}
`;
function MyComponent() {
return <button css={buttonStyle}>Click me</button>;
}CSS Modules with Dynamic Imports โ
/* Button.module.css */
.button {
background: blue;
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
}
.button:hover {
background: darkblue;
}// Button.jsx
import styles from './Button.module.css';
function Button({ children, ...props }) {
return (
<button className={styles.button} {...props}>
{children}
</button>
);
}๐๏ธ CSS Scoping Strategies โ
BEM Methodology โ
Use Block, Element, Modifier naming convention to avoid conflicts:
/* Main Application */
.main-app__button {
background: blue;
}
.main-app__button--primary {
background: darkblue;
}
/* Micro Application */
.micro-app__button {
background: red;
}
.micro-app__button--large {
padding: 15px 30px;
}Namespace Prefixing โ
Add unique prefixes to all CSS classes:
/* User Management Micro App */
.user-mgmt-container { }
.user-mgmt-header { }
.user-mgmt-button { }
/* Product Catalog Micro App */
.product-cat-container { }
.product-cat-header { }
.product-cat-button { }CSS Custom Properties (Variables) โ
Use CSS variables for consistent theming without conflicts:
/* Main Application - Define theme variables */
:root {
--primary-color: #007bff;
--secondary-color: #6c757d;
--success-color: #28a745;
--danger-color: #dc3545;
}
/* Micro Application - Use theme variables */
.micro-app-button {
background: var(--primary-color);
color: white;
}๐ญ Runtime Style Management โ
Dynamic CSS Loading โ
Load CSS dynamically when micro applications mount:
// Lifecycle hooks for dynamic CSS loading
const lifeCycles = {
async beforeMount(app) {
// Load micro app specific CSS
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `${app.entry}/static/css/main.css`;
link.id = `${app.name}-styles`;
document.head.appendChild(link);
},
async afterUnmount(app) {
// Remove micro app CSS
const link = document.getElementById(`${app.name}-styles`);
if (link) {
document.head.removeChild(link);
}
}
};
registerMicroApps([
{
name: 'micro-app',
entry: '//localhost:8080',
container: '#container',
activeRule: '/micro-app'
}
], lifeCycles);CSS Scoping with PostCSS โ
Use PostCSS plugins to automatically scope CSS:
// postcss.config.js
module.exports = {
plugins: [
require('postcss-prefixwrap')('.micro-app-container')
]
};Input CSS:
.button {
background: blue;
}Output CSS:
.micro-app-container .button {
background: blue;
}๐ง Framework-Specific Solutions โ
React Applications โ
Using CSS Modules with Create React App โ
// Button.module.css
.button {
background: blue;
color: white;
}
// Button.jsx
import styles from './Button.module.css';
function Button({ children }) {
return <button className={styles.button}>{children}</button>;
}Custom CSS Prefix Hook โ
import { useMemo } from 'react';
function useCSSPrefix(prefix) {
return useMemo(() => {
return (className) => `${prefix}-${className}`;
}, [prefix]);
}
// Usage
function MyComponent() {
const cx = useCSSPrefix('user-mgmt');
return (
<div className={cx('container')}>
<button className={cx('button')}>Click me</button>
</div>
);
}Vue Applications โ
Scoped Styles โ
<template>
<div class="component">
<button class="button">Click me</button>
</div>
</template>
<style scoped>
.component {
padding: 20px;
}
.button {
background: blue;
color: white;
}
</style>CSS Modules in Vue โ
<template>
<div :class="$style.component">
<button :class="$style.button">Click me</button>
</div>
</template>
<style module>
.component {
padding: 20px;
}
.button {
background: blue;
color: white;
}
</style>Angular Applications โ
ViewEncapsulation โ
import { Component, ViewEncapsulation } from '@angular/core';
@Component({
selector: 'app-my-component',
template: `
<div class="component">
<button class="button">Click me</button>
</div>
`,
styles: [`
.component {
padding: 20px;
}
.button {
background: blue;
color: white;
}
`],
encapsulation: ViewEncapsulation.Emulated // Default
})
export class MyComponent { }๐ช Component Library Strategies โ
Design System Approach โ
Create a shared design system that all micro applications consume:
// shared-design-system/Button.js
export const Button = ({ variant = 'primary', size = 'medium', children, ...props }) => {
const baseClasses = 'btn';
const variantClasses = {
primary: 'btn-primary',
secondary: 'btn-secondary'
};
const sizeClasses = {
small: 'btn-sm',
medium: 'btn-md',
large: 'btn-lg'
};
const className = [
baseClasses,
variantClasses[variant],
sizeClasses[size]
].join(' ');
return <button className={className} {...props}>{children}</button>;
};CSS Custom Properties for Theming โ
/* Design system CSS */
:root {
--btn-primary-bg: #007bff;
--btn-primary-color: white;
--btn-secondary-bg: #6c757d;
--btn-secondary-color: white;
--btn-padding-sm: 8px 12px;
--btn-padding-md: 10px 16px;
--btn-padding-lg: 12px 20px;
}
.btn {
border: none;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
}
.btn-primary {
background: var(--btn-primary-bg);
color: var(--btn-primary-color);
}
.btn-secondary {
background: var(--btn-secondary-bg);
color: var(--btn-secondary-color);
}
.btn-sm { padding: var(--btn-padding-sm); }
.btn-md { padding: var(--btn-padding-md); }
.btn-lg { padding: var(--btn-padding-lg); }๐ซ Third-Party Library Handling โ
Isolating Third-Party Styles โ
When using third-party libraries that inject global styles:
// Load third-party CSS in isolation
const loadLibraryStyles = (libraryName, cssUrl) => {
return new Promise((resolve) => {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
const iframeDoc = iframe.contentDocument;
const link = iframeDoc.createElement('link');
link.rel = 'stylesheet';
link.href = cssUrl;
link.onload = () => {
// Extract and scope the CSS
const styles = Array.from(iframeDoc.styleSheets[0].cssRules)
.map(rule => rule.cssText)
.join('\n');
const scopedStyles = scopeCSS(styles, `.${libraryName}-container`);
const style = document.createElement('style');
style.textContent = scopedStyles;
style.id = `${libraryName}-scoped-styles`;
document.head.appendChild(style);
document.body.removeChild(iframe);
resolve();
};
iframeDoc.head.appendChild(link);
});
};CSS-in-JS for Third-Party Integration โ
import { createGlobalStyle } from 'styled-components';
// Scope third-party styles to specific container
const AntdStyles = createGlobalStyle`
.micro-app-container {
.ant-btn {
/* Override Ant Design button styles specifically for this micro app */
border-radius: 8px;
}
.ant-table {
/* Override Ant Design table styles */
border: 1px solid #f0f0f0;
}
}
`;
function MicroApp() {
return (
<div className="micro-app-container">
<AntdStyles />
{/* Your micro app content */}
</div>
);
}๐ Debugging Style Conflicts โ
Development Tools โ
Style Inspection Script โ
// Add to browser console for debugging
const findStyleConflicts = (selector) => {
const elements = document.querySelectorAll(selector);
elements.forEach((el, index) => {
const styles = window.getComputedStyle(el);
const rules = [];
for (let i = 0; i < document.styleSheets.length; i++) {
try {
const sheet = document.styleSheets[i];
const cssRules = sheet.cssRules || sheet.rules;
for (let j = 0; j < cssRules.length; j++) {
if (el.matches(cssRules[j].selectorText)) {
rules.push({
selector: cssRules[j].selectorText,
rule: cssRules[j].cssText,
sheet: sheet.href || 'inline'
});
}
}
} catch (e) {
// Cross-origin stylesheet
}
}
console.log(`Element ${index + 1}:`, el);
console.log('Applied styles:', rules);
});
};
// Usage: findStyleConflicts('.button');Style Source Tracker โ
// Track which micro app loaded which styles
const styleTracker = {
styles: new Map(),
track(appName, styleElement) {
if (!this.styles.has(appName)) {
this.styles.set(appName, []);
}
this.styles.get(appName).push(styleElement);
},
getByApp(appName) {
return this.styles.get(appName) || [];
},
getConflicts() {
const allSelectors = new Map();
this.styles.forEach((styles, appName) => {
styles.forEach(style => {
// Parse CSS and check for selector conflicts
// Implementation depends on CSS parser
});
});
return allSelectors;
}
};Runtime Conflict Detection โ
// Detect style conflicts at runtime
const detectStyleConflicts = () => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.tagName === 'STYLE' || node.tagName === 'LINK') {
checkForConflicts(node);
}
});
}
});
});
observer.observe(document.head, { childList: true });
};
const checkForConflicts = (styleNode) => {
// Implementation to check for CSS selector conflicts
console.warn('New style node added:', styleNode);
};๐ Performance Considerations โ
CSS Loading Performance โ
// Optimize CSS loading for micro applications
const optimizedCSSLoader = {
cache: new Map(),
async loadCSS(url, appName) {
if (this.cache.has(url)) {
return this.cache.get(url);
}
const response = await fetch(url);
const css = await response.text();
const optimizedCSS = this.optimizeCSS(css, appName);
this.cache.set(url, optimizedCSS);
return optimizedCSS;
},
optimizeCSS(css, appName) {
// Remove unused selectors
// Add app-specific prefixes
// Minify if needed
return css.replace(/(\.[a-zA-Z-_]+)/g, `.${appName}-$1`);
}
};Bundle Splitting for Styles โ
// webpack.config.js - Split CSS by micro app
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true
}
}
}
}
};๐ฏ Best Practices Summary โ
โ Do's โ
- Use qiankun's built-in isolation when possible
- Implement consistent naming conventions (BEM, namespacing)
- Use CSS-in-JS for automatic isolation
- Create a shared design system for consistency
- Test style isolation in development
- Monitor for conflicts in production
- Document style guidelines for your team
โ Don'ts โ
- Don't rely on global CSS in micro applications
- Don't use overly generic class names (.button, .container)
- Don't forget to clean up styles when unmounting
- Don't ignore third-party library styles
- Don't skip testing style isolation
- Don't use !important unless absolutely necessary
๐ช Real-World Example โ
Here's a complete example of a well-isolated micro application:
// MicroApp.jsx
import React from 'react';
import styled, { createGlobalStyle } from 'styled-components';
// Global styles scoped to this micro app
const GlobalStyles = createGlobalStyle`
.user-management-app {
font-family: 'Inter', sans-serif;
* {
box-sizing: border-box;
}
}
`;
// Styled components with isolation
const Container = styled.div`
padding: 20px;
background: #f8f9fa;
min-height: 100vh;
`;
const Header = styled.h1`
color: #343a40;
margin-bottom: 20px;
`;
const Button = styled.button`
background: ${props => props.primary ? '#007bff' : '#6c757d'};
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
&:hover {
opacity: 0.9;
}
`;
function UserManagementApp() {
return (
<div className="user-management-app">
<GlobalStyles />
<Container>
<Header>User Management</Header>
<Button primary>Add User</Button>
<Button>Cancel</Button>
</Container>
</div>
);
}
export default UserManagementApp;๐ Related Documentation โ
- Performance Optimization - Optimize loading and runtime performance
- Error Handling - Handle CSS-related errors
- Configuration - Configure style isolation options
- Debugging - Debug style conflicts
