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.phptoCss()— accumulates fragments in an array andimplodes once instead of repeated string concatenation, and uses pre-computed indent strings.css-parser.php— compares characters directly instead of going throughord(), and tracks buffer lengths instead of recomputingstrlen().
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.jsonSample results on PHP 8.4.5 / Node.js v22.17.0 (Apple M1):
| Benchmark | PHP | TypeScript |
|---|---|---|
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 suiteThere are 4,074 passing tests. The largest suites:
| Suite | Tests | Source |
|---|---|---|
| Core (utilities, variants, integration) | 1,322 | extracted from .test.ts |
| API coverage (utilities, modifiers, variants, directives, plugins) | 1,774 | exhaustive API tests |
| PHP-specific unit tests (theme, design-system, utils) | 300 | various .test.ts ports |
Public API (tw::generate, tw::compile, inspection) | 140 | API tests |
| Companion libraries (clsx, tailwind-merge, CVA) | 129 | reference 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 + analysePHPStan 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.