css-in-js在qiankun微前端切换丢失样式问题(styled-components/emotion)

背景

开门见山,我们在解该问题前先做一个 css in js 插入 style 标签的前提介绍:

方案说明
快速方案A目前使用现代 css style sheet 也就是 cssom 的 api 去操作样式是性能最好的,会把一堆 css 放到一个 <style></style> 标签里,这种方案速度很快,支持万级别的样式插入
慢速方案B而早期是一个 style 标签对应插入一个样式,这样会比较慢,但是他在开发环境会很方便于修改和调试
问题

问题是什么呢?

问题是在 qiankun 里,非第一次加载同一个子应用时(比如切换了子应用或者在主应用和子应用间切换),会随机性产生丢失 cssom 的样式问题,可见相关 issue :

解法

有了以上基础知识铺垫,很明显,既然是 cssom 导致的,那我们只需要将其 快速方案A 切换为 慢速方案B ,返璞归真即可。

以下介绍两个库( styled-componentsemotion )的具体解法,其他库均同理,请自行探索源码

styled-components

sc 有两种解法,为了回退到 B 旧插入模式,配置外层 Context 即可:

import { StyleSheetManager } from 'styled-components'

export default function App() {

  return (
    <StyleSheetManager disableCSSOMInjection>
      {/* ... */}
    </StyleSheetManager>
  )
}

此处 disableCSSOMInjection 即代表回退到旧式单 style 对应单 css 方案。

除此之外,还可以通过环境变量解,以下是源码片段,我们来看两个逻辑:

// 默认值逻辑

const defaultOptions: SheetOptions = {
  isServer: !IS_BROWSER,
  // ↓ 这里是该 option 的默认值获取处
  useCSSOMInjection: !DISABLE_SPEEDY,
};

// ↓ 通过环境变量判断了默认取值
export const DISABLE_SPEEDY = Boolean(
  typeof SC_DISABLE_SPEEDY === 'boolean'
    ? SC_DISABLE_SPEEDY
    : typeof process !== 'undefined' &&
      typeof process.env.REACT_APP_SC_DISABLE_SPEEDY !== 'undefined' &&
      process.env.REACT_APP_SC_DISABLE_SPEEDY !== ''
    ? process.env.REACT_APP_SC_DISABLE_SPEEDY === 'false'
      ? false
      : process.env.REACT_APP_SC_DISABLE_SPEEDY
    : typeof process !== 'undefined' &&
      typeof process.env.SC_DISABLE_SPEEDY !== 'undefined' &&
      process.env.SC_DISABLE_SPEEDY !== ''
    ? process.env.SC_DISABLE_SPEEDY === 'false'
      ? false
      : process.env.SC_DISABLE_SPEEDY
    : process.env.NODE_ENV !== 'production'
);

也就是说你可以通过如下配置 env 环境变量实现默认关闭:

// .env
SC_DISABLE_SPEEDY=false
// or (in cra, `REACT_APP` prefix env will auto inject)
REACT_APP_SC_DISABLE_SPEEDY=false
emotion

emotion 的解法也同理,配置 speedyfalse 即可,注意你需要提前安装 @emotion/cache 包来提供外层自定义 Context,因为 emotion 会缓存 style 到 cache store 内:

pnpm add @emotion/cache

配置 Cache Context:

import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react'

const myCache = createCache({
  key: 'x',
  speedy: false,
})

export default function App() {

  return (
    <CacheProvider value={myCache}>
      {/* ... */}
    </CacheProvider>
  )
}

值得一提的是,emotion 这里必须要配置唯一 key 作为所有样式的 prefix ,是一个不太好的地方,也就是说至少给我们带来了 n * 1 个字节的体积增大( n 为 emotion 使用次数 )。

快速带大家看下源码:

// 片段 1:默认值逻辑
constructor(options: Options) {
  this.isSpeedy =
    options.speedy === undefined
      ? process.env.NODE_ENV === 'production'
      : options.speedy
}

// --------------------------------------------

// 片段 2:插入标签部分逻辑
insert(rule: string) {

  // 🌈 这里如果是 false 就会只插入一个标签
  if (this.ctr % (this.isSpeedy ? 65000 : 1) === 0) {
    this._insertTag(createStyleElement(this))
  }

  // ...省略很多行

  if (this.isSpeedy) {
    const sheet = sheetForTag(tag)
    
    // ...省略很多行
    
  } else {
  	// 🌈 非 cssom 式操作
    tag.appendChild(document.createTextNode(rule))
  }
  
}

如果以上源码都看不懂也无所谓,只要记住 speedy 这个词是代表快速插入即可,如果是其他 css in js 库可以朝着 speedy 关键词找。

另外需要注意的是,在开发环境下,为了便于调试和修改,不管是 sc 还是 emotion 默认都开启了 慢速模式B (可在上面的源码片段中追溯),这也是在本地不会发现端倪构建后才出现问题的一个点。

总结

最后我们总结思考下:

  1. 为了避免 qiankun 问题,不改动 qiankun 代码的前提下,回退到 B方案 是目前唯一解,因此选择 sc 还是 emotion 谁快谁慢应该没区别了,也就是说享受不到 cssom 还是库优化的好处。

  2. emotion 使用姿势很多,sc 比较单一,但 emotion 多姿势的代价是要兼容他的 css 对应的 jsx runtime 注入,这需要额外的 babel 工作量。

  3. 回退到 B方案 时,sc 配置比较灵活(甚至支持环境变量隐式配置),而 emotion 配置稍复杂,还存在体积增大的副作用。

  4. 用 css in js 就要 babel 插件压缩,同时有了 babel 插件才能在开发时显示类名,而到了 swc 未来,nextjs 正在写 sc 的 swc 插件,有 roadmap,进度虽然缓慢,但 emotion 的 swc 插件却还没打算写,甚至都不用说进度了。

综合来看,建议使用 sc 比较面向未来,当然是在 qiankun 场景下,如果非 qiankun 场景,建议使用 emotion 。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值