Absolute / domain-relative path for asset URLs in CSS

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

<?php
namespace App;
use function Roots\asset;
add_action('after_setup_theme', function () {
    // Add frontend styles as editor styles
    add_editor_style(asset('app.css')->uri());
});

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:
https://core.trac.wordpress.org/ticket/55728#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:

<?php
namespace App;
use function Roots\asset;
use Webmozart\PathUtil\Path; // library is used
add_action('after_setup_theme', function () {
    add_theme_support('editor-styles');
    // 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);
    add_editor_style($relCssPath);
});

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());
    add_editor_style($relCssPath);
});
5 Likes

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
        bundle('admin')->enqueueCss();
    }
});

@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”
    bundle('app')->enqueue();
}, 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).