TailwindPHP
Advanced

Performance & Testing

TailwindPHP optimizes for correctness first. Where a PHP-specific change speeds things up without altering output, it is applied and marked @port-deviation:performance. Parity with TailwindCSS is not assumed — it is continuously verified against Tailwind's own test suite.

Performance

This is build-time CSS generation. The PHP port prioritizes matching TailwindCSS byte-for-byte over raw throughput, and the TypeScript original stays faster regardless — V8's JIT compiles the hot paths to native code in a way PHP cannot match. That is expected and fine for the intended use case.

A few hot paths carry hand-tuned optimizations that preserve identical output:

  • ast.php toCss() — accumulates fragments in an array and implodes once instead of repeated string concatenation, and uses pre-computed indent strings.
  • css-parser.php — compares characters directly instead of going through ord(), and tracks buffer lengths instead of recomputing strlen().

Both are documented with @port-deviation:performance and gated on the test suite — they cannot change the generated CSS.

Benchmarks

The benchmarks/ directory contains a comparison harness. Run it with:

composer bench         # PHP vs TypeScript
composer bench:save    # write benchmarks/results.json

Sample results on PHP 8.4.5 / Node.js v22.17.0 (Apple M1):

BenchmarkPHPTypeScript
css-parser on preflight.css~816 ops/s~15.45K ops/s (≈20× faster)
toCss~65.85K ops/s~2.59M ops/s (≈39× faster)

TypeScript is faster for these low-level operations, as expected. For full-page CSS generation — the actual use case — PHP performance is adequate (roughly 14 ms per page in the sample run). For repeated identical input, the file cache removes the cost entirely.

Testing & parity

Tests exist to guarantee the PHP port produces the same output as TailwindCSS. Two approaches work together.

1. Extraction-based tests. Scripts in test-coverage/ parse TailwindCSS's own .test.ts files and extract the test cases to JSON. PHPUnit then feeds those cases through TailwindPHP and compares the output against the expected CSS. Because the expectations come straight from upstream, any divergence in PHP behavior surfaces as a failure. These cover the complex surfaces: utilities, variants, integration, CSS functions, and the browser-level ui.spec tests.

2. Hand-ported unit tests. Simpler TypeScript test files (AST, CSS parsing, escaping, candidate parsing, value parsing, …) are ported directly to PHP and live beside their source as *.test.php, mirroring the structure and assertions of the originals.

The loop is extract → run → verify: re-extract from the current TailwindCSS reference, run the suite, and any drift fails. This keeps the port in lockstep with upstream and means a TailwindCSS update can be validated mechanically rather than by inspection. Drift fails CI.

composer extract   # re-extract test cases from the TailwindCSS reference
composer test      # run the full suite

There are 4,074 passing tests. The largest suites:

SuiteTestsSource
Core (utilities, variants, integration)1,322extracted from .test.ts
API coverage (utilities, modifiers, variants, directives, plugins)1,774exhaustive API tests
PHP-specific unit tests (theme, design-system, utils)300various .test.ts ports
Public API (tw::generate, tw::compile, inspection)140API tests
Companion libraries (clsx, tailwind-merge, CVA)129reference suites

Code quality

Formatting and static analysis run through Composer scripts:

composer lint      # Pint --test  (check formatting)
composer format    # Pint         (fix formatting)
composer analyse   # PHPStan      (static analysis, level 3)
composer quality   # lint + analyse

PHPStan runs at level 3 — higher levels conflict with the function-based architecture the port uses. All source files are analyzed; test files are excluded.

For the structure these tests verify and where deviations are recorded, see Architecture.

On this page