CSS
Bun 的打包工具内置支持带有现代特性的 CSS
Bun 的打包工具内置支持 CSS,具有以下功能:
- 将现代/未来特性转换为可在所有浏览器上运行的代码(包括添加厂商前缀)
- 压缩
- CSS 模块
- Tailwind(通过原生打包工具插件)
转译
默认启用转译和厂商前缀处理,因此你可以使用现代及未来的 CSS 特性,而无需担心浏览器兼容性。
Bun 的 CSS 解析器和打包器直接移植自 LightningCSS,其打包方式受 esbuild 启发。转译器会将现代 CSS 语法转换为向后兼容的等效写法,使其能够在各种浏览器中正常运行。
浏览器兼容性
默认情况下,Bun 的 CSS 打包目标浏览器包括:
- Edge 80+
- Firefox 78+
- Chrome 80+
- Safari 14+
- Opera 67+
语法降级
嵌套
借助 CSS 嵌套,你可以直接在父级代码块中编写子级样式,而无需在 CSS 文件中重复父级选择器。
/* 使用嵌套 */
.card {
background: white;
border-radius: 4px;
.title {
font-size: 1.2rem;
font-weight: bold;
}
.content {
padding: 1rem;
}
}
Bun 的 CSS 打包工具会自动将此嵌套语法转换成传统的扁平 CSS,以兼容所有浏览器:
/* 编译输出 */
.card {
background: white;
border-radius: 4px;
}
.card .title {
font-size: 1.2rem;
font-weight: bold;
}
.card .content {
padding: 1rem;
}
你还可以在选择器中嵌套媒体查询和其他 at 规则:
.responsive-element {
display: block;
@media (min-width: 768px) {
display: flex;
}
}
编译后为:
.responsive-element {
display: block;
}
@media (min-width: 768px) {
.responsive-element {
display: flex;
}
}
颜色混合
color-mix() 函数会在所选色彩空间中按给定比例混合两种颜色。使用它可以创建颜色变体,而无需自行计算结果值。
.button {
/* 在 RGB 色彩空间中以 30%:70% 混合蓝色和红色 */
background-color: color-mix(in srgb, blue 30%, red);
/* 为悬停状态创建较浅的变体 */
&:hover {
background-color: color-mix(in srgb, blue 30%, red, white 20%);
}
}
Bun 的 CSS 打包工具会在构建时评估这些颜色混合(当所有颜色值已确定,非 CSS 变量),生成适用于所有浏览器的静态颜色值:
.button {
/* 精确计算得到的结果色 */
background-color: #b31a1a;
}
.button:hover {
background-color: #c54747;
}
相对颜色
相对颜色语法可以修改现有颜色的单个组成部分。无需重新计算整个颜色,即可调整亮度、饱和度或单独的颜色通道。
.theme-color {
/* 以基础色为起点,将亮度增加 15% */
--accent: lch(from purple calc(l + 15%) c h);
/* 以我们的品牌蓝色为基色,制作一个去饱和版本 */
--subtle-blue: oklch(from var(--brand-blue) l calc(c * 0.8) h);
}
Bun 的 CSS 打包工具会在构建时计算这些相对颜色修改(不使用 CSS 变量时),生成适用于浏览器的静态颜色值:
.theme-color {
--accent: lch(69.32% 58.34 328.37);
--subtle-blue: oklch(60.92% 0.112 240.01);
}
可将其用于主题生成、无障碍颜色变体,或根据基础颜色生成颜色阶,而不是为每个值硬编码。
LAB 颜色
现代 CSS 支持感知均匀的颜色空间 LAB、LCH、OKLAB 和 OKLCH,它们可以表示超出标准 RGB 色域的颜色。
.vibrant-element {
/* 一种超出 sRGB 色域边界的鲜艳红色 */
color: lab(55% 78 35);
/* 使用感知色彩空间的平滑渐变 */
background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg));
}
Bun 的 CSS 打包器会将这些颜色格式转换为向后兼容的替代方案,以支持不支持这些格式的浏览器:
.vibrant-element {
/* 退回到最接近的 RGB 近似 */
color: #ff0f52;
/* 支持更宽色域的浏览器使用 P3 备选 */
color: color(display-p3 1 0.12 0.37);
/* 支持该格式的浏览器保持原值 */
color: lab(55% 78 35);
background: linear-gradient(to right, #cd4e15, #3887ab);
background: linear-gradient(to right, oklch(65% 0.25 10deg), oklch(65% 0.25 250deg));
}
颜色函数
color() 函数可以在传统 RGB 之外的预定义色彩空间中指定颜色,让你能够使用更宽的色域。
.vivid-element {
/* 使用 Display P3 色彩空间实现更宽色域 */
color: color(display-p3 1 0.1 0.3);
/* 使用 A98 RGB 色彩空间 */
background-color: color(a98-rgb 0.44 0.5 0.37);
}
对于不支持这些色彩空间的浏览器,Bun 的 CSS 打包器会添加 RGB 备选值:
.vivid-element {
/* 优先使用 RGB 备选以获得最大兼容 */
color: #fa1a4c;
/* 保留原色以支持兼容浏览器 */
color: color(display-p3 1 0.1 0.3);
background-color: #6a805d;
background-color: color(a98-rgb 0.44 0.5 0.37);
}
HWB 颜色
HWB(色相、白度、黑度)颜色模型基于纯色相中混合了多少白色或黑色来表示颜色。与 RGB 或 HSL 值相比,使用这种方式可以更直接地创建浅色和深色变体。
.easy-theming {
/* 纯青色,无白无黑 */
--primary: hwb(180 0% 0%);
/* 同色相,加入 20% 白色(色调) */
--primary-light: hwb(180 20% 0%);
/* 同色相,加入 30% 黑色(阴影) */
--primary-dark: hwb(180 0% 30%);
/* 添加白色和黑色的柔和版本 */
--primary-muted: hwb(180 30% 20%);
}
Bun 的 CSS 打包器会将 HWB 颜色转换为 RGB,以兼容所有浏览器:
.easy-theming {
--primary: #00ffff;
--primary-light: #33ffff;
--primary-dark: #00b3b3;
--primary-muted: #339999;
}
颜色表示法
现代 CSS 支持使用空格分隔的 RGB 和 HSL 值(不带逗号),以及带 alpha 通道的十六进制颜色。
.modern-styling {
/* 空格分隔的 RGB 表示法(无逗号) */
color: rgb(50 100 200);
/* 空格分隔带 alpha 的 RGB */
border-color: rgba(100 50 200 / 75%);
/* 带 alpha 通道的 8 位十六进制 */
background-color: #00aaff80;
/* 简化的 HSL 表示 */
box-shadow: 0 5px 10px hsl(200 50% 30% / 40%);
}
Bun 的 CSS 打包器会将这些格式转换为旧版浏览器支持的格式:
.modern-styling {
/* 转为旧版需要的逗号格式 */
color: rgb(50, 100, 200);
/* 透明度通道妥善处理 */
border-color: rgba(100, 50, 200, 0.75);
/* 十六进制带 alpha 转为 rgba */
background-color: rgba(0, 170, 255, 0.5);
box-shadow: 0 5px 10px rgba(38, 115, 153, 0.4);
}
light-dark() 颜色函数
light-dark() 函数接受两种颜色,并根据当前颜色方案应用其中一种,使样式无需使用媒体查询即可遵循用户的系统偏好。
:root {
/* 定义支持的颜色方案 */
color-scheme: light dark;
}
.themed-component {
/* 根据系统偏好自动选择颜色 */
background-color: light-dark(#ffffff, #121212);
color: light-dark(#333333, #eeeeee);
border-color: light-dark(#dddddd, #555555);
}
/* 需要时覆盖系统偏好 */
.light-theme {
color-scheme: light;
}
.dark-theme {
color-scheme: dark;
}
对于不支持 light-dark() 的浏览器,Bun 的 CSS 打包器会将其转换为带有回退值的 CSS 变量:
:root {
--buncss-light: initial;
--buncss-dark: ;
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
--buncss-light: ;
--buncss-dark: initial;
}
}
.light-theme {
--buncss-light: initial;
--buncss-dark: ;
color-scheme: light;
}
.dark-theme {
--buncss-light: ;
--buncss-dark: initial;
color-scheme: dark;
}
.themed-component {
background-color: var(--buncss-light, #ffffff) var(--buncss-dark, #121212);
color: var(--buncss-light, #333333) var(--buncss-dark, #eeeeee);
border-color: var(--buncss-light, #dddddd) var(--buncss-dark, #555555);
}
逻辑属性
CSS 逻辑属性根据文档的书写模式和文本方向,而不是屏幕的物理方向来定义布局、间距和尺寸,因此布局可以适应不同的书写系统。
.multilingual-component {
/* 适应书写方向的外边距 */
margin-inline-start: 1rem;
/* 无论文本方向,都合理的内边距 */
padding-block: 1rem 2rem;
/* 顶部起始角的边框半径 */
border-start-start-radius: 4px;
/* 尊重书写模式的尺寸 */
inline-size: 80%;
block-size: auto;
}
对于不完全支持逻辑属性的浏览器,Bun 的 CSS 打包器会针对每种文本方向将其编译为物理属性:
/* 左到右语言 */
.multilingual-component:dir(ltr) {
margin-left: 1rem;
padding-top: 1rem;
padding-bottom: 2rem;
border-top-left-radius: 4px;
width: 80%;
height: auto;
}
/* 右到左语言 */
.multilingual-component:dir(rtl) {
margin-right: 1rem;
padding-top: 1rem;
padding-bottom: 2rem;
border-top-right-radius: 4px;
width: 80%;
height: auto;
}
如果不支持 :dir() 选择器,Bun 会生成额外的回退方案。
:dir() 选择器
:dir() 伪类根据元素的文本方向(RTL 或 LTR)设置样式,该方向由文档或显式的 direction 属性决定。使用它可以编写无需 JavaScript 的方向感知样式。
/* 根据文本方向应用不同样式 */
.nav-arrow:dir(ltr) {
transform: rotate(0deg);
}
.nav-arrow:dir(rtl) {
transform: rotate(180deg);
}
/* 基于文本流的位置定位元素 */
.sidebar:dir(ltr) {
border-right: 1px solid #ddd;
}
.sidebar:dir(rtl) {
border-left: 1px solid #ddd;
}
对于不支持 :dir() 选择器的浏览器,Bun 的 CSS 打包器会将其转换为兼容性更广的 :lang() 选择器,并使用适当的语言映射:
/* 作为回退转换为基于语言的选择器 */
.nav-arrow:lang(en, fr, de, es, it, pt, nl) {
transform: rotate(0deg);
}
.nav-arrow:lang(ar, he, fa, ur) {
transform: rotate(180deg);
}
.sidebar:lang(en, fr, de, es, it, pt, nl) {
border-right: 1px solid #ddd;
}
.sidebar:lang(ar, he, fa, ur) {
border-left: 1px solid #ddd;
}
如果不支持向 :lang() 传入多个参数,Bun 会生成进一步的回退方案。
:lang() 选择器
:lang() 伪类根据元素的语言定位元素。要为相关语言组合规则,可以向单个 :lang() 传入多个语言代码。
/* 针对中日韩语言的排版调整 */
:lang(zh, ja, ko) {
line-height: 1.8;
font-size: 1.05em;
}
/* 不同语言组的引号样式 */
blockquote:lang(fr, it, es, pt) {
font-style: italic;
}
blockquote:lang(de, nl, da, sv) {
font-weight: 500;
}
对于不支持在 :lang() 选择器中使用多个参数的浏览器,Bun 的 CSS 打包器会将此语法转换为行为相同的 :is() 选择器:
/* 使用 :is() 组合多个语言以提高兼容 */
:is(:lang(zh), :lang(ja), :lang(ko)) {
line-height: 1.8;
font-size: 1.05em;
}
blockquote:is(:lang(fr), :lang(it), :lang(es), :lang(pt)) {
font-style: italic;
}
blockquote:is(:lang(de), :lang(nl), :lang(da), :lang(sv)) {
font-weight: 500;
}
如果需要,Bun 还可以为 :is() 生成额外的回退方案。
:is() 选择器
:is() 伪类函数(之前称为 :matches())接收一个选择器列表,如果列表中的任意选择器匹配,则匹配成功。
/* 不用分别写这些 */
/*
.article h1,
.article h2,
.article h3 {
margin-top: 1.5em;
}
*/
/* 可以写成 */
.article :is(h1, h2, h3) {
margin-top: 1.5em;
}
/* 复杂示例,多个组合 */
:is(header, main, footer) :is(h1, h2, .title) {
font-family: "Heading Font", sans-serif;
}
对于不支持 :is() 的浏览器,Bun 的 CSS 打包工具提供使用厂商前缀的替代写法:
/* 使用 -webkit-any 回退 */
.article :-webkit-any(h1, h2, h3) {
margin-top: 1.5em;
}
/* 使用 -moz-any 回退 */
.article :-moz-any(h1, h2, h3) {
margin-top: 1.5em;
}
/* 现代浏览器保留原写法 */
.article :is(h1, h2, h3) {
margin-top: 1.5em;
}
/* 复杂示例的回退 */
:-webkit-any(header, main, footer) :-webkit-any(h1, h2, .title) {
font-family: "Heading Font", sans-serif;
}
:-moz-any(header, main, footer) :-moz-any(h1, h2, .title) {
font-family: "Heading Font", sans-serif;
}
:is(header, main, footer) :is(h1, h2, .title) {
font-family: "Heading Font", sans-serif;
}
:not() 选择器
:not() 伪类会排除匹配某个选择器的元素。现代版本支持多个参数,可以通过一个 :not() 排除多种模式。
/* 选择所有按钮,排除主按钮和次按钮 */
button:not(.primary, .secondary) {
background-color: #f5f5f5;
border: 1px solid #ddd;
}
/* 应用样式于所有标题,排除侧边栏或页脚内的 */
h2:not(.sidebar *, footer *) {
margin-top: 2em;
}
对于不支持 :not() 中多个参数的浏览器,Bun 的 CSS 打包器会将此语法转换为行为相同且兼容性更好的形式:
/* 转换为使用带 :is() 的 :not() */
button:not(:is(.primary, .secondary)) {
background-color: #f5f5f5;
border: 1px solid #ddd;
}
h2:not(:is(.sidebar *, footer *)) {
margin-top: 2em;
}
如果不支持 :is(),Bun 会生成进一步的回退:
/* 额外回退以确保最大兼容 */
button:not(:-webkit-any(.primary, .secondary)) {
background-color: #f5f5f5;
border: 1px solid #ddd;
}
button:not(:-moz-any(.primary, .secondary)) {
background-color: #f5f5f5;
border: 1px solid #ddd;
}
button:not(:is(.primary, .secondary)) {
background-color: #f5f5f5;
border: 1px solid #ddd;
}
转换后的选择器保留了原始选择器的优先级和行为。
数学函数
CSS 包含以下数学函数:
- 标准数学函数:
round()、mod()、rem()、abs()、sign() - 三角函数:
sin()、cos()、tan()、asin()、acos()、atan()、atan2() - 指数函数:
pow()、sqrt()、exp()、log()、hypot()
.dynamic-sizing {
/* 限定值在最小和最大之间 */
width: clamp(200px, 50%, 800px);
/* 四舍五入到最接近的倍数 */
padding: round(14.8px, 5px);
/* 动画或布局的三角函数 */
transform: rotate(calc(sin(45deg) * 50deg));
/* 组合多个函数的复杂数学 */
--scale-factor: pow(1.25, 3);
font-size: calc(16px * var(--scale-factor));
}
当所有值都是已知常量(而非变量)时,Bun 的 CSS 打包器会在构建时计算这些表达式:
.dynamic-sizing {
width: clamp(200px, 50%, 800px);
padding: 15px;
transform: rotate(35.36deg);
--scale-factor: 1.953125;
font-size: calc(16px * var(--scale-factor));
}
媒体查询范围
媒体查询范围语法使用比较运算符(<、>、<=、>=)表示断点,而不是使用更冗长的 min- 和 max- 前缀。
/* 比较运算符的现代写法 */
@media (width >= 768px) {
.container {
max-width: 720px;
}
}
/* 使用 <= 和 >= 的包含范围 */
@media (768px <= width <= 1199px) {
.sidebar {
display: flex;
}
}
/* 使用 < 和 > 的排他范围 */
@media (width > 320px) and (width < 768px) {
.mobile-only {
display: block;
}
}
Bun 的 CSS 打包器会将范围查询转换为传统的媒体查询语法,以兼容所有浏览器:
/* 转换为传统 min/max 语法 */
@media (min-width: 768px) {
.container {
max-width: 720px;
}
}
@media (min-width: 768px) and (max-width: 1199px) {
.sidebar {
display: flex;
}
}
@media (min-width: 321px) and (max-width: 767px) {
.mobile-only {
display: block;
}
}
简写属性
CSS 引入了几种将多个长属性组合在一起的简写属性。
/* 对齐简写 */
.flex-container {
/* align-items 和 justify-items 的简写 */
place-items: center start;
/* align-content 和 justify-content 的简写 */
place-content: space-between center;
}
.grid-item {
/* align-self 和 justify-self 的简写 */
place-self: end center;
}
/* 双值溢出属性 */
.content-box {
/* 第一个值对应水平,第二个垂直 */
overflow: hidden auto;
}
/* 增强的文本装饰 */
.fancy-link {
/* 结合多条文本装饰属性 */
text-decoration: underline dotted blue 2px;
}
/* 双值 display 语法 */
.component {
/* 外层和内层显示类型 */
display: inline flex;
}
对于不支持这些简写属性的浏览器,Bun 会将它们转换为对应的组件长属性:
.flex-container {
/* 展开对齐属性 */
align-items: center;
justify-items: start;
align-content: space-between;
justify-content: center;
}
.grid-item {
align-self: end;
justify-self: center;
}
.content-box {
/* 分别控制溢出方向 */
overflow-x: hidden;
overflow-y: auto;
}
.fancy-link {
/* 单独文本装饰属性 */
text-decoration-line: underline;
text-decoration-style: dotted;
text-decoration-color: blue;
text-decoration-thickness: 2px;
}
.component {
/* 单值 display */
display: inline-flex;
}
双位置渐变
双位置渐变语法会在两个相邻位置指定相同的颜色,从而创建硬色标:产生锐利的过渡,而不是平滑的渐变。它适用于条纹、色带以及其他多色设计。
.striped-background {
/* 在 30%-40% 制造绿色到红色的硬切换 */
background: linear-gradient(
to right,
yellow 0%,
green 20%,
green 30%,
red 30%,
/* 双点位置制造硬边界 */ red 70%,
blue 70%,
blue 100%
);
}
.progress-bar {
/* 制造明显色块 */
background: linear-gradient(
to right,
#4caf50 0% 25%,
/* 绿色从 0% 到 25% */ #ffc107 25% 50%,
/* 黄色从 25% 到 50% */ #2196f3 50% 75%,
/* 蓝色从 50% 到 75% */ #9c27b0 75% 100% /* 紫色从 75% 到 100% */
);
}
对于不支持此语法的浏览器,Bun 的 CSS 打包器会通过复制颜色标记,将其转换为传统格式:
.striped-background {
background: linear-gradient(
to right,
yellow 0%,
green 20%,
green 30%,
red 30%,
/* 分成两段色点 */ red 70%,
blue 70%,
blue 100%
);
}
.progress-bar {
background: linear-gradient(
to right,
#4caf50 0%,
#4caf50 25%,
/* 两段色点绿段 */ #ffc107 25%,
#ffc107 50%,
/* 两段色点黄段 */ #2196f3 50%,
#2196f3 75%,
/* 两段色点蓝段 */ #9c27b0 75%,
#9c27b0 100% /* 两段色点紫段 */
);
}
system-ui 字体
system-ui 通用字体系列使用设备的原生 UI 字体。
.native-interface {
/* 使用系统默认 UI 字体 */
font-family: system-ui;
}
.fallback-aware {
/* 带显式回退的系统 UI 字体 */
font-family: system-ui, sans-serif;
}
对于不支持 system-ui 的浏览器,Bun 的 CSS 打包器会将其扩展为跨平台字体堆栈:
.native-interface {
/* 为所有主流平台扩展 */
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Noto Sans",
Ubuntu,
Cantarell,
"Helvetica Neue";
}
.fallback-aware {
/* 扩展字体堆栈后保留原轮廓回退 */
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Noto Sans",
Ubuntu,
Cantarell,
"Helvetica Neue",
sans-serif;
}
扩展后的字体堆栈包含适用于 macOS/iOS、Windows、Android 和 Linux 的系统字体,以及针对旧版浏览器的回退字体。
CSS 模块
Bun 的打包器还支持 CSS 模块,具有以下功能:
- 无需配置即可检测 CSS 模块文件(
.module.css) - 组合(
composes属性) - 将 CSS 模块导入 JSX/TSX
- 针对 CSS 模块无效用法的警告/错误
CSS 模块是一种 CSS 文件(扩展名为 .module.css),其中所有类名和动画都限定在该文件的作用域内。这有助于避免类名冲突,因为 CSS 声明默认是全局作用域的。
Bun 的打包器会将局部作用域的类名转换为唯一标识符。
快速入门
创建一个以 .module.css 结尾的 CSS 文件:
.button {
color: red;
}
.button {
color: blue;
}
然后你可以导入此文件,比如在 TSX 文件中:
import styles from "./styles.module.css";
import otherStyles from "./other-styles.module.css";
export default function App() {
return (
<>
<button className={styles.button}>红色按钮!</button>
<button className={otherStyles.button}>蓝色按钮!</button>
</>
);
}
导入 CSS 模块会得到一个对象,该对象将每个类名映射到其唯一标识符:
import styles from "./styles.module.css";
import otherStyles from "./other-styles.module.css";
console.log(styles);
console.log(otherStyles);
输出结果如下:
{
button: "button_123";
}
{
button: "button_456";
}
每个文件中的类名都是唯一的,因此不会发生冲突。
组合
CSS 模块可以将类选择器组合在一起,以便在多个类之间复用样式规则。
例如:
.button {
composes: background;
color: red;
}
.background {
background-color: blue;
}
这与以下写法相同:
.button {
background-color: blue;
color: red;
}
.background {
background-color: blue;
}
使用 composes 时需要遵循以下两条规则:
#button {
/* 无效!`#button` 不是类选择器 */
composes: background;
}
.button,
.button-secondary {
/* 无效!`.button, .button-secondary` 不是简单选择器 */
composes: background;
}
从独立 CSS 模块文件组合
你也可以从其他 CSS 模块文件中组合类:
.background {
background-color: blue;
}
.button {
composes: background from "./background.module.css";
color: red;
}