Problem/Motivation
Currently, CSS aggregates can contain either root-relative non-CDN URLs or protocol-relative CDN URLs.
Both work! Because within a CSS file, it's simply unnecessary to alter file URLs: root-relative file URLs are relative to the CSS file's base URL, and hence relative to the CDN domain. This is guaranteed to work for Origin Pull CDNs, which are the only CDNs the D8 CDN module supports.
The only problem here is that it's confusing: currently, aggregate A can contain no CDN URLs, and aggregate B can contain only CDN URLs.
The other problem is that when you change CDN settings, the changes will not be visible immediately; they'll only become visible after the current aggregate has expired, i.e. it'll only become visible after 30 days.
Proposed resolution
- Leave it as it is, since it does not and cannot cause problems, but then it should be documented.
Add a cache tag for CSS aggregates toWe should never delete CSS aggregates because pages cached by search engines still reference those aggregates.\Drupal\Core\Asset\AssetResolver::getCssAssets(), which we can then invalidate. This requires a core patch.- Allow
file_create_url()to receive a context. This would allow the CDN module to not alter the file URL if it's referenced from a CSS file. This requires a core patch. Note that this also works when the "Forever cacheable files" setting is enabled: files referenced in the CSS file then use root-relative URLs.
For example, even if//cdn-example.com/cdn/farfuture/tG2ezMbStSbnCryLDs2r7b10f-VKv7I8S3hK2lPnmEM/1474536635/sites/default/files/css/css_UcklJvyLvvpIDbvEa9ARtoihbJeEE0_YBOaolKktIAc.css?odwh4jhas a signed URL, all referenced URLs look like/core/themes/bartik/images/tabs-border.png, which means they are resolved to//cdn-example.com/core/themes/bartik/images/tabs-border.png, which works fine. It'd only break (like in D7) if those URLs were using relative URLs likeimage.pngor../image.png, which is no longer the case in Drupal 8.
One could argue that those referenced URLs then are not "Forever cacheable files", but note that we wouldn't be aware of modifications to those referenced files anyway, because the aggregates are cached. Which means that we'd be serving e.g. the same background image forever even if it changed in the mean time. Therefore it is actually more accurate to not have those files be served from a CDN. Therefore this is probably the better option. - Modify the CSS aggregate filename to include a hash of the CDN settings, this would ensure that a different aggregate is served depending on CDN settings. This requires a core patch, and it turns out #1014086: Stampedes and cold cache performance issues with css/js aggregation is already introducing this in an indirect way.
Remaining tasks
See #10.
User interface changes
None.
API changes
None.
Data model changes
None.
| Comment | File | Size | Author |
|---|---|---|---|
| #28 | 2745109-20.patch | 10.93 KB | wim leers |
| #23 | 2745109-23-debug.patch | 10.95 KB | wim leers |
Comments
Comment #2
wim leersFor now, assuming option 1: , since it's the least amount of work, and because it brings a final release within closer reach.
Comment #3
wim leersIn the mean time, #2745115: Port CDNCssUrlTestCase in tests/cdn.test removed the now unnecessary test coverage.
Comment #4
wim leersAdding one extra bit of information to the IS from #2745115-2: Port CDNCssUrlTestCase in tests/cdn.test, that makes this issue much easier to understand.
Comment #5
wim leersI first decorated
CssCollectionGrouper'sgroup()method. Unfortunately, that doesn't work, because the hash (and CSS aggregate filename) is generated based solely on CSS filenames, not on any other metadata.So, what I actually need to override/decorate, is
CssCollectionOptimizer::generateHash. Which is unfortunately protected, unlikeCssCollectionGrouper::group().The therefore less elegant solution albeit working one is the one attached.
Comment #6
wim leersUnfortunately, this will likely cease to work correctly as soon as #1014086: Stampedes and cold cache performance issues with css/js aggregation lands, or as soon as any other module is overriding the CSS collection optimizer.
So, likely a better solution is to override the asset dumper. Although that too could be impacted by #1014086: Stampedes and cold cache performance issues with css/js aggregation, but at least it'll work fine in collaboration with other contrib modules, like
advagg. Except…advaggitself already overrides all of core's asset handling services. So the CDN module would have to implement a hook for AdvAgg too.That'd look somewhat like the attached patch (not letting that one be tested, because it's only a PoC, it's incomplete). The problem there is that we duplicate 99% of the logic in
AssetDumper… just to be able to modify the generated filename. This is terrible for maintainability: I'd have to monitor core'sAssetDumperfor changes, and make sure I copy those over…Comment #7
wim leersSo, in conclusion, no matter what I do, I'm in a bad place. Because the CDN module is unable to just modify the generated filenames. It is able to do the necessary cache invalidation (invalidating the
library_info) cache tag. But not modify the generated filename.So, what we need, is a simpler way to modify the generated filename for CSS aggregates.
Fortunately… it's looking like #1014086: Stampedes and cold cache performance issues with css/js aggregation will add exactly that: if that ends up adding
AssetDumperUriInterfacelike it does today:… then I can just decorate that service and decorate only the
dump()method. It'd then still need to duplicate some code, but far less of it:That'd be far more maintainable: chances of that ever changing are very very low.
So, postponing this on #1014086: Stampedes and cold cache performance issues with css/js aggregation.
Comment #8
wim leersClarifying title & issue summary.
Which made me realize that there are still two possible solutions:
file_create_url()to receive a context. This would allow the CDN module to not alter the file URL if it's referenced from a CSS file. This requires a core patch. Note that this also works when the "Forever cacheable files" setting is enabled: files referenced in the CSS file then use root-relative URLs.For example, even if
//cdn-example.com/cdn/farfuture/tG2ezMbStSbnCryLDs2r7b10f-VKv7I8S3hK2lPnmEM/1474536635/sites/default/files/css/css_UcklJvyLvvpIDbvEa9ARtoihbJeEE0_YBOaolKktIAc.css?odwh4jhas a signed URL, all referenced URLs look like/core/themes/bartik/images/tabs-border.png, which means they are resolved to//cdn-example.com/core/themes/bartik/images/tabs-border.png, which works fine. It'd only break (like in D7) if those URLs were using relative URLs likeimage.pngor../image.png, which is no longer the case in Drupal 8.One could argue that those referenced URLs then are not "Forever cacheable files", but note that we wouldn't be aware of modifications to those referenced files anyway, because the aggregates are cached. Which means that we'd be serving e.g. the same background image forever even if it changed in the mean time. Therefore it is actually more accurate to not have those files be served from a CDN (which also means the Drupal 7 version of the CDN module gets this wrong). Therefore this is probably the better option.
(Copied verbatim from the updated issue summary.)
In fact, thinking about it more, we do not need #1014086: Stampedes and cold cache performance issues with css/js aggregation, we only need #3!
We need to not modify URLs inside CSS aggregates because otherwise we'll end up serving those referenced files with Far Future expiration headers, which is wrong.
Then there also is no more need to generate a new aggregate whenever CDN settings change… because there's no point in changing them! We want them to always be the same:
Comment #9
wim leersPatch that implements #8's conclusion.
Comment #10
wim leersI talked it through with catch, who's working on #1014086: Stampedes and cold cache performance issues with css/js aggregation. He's +1 on this approach. His main remark is that the code here could be simplified: rather than decorating that service which sets a global variable, we could rely on a route match. The downside of that is that it will probably not work for contrib modules. So the approach in this patch is probably the most reliable one.
The only downside to this approach is that not all assets will have Far Future expiration headers. But correctness should come first, and this is definitely better for correctness.
As a bonus, this is better for simplicity/maintainability/performance: we won't have to regenerate CSS aggregates anymore upon enabling the CDN module, nor when changing settings. And even better: having CSS aggregation enabled will no longer be a requirement of using the setting!
Comment #11
wim leersUpdating title accordingly.
Comment #12
wim leersThis will still need a test:
\Drupal\Tests\cdn\Functional\CdnIntegrationTest::testCss(), which tests:In the mean time, this last reroll removes additional things that can be removed thanks to the changes in #10.
Comment #13
wim leerss/optimize/clean/
Comment #14
wim leersAnd here's the missing test coverage.
Comment #17
wim leersComment #20
wim leersComment #23
wim leersNow it's getting on my nerves. This is adding debug output.
Comment #26
wim leersThe output for #23 is:
That's wrong. It looks like this slipped through the cracks of the test coverage of #2708787: Port Far Future expiration to 8.x-3.x. It should be:
i.e. the "base path" is in the wrong location. Because the code introduced by #2708787: Port Far Future expiration to 8.x-3.x is overly simplistic: it just prefixes the root-relative URL rather than splitting it up as necessary:
should be something like
Comment #27
wim leersOpened #2806241: Far Future expiration functionality doesn't work when Drupal is not installed in the root of a domain for that and fixed it there.
Comment #28
wim leersReuploading #20.
Comment #29
wim leersThere we are! :)
Comment #30
wim leersComment #32
wim leers