Open-Source Wikis

/

Tailwind CSS

/

How to contribute

/

Tooling

tailwindlabs/tailwindcss

Tooling

Turborepo

turbo.json defines the task pipeline. The interesting wrinkle is the explicit task definition for @tailwindcss/oxide#build and @tailwindcss/oxide#dev:

{
  "@tailwindcss/oxide#build": {
    "dependsOn": ["^build"],
    "outputs": ["./index.d.ts", "./index.js", "./*.node"],
    "inputs": [
      "./src/**/*",
      "./build.rs",
      "./package.json",
      "./Cargo.toml",
      "./.cargo/config.toml",
      "../oxide/src/**/*",
      "../oxide/Cargo.toml",
      "../Cargo.toml",
      "../package.json",
    ],
  },
}

This is needed because the @tailwindcss/oxide npm package (in crates/node/) builds via build.rs, which calls cargo build — Turborepo can't infer the Rust source's input/output graph automatically.

globalPassThroughEnv: ["RUSTUP_HOME"] lets the Rust build pick up a custom rustup install path when CI runs.

tsup

Every TypeScript package uses tsup for bundling. Each package has a tsup.config.ts that configures entry points, formats (ESM/CJS), and dts (.d.ts emission). Tsup invokes esbuild under the hood.

The tsup-node binary (which is what the build scripts call) is just tsup with Node-specific defaults.

@tailwindcss/standalone is the exception — it builds with Bun instead of tsup, see packages/@tailwindcss-standalone/scripts/build.ts.

Vitest

Vitest is the test runner everywhere. The root vitest.config.ts defines a workspace with two projects:

  • node — covers most files.
  • jsdom — used for the browser bundle's tests.

Each package can override with its own vitest.config.ts (e.g. packages/tailwindcss/vitest.config.ts).

Common flags:

pnpm vitest                          # interactive
pnpm vitest run                      # single pass
pnpm vitest run packages/tailwindcss # only that package
pnpm vitest run -- -u                # update snapshots
pnpm vitest bench                    # run *.bench.ts

Cargo

The Cargo.toml workspace at the repo root pulls in crates/*. The release profile is configured for LTO:

[profile.release]
lto = true

That keeps the published @tailwindcss/oxide binary small.

The crates/node/.cargo/config.toml (visible in the directory listing but not shown here) selects the right NAPI target per platform.

Bun (for @tailwindcss/standalone)

The standalone CLI is built with Bun. Build script: packages/@tailwindcss-standalone/scripts/build.ts. It produces native binaries for every supported platform by depending on per-platform @parcel/watcher-* and lightningcss-* packages and letting Bun statically link them.

Bun's static analyzer can't see through certain dynamic imports, which is why patches/ contains:

  • @parcel/watcher@2.5.1.patch
  • lightningcss@1.32.0.patch

These patches make those modules statically analyzable.

Lightning CSS

lightningcss is used for the final optimization pass in @tailwindcss/node (packages/@tailwindcss-node/src/optimize.ts). Adapters that opt into optimize: true (or default to it in production) hand the compiler's output to Lightning CSS for minification, vendor prefixing, and modern syntax lowering.

lightningcss is not a runtime dependency of the compiler. It only runs in the Node-side adapters.

jiti

jiti is used by @tailwindcss/node to load JavaScript config files (tailwind.config.js) and plugins on-the-fly without a separate compile step. It supports TypeScript configs out of the box. See packages/@tailwindcss-node/src/compile.ts.

enhanced-resolve

enhanced-resolve (the same module Webpack uses internally) backs the loadStylesheet and loadModule resolvers in @tailwindcss/node and @tailwindcss/cli. This is how @import 'tailwindcss', @plugin '…', and @config '…' find their files when not running under a bundler.

Parcel watcher

@parcel/watcher is the file-system watcher used by @tailwindcss/cli (packages/@tailwindcss-cli/src/commands/build/index.ts) and the standalone CLI. Vite and Webpack provide their own watchers, so they don't use it.

CI workflows

Four workflow files in .github/workflows/:

  • ci.yml — runs lint and unit tests on every PR.
  • integration-tests.yml — runs the integration suite. Triggered on PRs that opt-in via [ci-all] and on main.
  • prepare-release.yml — orchestrates the release-prep PR.
  • release.yml — publishes to npm.

The release workflows are large (12K and 13K bytes respectively) because they handle Rust binary cross-compilation, NPM tarball assembly, and the standalone CLI builds.

scripts/

A handful of one-shot scripts:

  • pack-packages.mjs — produces tarballs for integration tests.
  • version-packages.mjs — bumps versions for release.
  • lock-pre-release-versions.mjs — pins pre-release versions before publish.
  • pre-publish-optimizations.mjs — strips dev-only code paths before publish.
  • release-channel.js — determines whether a release is latest, next, or insiders.
  • release-notes.mjs — generates the release notes payload.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Tooling – Tailwind CSS wiki | Factory