- Issue created by @stjuan627
- Status changed to Needs review
about 2 months ago 11:24pm 21 January 2025 - 🇳🇿New Zealand garethhallnz
I too have run into a similar issue; for me most of out users are authenticated and Drupal's internal page cache module and even the dynamic page cache module does very little to help. Reverse proxy cache like varnish will obviously help but not all sites have access to such systems.
I have built on top of @stjuan627 patch and expanded on his work.
Why This Feature is Needed
During my testing, I observed that while Drupal’s internal page cache works well for anonymous users, performance for authenticated users suffered significantly. On average, page load times for authenticated users were 2-3 seconds slower due to the lack of caching for compiled Tailwind CSS. This is because authenticated users often bypass Drupal’s internal page cache, leading to repeated CSS compilation for every request.
The new caching feature addresses this issue by:
Improving Performance for Authenticated Users:
- By caching compiled CSS, authenticated users no longer experience the overhead of repeated CSS compilation, resulting in faster page load times.
Enhancing Performance for Anonymous Users:
- While anonymous users already benefit from Drupal’s internal page cache, this feature adds an additional layer of optimization by caching compiled CSS separately. This reduces the time required to generate the final HTML response.
Scalability:
- High-traffic sites, especially those with a mix of anonymous and authenticated users, will see significant performance improvements, as the caching mechanism reduces server load and improves response times.New Features
1. CSS Caching for Improved Performance
I’ve added a caching layer to store compiled Tailwind CSS, significantly reducing the need for repeated compilation and improving response times for subsequent requests. Key highlights include:
Configurable Cache Settings:
- Enable or disable CSS caching via the theme settings form.
- Set a cache lifetime, with options ranging from 1 hour to 90 days or permanent storage.
- Choose a hashing algorithm for cache key generation (default: `xxh3` for optimal performance).Automatic Cache Invalidation:
- The cache is automatically cleared when theme settings are saved or when the cache is manually cleared via Drupal’s cache administration page.Efficient Cache Key Generation:
- Cache keys are generated based on the unique CSS classes found in your content and the active theme, ensuring accurate and efficient caching.2. Extraction of Unique CSS Classes
To optimize CSS compilation, I’ve introduced a new method to extract and deduplicate CSS classes from your HTML content. This ensures that only the necessary CSS is compiled and cached, reducing overhead and improving performance.
3. Enhanced Theme Settings Form
The theme settings form has been updated to include new options for managing the caching feature:
- A new section allows administrators to configure caching settings, including enabling/disabling caching, selecting a hashing algorithm, and setting the cache lifetime.Improvements
Dependency Injection:
- The `HttpMiddleware` class now injects the `CacheBackendInterface` service, enabling seamless integration with Drupal’s caching system.Code Refactoring:
- Added helper methods (`injectStyleTag()` and `generateCacheKey()`) to streamline the caching and CSS injection process.Performance Optimization:
- Reduced redundant CSS compilation by leveraging cached results, improving response times for high-traffic sites.How to Use the New Features
- Enable Caching:
- Navigate to the theme settings form (`/admin/appearance/settings/[your_theme]`).
- Under the "Experimental features" section, enable CSS caching and configure the cache lifetime and hashing algorithm. - Monitor Cache Performance:
- Use Drupal’s cache administration page (`/admin/config/development/performance`) to monitor and clear the cache as needed. - Test and Optimize:
- Test the caching feature in a staging environment to ensure compatibility with your site’s configuration.
- Adjust the cache lifetime and hashing algorithm based on your performance and security requirements.
Backward Compatibility
This release is fully backward-compatible with existing configurations. However, the caching feature is marked as experimental, so I recommend thorough testing before enabling it in production environments.
Bug Fixes
- Fixed an issue where redundant CSS compilation could occur for repeated requests.
- Improved error handling during CSS compilation to ensure graceful fallbacks. - Enable Caching:
- 🇦🇹Austria hudri Austria
Hello @garethhallnz and @stjuan627,
sorry for not giving feeback for so long. I've been testing your patches now, and I'm really impressed by the performance gain. I've been testing it on my own sites, and the speed boost was massive on sites with complex Tailwind files. I really like the work done here and I think many users will greatly benefit from this feature!I'd like to reduce the interface a bit though. While the documentation is great, it might just be a bit to much for the average site admin, and the options seem to be a bit of academic nature, and not that essential in real operations.
Is there really any need to let the user pick different hashing algorithms? Even for ancient MD5, where collisions are easy to craft, they rarely happen naturally in real world. Since this is not a security related hashing, I can't see any situation where I'd advice a site admin not pick the fastest option.
And do we really need an additional cache lifetime setting here? Due Tailwind's atomic single responsibility classes, it is extremely unlikely that a cache hit will ever be outdated. Even if that edge case ever happens, a simple cache clear will solve the problem.
I don't think that the extra UI complexity of choosing hash algorithms and cache lifetimes will lead to better performance or less
problems in the wild. Therefore I think a simple on/off switch is enough.Do you see any problems with removing hash or lifetime options?
- 🇳🇿New Zealand garethhallnz
Thank you for your thoughtful feedback and for testing. I'm really happy to hear the performance improvements were noticeable!
My thought are:Hashing Algorithm Selection
I completely agree that hash collisions are not a real concern here. The reason I included multiple options was for compatibility. While xxh3 is significantly faster (around 30x faster than md5), it was only introduced in PHP 8.1. Since the module still supports Drupal 9 via 1.0.x, there are likely users running PHP 7.4 or 8.0 who wouldn’t have access to xxh3.Options:
- Automatically use xxh3 if it's available and fall back to md5 when it's not, without exposing this as a user-configurable option. This simplifies the UI while ensuring backward compatibility.
- Drop support for 1.0.x so we only need to support PHP 8.1+ and thus know that xxh3 is available.
Cache Lifetime Setting
The primary reason I added this was to allow flexibility for different caching strategies, particularly in setups using reverse proxy caches like Varnish. For example Lagoon documentation https://docs.lagoon.sh/applications/drupal/services/varnish/ explicitly recommends disabling Drupal’s Internal Page Cache when using a reverse proxy, which means relying on alternative caching mechanisms.In my own use case, I’ve found that authenticated users often benefit from different cache policies than anonymous users. That said, I see your point about Tailwind’s atomic nature making stale cache entries unlikely.
I think this setting should remain different because if this module depends on the lifetime setting from the Internal Page Cache module then we really so set the dependency array in the info file.
Options:
- Default to permanent caching (CACHE_PERMANENT) but allow developers to override it via hook_alter().
- Default to permanent caching (CACHE_PERMANENT), remove the cache lifetime setting from the UI but allow an override in settings.php
I personally don't like the above and far prefer the current setup.
Final Thoughts
I completely understand wanting to keep the UI simple, and I agree that too many options can be overwhelming for site admins. However, I’d argue that most developers using Tailwind in Drupal are already fairly technical, given how much custom Twig and hook-based class injection is often required to make it work well.Ultimately, I was scratching an itch and didn’t test everything to death, which is why I marked it as experimental. My personal experience has taught me that caching can be a pain in the butt, so we should tread lightly until we are confident that our assumptions are true (Mostly mine :))
That said, if you feel strongly about reducing the UI complexity, I think the best path forward would be:
I would personally suggest creating a version 2 that drops support for 1.0.x. This way, we can ensure PHP 8.1 is used, allowing us to remove the hashing configuration. Since this module doesn’t depend on the Internal Page Cache (nor do I think it should), I believe having the cache lifetime setting in the UI is reasonable.
- 🇦🇹Austria hudri Austria
Thanks for your feedback. I think we should go settings.php then, because this way it is easy to create environment specific overrides there, e.g.
$settings['tailwind_jit_cache_lifetime'] = $_ENV['SOME_VARNISH_SETTING'];
I currently have some spare time and will try to implement this with a custom cache bin (which also has the side effect of getting a free override mechanism for the cache size)
- 🇦🇹Austria hudri Austria
Created a new MR based upon the work of @garethhallnz and @stjuan627
- caches compiled CSS in a custom database cache bin ==> cache size and lifetime can be overwritten in settings.php if necessary
- caches HTML and Ajax requests
- caches by class attributes (not individual class names, because
.foo .bar
is not the same as.foo.bar
) - but class attributes are normalized for better cachability (sorted, normalized to single whitespace, filter core's
.js-view-dom-id-XXX
class) - UI simplified to single boolean toggle per theme
- Update hook enables caching by default, as it will be useful and recommended for almost all users
- added hook_uninstall()
Hot-Link to Patch: https://git.drupalcode.org/project/tailwind_jit/-/merge_requests/9.patch
We are currently testing this, if all goes well a new stable release is planned in one or two weeks.