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
The PHP port prioritizes matching TailwindCSS output 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. For CSS generated per request or per page, reuse one compiler (see generateExact()) and persist its construction state with stateCacheFile.
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.json
Sample 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 suite
There are 4,427 passing tests as of release 1.7.0 (TailwindCSS v4.3.3 reference):
| Suite | Tests | Source |
|---|---|---|
Extracted TailwindCSS suites (utilities 706, variants 164, index 148, css-functions 64, ui.spec 68) |
1,150 | extracted from .test.ts / .spec.ts |
API coverage (utilities, modifiers, variants, directives, plugins, imports, @source, caching, public API) |
2,252 | tests/ |
| Unit tests beside the source (parser, AST, candidates, theme, utils, selector parser, lightningcss transforms, minifier) | 767 | *.test.php ports and PHP-specific tests |
| CLI (argument parsing, output, builds) | 84 | tests/Cli*Test.php |
| Plugin ports (typography, forms) | 45 | *.test.php beside the plugins |
| 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 4)
composer quality # lint + analyse
PHPStan runs at level 4 (phpstan.neon), with a short list of ignored patterns for the dynamic array shapes the function-based architecture uses. All source files are analyzed; test files are excluded.
For the structure these tests verify and where deviations are recorded, see Architecture.