webpack-encore: Incorrect paths for fonts

My context: I host my webiste under a given path. I do not use symfony but we don’t care here. I used (of course) webpack-encore to manage my assets. It works well except for fonts because of my extra path.

My website is hosted under /my-path. I generate my stylesheets using sass. Stylesheets paths are composed of my base path and the path found in manifest.json. I’ve got for example /my-path/build/css/style.css I use font-awesome to add web-fonts. The url() in the generated stylesheets is url(/build/fonts/fontawesome-webfont.eot); and so one. I don’t know (just start to investigate) how to fix it. I usually used webpack (without webpack-encore) and webpack used a relative path to fonts from stylesheets instead of an absolute path; something like:

@font-face {
  font-family: 'FontAwesome';
  src: url(./fonts/fontawesome-webfont.eot);
  src: url(./fonts/fontawesome-webfont.eot?#iefix&v=4.7.0) format("embedded-opentype"), url(./fonts/fontawesome-webfont.woff2) format("woff2"), url(./fonts/fontawesome-webfont.woff) format("woff"), url(./fonts/fontawesome-webfont.ttf) format("truetype"), url(./images/fontawesome-webfont.svg#fontawesomeregular) format("svg");
  font-weight: normal;
  font-style: normal;
}

And I’ve got

@font-face {
  font-family: 'FontAwesome';
  src: url(/my/public/conf/path/build/fonts/fontawesome-webfont.eot);
  src: url(/my/public/conf/path/build/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0) format("embedded-opentype"), url(/my/public/conf/path/build/fonts/fontawesome-webfont.woff2) format("woff2"), url(/my/public/conf/path/build/fonts/fontawesome-webfont.woff) format("woff"), url(/my/public/conf/path/build/fonts/fontawesome-webfont.ttf) format("truetype"), url(/my/public/conf/path/build/images/fontawesome-webfont.svg#fontawesomeregular) format("svg");
  font-weight: normal;
  font-style: normal;
}

Any idea to solve that issue is appreciate. If you have any question, don’t hesitate.

About this issue

  • Original URL
  • State: open
  • Created 7 years ago
  • Comments: 16 (7 by maintainers)

Most upvoted comments

Thanks for the details!

Try this:

var Encore = require('@symfony/webpack-encore');

Encore
    .setOutputPath('./build/')
    .setPublicPath('/my-path/themes/default/build')
    .setManifestKeyPrefix('themes/default/build')

    // ... everything else the same
;

// export the final configuration
module.exports = Encore.getWebpackConfig();

The issue is that your setPublicPath isn’t correct. Because you’re under a sub-directory, your true public path is /my-path/themes/default/build. You need to tell webpack your actual, true public path because (in some cases - specifically something called code splitting) - webpack needs to be able to make AJAX requests back for assets (and it uses the publicPath to know where those live).

This should fix your problem - it will dump something like this:

@font-face {
  font-family: 'FontAwesome';
  src: url(/my-path/themes/default/build/fonts/fontawesome-webfont.eot);
  src: url(/my-path/themes/default/build/fonts/fontawesome-webfont.eot?#iefix&v=4.7.0) format("embedded-opentype"), url(/my-path/themes/default/build/fonts/fontawesome-webfont.woff2) format("woff2"), url(/my-path/themes/default/build/fonts/fontawesome-webfont.woff) format("woff"), url(/my-path/themes/default/build/fonts/fontawesome-webfont.ttf) format("truetype"), url(/my-path/themes/default/build/images/fontawesome-webfont.svg#fontawesomeregular) format("svg");
  font-weight: normal;
  font-style: normal;
}

It shouldn’t matter to us whether these paths are absolute or relative - so I wouldn’t worry about that detail. Webpack just makes sure that these paths are correct - as long as you give it the correct public path 😃.

Let me know if this fixes things!

Sorry for the noise on this one but I just figured out a solution to the problem I described above using Symfony’s Json manifest versioning strategy.

After retrieving the versioned path from the manifest, Symfony’s PathPackage checks if it is an absolute URL or it starts with ‘/’ and if true returns the value as is. If false, the method prefixes the correct base path to the path and returns that instead: https://github.com/symfony/symfony/blob/d8ee14f9a075f8a45c261e810c4caa441ae72ca3/src/Symfony/Component/Asset/PathPackage.php#L60-L67

In practical terms, say for example we have a Symfony app hosted under /var/www/my-app which we access via http://local.domain/my-app/web and we have the following config in our webpack.config.js:

Encore
    .setOutputPath('web/build')
    .setPublicPath('/build')

We run yarn run encore dev which generates our manifest file with an entry like so:

{
    "build/css/style.css": "/build/css/style.css"
}

In a twig template, a call to asset('build/css/style.css') will output /build/css/style.css because it starts with a ‘/’ but this actually requests http://local.domain/build/css/style.css which does not exist.

Removing the ‘/’ from the beginning of the publicPath forces Symfony to add the correct base path so our call to asset('build/css/style.css') now outputs /my-app/build/css/style.css which is the correct path.

Passing a non-absolute or non ‘/’ prefixed value to setPublicPath currently throws an error: https://github.com/symfony/webpack-encore/blob/799720602aab2069f827704e4a6d672d03d38d76/lib/WebpackConfig.js#L122-L127

To get around this I’m using the configureManifestPlugin method like so:

    .configureManifestPlugin(function (options) {
        if(options.publicPath.indexOf('/') === 0) {
            options.publicPath = options.publicPath.substr(1);
        }

        return options;
    })

This only fixes the issue when using Symfony and its manifest based versioning strategy so I’m not sure what the implications of removing the thrown error in setPublicPath would be to other non-Symfony apps.

Any movement on this? We run multiple Symfony installs under sub-directories e.g:

http://example.org/site-1/web/app_dev.php http://example.org/site-2/web/app_dev.php

We’re finding it hard to use Encore with this set up for the reasons discussed above. I added the following to my webpack.config.js:

webpackConfig.module.rules[2].options.publicPath = '../';
webpackConfig.module.rules[3].options.publicPath = '../';

This fixes the issue with paths referenced in code but the manifest.json still starts each file with /assets, so it tries to load the non-existent URL http://example.org/assets/asset.png instead of http://example.org/site-1/web/assets/asset.png

Maybe a solution to be independant of the URL of you site and if your application is a subdirectoy. I can run the symfony 5.x demo with webpack ( the awefont are loaded)

In your weebpack.config.js file replace

var Encore = require('@symfony/webpack-encore');

Encore
    .setOutputPath('public/build/')
    .setPublicPath('/build')
    .cleanupOutputBeforeBuild()
    .autoProvidejQuery()
....


By:

var Encore = require('@symfony/webpack-encore');


Encore
    .setOutputPath('public/build/')
    .setPublicPath('build')
    .configureLoaderRule('fonts', loaderRule => {
        loaderRule.options.publicPath = './';
    })
    .cleanupOutputBeforeBuild()
    .autoProvidejQuery()

I tried setting the output path to public/build and the public path to build. It does fix the manifest.json, but then the urls of the fonts referenced in my css begin with build/fonts/... which resolves to http://mysite.local/build/build/fonts/.... Is there some way to force the font urls to font/... ?

Edit: I tried using useRelativePath with no luck, but adding config.module.rules[3].options.publicPath = './' works for now, in all environments, thanks Ryan.

Hmm, interesting…

I wonder if we could update Encore to use relative paths internally. I mean, logically, it makes sense to output a CSS file with a relative path to the fonts - webpack knows where both are placed. Here’s an issue that’s somewhat related: https://github.com/webpack-contrib/extract-text-webpack-plugin/issues/27

I would need to do some playing…

Until then, the hack would be to change this key manually. It would look something like this:

// webpack.config.js
// ...

var config = Encore.getWebpackConfig();
config.module.rules[3].options.publicPath = '../'

module.exports = config;

The 3 key should be the key to this loader. But, as this is a bit of a hack, that could change in future releases. Longer term, this should get a more proper solution - i.e. using relative paths (if possible) or adding a decent hook 😃.