当前位置: 首页 > Web前端 > CSS

微前端架构下的样式控制方式:使用CSSVariable实现“样式参数”

时间:2023-03-30 14:16:48 CSS

背景目前社区比较关注微前端应用样式的“隔离”。网上已经有很多文章介绍微前端的风格隔离方案。但是,如果微应用的风格只有“隔离性”而没有“可控性”,那么微应用的风格就是固定的:就像一个不接受外部参数的函数,永远只做预先定义好的事情并且不接受用户的呼叫控制和配置。在这种死板的微应用架构下,宿主应用很难控制整个站点的风格主题,因为微应用本身就具有封闭性和顽固性。应用场景将大大减少。例如,以下需求很难实现:主题品牌颜色从橙色升级为浅蓝色,浅色/深色主题在线切换。同一个微应用在多个主机中使用。在某些主机中,它显示为橙色;在其他情况下,它看起来是蓝色的。以往的实现方式需要巨大的成本:比如对每个微应用,为每个主题构建一个css,然后根据宿主当前的主题状态,使用js切换每个微应用的css。而且,任何涉及主题的修改都需要开发者修改、重建、发布多个微应用,非常繁琐。根本原因在于微应用的样式是硬编码的(并实现了样式隔离),无法通过宿主的配置来改变,主应用和子应用之间缺乏信息传递。因此,风格控制的需求就诞生了:当来自不同项目(不同团队,或者不同时期开发的应用)的UI集成在一起时,需要风格控制来实现风格的封装性和可控性。样式控制理论也适用于业务组件和块。样式控制方案介绍本文分享一种通过CSSVariable实现微应用样式控制的方案:微应用的样式不再是固定的,宿主应用可以通过“传参”的方式进行控制。在这个方案中,微应用的样式就像一个带有可选参数的函数:functionrenderStyle(cssVar1=defaultVar1,cssVar2=defaultVar2){//使用cssVar1,cssVar2,cssVar3来渲染样式//其中cssVar3来自环境捕获通过闭包,调用者无需显式传入}在封装内部样式的同时,对外提供了可配置的API。更灵活地满足各种场景的需求。根据样式变量的默认来源,样式控制方式可以分为两种:隔离优先:微应用有自己的默认主题。默认情况下,它使用自己的主题,不受宿主环境的影响。开箱即用,无需任何配置。主人在掌控之中。当宿主想要控制风格时,可以准确、按需、显式覆盖每个微应用的主题变量,确保整个应用和谐一致。继承优先级:微应用的样式变量默认继承宿主环境的值。主机不需要显式覆盖。下面通过几个简单的例子来介绍本方案的实现思路。隔离优先模式的微应用默认使用自己的主题,不受托管环境的影响。宿主可以显式覆盖其中的样式变量。开箱即用的默认主题是微应用挂载的样式:/*microappcss*/.widget-k7na5-default-theme{--button-bg:orange;}.widget-k7na5-btn{background-color:var(--button-bg);}其中widget-k7na5为微应用的类名前缀,实现样式隔离。可以使用网上各种实现样式隔离的方法(如css模块、css-in-js),都可以与本方案结合使用。其DOM结构如下:button

因此,此微应用将显示默认的橙色主题。开箱即用,无需配置。它的关键点是在微应用的根元素上定义一个默认的样式变量;然后在微应用内部,引用样式变量来实现样式,而不是硬编码样式中的具体值。即使宿主不小心使用了同名的变量名(比如宿主css有html{--button-bg:red;}),微应用也不会受到影响。因为微应用根元素将这个变量重置为默认值,这样可以保证良好的封装和隔离。主播显式覆盖微应用的样式变量如果主播想把整个站点的主题升级为蓝色,那么微应用就不能继续呈现橙色了。因此宿主需要能够覆盖微应用的默认主题。怎么做?首先,微应用应提供一个API,允许宿主配置微应用根元素的类名。/*宿主js*///宿主加载微应用时,自定义微应用根元素类名functionApp(){return}所以微应用有如下DOM结构:button
微应用加载的样式和之前一样,不用改:/*微应用css*/.widget-k7na5-default-theme{--button-bg:orange;}.widget-k7na5-btn{background-color:var(--button-bg);}因此,微应用的样式始终只准备一份,并且无需使用js进行动态切换,实现和维护都非常简单。宿主的样式包括以下主题变量定义:/*宿主css*//*选择器权重高于微应用自身的变量定义*/.theme-blue.theme-blue{--button-bg:blue;}完毕!现在微应用中的按钮将以蓝色主题显示!这里的关键点是宿主覆盖了微应用根元素上的cssVar样式变量定义。宿主只需要使用该方法为每个微应用添加类名.theme-blue,即可将整个站点统一为一个蓝色主题。在此主题升级过程中,各微应用无需进行任何改动或发布。通过styleAPI维护封装注意宿主从未侵入过微应用的内部实现,维护微应用的封装。宿主仅使用以下API:通过className自定义根元素类名。微应用支持使用cssVar变量名进行配置。它们就像函数的命名参数,同时也是一种API。如果微应用有一些内部的cssVar变量名,你不想在外部使用,你可以使用特殊的命名规则来避免它们。只要这两个API保持稳定,宿主和微应用就可以独立迭代。封装满足灵活性。进阶范例:精准控制能力该方案简单灵活,宿主可以精准控制每一个微应用,为不同的微应用传入不同的样式变量。宿主js如下,每个微应用传入主题类名:/*宿主js*///宿主加载微应用时,微应用根元素的类名可以自定义functionApp(){return(<>);}host的style包含了每一个主题类名的变量定义:/*hostcss*//*selectorweightishigherthanthewidget'sownvariabledefinition*/.theme-blue.theme-blue{--button-bg:blue;}.theme-green.theme-green{--button-bg:green;}在这种情况下,前者将显示为蓝色主题,而后者将显示为绿色主题。两者互不干扰,完全在宿主的掌控之中。继承优先模式在前面的隔离优先模式中,微应用默认显示自己的主题。宿主可以覆盖微应用的主题,但是需要显式自定义每个微应用的类名,有点麻烦。cssVar也可以实现继承优先级方案:小程序默认继承宿主的样式变量,宿主不需要向微程序传递额外的配置。以方便为代价换取控制的清晰度(在隔离优先模式中,微应用主题由根元素类名明确控制)。Hostjs:/*Hostjs*/functionApp(){return}其中disableDefaultTheme使得微应用根元素没有默认主题的类名.因此,微应用DOM结构如下:button
hostcss:/*Hostcss*/html{--button-bg:blue;}注意host直接在全局作用域定义主题变量。无需为每个微应用的根元素定义变量。微应用加载的css和之前一样,不用改:/*微应用css*//*注意现在根元素没有.default-theme类名,所以这条规则有不生效*/.widget-k7na5-default-theme{--button-bg:orange;}.widget-k7na5-btn{background-color:var(--button-bg);}这样,微-app会默认从主机环境中读取--button-bg的值,显示为蓝色。当然,宿主仍然可以显式覆盖微应用的样式变量。总结本文讨论了微应用和宿主之间的两种样式控制模式:隔离优先级:默认使用微应用自身的样式变量,宿主可以显式覆盖继承优先级:宿主环境的样式变量被继承默认。这两种模式可以结合使用,比如微应用的部分样式变量使用【隔离优先模式】,其他样式变量使用【继承优先模式】。请注意,在前面的所有示例中,微应用加载的css都没有改变。我们只需要对根元素的类名进行操作,就可以实现上述微应用风格的控制方式。所以微应用不需要为每个模式捆绑一份css。js的实现也很简单(只是操作根元素类名)。宿主应用程序可以轻松控制和切换整个站点的风格。这两种模式的实现可以内置到微前端框架中。微应用开发者只需要定义自己想要使用的主题变量、变量的默认值、变量的控制方式即可。根元素的类名由微应用加载器管理。