Skip to content

Caching & Minification

Three production concerns sit at the edge of the pipeline: avoiding recompilation of identical input, avoiding the cost of building a compiler on every request, and shrinking the generated CSS. All three are PHP-side helpers around the compiler.

Caching

CSS generation runs the whole pipeline on every call. For content that doesn't change between requests, the file-based cache writes the result to disk and serves it on subsequent calls.

Pass cache to tw::generate() — true for the default directory, or a path string for a custom one:

PHP
use TailwindPHP\tw;

// Cache to the default directory: sys_get_temp_dir() . '/tailwindphp'
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'cache' => true,
]);

// Cache to a custom directory
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'cache' => '/path/to/cache',
]);

// With a time-to-live (seconds)
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'cache' => true,
    'cacheTtl' => 3600, // expire after 1 hour
]);

The cache directory is created automatically if it doesn't exist.

Cache key

The key is derived from the content, the CSS configuration, and the minify flag — hashed into the filename tailwind_<hash>.css. Any change to the markup, the CSS input, or whether minification is on produces a different key, and therefore a different file.

The CSS configuration in the key is the inline css plus the contents of the files named in importPaths. Two inputs are not part of it: files pulled in by a nested @import inside those files, and the TailwindPHP version. Clear the cache after editing a nested import or upgrading the package, or the old output keeps being served.

Time-to-live

cacheTtl is a lifetime in seconds. On a cache hit, the file's modification time is compared against the current time; if the file is older than cacheTtl, it is treated as a miss and recompiled. With no cacheTtl, entries never expire — clear them explicitly.

Clearing the cache

tw::clearCache() (or the clearCache() function) removes the tailwind_*.css files from a cache directory and returns the number of files deleted.

PHP
use TailwindPHP\tw;
use function TailwindPHP\clearCache;

$deleted = tw::clearCache();                  // default directory
$deleted = tw::clearCache('/path/to/cache');  // custom directory
clearCache('/path/to/cache');                 // function form

Compiler state cache

The output cache only helps when the same content comes back. When the content changes on every call, the expensive part is building the compiler itself: resolving @import "tailwindcss", parsing the bundled resources, and accumulating the theme. That phase depends only on the input CSS, so it can be stored and reused.

Pass stateCacheFile to tw::compile() (or new TailwindCompiler($css, $options)):

PHP
use TailwindPHP\tw;

$compiler = tw::compile($themeCss, [
    'stateCacheFile' => __DIR__ . '/cache/tailwind-state.php',
]);

$css = $compiler->generateExact($html, true);

On the first construction the state is written to that path as a plain PHP file, which OPcache can share across requests; later constructions load it instead of parsing again. The output is byte-identical to an uncached compiler.

  • The file is validated against an engine fingerprint (the size and mtime of TailwindPHP's sources and resources), a key derived from the input CSS and the base and importSearchPaths options, and the size and mtime of every file the input imported. A mismatch rebuilds and rewrites it, so upgrading TailwindPHP or editing an imported file needs no manual step.
  • You own the path: use one file per distinct input CSS, in a directory the web server can write to. Writes are atomic (a temporary file plus rename()).
  • Inputs that use a custom importResolver bypass the cache, because the resolver's behavior cannot be fingerprinted.
  • The same cache is available around the lower-level compile() array API as TailwindPHP\StateCache\compileCssCached($css, $cacheFile, $options).

tw::generate() does not take stateCacheFile; it builds a fresh compiler on every call. Use a compiler instance when construction cost matters.

Minification

Minification shrinks the output for production. The minify flag emits minified CSS directly during serialization, so it is both the fastest and the most precise path; tw::minify() runs the string-based minifier (src/_tailwindphp/CssMinifier.php) over an existing CSS string:

PHP
use TailwindPHP\tw;

// Minify during generation (serializer path, preferred)
$css = tw::generate([
    'content' => '<div class="flex p-4">Hello</div>',
    'minify' => true,
]);

// Or minify an existing string (string-based post-pass)
$css = tw::generate('<div class="flex p-4">Hello</div>');
$minified = tw::minify($css);

The two paths produce equivalent structure but not identical bytes. The serializer path preserves descendant :not() selectors that the string pass collapses, so prefer the minify flag for generated output and reserve tw::minify() for CSS that arrives as a string.

What it does

Both paths apply the same set of size reductions:

Step Effect
Remove comments Strips /* … */ blocks
Collapse whitespace Collapses runs of whitespace and removes it around { } ; : , and selector combinators
Shorten hex colors #ffffff → #fff, #aabbcc → #abc
Remove zero units 0px → 0 (preserves 0s/0ms time values)
Shorten font-weight font-weight:normal → 400, font-weight:bold → 700
Remove empty rules Drops selectors with empty declaration blocks

By design it does not merge duplicate selectors or combine shorthand properties — both make debugging harder and can affect the cascade.

CLI: --minify vs --optimize

The CLI exposes the serializer minify path through two flags:

Shell
# Optimize and minify (smallest output)
tailwindphp -i ./src/app.css -o ./dist/styles.css --minify   # or -m

# Meant to optimize without minifying
tailwindphp -i ./src/app.css -o ./dist/styles.css --optimize

In the current release --optimize also minifies, so both commands write the same bytes (#9). The lightningcss-equivalent transforms run on every build either way, so plain output (no flag) is already the optimized, readable form.

For the rest of the public API — tw::generate(), tw::compile(), inspection methods, and input formats — see the API reference.