Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Incorrect paths for fonts #88

Open
nikrou opened this issue Jul 11, 2017 · 16 comments
Open

Incorrect paths for fonts #88

nikrou opened this issue Jul 11, 2017 · 16 comments

Comments

@nikrou
Copy link

nikrou commented Jul 11, 2017

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.

@weaverryan
Copy link
Member

Hey @nikrou!

Hmm. Could you post your webpack.config.js file? I'm specifically curious what your setPublicPath() looks like - things are done a little bit different under a subdirectory (http://symfony.com/doc/current/frontend/encore/faq.html#my-app-lives-under-a-subdirectory).

Cheers!

@nikrou
Copy link
Author

nikrou commented Jul 12, 2017

@weaverryan

my config file:

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

Encore
    .setOutputPath('./build/')
    .setPublicPath('/themes/default/build')
    .cleanupOutputBeforeBuild()
    .addEntry('js/index', './src/js/index.js')
    .addStyleEntry('css/style', './src/scss/style.scss')
    .enableSassLoader()
    .autoProvidejQuery()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
;

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

I used manifest.json:

{
  "themes/default/build/css/style.css": "/themes/default/build/css/style.css",
  "themes/default/build/fonts/fontawesome-webfont.eot?v=4.7.0": "/themes/default/build/fonts/fontawesome-webfont.eot",
  "themes/default/build/fonts/fontawesome-webfont.ttf?v=4.7.0": "/themes/default/build/fonts/fontawesome-webfont.ttf",
  "themes/default/build/fonts/fontawesome-webfont.woff2?v=4.7.0": "/themes/default/build/fonts/fontawesome-webfont.woff2",
  "themes/default/build/fonts/fontawesome-webfont.woff?v=4.7.0": "/themes/default/build/fonts/fontawesome-webfont.woff",
  "themes/default/build/images/fontawesome-webfont.svg?v=4.7.0": "/themes/default/build/images/fontawesome-webfont.svg",
  "themes/default/build/js/index.js": "/themes/default/build/js/index.js"
}

And I generate the link:

<link rel="stylesheet" href="/my-path/themes/default/build/css/style.css">

The main problem is that my style.css file contains absolute link to font awesome files:

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

I read documentation about setManifestPrefix method but it only changes manifest.json. It's not related with my issue.
I will generated a plain webpack config file without encore and diff with that one to help you understand, if you want.

@nikrou
Copy link
Author

nikrou commented Jul 12, 2017

I wish I could have something like that in my style.css file:

@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(./build/images/fontawesome-webfont.svg#fontawesomeregular) format("svg");
  font-weight: normal;
  font-style: normal;
}

That css part can be generated with the following file-loader config:

 {
	test: /\.(woff|woff2|ttf|eot|otf)$/,
	loader: 'file-loader',
	options: {
           name: 'fonts/[name].[ext]',
	   publicPath: '../'
	}
 }

But webpack-encore use something like that:

{
   test: /\.(woff|woff2|ttf|eot|otf)$/,
   loader: 'file-loader',
   options: { 
      name: 'fonts/[name].[ext]',
      publicPath: '/themes/default/build/' 
   }
}

I don't find a way to override webpack-encore default configuration for module rules.
I know the idea of webpack-encore is to use webpack without pain and it makes the job. What's a good tool. But it would be great if the configuration can be tweak.

@weaverryan
Copy link
Member

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!

@nikrou
Copy link
Author

nikrou commented Jul 13, 2017

@weaverryan
Thanks for your solution. But it can't work for me. I don't know the extra path from my point of view. It's an extra path coming from urls. I develop a web application. The extra is usually the application name on shared hosting. But people who have access as admin can install my application at root without extra path.
I can do the job for stylesheets and added in html view the extra path if needed. But fonts must be relative to stylesheets.

My idea is to be able to override rules in method() buildRulesConfig from lib/config-generator.js. The array rules is hard coded and can not be override.

@weaverryan
Copy link
Member

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: webpack-contrib/extract-text-webpack-plugin#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 :).

@nikrou
Copy link
Author

nikrou commented Jul 13, 2017

Thanks @weaverryan. It's a bit of hack. But instead of something more decent...
I try something too : https://github.com/symfony/webpack-encore/compare/master...nikrou:issue-88?expand=1
I can make that pull request and improve it if you think it's interesting.

@weaverryan
Copy link
Member

A PR would be cool :). I don't know yet if we should do this approach, or something more "generic" (e.g. a "hook" to edit the config for any rule), but a PR would let us review. And I'm still curious if there's a way to always make the paths relative - that'll take more research.

For the PR - make sure that the fonts and images public paths default to the normal publicPath if they're not explicitly set.

@nikrou
Copy link
Author

nikrou commented Jul 14, 2017

@weaverryan
Ok let's go for a PR. I will add tests to check publicPath.
Done !

@andyexeter
Copy link
Contributor

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

@Lyrkan
Copy link
Collaborator

Lyrkan commented Oct 11, 2017

Wouldn't using the useRelativePath option of file-loader be enough to solve this issue ?

@andyexeter
Copy link
Contributor

andyexeter commented Oct 30, 2017

useRelativePath could fix the issue for assets referenced in code but it won't fix the issue with the generated manifest.json file.

I read the docs at http://symfony.com/doc/current/frontend/encore/faq.html#my-app-lives-under-a-subdirectory and it suggests using setManifestPrefix and passing setPublicPath the true public path including subdirectory. The problem with this approach is that the config is then coupled to the environment.

In our project, /myAppSubdir/build is the public path in the development environment but in the production environment the public path is just /build because it's not hosted under a subdirectory in that environment.

Is there any way around this? The only way I can think of is to use an environment variable and set the public path to the value of it, but the problem with that is we'd need to set an environment variable for every one of our projects. Setting the public path conditionally based on Encore.isProduction() won't work because the project also lives under a subdirectory in the staging environment under which Encore.isProduction() is true.

@weaverryan I'm aware that although related, this situation is not exactly on-topic with the original issue, let me know if you want me to open a new one.

@andyexeter
Copy link
Contributor

andyexeter commented Oct 30, 2017

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:

if (publicPath.includes('://') === false && publicPath.indexOf('/') !== 0) {
// technically, not starting with "/" is legal, but not
// what you want in most cases. Let's not let the user make
// a mistake (and we can always change this later).
throw new Error('The value passed to setPublicPath() must start with "/" or be a full URL (http://...)');
}

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.

@vctls
Copy link

vctls commented Sep 5, 2018

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.

@julkwel
Copy link

julkwel commented Nov 23, 2019

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.

this solve my issues too , thanks @weaverryan

@athos99
Copy link

athos99 commented Dec 2, 2020

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()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants