运行时包管理器打包器测试运行器代码检查格式化指南参考博客反馈

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 文件中重复父级选择器。

styles.css
/* 使用嵌套 */
.card {
  background: white;
  border-radius: 4px;

  .title {
    font-size: 1.2rem;
    font-weight: bold;
  }

  .content {
    padding: 1rem;
  }
}

Bun 的 CSS 打包工具会自动将此嵌套语法转换成传统的扁平 CSS,以兼容所有浏览器:

styles.css
/* 编译输出 */
.card {
  background: white;
  border-radius: 4px;
}

.card .title {
  font-size: 1.2rem;
  font-weight: bold;
}

.card .content {
  padding: 1rem;
}

你还可以在选择器中嵌套媒体查询和其他 at 规则:

styles.css
.responsive-element {
  display: block;

  @media (min-width: 768px) {
    display: flex;
  }
}

编译后为:

styles.css
.responsive-element {
  display: block;
}

@media (min-width: 768px) {
  .responsive-element {
    display: flex;
  }
}

颜色混合

color-mix() 函数会在所选色彩空间中按给定比例混合两种颜色。使用它可以创建颜色变体,而无需自行计算结果值。

styles.css
.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 变量),生成适用于所有浏览器的静态颜色值:

styles.css
.button {
  /* 精确计算得到的结果色 */
  background-color: #b31a1a;
}

.button:hover {
  background-color: #c54747;
}

相对颜色

相对颜色语法可以修改现有颜色的单个组成部分。无需重新计算整个颜色,即可调整亮度、饱和度或单独的颜色通道。

styles.css
.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 变量时),生成适用于浏览器的静态颜色值:

styles.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 色域的颜色。

styles.css
.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 打包器会将这些颜色格式转换为向后兼容的替代方案,以支持不支持这些格式的浏览器:

styles.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 之外的预定义色彩空间中指定颜色,让你能够使用更宽的色域。

styles.css
.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 备选值:

styles.css
.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 值相比,使用这种方式可以更直接地创建浅色和深色变体。

styles.css
.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,以兼容所有浏览器:

styles.css
.easy-theming {
  --primary: #00ffff;
  --primary-light: #33ffff;
  --primary-dark: #00b3b3;
  --primary-muted: #339999;
}

颜色表示法

现代 CSS 支持使用空格分隔的 RGB 和 HSL 值(不带逗号),以及带 alpha 通道的十六进制颜色。

styles.css
.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 打包器会将这些格式转换为旧版浏览器支持的格式:

styles.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() 函数接受两种颜色,并根据当前颜色方案应用其中一种,使样式无需使用媒体查询即可遵循用户的系统偏好。

styles.css
: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 变量:

styles.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 逻辑属性根据文档的书写模式和文本方向,而不是屏幕的物理方向来定义布局、间距和尺寸,因此布局可以适应不同的书写系统。

styles.css
.multilingual-component {
  /* 适应书写方向的外边距 */
  margin-inline-start: 1rem;

  /* 无论文本方向,都合理的内边距 */
  padding-block: 1rem 2rem;

  /* 顶部起始角的边框半径 */
  border-start-start-radius: 4px;

  /* 尊重书写模式的尺寸 */
  inline-size: 80%;
  block-size: auto;
}

对于不完全支持逻辑属性的浏览器,Bun 的 CSS 打包器会针对每种文本方向将其编译为物理属性:

styles.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 的方向感知样式。

styles.css
/* 根据文本方向应用不同样式 */
.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() 选择器,并使用适当的语言映射:

styles.css
/* 作为回退转换为基于语言的选择器 */
.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() 传入多个语言代码。

styles.css
/* 针对中日韩语言的排版调整 */
: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() 选择器:

styles.css
/* 使用 :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())接收一个选择器列表,如果列表中的任意选择器匹配,则匹配成功。

styles.css
/* 不用分别写这些 */
/* 
.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 打包工具提供使用厂商前缀的替代写法:

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() 排除多种模式。

styles.css
/* 选择所有按钮,排除主按钮和次按钮 */
button:not(.primary, .secondary) {
  background-color: #f5f5f5;
  border: 1px solid #ddd;
}

/* 应用样式于所有标题,排除侧边栏或页脚内的 */
h2:not(.sidebar *, footer *) {
  margin-top: 2em;
}

对于不支持 :not() 中多个参数的浏览器,Bun 的 CSS 打包器会将此语法转换为行为相同且兼容性更好的形式:

styles.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 会生成进一步的回退:

styles.css
/* 额外回退以确保最大兼容 */
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()
styles.css
.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 打包器会在构建时计算这些表达式:

styles.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- 前缀。

styles.css
/* 比较运算符的现代写法 */
@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 打包器会将范围查询转换为传统的媒体查询语法,以兼容所有浏览器:

styles.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 引入了几种将多个长属性组合在一起的简写属性。

styles.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 会将它们转换为对应的组件长属性:

styles.css
.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;
}

双位置渐变

双位置渐变语法会在两个相邻位置指定相同的颜色,从而创建硬色标:产生锐利的过渡,而不是平滑的渐变。它适用于条纹、色带以及其他多色设计。

styles.css
.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 打包器会通过复制颜色标记,将其转换为传统格式:

styles.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 字体。

styles.css
.native-interface {
  /* 使用系统默认 UI 字体 */
  font-family: system-ui;
}

.fallback-aware {
  /* 带显式回退的系统 UI 字体 */
  font-family: system-ui, sans-serif;
}

对于不支持 system-ui 的浏览器,Bun 的 CSS 打包器会将其扩展为跨平台字体堆栈:

styles.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 文件:

styles.module.css
.button {
  color: red;
}
other-styles.module.css
.button {
  color: blue;
}

然后你可以导入此文件,比如在 TSX 文件中:

app.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 模块会得到一个对象,该对象将每个类名映射到其唯一标识符:

app.tsx
import styles from "./styles.module.css";
import otherStyles from "./other-styles.module.css";

console.log(styles);
console.log(otherStyles);

输出结果如下:

app.tsx
{
  button: "button_123";
}

{
  button: "button_456";
}

每个文件中的类名都是唯一的,因此不会发生冲突。

组合

CSS 模块可以将类选择器组合在一起,以便在多个类之间复用样式规则。

例如:

styles.module.css
.button {
  composes: background;
  color: red;
}

.background {
  background-color: blue;
}

这与以下写法相同:

styles.module.css
.button {
  background-color: blue;
  color: red;
}

.background {
  background-color: blue;
}

使用 composes 时需要遵循以下两条规则:

styles.module.css
#button {
  /* 无效!`#button` 不是类选择器 */
  composes: background;
}

.button,
.button-secondary {
  /* 无效!`.button, .button-secondary` 不是简单选择器 */
  composes: background;
}

从独立 CSS 模块文件组合

你也可以从其他 CSS 模块文件中组合类:

background.module.css
.background {
  background-color: blue;
}
styles.module.css
.button {
  composes: background from "./background.module.css";
  color: red;
}