微前端下多个Styled-Component样式冲突及其解决方案

1,284 阅读4分钟

问题

用户在访问业务系统时,突然发现一个问题,页面A发生错乱导致业务流程中断。运营反馈到我们这边,确认用户的操作系统和浏览器类型和版本后,使用相同的系统和浏览器版本直接访问页面A无法复现,尝试按照用户的访问页面顺序,在访问页面B之后再访问页面A之后,A、B页面样式会发生错乱,问题复现。

原因分析

  1. 访问B页面到访问A页面,样式才出现问题,且返回去访问B页面也会样式错乱,猜测是两个页面的样式发生冲突。
  1. 现有项目使用微前端架构,有一个主应用,并对接不同子应用,各自子应用由不同的团队进行维护。对于样式有些团队使用CSS Module,有些则使用styled-components。仔细研究发现,这两个子应用都是使用styled-components, 在使用开发者工具查看冲突的样式之后,发现两个子应用生成的类名发生冲突,导致样式错乱。

image.png

  1. 对于styled-components生成的类名应该是唯一的,为什么会生成相同的类名?查看是不是因为使用相同的盒子包裹组件,翻看组件源代码,发现是不同的组件,且进一步发现相同类名的位置,随机不规律,会出现在不同的组件,故排除这个原因。
  1. 去styled-components的github issue查看有没有别人要相同的问题,查找到这个问题[Micro Frontend] Duplicate class name if there is multiple styled-components,里面也提到了类似的问题。至此,我们找到引起这个问题的根源:styled-components只能保证一个实例下生成的类名保证唯一,对于不同子应用,每个都会生成一个独立styled-components的实例,因此无法保证生成的类名不唯一。
  1. 虽然我们找到引起问题的直接原因,但进一步深挖,查看styled-components的行为,其会将生成的样式通过style标签注入到页面head部分,使用styled-components的子应用每次打开,都会往头部进行注入样式,导致样式发生重构。其本质是微前端 框架在卸载子应用的时候,副作用没有完全清除干净,导致头部的样式style标签没有进行清除,进而会影响到其他子应用,同时这也会导致随着打开页面的增多,网页文件越来越大,影响页面的性能。

解决方案

方案选型

根据分析的原因,可以有两种解决方案:

  1. 方案一: 推动微前端架构层面进行修改

    1. 优点:每次子应用卸载都完全清除相关的副作用,即使生成了相同的类名,也没关系,能够有效保证子应用间样式的隔离。
    2. 缺点:需要推动架构同学的去修改,修改的排期不定,而且一旦完成修复,微前端架构需要进行升级,升级底层架构有比较大的风险,需要留有一段的时间作为缓冲,需要进行充分的测试,避免给用户造成损失。
  1. 方案二: 修改类名前缀,每个子应用的类名前缀保持唯一,这样样式就不会发生冲突

    1. 优点:避免子应用的样式冲突,修复时间可控
    2. 缺点:需要额外的代码进行开发,且页面性能的问题没有解决。

由于用户迫切需要使用这个功能,同时避免经济损失,需要尽快修复。综上所述,决定使用方案二修复这个问题,同时反馈负责微前端架构的同学让他们排期修复,避免其他业务也会受到这个缺陷影响。

修复方法

  1. 修改前缀

官方有提供babel-plugin-styled-components插件修改类名前缀。

babel: {
    babelOptions: {
      plugins:[
       ['babel-plugin-styled-components', {namespace: [每个子应用特有的变量]}],
      ],
    },
  }

但是这个修改只可以修改第一个类名,而这个类名是不控制样式,是第二个类名才是控制样式的。

  1. styled-components类名生成规则

进一步研究styled-components类名生成的规则,一个是sc前缀类名,另一个是个hash类名。通过这篇文章深入理解styled-components运行机制,了解其类名受三个参数的影响:styledComponentId generatedClassName props.className

看了类名生成的规则,第二个类名的生成受第一个类名的影响,要保证类名的唯一,可以通过修改第一个类名的前缀,使得第一个类名唯一,从而使得第二个类名碰撞的概率降低。

在项目中实际尝试这种方法,没有发生样式冲突,至此,这个问题搞定,收工!!