首页 > web前端 > css教程 > CSS在打字稿中使用香草 - 提取

CSS在打字稿中使用香草 - 提取

Jennifer Aniston
发布: 2025-03-19 09:16:16
原创
799 人浏览过

CSS in TypeScript with vanilla-extract

vanilla-extract:一款新型的、与框架无关的CSS-in-TypeScript库。它提供了一种轻量级、健壮且直观的样式编写方式。vanilla-extract并非强制性的CSS框架,而是一个灵活的开发者工具。近年来,CSS工具领域相对稳定,PostCSS、Sass、CSS Modules和styled-components等工具在2017年之前就已经出现(有些甚至更早),并且至今仍很流行。Tailwind是近年来少数几款在CSS工具领域带来变革的工具之一。

vanilla-extract旨在再次引发变革。它于今年发布,并受益于一些最新的趋势,包括:

  • JavaScript开发者转向TypeScript
  • 浏览器对CSS自定义属性的支持
  • 实用优先样式

vanilla-extract中包含许多巧妙的创新,我认为这使其意义重大。

零运行时开销

CSS-in-JS库通常在运行时将样式注入文档中。这有一些好处,包括关键CSS提取和动态样式。

但通常情况下,单独的CSS文件性能更好。这是因为JavaScript代码需要经过更昂贵的解析/编译过程,而单独的CSS文件可以被缓存,同时HTTP2协议降低了额外请求的成本。此外,自定义属性现在可以免费提供许多动态样式。

因此,vanilla-extract效仿Linaria和astroturf,而不是在运行时注入样式。这些库允许您使用在构建时被提取并用于构建CSS文件的JavaScript函数来编写样式。尽管您使用TypeScript编写vanilla-extract,但这不会影响生产JavaScript包的整体大小。

TypeScript支持

vanilla-extract的一大价值主张是它提供了类型检查。如果保持代码库的类型安全非常重要,那么为什么不也对样式做同样的事情呢?

TypeScript提供了许多好处。首先是自动完成。如果您键入“fo”,那么在支持TypeScript的编辑器中,您会得到一个下拉列表,其中包含字体选项(fontFamily、fontKerning、fontWeight或其他匹配项)供您选择。这使得CSS属性在编辑器的便捷性下变得易于发现。如果您不记得fontVariant的名称,但知道它以“font”开头,您可以键入它并滚动浏览选项。在VS Code中,您无需下载任何额外工具即可实现此功能。

这极大地加快了样式的编写速度:

这也意味着您的编辑器会时刻监视您,确保您不会犯任何拼写错误,这些错误可能会导致令人沮丧的bug。

vanilla-extract类型还在其类型定义中提供了语法解释指向您正在编辑的CSS属性的MDN文档的链接。当样式行为异常时,这省去了疯狂谷歌搜索的步骤。

使用TypeScript编写意味着您使用驼峰式命名法来表示CSS属性,例如backgroundColor。对于习惯使用常规CSS语法(例如background-color)的开发者来说,这可能需要一些改变。

集成

vanilla-extract为所有最新的打包工具提供了第一方集成。以下是它目前支持的完整集成列表:

  • webpack
  • esbuild
  • Vite
  • Snowpack
  • NextJS
  • Gatsby

它也完全与框架无关。您只需从vanilla-Extract导入类名,这些类名会在构建时转换为字符串。

使用方法

要使用vanilla-Extract,您可以编写一个.css.ts文件,您的组件可以导入该文件。对这些函数的调用会在构建步骤中转换为哈希和作用域类名字符串。这听起来可能类似于CSS Modules,这并非巧合:vanilla-Extract的创建者之一Mark Dalgleish也是CSS Modules的共同创建者。

style() 函数

您可以使用style()函数创建一个自动作用域的CSS类。您传入元素的样式,然后导出返回值。在您的用户代码中的某个地方导入此值,它将转换为作用域类名。

// title.css.ts
import {style} from "@vanilla-extract/css";

export const titleStyle = style({
  backgroundColor: "hsl(210deg,30%,90%)",
  fontFamily: "helvetica, Sans-Serif",
  color: "hsl(210deg,60%,25%)",
  padding: 30,
  borderRadius: 20,
});
登录后复制
// title.ts
import {titleStyle} from "./title.css";

document.getElementById("root").innerHTML = `<h1>Vanilla Extract</h1>`;
登录后复制

媒体查询和伪选择器也可以包含在样式声明中:

// title.css.ts
backgroundColor: "hsl(210deg,30%,90%)",
fontFamily: "helvetica, Sans-Serif",
color: "hsl(210deg,60%,25%)",
padding: 30,
borderRadius: 20,
"@media": {
  "screen and (max-width: 700px)": {
    padding: 10
  }
},
":hover":{
  backgroundColor: "hsl(210deg,70%,80%)"
}
登录后复制

这些style函数调用是对CSS的轻量级抽象——所有属性名称和值都映射到您熟悉的CSS属性和值。需要习惯的一个变化是,值有时可以声明为数字(例如padding: 30),默认为像素单位值,而某些值需要声明为字符串(例如padding: "10px 20px 15px 15px")。

style函数中的属性只能影响单个HTML节点。这意味着您不能使用嵌套来声明元素子元素的样式——这在Sass或PostCSS中您可能习惯了。相反,您需要分别设置子元素的样式。如果子元素需要基于父元素的不同样式,您可以使用selectors属性添加依赖于父元素的样式:

// title.css.ts
export const innerSpan = style({
  selectors:{[`${titleStyle} &`]:{
    color: "hsl(190deg,90%,25%)",
    fontStyle: "italic",
    textDecoration: "underline"
  }}
});
登录后复制
// title.ts
import {titleStyle,innerSpan} from "./title.css";
document.getElementById("root").innerHTML = 
`<h1>Vanilla Extract</h1>
Unstyled`;
登录后复制

或者,您也可以使用主题API(我们接下来会讲到)在父元素中创建自定义属性,这些属性由子节点使用。这听起来可能比较严格,但它被故意设计成这样是为了提高大型代码库的可维护性。这意味着您将确切地知道项目中每个元素的样式在哪里声明。

主题

您可以使用createTheme函数在TypeScript对象中构建变量:

// title.css.ts
import {style,createTheme } from "@vanilla-extract/css";

// 创建主题
export const [mainTheme,vars] = createTheme({
  color:{
    text: "hsl(210deg,60%,25%)",
    background: "hsl(210deg,30%,90%)"
  },
  lengths:{
    mediumGap: "30px"
  }
})

// 使用主题
export const titleStyle = style({
  backgroundColor:vars.color.background,
  color: vars.color.text,
  fontFamily: "helvetica, Sans-Serif",
  padding: vars.lengths.mediumGap,
  borderRadius: 20,
});
登录后复制

然后,vanilla-extract允许您创建主题的变体。TypeScript帮助它确保您的变体使用相同的属性名称,因此如果您忘记向主题添加background属性,您会收到警告。

以下是如何创建常规主题和暗模式:

// title.css.ts
import {style,createTheme } from "@vanilla-extract/css";

export const [mainTheme,vars] = createTheme({
  color:{
    text: "hsl(210deg,60%,25%)",
    background: "hsl(210deg,30%,90%)"
  },
  lengths:{
    mediumGap: "30px"
  }
})
// 主题变体 - 注意这部分不使用数组语法
export const darkMode = createTheme(vars,{
  color:{
    text:"hsl(210deg,60%,80%)",
    background: "hsl(210deg,30%,7%)",
  },
  lengths:{
    mediumGap: "30px"
  }
})
// 使用主题
export const titleStyle = style({
  backgroundColor: vars.color.background,
  color: vars.color.text,
  fontFamily: "helvetica, Sans-Serif",
  padding: vars.lengths.mediumGap,
  borderRadius: 20,
});
登录后复制

然后,使用JavaScript,您可以动态地应用vanilla-extract返回的类名来切换主题:

// title.ts
import {titleStyle,mainTheme,darkMode} from "./title.css";

document.getElementById("root").innerHTML = 
`<div>
  <h1>Vanilla Extract</h1>
  Dark mode
</div>`
登录后复制

这是如何在底层工作的?您在createTheme函数中声明的对象将转换为附加到元素类的CSS自定义属性。这些自定义属性经过哈希处理以防止冲突。我们的mainTheme示例的输出CSS如下所示:

.src__ohrzop0 {
  --color-brand__ohrzop1: hsl(210deg,80%,25%);
  --color-text__ohrzop2: hsl(210deg,60%,25%);
  --color-background__ohrzop3: hsl(210deg,30%,90%);
  --lengths-mediumGap__ohrzop4: 30px;
}
登录后复制

而我们的darkMode主题的CSS输出如下所示:

.src__ohrzop5 {
  --color-brand__ohrzop1: hsl(210deg,80%,60%);
  --color-text__ohrzop2: hsl(210deg,60%,80%);
  --color-background__ohrzop3: hsl(210deg,30%,10%);
  --lengths-mediumGap__ohrzop4: 30px;
}
登录后复制

因此,我们只需要更改用户代码中的类名即可。将darkmode类名应用于父元素,mainTheme自定义属性将被darkMode自定义属性替换。

Recipes API

stylecreateTheme函数本身就足以样式化一个网站,但vanilla-extract提供了一些额外的API来提高可重用性。Recipes API允许您为元素创建许多变体,您可以在标记或用户代码中从中选择。

首先,需要单独安装它:

npm install @vanilla-extract/recipes
登录后复制

以下是它的工作原理。您导入recipe函数并传入一个包含basevariants属性的对象:

// button.css.ts
import { recipe } from '@vanilla-extract/recipes';

export const buttonStyles = recipe({
  base:{
    // 应用于所有按钮的样式都放在这里
  },
  variants:{
    // 我们从中选择的样式放在这里
  }
});
登录后复制

base中,您可以声明将应用于所有变体的样式。在variants中,您可以提供不同的方式来自定义元素:

// button.css.ts
import { recipe } from '@vanilla-extract/recipes';
export const buttonStyles = recipe({
  base: {
    fontWeight: "bold",
  },
  variants: {
    color: {
      normal: {
        backgroundColor: "hsl(210deg,30%,90%)",
      },
      callToAction: {
        backgroundColor: "hsl(210deg,80%,65%)",
      },
    },
    size: {
      large: {
        padding: 30,
      },
      medium: {
        padding: 15,
      },
    },
  },
});
登录后复制

然后您可以在标记中声明要使用的变体:

// button.ts
import { buttonStyles } from "./button.css";

Click me
登录后复制

vanilla-extract利用TypeScript为您自己的变体名称提供自动完成!

您可以随意命名变体,并在其中放入任何您想要的属性,例如:

// button.css.ts
export const buttonStyles = recipe({
  variants: {
    animal: {
      dog: {
        backgroundImage: 'url("./dog.png")',
      },
      cat: {
        backgroundImage: 'url("./cat.png")',
      },
      rabbit: {
        backgroundImage: 'url("./rabbit.png")',
      },
    },
  },
});
登录后复制

您可以看到这对于构建设计系统非常有用,因为您可以创建可重用的组件并控制它们变化的方式。这些变化使用TypeScript变得很容易发现——您只需要键入CMD/CTRL Space(在大多数编辑器上),您就会得到一个下拉列表,其中列出了自定义组件的不同方法。

实用优先与Sprinkles

Sprinkles是一个基于vanilla-extract构建的实用优先框架。vanilla-extract文档是这样描述它的:

基本上,它就像构建您自己的零运行时、类型安全的Tailwind、Styled System等版本。

因此,如果您不喜欢命名事物(我们都做噩梦,创建了一个外部包装div,然后意识到我们需要用……外部外部包装器包装它),Sprinkles可能是您首选的vanilla-extract使用方式。

Sprinkles API也需要单独安装:

npm install @vanilla-extract/sprinkles
登录后复制

现在我们可以创建一些构建块供我们的实用函数使用。让我们通过声明几个对象来创建一个颜色和长度列表。JavaScript键名可以是任何我们想要的。值需要是我们计划使用的CSS属性的有效CSS值:

// sprinkles.css.ts
const colors = {
  blue100: "hsl(210deg,70%,15%)",
  blue200: "hsl(210deg,60%,25%)",
  blue300: "hsl(210deg,55%,35%)",
  blue400: "hsl(210deg,50%,45%)",
  blue500: "hsl(210deg,45%,55%)",
  blue600: "hsl(210deg,50%,65%)",
  blue700: "hsl(207deg,55%,75%)",
  blue800: "hsl(205deg,60%,80%)",
  blue900: "hsl(203deg,70%,85%)",
};

const lengths = {
  small: "4px",
  medium: "8px",
  large: "16px",
  humungous: "64px"
};
登录后复制

我们可以使用defineProperties函数声明这些值将应用于哪些CSS属性:

  • 向其传递一个包含properties属性的对象。
  • properties中,我们声明一个对象,其中是用户可以设置的CSS属性(这些需要是有效的CSS属性),而是我们之前创建的对象(我们的颜色和长度列表)。
// sprinkles.css.ts
import { defineProperties } from "@vanilla-extract/sprinkles";

const colors = {
  blue100: "hsl(210deg,70%,15%)"
  // etc.
}

const lengths = {
  small: "4px",
  // etc.
}

const properties = defineProperties({
  properties: {
    // 此对象的键需要是有效的CSS属性
    // 值是我们为用户提供的选项
    color: colors,
    backgroundColor: colors,
    padding: lengths,
  },
});
登录后复制

然后最后一步是将defineProperties的返回值传递给createSprinkles函数,并导出返回值:

// sprinkles.css.ts
import { defineProperties, createSprinkles } from "@vanilla-extract/sprinkles";

const colors = {
  blue100: "hsl(210deg,70%,15%)"
  // etc.
}

const lengths = {
  small: "4px",
  // etc. 
}

const properties = defineProperties({
  properties: {
    color: colors,
    // etc. 
  },
});
export const sprinkles = createSprinkles(properties);
登录后复制

然后我们可以在组件内通过在class属性中调用sprinkles函数并为每个元素选择我们想要的选项来开始内联样式化。

// index.ts
import { sprinkles } from "./sprinkles.css";
document.getElementById("root").innerHTML = `Click me
`;
登录后复制

JavaScript输出为每个样式属性保存一个类名字符串。这些类名与输出CSS文件中的单个规则匹配。

Click me
登录后复制

如您所见,此API允许您使用一组预定义的约束在标记内设置元素的样式。您还可以避免为每个元素想出类名的困难任务。结果是感觉非常像Tailwind的东西,但也受益于围绕TypeScript构建的所有基础设施。

Sprinkles API还允许您编写条件和简写,以使用实用程序类创建响应式样式。

总结

vanilla-extract感觉是CSS工具领域的一大进步。在将其构建为一个直观、健壮的样式化解决方案方面,投入了大量的思考,该解决方案利用了静态类型提供的所有功能。

进一步阅读

  • vanilla-extract 文档
  • Mark关于vanilla-extract的演讲
  • vanilla-extract Discord
  • CodeSandbox上的代码示例

This revised output maintains the original meaning while using different wording and sentence structures. The images remain in their original format and location.

以上是CSS在打字稿中使用香草 - 提取的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板