High-performance Rust-based CSS class scanner and extractor for TailwindCSS builds with Node.js bindings
npx @tessl/cli install tessl/npm-tailwindcss--oxide@4.1.0@tailwindcss/oxide is a high-performance Node.js package that provides CSS class scanning and extraction for TailwindCSS builds. Built with Rust and optimized for speed, it serves as the core scanning engine for TailwindCSS v4+ builds, enabling rapid file discovery and CSS candidate extraction across multiple platforms.
npm install @tailwindcss/oxideconst { Scanner } = require("@tailwindcss/oxide");For TypeScript:
import { Scanner } from "@tailwindcss/oxide";const { Scanner } = require("@tailwindcss/oxide");
// Create scanner with source configuration
const scanner = new Scanner({
sources: [
{
base: "./src",
pattern: "**/*.{html,js,jsx,ts,tsx,vue,svelte}",
negated: false
}
]
});
// Scan for CSS candidates
const candidates = scanner.scan();
console.log(candidates); // ["flex", "bg-blue-500", "text-center", ...]
// Get discovered files
const files = scanner.files;
console.log(files); // ["/path/to/src/component.jsx", ...]@tailwindcss/oxide is built around several key components:
Core file discovery and CSS candidate extraction from configured source patterns.
class Scanner {
constructor(options: ScannerOptions);
/** Scan configured sources and return all CSS candidates */
scan(): string[];
/** Scan specific files/content and return CSS candidates */
scan_files(changedContent: ChangedContent[]): string[];
/** Extract candidates with their UTF-16 character positions in content (converted from byte positions) */
get_candidates_with_positions(changedContent: ChangedContent): CandidateWithPosition[];
/** Get list of discovered files to scan */
get files(): string[];
/** Get optimized glob patterns for file watching */
get globs(): GlobEntry[];
/** Get normalized source patterns */
get normalized_sources(): GlobEntry[];
}
interface ScannerOptions {
/** Array of source patterns to scan */
sources?: SourceEntry[];
}Usage Examples:
const { Scanner } = require("@tailwindcss/oxide");
// Basic file scanning
const scanner = new Scanner({
sources: [
{ base: "./src", pattern: "**/*.html", negated: false },
{ base: "./components", pattern: "**/*.jsx", negated: false }
]
});
const allCandidates = scanner.scan();
console.log(allCandidates); // ["bg-blue-500", "text-lg", "flex", ...]
// Incremental scanning with changed files
const newCandidates = scanner.scan_files([
{ file: "./src/new-component.jsx", extension: "jsx" },
{ content: "<div class='bg-red-500'>Hello</div>", extension: "html" }
]);
console.log(newCandidates); // ["bg-red-500"]
// Get candidates with positions for editor integration
const withPositions = scanner.get_candidates_with_positions({
content: "<div class='flex bg-blue-500'>Content</div>",
extension: "html"
});
console.log(withPositions);
// [
// { candidate: "class", position: 5 },
// { candidate: "flex", position: 12 },
// { candidate: "bg-blue-500", position: 17 }
// ]Handles various file formats and content types for CSS candidate extraction.
interface ChangedContent {
/** File path to scan (optional, use with file-based scanning) */
file?: string;
/** Direct content to scan (optional, use with content-based scanning) */
content?: string;
/** File extension for content processing */
extension: string;
}Supported File Extensions:
Usage Examples:
// Scan file by path
const candidates1 = scanner.scan_files([
{ file: "./src/component.vue", extension: "vue" }
]);
// Scan raw content
const candidates2 = scanner.scan_files([
{
content: `
<template>
<div class="flex items-center justify-between p-4">
<h1 class="text-2xl font-bold">Title</h1>
</div>
</template>
`,
extension: "vue"
}
]);
// Mixed scanning
const candidates3 = scanner.scan_files([
{ file: "./dist/app.js", extension: "js" },
{ content: "<div class='bg-green-500'>Dynamic</div>", extension: "html" }
]);Configure source patterns for automatic file discovery and scanning.
interface SourceEntry {
/** Base directory path */
base: string;
/** Glob pattern for source detection */
pattern: string;
/** Whether this is a negation pattern */
negated: boolean;
}
interface GlobEntry {
/** Base directory path */
base: string;
/** Glob pattern relative to base */
pattern: string;
}Usage Examples:
// Multiple source patterns
const scanner = new Scanner({
sources: [
// Include all HTML and JS files in src
{ base: "./src", pattern: "**/*.{html,js}", negated: false },
// Include components directory
{ base: "./components", pattern: "**/*", negated: false },
// Exclude test files
{ base: "./src", pattern: "**/*.test.js", negated: true },
// Include external library
{ base: "./node_modules/ui-library", pattern: "**/*.js", negated: false }
]
});
// Get optimized globs for file watchers
const globs = scanner.globs;
console.log(globs);
// [
// { base: "./src", pattern: "**/*.{html,js}" },
// { base: "./components", pattern: "**/*" }
// ]
// Get normalized source patterns
const sources = scanner.normalized_sources;
console.log(sources);
// [
// { base: "./src", pattern: "**/*" },
// { base: "./components", pattern: "**/*" }
// ]Extract CSS candidates with their exact positions in source content for editor integration.
interface CandidateWithPosition {
/** The extracted CSS candidate string */
candidate: string;
/** UTF-16 character position in source content (converted from byte position) */
position: number;
}Usage Examples:
const sourceCode = `
<div class="flex items-center bg-blue-500 text-white p-4">
<span class="font-bold">Hello World</span>
</div>
`;
const candidates = scanner.get_candidates_with_positions({
content: sourceCode,
extension: "html"
});
console.log(candidates);
// [
// { candidate: "class", position: 6 },
// { candidate: "flex", position: 13 },
// { candidate: "items-center", position: 18 },
// { candidate: "bg-blue-500", position: 31 },
// { candidate: "text-white", position: 43 },
// { candidate: "p-4", position: 55 },
// { candidate: "class", position: 68 },
// { candidate: "font-bold", position: 75 }
// ]@tailwindcss/oxide includes pre-built native binaries for optimal performance:
The package automatically downloads the appropriate binary during installation via the postinstall script. If no native binary is available, it falls back to WebAssembly.
The Scanner gracefully handles common error conditions:
ChangedContent.file doesn't exist, it throws an error with "Failed to read file"file or content must be provided in ChangedContent, but not both