问题
用户在访问业务系统时,突然发现一个问题,页面A发生错乱导致业务流程中断。运营反馈到我们这边,确认用户的操作系统和浏览器类型和版本后,使用相同的系统和浏览器版本直接访问页面A无法复现,尝试按照用户的访问页面顺序,在访问页面B之后再访问页面A之后,A、B页面样式会发生错乱,问题复现。
原因分析
- 访问B页面到访问A页面,样式才出现问题,且返回去访问B页面也会样式错乱,猜测是两个页面的样式发生冲突。
- 现有项目使用微前端架构,有一个主应用,并对接不同子应用,各自子应用由不同的团队进行维护。对于样式有些团队使用CSS Module,有些则使用styled-components。仔细研究发现,这两个子应用都是使用styled-components, 在使用开发者工具查看冲突的样式之后,发现两个子应用生成的类名发生冲突,导致样式错乱。
- 对于styled-components生成的类名应该是唯一的,为什么会生成相同的类名?查看是不是因为使用相同的盒子包裹组件,翻看组件源代码,发现是不同的组件,且进一步发现相同类名的位置,随机不规律,会出现在不同的组件,故排除这个原因。
- 去styled-components的github issue查看有没有别人要相同的问题,查找到这个问题[Micro Frontend] Duplicate class name if there is multiple styled-components,里面也提到了类似的问题。至此,我们找到引起这个问题的根源:styled-components只能保证一个实例下生成的类名保证唯一,对于不同子应用,每个都会生成一个独立styled-components的实例,因此无法保证生成的类名不唯一。
- 虽然我们找到引起问题的直接原因,但进一步深挖,查看styled-components的行为,其会将生成的样式通过style标签注入到页面head部分,使用styled-components的子应用每次打开,都会往头部进行注入样式,导致样式发生重构。其本质是微前端 框架在卸载子应用的时候,副作用没有完全清除干净,导致头部的样式style标签没有进行清除,进而会影响到其他子应用,同时这也会导致随着打开页面的增多,网页文件越来越大,影响页面的性能。
解决方案
方案选型
根据分析的原因,可以有两种解决方案:
-
方案一: 推动微前端架构层面进行修改
- 优点:每次子应用卸载都完全清除相关的副作用,即使生成了相同的类名,也没关系,能够有效保证子应用间样式的隔离。
- 缺点:需要推动架构同学的去修改,修改的排期不定,而且一旦完成修复,微前端架构需要进行升级,升级底层架构有比较大的风险,需要留有一段的时间作为缓冲,需要进行充分的测试,避免给用户造成损失。
-
方案二: 修改类名前缀,每个子应用的类名前缀保持唯一,这样样式就不会发生冲突
- 优点:避免子应用的样式冲突,修复时间可控
- 缺点:需要额外的代码进行开发,且页面性能的问题没有解决。
由于用户迫切需要使用这个功能,同时避免经济损失,需要尽快修复。综上所述,决定使用方案二修复这个问题,同时反馈负责微前端架构的同学让他们排期修复,避免其他业务也会受到这个缺陷影响。
修复方法
-
修改前缀
官方有提供babel-plugin-styled-components插件修改类名前缀。
babel: {
babelOptions: {
plugins:[
['babel-plugin-styled-components', {namespace: [每个子应用特有的变量]}],
],
},
}
但是这个修改只可以修改第一个类名,而这个类名是不控制样式,是第二个类名才是控制样式的。
-
styled-components类名生成规则
进一步研究styled-components类名生成的规则,一个是sc前缀类名,另一个是个hash类名。通过这篇文章深入理解styled-components运行机制,了解其类名受三个参数的影响:styledComponentId generatedClassName props.className。
看了类名生成的规则,第二个类名的生成受第一个类名的影响,要保证类名的唯一,可以通过修改第一个类名的前缀,使得第一个类名唯一,从而使得第二个类名碰撞的概率降低。
在项目中实际尝试这种方法,没有发生样式冲突,至此,这个问题搞定,收工!!