Problem/Motivation

CSS/JS optimized assets directory is hardcoded to public://{css,js}.

A user may want to store optimized assets in a different path for better performance or reliability, for example when public:// is in a slower/unreliable filesystem (for example a network share) and optimized assets can be placed in a faster one.

At present this can only be achieved by adding a symlinks from public://css to /faster/css. It is not a desirable solution for several reasons:

* The symlink lives in the slower filesystem and depends on its availability
* Bad performance
* Fragile/tangled setup

Proposed resolution

* Make optimized assets path configurable via a settings variable file_assets_path. This is the path to the parent directory under which css/ and js/ subfolders live. The default value for this setting is public://, so by default it works the same as now.
* Create assets:// stream wrapper that abstracts the implementation details of supporting a custom path or the default of public://

Remaining tasks

* Discuss the proposed solution and implementation (settings vs config?, stream wrapper?)
* Create a patch for the agreed solution/implementation
* Add tests
* Review
* ...

User interface changes

There's a new read-only entry in admin/config/media/file-system similar to the one for public/private file system paths, indicating the configured path for the assets folder.

API changes

Developers of contrib modules must start using the new stream wrapper:

before

$css_base_path = "public://css";
$js_base_path = "public://js";

after

$css_base_path = "assets://css";
$js_base_path = "assets://js";

Data model changes

No.

Release notes snippet

The file location for Drupal's asset aggregation system is now configurable in settings.php via $settings['file_assets_path'].

Issue fork drupal-3027639

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

jonhattan created an issue. See original summary.

jonhattan’s picture

Issue summary: View changes
StatusFileSize
new3.02 KB
jonhattan’s picture

Issue summary: View changes
StatusFileSize
new3.6 KB

I missed a change in previous patch.

jonhattan’s picture

StatusFileSize
new5.24 KB

Try and fix the tests

jonhattan’s picture

Status: Active » Needs review
jonhattan’s picture

Issue summary: View changes
jonhattan’s picture

Up for discussion:

  1. I decided to pick system.file::assets.js_base_path because system.file is the config object related to /admin/config/media/file-system, where other system paths are set up
  2. I decided to not expose an UI for two reasons:
    1. It is a uncommon feature much people needs not to change. Just in case, it could be a markup form element similar to public and private paths
    2. A more practical one: the system_check_directory assumes file_public_path is the only public web-servable path, any other is configured with a private-folder .htaccess
  3. assets.js_base_path is also argueable. I choose that name because internally the classes dealing with aggregation/optimization uses Asset naming
  4. Alternatively to point 1, other place for this config could be near the "Aggregate CSS/JS files" checkboxes in admin/config/development/performance, corresponding to system.performance::css.

So questions are:

  • UI or not? this imply to generalize system_check_directory and accept more than one public path
  • system.file::assets.js_base_path or system.performance::css.base_path?
leandro713’s picture

- the patch looks great for me, seems it works as is in #4
- no worries with variables nomenclature
- i do vote for a UI for the shake of usability :-)

if i'm interested in change assets location i would prefer to specify a given location (in a friendly way, which is always better);
so yes, i would put a markup possibliting this in Performance

vorapoap’s picture

Will this be merged to core soon?
Is this amendable via settings.php or only via drush?

vorapoap’s picture

OK.. it can be set via

$config['system.file']['assets']['css_base_path']
$config['system.file']['assets']['js_base_path']

I suggest that the patch should provide default directory for those who just apply this patch for the first time e.g. from Drupal 8.6.x. Meaning for those upgrade drupal from composer to not see the broken site. From my understanding, those yml in config doesn't reapply automatically by clearing cache..

jonhattan’s picture

Title: Make css/js aggregates path configurable » Make css/js optimized assets path configurable
Issue summary: View changes
jonhattan’s picture

StatusFileSize
new7.38 KB

New patch with a new approach:

* Use Settings instead of Config
* UI to show the current value, but changes must be done in settings.php
* Configure the parent folder instead of css and js individually

(Issue summary is updated to reflect this)

Things I don't like:

* Providing always a default value in Settings::get('file_assets_path', 'public://'); is a step back
* Supporting both stream wrapper uris or relative paths is a mess because of the traling slash ('public://' + 'css' vs 'sites/default/assets' + '/css')

Is a good idea to create a new stream wrapper OptimizedAssets to mask this two cases?

jonhattan’s picture

StatusFileSize
new8.47 KB
new6.81 KB

Added assets:// stream wrapper. Code is cleaner now.

jonhattan’s picture

Issue summary: View changes

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

jose reyero’s picture

StatusFileSize
new7.98 KB

Updated for 8.8.x

miguelarber’s picture

StatusFileSize
new7.97 KB

When trying to apply the patch drupal-3027639-15.patch via Composer in 8.8.x a generic error is thrown saying that the patch could not be applied (but this might be due to permission issues when trying to update the file settings.default.php). After appliying it manually, it seems to update the code properly, however, a warning is displayed by drush when executing cache-rebuild. This warning is way more specific than the composer error and states that AssetsStream::basePath should be compatible PublicStream::basePath. A quick comparison between PublicStream in 8.7.x and PublicStream in 8.8.x shows that the method param type has changed: it no longer uses \SplString as the param type. In conclusion, the patch needs an update to properly work in 8.8.x (or at least, not throwing any warnings when running cache-rebuild via drush). Attached to this comment there is an update that solves the conflict between AssetsStream::basePath and PublicStream::basePath.

Status: Needs review » Needs work

The last submitted patch, 18: drupal-3027639-16.patch, failed testing. View results
- codesniffer_fixes.patch Interdiff of automated coding standards fixes only.

Lal_’s picture

Status: Needs work » Needs review
StatusFileSize
new8.66 KB
new4.72 KB

It do a digging inside the assets... I am unsure about this patch.. please check the interdiff

Lal_’s picture

StatusFileSize
new8.64 KB
new351 bytes

coding standards

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

jonhattan’s picture

Issue summary: View changes
amitaibu’s picture

+++ b/core/lib/Drupal/Core/StreamWrapper/AssetsStream.php
@@ -0,0 +1,62 @@
+    return t('Optimized assets files');

I think we should use StringTranslationTrait and $this->t() instead of t()

catch’s picture

Status: Needs review » Needs work
Issue tags: +Performance

Yes StringTranslationTrait makes sense, marking needs work for that.

This seems like a good idea to me, would help when public:// isn't local.

ankithashetty’s picture

Status: Needs work » Needs review
StatusFileSize
new8.78 KB
new7.62 KB

Rerolled the patch in #21 and addressed #25, thanks!

voleger made their first commit to this issue’s fork.

voleger’s picture

+++ b/core/lib/Drupal/Core/File/HtaccessWriter.php
@@ -107,6 +107,10 @@ public function defaultProtectedDirs() {
+    if ($assets_path != 'public://') {

+++ b/core/lib/Drupal/Core/StreamWrapper/AssetsStream.php
@@ -0,0 +1,64 @@
+    if ($path == 'public://') {
...
+    if ($path == 'public://') {

Strict comparison can be used here

ankithashetty’s picture

StatusFileSize
new8.78 KB
new1.34 KB

Updated patch in #27 to address #29, thanks!

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

beunerd’s picture

StatusFileSize
new8.15 KB

Please disregard.

Removed the updates to sites/default/default.settings.php as those are already handled in scaffolding.

Status: Needs review » Needs work

The last submitted patch, 32: optimizedassets_3027639_32.patch, failed testing. View results

beunerd’s picture

StatusFileSize
new8.77 KB

Updated line 146 in PerformanceForm.php, and added back default.settings.php in sites/default.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Status: Needs work » Needs review
sassafrass’s picture

Was hoping to be able to serve compressed css and js from a folder different than the default public://css and public://js. My use case is that I am serving content via Akamai simultaneously from two different origins as we migrate the site from D7 to D9. Akamai doesn't know which origin to serve the correct css/js from because they are the same folders on both sites.

Unfortunately, I could not apply the latest patch.

Installing drupal/core (9.4.x-dev efd8f73)
Could not apply patch! Skipping. The error was: Cannot apply patch https://www.drupal.org/files/issues/2021-10-14/core-optimized-assets-302...

cilefen’s picture

The patch applies to the 9.4x and 10.0.x branches. The commit "9.4.x-dev efd8f73" seems to reference does not exist in Drupal's commit history. What kind of repo is that?

I just sent it out to test again on 9.4.x.

catch’s picture

It wouldn't hurt to have some test coverage for this, however there is no test coverage of the writing-to-disk logic in core at all at the moment (we do have tests for lots of other aggregation logic).

There are however some tests in #1014086: Stampedes and cold cache performance issues with css/js aggregation which cover this, and because they're functional tests it would be possible to run them with and without a customised asset directory.

berdir’s picture

Having a stream wrapper configured to another stream wrapper seems quite unusual and would also be slower, no?

If we really want a stream wrapper for that, could we just make the configuration for it fall back to the same configuration as public:// uses?

FWIW, I don't really understand why we need a stream wrapper for this at all and not just make a setting that defaults to public://? It's contained within those services and not really an API and I'd assume there's almost always going to be another stream wrapper behind it anyway? If you have a use case to put it on S3 for example, you'll need to configure it to use a stream wrapper provided by that integration.

We actually just had a weird use case where this would have been useful. We had a problem with a CDN that had a broken file and it took us a while to clear that. With a setting, we could have switched to a different path and deliver a different filename.

catch’s picture

FWIW, I don't really understand why we need a stream wrapper for this at all and not just make a setting that defaults to public://? It's contained within those services and not really an API and I'd assume there's almost always going to be another stream wrapper behind it anyway? If you have a use case to put it on S3 for example, you'll need to configure it to use a stream wrapper provided by that integration.

I think it's the reverse case that's more common. s3fs module can take over the public:// stream wrapper to put all uploads on s3, but you then don't want generated assets on there.

berdir’s picture

Ok, I assumed that if you have a shared file system you either have multiple web hosts (then the files need to be shared) or no local file system at all.

The path needs to be both a physical path and something that file_create_url() (now url generator) can handle, so I think you could still put sites/default/files in the setting and that would work too. slightly more complicated for the few usages outside of the dumper, but it's arguably bad abstraction anyway that these other services hardcode that path anyway.

either way, atleast we should IMHO not pass the requests through two usercode streamwrappers, which is what is happening now?

catch’s picture

Yes removing the stream wrapper doubling sounds good, and if it's possible to remove the stream wrapper but still make it simple to have an alternative local path without relying on public:// that's great.

beunerd’s picture

@catch - #41 is exactly our use case.

jonhattan’s picture

FYI interdiff in #13 shows how the code looked before implementing assets://.

I added the stream wrapper just for abstraction of the differences between custom path vs public://. I don't see how inheritance can make it slower.

jonhattan’s picture

FTR, If anyone interested I created a supplementary module https://www.drupal.org/project/optimized_assets_proxy for multi-webhead architecture.

drfuzetto’s picture

StatusFileSize
new8.15 KB

I had the same problem as Comment#37. The problem was with the default.settings.php.
I recreated the patch without those changes. Isn't the default.settings.php built from the scaffold file?
My patch works with 9.3.

Status: Needs review » Needs work

The last submitted patch, 47: core-optimized-assets-3027639-47.patch, failed testing. View results

yogeshmpawar’s picture

Status: Needs work » Needs review
StatusFileSize
new8.78 KB
new642 bytes

Updated patch will fix test failure.

berdir’s picture

> I recreated the patch without those changes. Isn't the default.settings.php built from the scaffold file?

In a composer project, yes. But the git repository has that file and the change needs to be included there, that's where the scaffold feature gets the files from.

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

catch’s picture

Here's a re-roll, also changing the fallback from public:// to Settings::get('file_public_path') to avoid the stream wrapper nesting. That should allow this patch to fix #3323934: Breaking changes for public:// takeover in D10.1 from #1014086 without any configuration on the part of the site owner.

catch’s picture

StatusFileSize
new8.81 KB
catch’s picture

Category: Feature request » Task

More of a task.

longwave’s picture

+++ b/core/lib/Drupal/Core/StreamWrapper/AssetsStream.php
@@ -0,0 +1,63 @@
+    $path = Settings::get('file_assets_path', 'public://');
+    if ($path === 'public://') {

What is this special case for? The docs say file_assets_path should be a local path, not a URI.

edit: if it's only for the default/unset case, why not use NULL?

catch’s picture

StatusFileSize
new1.78 KB
new9.07 KB

I'd missed a couple of public:// - uploading a new patch that addresses those.

The logic is that if the path isn't set, or if it's been explicitly set to exactly the same as the public file path, then we want it to work the same as the public stream wrapper - so it's try to preserve the ability to explicitly set the public files base URL.

I think it would be possible to use NULL and fall back too, but setting explicitly the same directory does seem like something that could happen.

cmlara’s picture

Since the storage path is configurable should we also have \Drupal\system\Routing\AssetRoutes::routes track the path?

I believe this will also create the same issues described in #2979669: Follow-up for #2752961: automatically deleting compiled Twig templates across multiple webheads regarding cleaning up files. We might want to start formally documenting these paths and their recommended cleanup schedules somewhere?

catch’s picture

StatusFileSize
new823 bytes
new9.87 KB

This should cover the first part of #59.

Multi-webhead files clean-up, maybe a new meta issue (and link a follow-up from that)?

cmlara’s picture

StatusFileSize
new9.87 KB
new630 bytes

Cleaning up stray ')' from patch #57/#59

I'm ok with multi-webhead being a new meta issue.

longwave’s picture

Developers of contrib modules must start using the new stream wrapper:

Is there a way we can help developers discover this, with a deprecation somewhere? Is there any contrib that will be broken on sites that use this new feature, but that is expecting assets in the old location?

catch’s picture

(example search https://git.drupalcode.org/search?group_id=2&scope=blobs&search=public%3...)

Had a quick look:

responsive_menu uses the stream wrappper, but it's just putting its own file in the directory, not actually interacting with assets at all, so it wouldn't matter:

breakpoint_css_filepath: public://css

css_flipper does similar.

So does gmap with public::/js

So these are all OK - they're just using those folders names as 'places css or js are written to', not interacting with the aggregates at all.

I also manually checked advagg module, and that does use public:// . $asset_type (which of course doesn't come up in the search) and will likely break, however advagg closely alters the internals of the aggregation system, so, either it'll continue working if it alters enough to essentially override all the core logic, or it will already need to update for 10.1 as per #3308099: Document which parts of the module are still relevant after aggregation changes in 10.1.0.

So overall, we should actually be pretty clear in terms of disruption, and this issue will simplify/fix s3fs module too.

longwave’s picture

Status: Needs review » Needs work
Issue tags: +Needs tests, +Needs release note

The code so far looks good, but this new feature needs test coverage, and we also should mention it in the release notes.

catch’s picture

Issue summary: View changes
Issue tags: -Needs release note

Added a change record and release note.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new6 KB
new14.09 KB

Added some test coverage. We have various implicit functional testing of the unconfigured fallback (such as in Umami tests), so changed the existing dedicated functional test to test the configured version. This caught some extra references to public:// that needed updating since other aggregation changes in 10.1 too.

catch’s picture

Issue tags: -Needs tests
smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative

For the new file AssetsStream can we typehint the new functions?

Applied patch on Drupal 10.1 with a standard install
Verified going to admin/config/media/file-system I see

Optimized assets file system path
sites/default/files

Updated my settings.php with $settings['file_assets_path'] = '../test';

Verified file-system page updated correctly

When I turn preprocess back on

$config['system.performance']['css']['preprocess'] = TRUE;
$config['system.performance']['js']['preprocess'] = TRUE;

The pages completely broke.

Did I miss a step? Nothing gets added to test folder

voleger’s picture

Addressed type hints for the AssetsStream class #67, added review comments in MR. Hide the files.

catch’s picture

Did I miss a step?

Did you do a full cache clear? The router needs to be rebuilt so that the routes serve the assets at the newly configured path.

smustgrave’s picture

Cleared cache multiple times.

catch’s picture

Status: Needs work » Needs review

Updated my settings.php with $settings['file_assets_path'] = '../test';

I missed this the first time I read it - the path has to be inside the webroot so that Drupal can take over routing, so ../test won't cut it. You could change it to something like 'files/assets'.

ambient.impact’s picture

@catch Yes, this would have to be inside the web root since the resulting files are served by the web server directly (Apache, nginx) rather than going through Drupal like in the case of private files. We use this on Omnipedia with $settings['file_assets_path'] = 'assets'; which results in aggregated assets being placed in https://omnipedia.app/assets/; the docblock in the merge request's settings.php clearly states this:

/**
 * Optimized assets path:
 *
 * A local file system path where optimized assets will be stored. This
 * directory must exist and be writable by Drupal. This directory must be
 * relative to the Drupal installation directory and be accessible over the
 * web.
 */

When in doubt, read the docblock. 😉

smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Retested patch #65
Created a folder web/files/assets.
Turned
$config['system.performance']['css']['preprocess'] = TRUE;
$config['system.performance']['js']['preprocess'] = TRUE;
Cleared cache

Verified assets were being placed in the folder. Page was rendering fine.

longwave’s picture

Status: Reviewed & tested by the community » Needs work

In the test change, the setting name is wrong. I think we need to add an assertion somewhere in the test to ensure the path to a generated asset is actually what we expect.

catch’s picture

I started working on fixing the test coverage.

Now fails when the asset URL doesn't match what we think we're setting it to.

Unfortunately I don't seem to be able to get ::writeSettings() and ::rebuildAll() to successfully change what the asset stream points to yet, so it will actually fail.

Manually testing works, so it's getting the stream wrapper to pick up the setting in tests.

catch credited olli.

catch’s picture

catch’s picture

Status: Needs work » Needs review

Test now asserts that the URL is what it should be, and the reason it was failing was because I missed the extra 'settings' key in the writeSettings() format as pointed out by @olli in the MR. Back to needs review now it's green again.

catch’s picture

Issue tags: -Needs Review Queue Initiative +Needs Review Queue Initiative. Drupal 10.1 target

Tagging as a Drupal 10.1 target since this will make things easier for s3fs (and possibly other contrib stream wrappers) after #1014086: Stampedes and cold cache performance issues with css/js aggregation.

catch’s picture

Issue tags: -Needs Review Queue Initiative. Drupal 10.1 target +Needs Review Queue Initiative, +Drupal 10.1 target
smustgrave’s picture

Status: Needs review » Reviewed & tested by the community

Tested same way as before (#74) and still seems to be working. Don't mind marking.

  • longwave committed d45cf927 on 10.1.x
    Issue #3027639 by catch, jonhattan, ankithashetty, Lal_, voleger,...
longwave’s picture

Status: Reviewed & tested by the community » Fixed
Issue tags: +10.1.0 release notes

Committed and pushed to 10.1.x, and published the change record. Thanks!

wim leers’s picture

Status: Fixed » Patch (to be ported)

Woah, had no idea this was happening!

From the perspective of modules like https://www.drupal.org/project/cdn, this is a BC break, because suddenly assets have different URIs (no more public://something but assets://something. I'm happy to make sure that that module is updated, but I think the change record should be more explicit and inform contrib/custom modules that they will need to apply the treatment they had for public:// for assets:// instead now.

Per #3323934: Breaking changes for public:// takeover in D10.1 from #1014086, this is also a breaking change for the https://www.drupal.org/project/s3fs module. But what isn't clear to me is how the CDN module will detect that assets:// is actually on S3. I think that means that the S3FS module should override the entire AssetsStream class to not do class AssetsStream extends PublicStream extends LocalStream anymore.

So the CDN module (and others) would have to detect at runtime whether the current class is an instance of LocalStream or not. That seems like a risk and something that we're lacking documentation for, especially if the very purpose of this new stream wrapper is to be overridden? 😅

cmlara’s picture

@Wim

From a s3fs standpoint, I had anticipated we would treat assets:// similar to temporary://, its there, it exists, its per server, and we don't modify it. The newest worst case is that each edge server has its own copy of CSS files however these are small and per #1014086: Stampedes and cold cache performance issues with css/js aggregation are quick to dynamically generate. As long as core doesn't start using assets:// for for files that can not be reproduced on demand (or are 'expensive' to produce) its safe for a multi-headed environment and s3fs doesnt need to take it over.

I suppose someone could submit a feature request sometime in the future that s3fs support being storage for assets:// as as well, however if they did so, I wonder if it would it be much different for CDN compared to when the s3fs modules replaces the public:// and private:// streamWrapeprs with s3 based storage?

catch’s picture

Yes the issue with s3fs and public files takeover vs. asset aggregation is that it breaks the routing that Drupal needs to generate the aggregate. Now that assets:// is it's own stream wrapper, s3fs can separate the treatment of assets:// and public:// and leave things alone.

I've used s3fs and public files takeover on at least one site, and the use case has been a site hosted somewhere with a somewhat restrictive amount of file storage (pantheon, platform.sh etc.) where you want lots of uploaded files on s3 instead for storage. For these, moving assets:// back to the Drupal server is ideal. I've been running this patch in production on at least one 9.5 site, and it works fine without any changes to s3fs at all - just to avoid the URL rewriting that s3fs does.

For CDN module, if nothing needed to change for #1014086: Stampedes and cold cache performance issues with css/js aggregation, then it is probably just pushing the files up to the CDN from assets:// that needs to be covered - however I'm not sure how CDN module handles image derivatives and now route-generated asset aggregates, been a little while (several years) since I looked tbh.

catch’s picture

Status: Patch (to be ported) » Fixed

I've updated the change record - @Wim Leers does that help?

https://www.drupal.org/node/3328126

This can't be backported, not sure if you meant to mark needs work, but moving back to fixed for now.

wim leers’s picture

High-level impact on contrib modules

The CDN module is not yet working on Drupal 10.1: #3347181: 10.1.x compatibility: tests are failing against Drupal 10.1.x due to upstream changes … and it definitely looks plausible that #1014086: Stampedes and cold cache performance issues with css/js aggregation caused that.

however I'm not sure how CDN module handles image derivatives and now route-generated asset aggregates, been a little while (several years) since I looked tbh.

The Drupal 8/9/10 version of the CDN module (version >=3) does not handle image derivatives, because there used to be no way to do that reliably. Work is happening at #3179753: Improve far-future support: generate dynamically generated files automatically (f.e. image style derivatives) to restore that. And you hit the nail on the head: if I want to support that, I'd much rather have that work generically for any route that generates assets rather than only image derivatives.

Why only CSS & JS assets, and not also image derivatives, i.e. all "optimized assets"?

Reading through that made me re-discover #2670404: Add new stream wrapper(s) to store generated files separately, which is a very closely related core issue. That issue is basically identical to this issue, except that it proposed to do it not only for CSS & JS aggregates, but all generated assets, including image derivatives. And in fact, assets:// sounds generic enough that it could really be used for any kind of asset (even generated an non-generated?). The change record makes this rather clear to: it's titled New assets:// stream wrapper for custom aggregate path location — it needs that "for …" part to clarify what it's intended to be used for. The code speaks about "optimized assets", which would include image derivatives too, wouldn't it?

Regression preventing contrib modules from working correctly

I've updated the change record - @Wim Leers does that help?

Not quite yet. Because the problem of "local vs not" is not yet addressed.

You refer to \Drupal\Core\StreamWrapper\TemporaryStream, but that has StreamWrapperInterface::LOCAL_HIDDEN as its type: always local, always hidden. That makes sense.

But AssetsStream says return StreamWrapperInterface::LOCAL_HIDDEN too. This is a problem because: the documentation says:

  /**
   * Refers to a local file system location.
   */
  const LOCAL = 0x0001;
…
  /**
   * Exposed in the UI and potentially web accessible.
   */
  const VISIBLE = 0x0010;

… CSS & JS aggregates surely are always web accessible? 😅 And then depending on whether S3FS's "takeover" functionality (see #3323934: Breaking changes for public:// takeover in D10.1 from #1014086) is used or not, it may be local or not. That's the problem I was referring to in #85.

That's a problem because it prevents modules like the CDN module from providing configuration, choices and behavior to the user based on the stream wrapper's metadata:

  1. \Drupal\cdn_ui\Form\CdnSettingsForm::buildForm() allows choosing to enable the CDN for any VISIBLE stream wrapper (since #2870435: Support additional stream wrappers):
    CDN UI
  2. … in this new world where stream wrappers are actually fairly likely to have their type change due to modules like S3FS swapping out the implementation, I'll have to write a new validation constraint to verify that the stream wrappers for which the CDN module is enabled is actually one of the VISIBLE type

Regardless of the new challenges for the CDN module (I'm happy to overcome those! 😊 It's great to see progress on this front! 🤩), I think this is a plain bug:

  /**
   * {@inheritdoc}
   */
  public static function getType(): int {
    return StreamWrapperInterface::LOCAL_HIDDEN;
  }

it should be:

  /**
   * {@inheritdoc}
   */
  public static function getType(): int {
    return StreamWrapperInterface::READ_VISIBLE;
  }

… because it's A) definitely web accessible and readable, B) even if it's local by default, it's just as likely not to be, so it's better to treat that aspect of this stream wrapper as a black box and hence omit WRITE and LOCAL. If you disagree with point B, then it should be StreamWrapperInterface::READ_VISIBLE | StreamWrapperInterface::LOCAL.

catch’s picture

Why only CSS & JS assets, and not also image derivatives, i.e. all "optimized assets"?

I've wanted this as well for image derivatives, also due to s3fs public takeover - i.e. it would allow you to have original PDFs + images on s3, but avoid a lot of complexity for image derivatives (no URL rewrites etc.). Also for a lot of configurations it would mean that page assets save the extra DNS request if you're serving from a files.example.com domain etc.

However for the purposes of this issue was doing 'one thing at a time'.

It would be straightforward to add a follow-up for image derivatives, but it also means the meaning of the configuration would change if we make it also apply to images... so should we combine everything in here?

cmlara’s picture

It would be straightforward to add a follow-up for image derivatives, but it also means the meaning of the configuration would change if we make it also apply to images... so should we combine everything in here?

I would be concerned there is actually more complexity involved and that moving ImageStyles to assets:// should be given a separate in depth discussion.

The biggest issue that comes to mind is that currently the asset:// streamWrapper must store files inside the docroot, however image style derivatives of private:// (and other non-public streamWrappers) should not be inside the docroot. That by itself may make asset:// unsuitable for storing all image styles.

Beyond that image styles are a different cost equation (bandwidth transfer. computational cost, local disk storage, etc) compared to css/js file creation and should be given a much deeper discussion around DoS potentials, especially as you start discussing multi-server with load balancing proxies. We may very easily significantly weaken the ITOK protection system.

My personal opinion based on recent work with the ImageStyle generation system is there are 'limitations' in the current design, and a centralized asset:// storage location may or may not make the issues worse depending upon actual implementation.

All of the above makes me believe that 'take it slow and handle it separate' is a good idea as this comes to ImageStyles.

#3298701: ImageStyleDownloadController routes do not limit schemes served is also related to ImageStyle storage changes.

From an s3fs standpoint:
I've certainly seen some requests to move ImageStyle storage away from the S3 bucket to local storage, that would certainly appease some of the s3fs user base, and would allow us to remove some areas of complexity we currently have in the code. I certainly have some concerns about impact and attack vectors that trying to generate ImageStyles from remote sources necessitate design implementations considerations, See #3298703: Core ImageStyleDownloadControler allow DoS for s3fs.

wim leers’s picture

#91: You're right this cannot work for private://. But \Drupal\image\Entity\ImageStyle::fileDefaultScheme() currently returns either private or public. What would the harm be in making that private (unchanged!) or assets? 🤔

#90: I think the configuration that this added already is fine precisely because it's so abstract: it says "optimized assets". Images are assets, so … I don't see what needs to change? It says that everywhere: in settings.php, in FileSystemForm, and so on. That's why I'm confused why it wasn't applied to image derivatives too! 😄 But yes, doing that in a follow-up definitely works 👍 My primary concern is the use of LOCAL_HIDDEN.

catch’s picture

Status: Needs work » Needs review
StatusFileSize
new828 bytes

I think we should actually use the same as PublicStream and stop overriding the method, which is LOCAL_NORMAL, which looks pretty consistent with #89 just a different way of doing it.

I also noticed there was a subtle change in how ::basePath works compared to public, so adding a change for that too.

If this is green and RTBC, then maybe we can quick fix it, but otherwise I think we should either roll back and recommit with the changes or open a (hopefully quick) follow-up given we're over 90 comments here.

Let's open a follow-up for image styles - we could potentially add it to assets:// if we can figure out how to transparently, or add image_styles://

cmlara’s picture

Images are assets, so

What I think we are discussing here is Drupal Language vs Plain English and its impact on the users. Its my understanding that anything created under one of the *AssetOptimizerInterface are "Assets", anything else is not. Perhaps including some wording along those lines would clear this up to avoid future confusion that Assets are only those files created by Asset Aggregation.

I think we should actually use the same as PublicStream and stop overriding the method, which is LOCAL_NORMAL, which looks pretty consistent with #89 just a different way of doing it.

Doesn't it being flagged LOCAL_NORMAL make it available in the Drupal FileSystem UI for every file field as a destination for files? It could even be configured as the Default storage location. Do we have enough verbiage in the display to discourage the use of the scheme for storing average files?

Note: This isn't a no vote, just want to make sure the consideration of the repercussions of the VISIBLE flag are considered as to what it means for most of the Drupal UI.

@Wim Leers
I know this helps s3fs significantly so I would hate to see a revert if we can't come to an agreement on visibility.

Are you closer to supporting the new D10 system with assets:// being present or being reverted? Is this perhaps really an issue that the VISIBLE key isn't the right configuration key to use in the CDN UI and maybe the right key just doesn't exist yet? Is there a future where we might have more streamWrappers that we want to use for core or contrib but don't want to be visible in the UI normally yet CDN might still need to modify them?

Edit: it’s assets://, plural.

catch’s picture

Status: Needs review » Fixed

#94 is a good point - there's no 'UI' constant, VISIBLE does double duty for 'accessible via web' and 'shown in the UI'. I don't think we want people configuring fields to point to this, so not sure what the best option is. Could CDN module check the class as a workaround? That wouldn't help when it's swapped out, but it would when it's just configured using the core class, and alternative implementations could change the visibility flags.

Split the base path issue to #3354090: Better default base path in assets stream wrapper. Given the visibility flag might need more discussion, going to split that out to and move this back to fixed. See you on #3354091: Asset stream wrapper may need to be selected in the UI for some cases/

catch’s picture

wim leers’s picture

#93: That works for me 👍

#94:

Its my understanding that anything created under one of the *AssetOptimizerInterface are "Assets", anything else is not.

Oh interesting, that's another reasonable way of looking at it, and that'd completely invalidate my reasoning of "CSS and JS assets are assets, but so are image derivatives".

Are you closer to supporting the new D10 system with assets:// being present or being reverted?

Yes, but I found a real regression blocking it 🫣 See #3354204: [regression] Since #1014086 generated CSS assets have absolute URLs without varying by url.site cache context.

Could CDN module check the class as a workaround? That wouldn't help when it's swapped out, but it would when it's just configured using the core class, and alternative implementations could change the visibility flags.

I can hardcode the exemption of assets:// despite it being VISIBLE. That's a pragmatic work-around for now. 👍 See you in #3354091: Asset stream wrapper may need to be selected in the UI for some cases!

Don't we also want a follow-up for image derivatives? I guess that needs its own stream wrapper based on #94's feedback? cross-posted with #96 — following!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

fabrondeau’s picture

Hello,
I would like to separate JS, CSS and PHP directories in the public files, into a "sites/default/files/assets" folder, for example.

I've used, in Drupal 10.4.5, $settings['file_assets_path'] but it does'nt works: no CSS/JS/PHP files are created, even after cache_rebuild.

Not sure that all the used "assets://css" or "assets://js" are found.
When I load a page, the css, js or oho folders aren't created.

How does $settings['file_assets_path'] is supposed to work please ?

Thank you by advance for an answer.

Regards

duaelfr’s picture