在构建时使用 Tailwind 和 lit-element

2025-06-04

在构建时使用 Tailwind 和 lit-element

过时的解决方案!

现在有一种更简单的方法可以做到这一点:

如果新的解决方案不起作用,或者您只是更喜欢这个,请继续阅读...


几天前,我写了一篇关于在运行时使用Tailwind和 Web 组件的文章:

当时,我其实一直在琢磨如何在构建时实现这一点,但却苦于找不到现成的解决方案。好消息:我找到了!

请记住,此示例特定于 lit-element

我的设置

与我之前的文章一样,使用了相同的设置:

  • 单个 Web 组件(在本例中为 lit-element)
  • esbuild
  • TypeScript

使用lit-element组件:



class MyElement extends LitElement {
  static styles = css`
    /*
     * Somehow we want tailwind's CSS to ultimately
     * exist here
     */
  `;

  render() {
    // We want these tailwind CSS classes to exist
    return html`<div class="text-xl text-black">
      I am a test.
    </div>`;
  }
}


Enter fullscreen mode Exit fullscreen mode

问题

正如我在上一篇文章中所讨论的,Tailwind 似乎不支持开箱即用的影子 DOM 或 Web 组件。

我之前通过使用twind解决了这个问题,这是一个很棒的小库,它充当“顺风运行时”并在运行时生成正确的样式表。

然而,并不是每个人都想要一个运行时解决方案,有些人已经有足够静态的 CSS,他们宁愿构建一次然后忘记它。

因此,正如您在上面的示例中看到的,我们的目标是将 tailwind 的 CSS 注入到组件的样式表中。

调查

在过去的一天左右的时间里,我们花了相当多的时间才找到下面的解决方案,其中包括发现一些错误和发现新的工具。

首先,我进行了一些谷歌搜索并发现:

postcss-js

这是一个用于处理“JS 中的 CSS”的 postcss 插件。听起来很有前景!

但事实并非如此,这是一个用于在 CSS 对象(CSS 的实际 JS 表示)和 CSS 字符串之间进行转换的插件。我们不需要这个,我们想要的是就地转换 CSS 字符串。

babel 插件

babel 插件从模板字面量中提取 CSS,并将其传递给 postcss 并替换原有的 CSS。这正是我们需要的!

但是……它是一个 Babel 插件,而我们不想用 Babel。所以这个也是不行的。

汇总插件

存在一个或两个汇总插件,它们的作用与“postcss-js”相同:它们转换为 CSS 对象或从 CSS 对象转换。

这又不是我们想要的。

自定义汇总插件

然后我制作了自己的 rollup 插件,它像 babel 插件一样提取模板文字并使用 postcss 对其进行处理。

这确实有效,但似乎有点矫枉过正,而且把我们束缚在了 rollup 上。我实在不想让解决方案依赖于其他构建工具。

制作自己的汇总插件很有趣,所以体验很好。

postcss-jsx(又名 postcss-css-in-js)

Andrey(postcss 维护者)此时建议我使用“postcss-jsx”。我之前在 Google 上看到过这个,但没能从文档中弄清楚如何让它与我的代码兼容。

但这听起来是正确的做法,所以我又试了一次!

第一次尝试,我成功让它处理了我元素的 CSS!成功了。虽然生成了一个巨大的样式表(全部都是 Tailwind 的),但看起来好像成功了。

错误 1

不过,还没那么快。我在浏览器中尝试了一下,结果出现了一个老掉牙的语法错误。第一个bug:postcss-jsx 没有转义输出 CSS 中的反引号。

Tailwind 的 CSS 包含带有反引号的注释,因此我们最终会生成如下语法错误的代码:



const style = css`
  /** Tailwind broke `my code with these backticks` */
`;


Enter fullscreen mode Exit fullscreen mode

这时,我注意到 postcss-jsx 已经无人维护,stylelint 的同事们已经 fork 了它。于是,我提交了调查中的第一个 bug:

https://github.com/stylelint/postcss-css-in-js/issues/89

错误 2

我在本地修复了 postcss-css-in-js 以避免反引号,所以我现在得到了一些输出。

当然,在软件包修复之前,这个方法对其他人不起作用。所以我想我们可以绕过它:使用 cssnano 完全删除注释——这样那些反引号注释就方便地消失了。

安装了 cssnano,将其添加到我的 postcss 配置中,并使用“lite”预设,因为我只想删除空规则和注释。

事实证明,cssnano-preset-lite 无法与 postcss-cli 兼容。另一个 bug:

https://github.com/cssnano/cssnano/issues/976

错误 3

我差点忘了,postcss-css-in-js 还有第三个错误:它会生成如下 AST:



Document {
  nodes: [
    Root { ... },
    Root { ... }
  ]
}


Enter fullscreen mode Exit fullscreen mode

事实证明,postcss 在字符串化嵌套根时遇到了问题。这次提交了 Bug,甚至尝试了 PR:

https://github.com/postcss/postcss/issues/1494

更新:已在 PostCSS 8.2.2 中修复!

解决方案

经过大量有趣的查找错误和研究解决方案之后,我终于找到了一个可行的方法。

来源

为了包含 Tailwind 的 CSS,我们完全按照其文档中的方式进行操作:



export class MyElement extends LitElement {
  public static styles = css`
    @tailwind base;
    @tailwind utilities;
    /* whatever other tailwind imports you want */
  `;
  // ...
}


Enter fullscreen mode Exit fullscreen mode

这些@tailwind指令稍后将被 postcss 替换为 tailwind 的实际 CSS。

依赖项

如上所述,我们需要以下内容:



$ npm i -D postcss @stylelint/postcss-css-in-js tailwindcss postcss-syntax postcss-discard-comments postcss-discard-empty


Enter fullscreen mode Exit fullscreen mode

构建脚本(package.json



{
  "scripts": {
    "build:js": "tsc && esbuild --bundle --format=esm --outfile=bundle.js src/index.ts",
    "build:css": "postcss -r bundle.js",
    "build": "npm run build:js && npm run build:css"
  }
}


Enter fullscreen mode Exit fullscreen mode

跑步npm run build将会:

  • 运行 typescript (使用noEmit: true)仅用于类型检查
  • 运行 esbuild 来创建 JS 包
  • 运行 postcss 并替换 JS 包的内容

tailwind.config.js



module.exports = {
  purge: [
   './bundle.js'
  ]
};


Enter fullscreen mode Exit fullscreen mode

bundle.js是我们之前用 esbuild 生成的。我们想从 bundle 中清除未使用的样式。

postcss.config.js



module.exports = {
  syntax: require('@stylelint/postcss-css-in-js'),
  plugins: [
    require('tailwindcss')(),
    require('postcss-discard-comments')(),
    require('postcss-discard-empty')()
  ]
};


Enter fullscreen mode Exit fullscreen mode

这里:

  • syntax告诉 postcss 如何读取我们的 JS 文件
  • tailwindcss注入 Tailwind 的 CSS,然后清除未使用的样式
  • postcss-discard-comments丢弃注释(这可以防止上面的错误 1)
  • postcss-discard-empty丢弃清除后留下的空规则顺风

注意:cssnano 可以替代最后两个插件,但由于上面的错误 2,我们在本例中没有这样做

构建它

我们之前的构建脚本现在应该可以工作了:



$ npm run build


Enter fullscreen mode Exit fullscreen mode

如果我们想要去除所有未使用的样式并在我们的配置中使用该purge选项,我们需要指定NODE_ENV



$ NODE_ENV=production npm run build


Enter fullscreen mode Exit fullscreen mode

Tailwind 将会拾取它并清除未使用的样式。

在开发和生产环境中启用清除

如果您始终希望清除发生,只需将您的 tailwind 配置更改为如下所示:



module.exports = {
  purge: {
    enabled: true,
    content: [
      './bundle.js'
    ]
  }
};


Enter fullscreen mode Exit fullscreen mode

此处对此有更详细的描述

优化它

我们可以做得更好一些。目前,我们正在为每个组件生成一个 Tailwind 样式表。

如果我们有多个组件,每个组件的样式表都会有整个应用程序使用的tailwind CSS 的副本(因为我们针对的是捆绑包,而不是单个文件)。

因此,我们最好使用多个组件共享的单一顺风模板:



// styles.ts
export const styles = css`
  @tailwind base;
  @tailwind utilities;
`;

// my-element.ts
import {styles} from './styles';
export class MyElement extends LitElement {
  static styles = [styles];
  public render() {
    return html`<p class="p-4">One</p>`;
  }
}

// another-element
import {styles} from './styles';
export class AnotherElement extends LitElement {
  static styles = [styles];
  public render() {
    return html`<p class="p-6">Two</p>`;
  }
}


Enter fullscreen mode Exit fullscreen mode

这意味着我们将生成一个可供所有组件重复使用的整体式顺风样式表。

在上面的例子中,.p-6.p-4(方法中使用的类render)都将存在于样式表中,并且所有其他未使用的样式都会被删除。

这是否是一种优化取决于你的用例。只需记住,“清除”操作是针对整个软件包,而不是单个文件。

有用的链接(我们使用的包)

包起来

正如我在上一篇文章中所说,我认为运行时 vs. 构建时是基于项目的偏好。有些人可能更适合使用运行时 twind 解决方案,而其他人则可能更适合使用构建时解决方案。

如果您的样式非常静态(即您在运行时实际上不会动态使用任何样式)或者您已经有类似的 postcss 构建过程,那么您可能应该同时处理 Tailwind。

在我的例子中,cssnano 的引入是为了绕过上面提到的 bug 2。不过你可能还是想在生产环境中使用它来节省一些字节。

玩得开心!

文章来源:https://dev.to/43081j/using-tailwind-at-build-time-with-web-components-1bhm
PREV
掌握 Git:13 种提高效率的高级技巧和快捷方式
NEXT
Vuex + TypeScript