很多开源的组件库普遍都使用了 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 进行预处理