组件库开发不一定需要 css-in-js

577 阅读3分钟

很多开源的组件库普遍都使用了 css-in-js 方案来解决样式的按需引入、动态变化、体积过大等问题,让用户在使用组件时不必再单独引入其 css 文件。比如 naive-ui 使用的 css-render组件级别的 CSS-in-JS - Ant Design 等等。当然也有 react 那边兴起的 unstyled 的原语化组件方案,比如 shadui , 这种方案将组件的样式全权交由使用者处理,组件库只负责组件的复杂逻辑与设计思想部分。

那么对于一个组件库的开发者来说,我们该怎么处理组件的样式问题呢?我在使用 css-in-js 方案时,在 js 里写样式,失去了 css 的提示,颜色快捷选取,stylelint 的规范,postcss、lightningcss 的编译时优化和浏览器兼容性polyfill,可谓是浑身难受。抛弃样式,只实现逻辑,写出来的组件库在用户使用时又需要对每一个使用的组件再次封装,也感觉很难受。

为什么不直接导入 css 为字符串呢

我知道大家都很讨厌在引入组件时还要引入样式文件,那么为什么不把编译完成的 css 字符串直接写在组件里,相当于帮用户做了 import css 呢?

偶然的机会,让我看到了另一种方案—— inline-css (我自己瞎起的) ,其实就是把 css 直接导入为字符串,然后在组件的初始化阶段动态插入到 head 。你可能会第一时间想到,vite 等工具的 css-inject 功能,但这个功能对于组件库来说是无效的,因为 css 的插入发生在全局而不是组件级别,使得导入的 css 在没使用该组件的情况下并不会被 tree-shake 掉。

esbuild 提供了社区插件 esbuild-plugin-inline-import 来实现该功能,我们只需要将插件配置一下便能实现 sass 等的预处理:

const inlinePlugin = inlineImportPlugin({
  filter: /^sass:/,
  transform: async (contents, args) => {
    const browers = browserslist()
    const browersTarget = browserslistToTargets(browers)
    const code = (await compileAsync(args.path)).css
    const res = lightCss({
      // eslint-disable-next-line node/prefer-global/buffer
      code: Buffer.from(code),
      minify: true,
      sourceMap: false,
      targets: browersTarget,
      filename: args.path,
    }).code.toString()
    return res
  },
})

然后我们就能直接 import css from 'sass:path/to/name.scss' 然后在组件的初始化过程插入到 dom 中即可。我们可以这么设计 inject 函数:

const alreadyInjected: string[] = []
export function injectStyle(style: string, id: string, refresh = false) {
  if (alreadyInjected.includes(id) && !refresh)
    return

  let styleElement = document.querySelector(`style#${id}`)

  if (styleElement) {
    styleElement.innerHTML = style
    return
  }

  styleElement = document.createElement('style')
  styleElement.id = id
  styleElement.innerHTML = style
  document.head.appendChild(styleElement)
  alreadyInjected.push(id)
}

一开始我尝试实现用 rollup 的插件实现该功能,但由于 vite 对于 css 的默认处理,使得其与 vite 难以兼容,后面采取了另一种实现思路对路径进行了二次处理才实现了兼容 rollup-plugin-inline-import ,插件默认集成了 lightningcss 与 sass,可以与 esbuild 语法兼容。

css-inline 的优点

  • 舒适的开发体验,使用你的 IDE 对于 css 的所有开发便利功能
  • 更好的性能 (对比 css-in-js ),直接编译为了字符串,不需要运行时编译
  • 与组件绑定,保证样式与组件的按需引入
  • 可以使用 postcss ,linghtningcss 等对 css 进行预处理