qiankun通过微前端技术将多个应用通过子应用集合在一块,为了避免js和css这两大维度的冲突,采取了沙箱机制来进行隔离,使应用之间的环境相互独立,互不干扰。
1. 沙箱的主要功能
- 隔离全局变量: 防止子应用之间的全局变量冲突,eg:
window
、navigator
。 -
样式隔离: 确保子应用的样式只作用于自身,不会影响其他子应用或主应用。
- JS 执行环境隔离: 确保子应用的 JavaScript 代码不会影响其他子应用或主应用。
- 模块作用域隔离:子应用的模块(如
Vue
、React
)不污染全局命名空间。
2. JS沙箱机制
2.1 快照沙箱(静态隔离)
在子应用挂载时,生成当前全局(Windows)变量的快照;子应用运行时,将对变量的修改存入modifyPropsMap;
在子应用卸载时,恢复这些全局变量到之前的状态。仅支持单子应用运行(同一时间仅运行一个子应用) 。
2.2 Proxy代理沙箱(动态隔离)
Proxy代理沙箱是主流方案
使用 Proxy
对象来拦截对全局变量的读写操作,从而实现隔离。为每个子应用创建代理对象fakeWindow
,通过Proxy
拦截对window
的读写操作。支持多子应用并行运行(多例模式)
3.css隔离机制
CSS是 根据选择器去全局匹配元素的,所以微前端应用之间很容易造成css样式冲突,这个时候就要实现css隔离
1 qiankun自带的隔离机制
1.1 experimentalStyleIsolation
自动为子应用的根 DOM 元素添加 data-qiankun-<app-id>
属性,使样式仅作用于当前子应用容器内部。动态为子应用的样式选择器添加唯一前缀(类似 Vue 的 scoped
属性),例如将 .button
转换为 [data-qiankun-app-id] .button
轻量级隔离,兼容性好,但无法隔离全局样式(如 body
标签)
若子应用样式权重不足(如使用 :where
选择器),可能被主应用覆盖
start({sandbox: {
enable: true,
experimentalStyleIsolation: true, // 加强样式隔离
}})
1.2 strictStyleIsolation(未来版本的qiankun可以会移除这个属性)
通过浏览器原生的 Shadow DOM 将子应用包裹在独立的 DOM 树中,外部样式无法渗透,且内部样式默认不溢出到主应用。子应用无需额外配置,子应用的样式和 DOM 自动被隔离在 Shadow DOM 内。
隔离彻底,但可能破坏依赖全局 DOM 的组件(如弹窗)
部分第三方库(如 Ant Design)在 Shadow DOM 中可能失效
start({sandbox: {
enable: true,
strictStyleIsolation: true, // 启用 Shadow DOM
}})
2 其它常用的css隔离机制
2.1 CSS-in-JS
将样式写在 JavaScript 中,通过动态生成唯一的类名实现作用域隔离。
- 实现方式:使用库如
styled-components
或emotion
。
import styled from 'styled-components';
const StyledButton = styled.button`
background-color: tomato;
padding: 10px;
`;
function Button() {
return <StyledButton>Click Me</StyledButton>;
}
2.2 类名作用域(Scoped CSS)
为子应用的类名添加唯一前缀或哈希值,确保样式仅作用于自身组件。
- 实现方式:通过构建工具(如 Webpack 的
css-loader
)或框架特性(如 Vue 的<style scoped>
)生成唯一类名。 - Vue 的
<style scoped>
通过 PostCSS 为组件内所有 DOM 元素添加唯一属性(如data-v-xxxx
),并修改 CSS 选择器为属性匹配模式。
2.3 CSS Module
它是一种将css利用模块化的思维来管理样式的一种 理念,每个文件都是一个独立的模块 ,这样可以产生局部作用域,通常使用Webpack等构建工具来实现。每个CSS文件中的选择器都会被添加一个唯一的前缀,即使两个文件中定义了相同的名字,也不会发生冲突。具体来说,Webpack会为每个CSS文件中的类名生成一个唯一的前缀,例如[path][name]__[local]--[hash:base64:5]
。
配置示例(Webpack):
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
modules: {
auto: true, // 或 auto: /.*\.module\.css$/, 仅处理特定文件
localIdentName: '[hash:base64:5]', // 哈希格式
},
},
},
],
},
],
},
};
使用示例(React 或 Vue):
// 组件文件(Button.module.css)
/* Button.module.css */
.button {
background-color: skyblue;
}
// 组件文件
import styles from './Button.module.css';
function Button() {
return <button className={styles.button}>Click Me</button>;
}
<!-- App.vue(子应用) -->
<template>
<div :class="$style.container">Content</div>
</template>
<style module>
.container { padding: 20px; }
</style>
2.4 BEM
(Block__Element--Modifier)是一种组件化命名规范,通过明确的语法结构避免 CSS 类名冲突。
Block(块):独立的、可复用的功能模块(如按钮、卡片、表单),命名以描述性词汇为基础(如 .header
或 .search-form
)。
Element(元素):块内组成部分(如按钮的文字、输入框的输入区域),块名__元素名
。
Modifier(修饰符):块或元素的变体状态(如按钮的“禁用”状态(.button--disabled)、卡片的“主题颜色”)命名格式为 块名--修饰符
或 块名__元素名--修饰符
。
/* CSS 样式 */
/* Block(基础样式) */
.button {
padding: 8px 16px;
background: #fff;
border: 1px solid #ccc;
}
/* 块的修饰符(主色按钮) */
.button--primary {
background: royalblue;
color: white;
}
/* 元素(文字部分) */
.button__text {
font-weight: bold;
}
/* 元素的修饰符(文字大写) */
.button__text--uppercase {
text-transform: uppercase;
}
/* 元素(图标) */
.button__icon {
margin-left: 8px;
}
/* 元素的修饰符(特定图标类型) */
.button__icon--arrow {
content: "→"; /* 具体图标代码 */
}
2.5 Shadow DOM
浏览器原生提供的 DOM 隔离机制,允许将一段 DOM 树(称为 Shadow Tree)附加到主文档中,形成一个独立的作用域 ,内部的样式默认不会溢出到外部,外部样式也不会影响内部元素。外部无法直接访问 Shadow DOM 的内部结构(除非显式开放)
- 两种模式:
- Open Mode:允许外部 JavaScript 访问 Shadow DOM 内容(
{ mode: 'open' }
)。 -
Closed Mode:完全隔离,外部无法访问(
{ mode: 'closed' }
,默认)。
- Open Mode:允许外部 JavaScript 访问 Shadow DOM 内容(
// 获取宿主元素(如 div)
const hostElement = document.getElementById('my-component');
// 创建 Shadow Root(Open 模式)
const shadowRoot = hostElement.attachShadow({ mode: 'open' });
// 在 Shadow Root 中添加内容
shadowRoot.innerHTML = `
<style>
.button { background: red; }
</style>
<button class="button">Click Me</button>
`;
在 qiankun中的实现
<!-- 主应用入口 -->
<template>
<div id="subapp-container"></div>
</template>
<script>
export default {
mounted() {
const container = document.getElementById('subapp-container');
const shadowRoot = container.attachShadow({ mode: 'closed' });
// 动态注入子应用内容(如通过 Qiankun)
shadowRoot.innerHTML = `
<style>
body { margin: 0; } /* 仅作用于 Shadow DOM 内 */
</style>
<div id="real-subapp-container"></div>
`;
// 挂载子应用到 real-subapp-container
// (此处调用 Qiankun 的子应用加载逻辑)
},
};
</script>
import { registerMicroApps } from 'qiankun';
registerMicroApps([
{
name: 'vue-subapp',
entry: '//localhost:8080',
container: '#subapp',
activeRule: '/vue',
// 创建 Shadow DOM 容器
loader: () => {
const container = document.getElementById('subapp');
container.attachShadow({ mode: 'closed' }); // 创建 Shadow DOM
},
// 卸载时清理
unload: () => {
const container = document.getElementById('subapp');
container.shadowRoot?.innerHTML = ''; // 清除内部内容
},
},
]);
<template>
<div>
<h1 style="color: red">子应用内容</h1>
<button class="global-button">全局样式?不了,我用 Shadow DOM!</button>
</div>
</template>
<script>
// 子应用样式完全隔离在 Shadow DOM 内,此处的 .global-button 不会污染主页面
</script>
4. qiankuncss样式隔离组合方案实践(推荐)
为最大程度降低冲突,建议组合使用以下技术:
- 核心隔离方式:
- 主应用:为子应用容器添加
experimentalStyleIsolation
。 - 子应用:在组件中使用 CSS Modules 或 CSS-in-JS。
- 主应用:为子应用容器添加
- 全局样式:
- 如果需定义全局样式,通过 BEM 规范命名(如
.subapp-card
)。
- 如果需定义全局样式,通过 BEM 规范命名(如
- 第三方库处理:
- 对于依赖全局样式库(如 Bootstrap),在子应用中使用
<style scoped>
或单独打包 CSS。
- 对于依赖全局样式库(如 Bootstrap),在子应用中使用
5 路由隔离
registerMicroApps
的 activeRule
配置精准控制路由的跳转
6 资源隔离
沙箱的 globals
和生命周期钩子 mount
/unmount,
隔离第三方库和全局状态。