Oxide Engine and Standalone Builds

Purpose and Scope

Oxide is the Rust side of Tailwind CSS’s class detection pipeline. It exists so the JavaScript-facing packages can ask a fast native engine to discover candidate class names, resolve source globs, and support repeated scans during local development. The official CLI installation flow describes Tailwind as scanning templates for class names, generating matching styles, and writing a static CSS file; Oxide is one of the repository components that makes that scanning step fast enough for watch mode and large projects. This page focuses on the Rust engine, its Node binding, the ignore-aware file walker, and the standalone CLI packaging role.

Sources: crates/oxide/src/lib.rs, crates/oxide/src/scanner/mod.rs, crates/node/src/lib.rs, packages/@tailwindcss-standalone/package.json

The important boundary to understand is that Oxide does not replace the Tailwind CSS compiler API. Instead, it supplies lower-level discovery primitives that higher-level packages can combine with CSS compilation, dependency tracking, and command-line rendering. The Rust library exposes modules for cursors, extraction machines, glob handling, path handling, scanning, and throughput measurement, then re-exports the public values that consumers need: glob entries, public source entries, changed content, and the scanner itself. That shape keeps the native layer focused on source traversal and candidate extraction while preserving a small public surface.

Sources: crates/oxide/src/lib.rs

Relevant Source Files

  • crates/oxide/src/lib.rs - Defines the public Rust module layout and re-exports GlobEntry, PublicSourceEntry, ChangedContent, and Scanner for consumers of the Oxide crate.
  • crates/oxide/src/main.rs - Provides a throughput-oriented executable harness that runs extractor machines against fixture input and reports extractor performance.
  • crates/oxide/src/scanner/mod.rs - Implements the scanner structure, scan inputs, scan results, source normalization, ignore-aware walking, candidate caching, glob generation, and changed-content scanning entry points.
  • crates/node/src/lib.rs - Exposes Oxide to JavaScript through N-API objects and methods, including scanner construction, full scans, incremental content scans, candidate positions, file lists, globs, and normalized sources.
  • crates/ignore/README.md - Documents the recursive directory iterator used by the scanner’s walking layer, including filtering by ignore files and configurable WalkBuilder behavior.
  • packages/@tailwindcss-standalone/package.json - Defines the private standalone CLI package, its binary entry, build script, bundled dependencies, and platform-specific build dependencies.

System-to-Code Mapping

The Rust crate’s root file is intentionally compact, but it is the best map of Oxide’s responsibilities. The cursor and extractor modules support token-level reading of template-like content. The glob and paths modules support source selection. The scanner module coordinates source discovery, file walking, and candidate accumulation. The throughput module supports performance measurement. By re-exporting only a few data types, the crate gives bindings and integrations a stable route into the engine without asking them to know every internal extractor machine or scanning helper.

Sources: crates/oxide/src/lib.rs, crates/oxide/src/main.rs

The executable in the Oxide crate is best read as a benchmark and diagnostics harness rather than the public Tailwind command. It loads fixture HTML, splits it into lines, runs the full extractor, black-boxes the result to avoid compiler elimination, and computes throughput over many iterations. The file also shows how individual extractor machines can be swapped in for focused measurement, because commented calls name machines for arbitrary properties, arbitrary values, candidates, CSS variables, modifiers, named utilities, named variants, strings, utilities, and variants. That is useful when diagnosing extraction correctness or performance regressions.

Sources: crates/oxide/src/main.rs

ConcernRust or package surfaceRole in the workflow
Candidate extractionExtractor, extractor machines, ChangedContentReads content bytes and returns utility candidates or CSS variable references.
Source scanningScanner, PublicSourceEntry, GlobEntryWalks configured sources, resolves globs, tracks files, and returns candidates.
JavaScript accessN-API Scanner classLets Node-based packages invoke the native scanner with JS-shaped inputs and outputs.
Ignore handlingignore::WalkBuilder behaviorKeeps traversal aligned with ignore files and common source filtering expectations.
Standalone packaging@tailwindcss/standalone packageBuilds a distributable CLI binary entry for users who want a standalone executable.

Scanner and Extraction Flow

The scanner source shows the operational lifecycle behind class detection. A scanner is constructed from public source entries, initializes tracing, converts public entries into private source entries, creates a walker, and stores state for extensions, files, directories, generated globs, candidates, modification times, and whether a full scan has already completed. That state matters because Tailwind’s watch-mode behavior must balance correctness with speed: the first scan discovers the project shape, while later scans can reuse known information and skip unchanged files when modification times are available.

Sources: crates/oxide/src/scanner/mod.rs

The scanner also encodes Tailwind’s source-selection semantics. Comments in the source distinguish automatic source detection from explicit glob patterns, and they call out edge cases such as explicit entries inside dependency directories, explicitly included binary-looking paths, and git-ignored files listed by the user. That matters because class detection should not blindly crawl every dependency or generated artifact, but it also must respect a user’s explicit source instructions. The result structure carries candidates, files, and globs so downstream code can both compile current CSS and set up future watchers.

Sources: crates/oxide/src/scanner/mod.rs

A useful mental model is that extraction and scanning are separate phases. Extraction reads bytes and identifies candidate spans inside one content stream. Scanning decides which content streams should be read, how glob sources are normalized, which files belong to the project, and how repeated scans should behave. The Oxide main harness demonstrates the extraction side directly, while the scanner module wraps extraction with filesystem and source-management concerns. Keeping these phases distinct is what lets the same native engine support full project scans, changed file scans, and position-aware editor features.

Sources: crates/oxide/src/main.rs, crates/oxide/src/scanner/mod.rs

Node Native API

The Node binding converts the Rust scanner into JavaScript-facing N-API objects. It defines object shapes for changed content, glob entries, source entries, scanner options, and candidate positions, then implements conversions into the Rust types. JavaScript callers can construct a scanner with optional sources, run a full scan, scan supplied changed content, ask for candidates with positions, and inspect derived files, globs, or normalized sources. This is the bridge that lets packages written in TypeScript benefit from Rust traversal and extraction without manually handling Rust data structures.

Sources: crates/node/src/lib.rs

There are two particularly important input modes in the binding. A changed item can be represented as a file path plus extension, or as in-memory content plus extension. The conversion layer maps file-backed inputs to Rust file changes and content-backed inputs to Rust content changes. The position API reads file content when necessary, then converts byte positions into UTF-16 positions before returning them to JavaScript. That conversion is important for editor and JavaScript environments, where string indices are commonly interpreted as UTF-16 code unit offsets instead of raw byte offsets.

Sources: crates/node/src/lib.rs

Compact API Reference

  • ChangedContent has optional file, optional content, and required extension fields.
  • GlobEntry has base and pattern fields.
  • SourceEntry has base, pattern, and negated fields.
  • ScannerOptions accepts optional sources.
  • Scanner.new(opts) constructs a native scanner from source entries or an empty source list.
  • scan() returns detected candidate strings from the configured sources.
  • scan_files(input) scans supplied changed content entries and returns candidate strings.
  • get_candidates_with_positions(input) returns candidate strings with JavaScript-friendly positions.
  • files, globs, and normalized_sources expose scanner-derived file and glob metadata.

Ignore-Aware Traversal

The scanner imports ignore crate types for gitignore handling and recursive walking, and the vendored ignore README explains the underlying behavior: it provides a fast recursive directory iterator that respects filters such as globs, file types, and ignore files. The README’s examples show both simple recursive walking and advanced configuration through a builder that can change default hidden-file behavior. In Tailwind’s context, this supports the expectation that source detection is broad enough to find templates but disciplined enough to avoid common ignored or irrelevant paths unless explicitly included.

Sources: crates/oxide/src/scanner/mod.rs, crates/ignore/README.md

Standalone CLI Build Role

The standalone package is a private package named for the standalone Tailwind CLI. Its package metadata exposes a tailwindcss binary pointing at a built distribution file, and its build script runs through Bun. Its dependencies include the workspace CLI and core Tailwind package, plus first-party official plugins for aspect ratio, forms, and typography. The package notes explain why platform binary packages are listed: Bun must be able to build the CLI for supported platforms, and patched native dependencies must be statically analyzable during that build process.

Sources: packages/@tailwindcss-standalone/package.json

This packaging role connects directly to the official installation story. The documented CLI path says users can install the CLI through npm, import Tailwind in a CSS file, run a command with input and output paths, and optionally watch for changes. It also states that the CLI is available as a standalone executable for users who do not want to install Node.js. In the repository, that standalone path is represented by the dedicated package, its binary declaration, and its platform-specific development dependencies for watcher and CSS processing support.

Sources: packages/@tailwindcss-standalone/package.json

Operational Guidance and Next Steps

When debugging class detection, start by separating the failure into source discovery, extraction, and integration layers. If files are not being considered, inspect normalized sources, generated globs, and the scanner’s file list through the Node binding. If files are scanned but candidates are missing, focus on extractor behavior and use the Rust harness as the clue for which machine family may be involved. If the npm CLI works but a standalone build does not, examine the standalone package dependencies and binary packaging assumptions rather than the scanner itself.

Sources: crates/node/src/lib.rs, crates/oxide/src/main.rs, packages/@tailwindcss-standalone/package.json

Related pages: read Detecting Classes in Source Files for user-facing source detection behavior, Node API for the JavaScript package that consumes native scanning, CLI Reference for command options, and Tailwind CLI Installation for the installation sequence that motivates standalone distribution.