Absolute / domain-relative path for asset URLs in CSS

The frontend styles are added as editor styles so Gutenberg can wrap and preview them:

namespace App;
use function Roots\asset;
add_action('after_setup_theme', function () {
    // Add frontend styles as editor styles

This works very well, all styles appear correctly in the editor (except some exceptions because of the extra editor DOM, etc).

However, the asset url(...)s inside the CSS are still relative to their stylesheet (e.g. url(images/water-sand.9a11c3.jpg). This is completely valid and works perfectly fine in frontend. But the Gutenberg editor postprocesses those styles, wrapping their selectors (notably the body element selector) in an extra wrapper class selector (.editor-styles-wrapper) and then injects those styles as inline styles.
By having those styles as inline styles, the references relative to the originating stylesheet won’t work anymore.

Now this probably be the job of the Gutenberg style postprocessing, but until this is fixed, would it be possible to let bud output those asset url(...)s relative to the site domain or as absolute URLs?
Then those URLs will still work, even in these inlined styles.

I think the way to do this is modifying the rules handling static assets.

bud doesn’t use url-loader or anything like that; all of the static assets handlers are defined using Rule.generator.

naive implementation:

["image", "font", "svg", "json"].map((key) => {
  app.build.rules[key].setGenerator(() => ({
    publicPath: "http://example.test/app/themes/sage/public/",

You might also need/want to set outputPath if you are doing this:

1 Like

Thanks, this works!

Gutenberg should actually rewrite those editor styles, but it clearly doesn’t happen, the editor styles are wrapped in an extra selector (.editor-styles-wrapper), but the url(...)s are still stylesheet-relative, and fail to load.
Related issue:

Alright, the underlying issue is the fact that currently remotely added editor styles (fetched by URI) don’t get a baseURL field when being used in the Gutenberg Block Editor. The missing baseURL field prevents the Gutenberg editor post-processing from rewriting those stylesheet-relative URLs, resulting in broken styles.

Relevant WordPress trac ticket:

The editor styles can be added as files by path relative to theme directory, so the styles are not remotely fetched and the baseURL field is added:

namespace App;
use function Roots\asset;
use Webmozart\PathUtil\Path; // library is used
add_action('after_setup_theme', function () {
    // Add frontend styles as editor styles
    // Must be added by relative path (not remote URI)
    // (@see https://core.trac.wordpress.org/ticket/55728#ticket)
    $absCssPath = asset('app.css')->path();
    $absTemplatePath = get_template_directory();
    // path to editor styles file must be relative to the theme directory
    $relCssPath = Path::makeRelative($absCssPath, $absTemplatePath);

1 Like

With latest roots/acorn release version 2.1.1 the assets now have a relativePath() function, which simplifies this code a lot:

namespace App;

use function Roots\asset;

add_action('after_setup_theme', function () {
    // Add frontend styles as editor styles
    // Must be added by relative path (not remote URI)
    // (@see https://core.trac.wordpress.org/ticket/55728#ticket)
    $relCssPath = asset('app.css')->relativePath(get_theme_file_path());

Update: I copied the wrong snippet here, this is for adding styles to the entire WP admin, not just Gutenberg. Please see my comment below Absolute / domain-relative path for asset URLs in CSS - #8 by tedw

FWIW I solved this issue by adding my styles like this:

 * Add custom admin styles
add_action('admin_enqueue_scripts', function($hook) {
    // Only add custom admin styles on certain views to prevent
    // conflicts with 3rd-party plugin styles (e.g. WS Form)
    // https://wordpress.stackexchange.com/a/22954/185703
    if ($hook == 'post-new.php' || $hook == 'post.php' || $hook == 'edit.php') {
        // Note: enqueueCss() only works of there’s also a JS file in the entry point
        // https://github.com/roots/acorn/issues/229
        // https://github.com/roots/bud/issues/1416
        // https://github.com/roots/acorn/blob/2bf3d1c8c79a23b34f5c10c52df7926c83ab9e46/src/Roots/Acorn/Assets/Concerns/Enqueuable.php

@tedw: Though this would not enqueue the frontend styles as editor styles, but the separate admin styles.
This is also a fine approach, though I prefer using the frontend styles also as editor styles.

@strarsis Apologies, I accidentally copied the wrong snippet! :man_facepalming:

This is the code I meant to copy:

 * Add custom Gutenberg editor CSS and JS assets
 * https://developer.wordpress.org/block-editor/tutorials/javascript/extending-the-block-editor/
 * https://soderlind.no/hide-block-styles-in-gutenberg/
add_action('enqueue_block_editor_assets', function() {
    // NOTE: This won’t apply CSS to block pattern preview iframes
    //       when “bud dev” is running since the CSS is injected,
    //       but it will work in production or after running “bud build”
}, 100);

Hm, but this would not add the styles as editor styles. The style isolation must be realized using .editor-styles-wrapper or a PostCSS plugin.
In contrast, when adding the styles as editor styles, the editor (Gutenberg) has to do the isolation by itself (wrapping the selectors with a styles wrapper selector).

1 Like

Chiming in on this – the add_editor_style() function doesn’t work unless you add theme support for editor-styles. Wasted a few hours figuring that out :man_facepalming:.

add_theme_support( ‘editor-styles’ );

1 Like

Hey guys, I got the same problem but I’m not using Sage, Can someone maybe help me figure out how to fix this in a block theme?

@ElvarP: Well, do you have editor-styles theme support enabled (add_theme_support( ‘editor-styles’ );) as @AlexHay pointed out?

Instead of using Sage specific asset functions, you would handle the path yourself, as:

add_action('after_setup_theme', function () {

Where relatives paths are resolved relative to the theme root directory.
Note: While specifying an URL instead of a path is possible, it currently causes issues with relative paths inside styles being post-processed by Gutenberg.

Adding the frontend-styles as-is, the Gutenberg editor will handle the style isolation automatically by post-processing the styles, but in the near future an iframe will be used instead.

Oh thanks for explaining, that was my problem. I was including the editor styles with specifying an URL so the style paths were wrong. Thanks for the fix!

I have a problem that might be related to this, but not quite the same i think.
I have a multisitesetup and when i’m building bud for production i get a cors-error since assets (images/fonts) are pointing to base domain.
For example:
multisite base: multisite.com
subdomainsite: site1.multisite.com
when resolving fonts on site1.multisite.com i get an CORS error since it tries to resolve the asset on multisite.com. I’m not really sure if this is an error in bud, wp or server setup but in wp_enqueue_scipts this function: bundle('app')->enqueue() calls

    public function enqueueCss(string $media = 'all', array $dependencies = [])
        $this->css(function ($handle, $src) use (&$dependencies, $media) {
            wp_enqueue_style($handle, $src, $dependencies, null, $media);
            $this->mergeDependencies($dependencies, [$handle]);

        return $this;

and $src there is https://multisite.com, but i want it to be https://site1.multisite.com any suggestions?

Any suggestions for this issue?
I’m thinking that one way to solve this might be to do a replace in $src from https:// to https://{siteurl} but in that case i think i would need do a proper wp_enqueue_style instead of bundle('app')->enqueue() but how do i access the handle from bundle?

You can use a WordPress filter for changing the src of enqueued stylesheets:

Of course, it would be better to find the underlying issue for why the hostname differs from what should be used.

Thanks! i investigated wp_enqueue_style but couldn’t find a good hook. I’ll check that one out and try if i can get around the problem.

its working!

add_filter('style_loader_src', function($src) {
    $mainHome = get_option('home');
    $home = get_option('home');
    return str_replace($mainHome, $home, $src);

dont like that switch_to_blog thing though, so might add “mainHome” to new site upon site creation or something so i can fetch $mainHome by doing get_option(‘mainHome’) on current site or something.

Thanks for pointing me in the right direction!