Skip to content

css样式隔离

qiankun现成方案

动态样式(默认)

单实例时通过动态添加/移除子应用的根元素(包含css style),避免各子应用之间的样式冲突. 但主子应用的样式无法避免.

多实例时子应用无法避免

TIP

script标签被移除时,已被浏览器解析的代码并不会受影响

html
<html lang="en">
<body>
  <!-- 移除掉第一个script -->
  <script>
    var a = 1
    let  = document.getElementsByTagName('script')[0]
    script0.parentElement.removeChild(script0) // 移除第一个script标签
    setTimeout(() => {
      console.log(2) // 1000ms后依旧执行,证明script标签被移除代码仍会被执行
    }, 1000)
  </script>
  <script>
    console.log(a) // 1  a变量依然可以输出
  </script>
</body>
</html>

Scoped CSS 作用域样式隔离

通过为子应用css选择器添加**前缀: div[data-qiankun="应用名"]**的方式,实现css样式相互不影响

js
// 实现原理
let style0 = document.getElementsByTagName('style')[0]
// console.log(style.sheet.cssRules) // 伪数组,每条选择器及其css样式
let arr = []
let prefix = 'div[data-qiankun="test"] ' // 前缀
let css = ''
style0.sheet.cssRules.forEach(({ cssText }, index) => {
    cssText = cssText.replace(/(.+)[^{]?/, `${prefix}$1\n`)
    css += cssText
})
console.log(css)
style0.textContent = css // 【赋值后样式生效】

cssRulesimage

缺点

由于给子应用所有的css选择器都添加了特定前缀,一些插入到body的DOM就无法应用到这些样式,例如:ElementUI的弹窗Dialog

scopedCss后,v-modal样式丢失image

scopedCss后,v-modal样式选择器被加前缀,此时得append到子应用自身DOM中遮罩层css才生效image

解决

将dialog的遮罩层放到子应用自身当中, 即:modal-append-to-body="false"【子应用需改造dialog属性,侵入性太大,不可取

单独加载Element css样式,使用 excludeAssetFilter 配置不被乾坤处理该样式【待实验】

Shadow DOM 样式隔离

通过shadow DOM隔离

js
const { innerHTML } = appElement
appElement.innerHTML = ''
let shadow = appElement.attachShadow({ mode: 'open' }) // 【主要调用DOM原生的attachShadow API创建shadow DOM环境】
shadow.innerHTML = innerHTML

缺点

由于给子应用应用了shadowDOM特性,css被强隔离,内部样式无法影响外部,一些插入到body的DOM就无法应用到这些样式,例如:ElementUI的弹窗Dialo


各应用避免css冲突流行方案

1、BEM命名约定

每个项目的css选择器添加特定前缀

2、Css Module

模块化CSS. 给每一个dom都给了一个独立的Hash, 可作为scoped CSS的替代方案

TIP

Css Module 需要在vue的template(jsx语法)中进行 className 的动态绑定.

  1. 需要先编写样式而不是先编写元素结构和定义

  2. 在 className 写法上由于需要使用获取对象属性的写法,会导致一些使用连字符的样式类名需要用中括号才行【这点可配置css-loader解决

js
// vue.config.js
// vue-cli天然支持css module;以下配置不写也可以
// css module与scoped不支持同时使用
css: {
  requireModuleExtension: false, // 不用指定module后缀
  loaderOptions: {
    css: {
      modules: {
        localIdentName: '[name]-[hash]' // 编译后的真正的类名
      },
      localsConvention: 'camelCaseOnly' // 只转为驼峰(camelCase: 保留原选择器的基础上还增加驼峰选择器)
    }
  }
},
// 将css打包成js对象,在template中使用
// xxx.vue
<template>
  // class的值会被编译成:test-31b137f69a6fb76865813bdcf69edf32
  <div :class="$style.test" >
    123 红色
  </div>
</template>
<style module>
.test {
  color: red
}
// $style为css modules对象
{ "test": "test-31b137f69a6fb76865813bdcf69edf32"}
</style>

3、css in js

react中的流行方案;用js写css

js
// styled-components:react的css in js流行库
import styled from 'styled-components'
const Button = styled.button`
        width: 100px;
        height: 100px;
        color: red;
      `
function App() {
// react对HTML的封装是 JSX 语言 
  return (
    <div className="App">
      <Button>123</Button>
    </div>
  );
}

export default App

4、scoped css

vue中的流行方案。通过为子应用css选择器添加前缀的方式,实现css样式相互不影响