angular / angular-cli

CLI tool for Angular
https://cli.angular.io
MIT License
26.77k stars 11.97k forks source link

Webpack sometimes does not properly resolve `url()` resources from package deep imports #24470

Open llRandom opened 1 year ago

llRandom commented 1 year ago

Command

build

Is this a regression?

The previous version in which this bug was not present was

No response

Description

I'm getting an error for fontawesome & icomoon references when switching to esbuild. It works fine for default angular build

Minimal Reproduction

  1. Install font-awesome@4.7
  2. Reference it from styles as @import 'font-awesome/scss/font-awesome';

Exception or Error

X [ERROR] [plugin angular-css-resource] Could not resolve "fonts/icomoon.eot?p2jara"

    src/styles.scss:10:11:
      10 │   src: url("fonts/icomoon.eot?p2jara");
         ╵            ~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can mark the path "fonts/icomoon.eot?p2jara" as external to exclude it from the bundle, which will remove this error.

X [ERROR] [plugin angular-css-resource] Could not resolve "fonts/icomoon.eot?p2jara#iefix"

    src/styles.scss:11:11:
      11 │   src: url("fonts/icomoon.eot?p2jara#iefix") format("embedded-open...
         ╵            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can mark the path "fonts/icomoon.eot?p2jara#iefix" as external to exclude it from the bundle, which will remove this error.

X [ERROR] [plugin angular-css-resource] Could not resolve "fonts/icomoon.ttf?p2jara"

    src/styles.scss:11:78:
      11 │ ...ed-opentype"), url("fonts/icomoon.ttf?p2jara") format("truetype...
         ╵                       ~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can mark the path "fonts/icomoon.ttf?p2jara" as external to exclude it from the bundle, which will remove this error.

X [ERROR] [plugin angular-css-resource] Could not resolve "fonts/icomoon.woff?p2jara"

    src/styles.scss:11:130:
      11 │ ...("truetype"), url("fonts/icomoon.woff?p2jara") format("woff"), ...
         ╵                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can mark the path "fonts/icomoon.woff?p2jara" as external to exclude it from the bundle, which will remove this error.

X [ERROR] [plugin angular-css-resource] Could not resolve "fonts/icomoon.svg?p2jara#icomoon"

    src/styles.scss:11:179:
      11 │ ...at("woff"), url("fonts/icomoon.svg?p2jara#icomoon") format("svg");
         ╵                    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  You can mark the path "fonts/icomoon.svg?p2jara#icomoon" as external to exclude it from the bundle, which will remove this error.

X [ERROR] [plugin angular-css-resource] Could not resolve "../fonts/fontawesome-webfont.eot?v=4.7.0"

    src/styles.scss:11383:11:
      11383 │   src: url("../fonts/fontawesome-webfont.eot?v=4.7.0");
            ╵            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

X [ERROR] [plugin angular-css-resource] Could not resolve "../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0"

    src/styles.scss:11384:11:
      11384 │   src: url("../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0") f...
            ╵            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

X [ERROR] [plugin angular-css-resource] Could not resolve "../fonts/fontawesome-webfont.woff2?v=4.7.0"

    src/styles.scss:11384:95:
      11384 │ ...pe"), url("../fonts/fontawesome-webfont.woff2?v=4.7.0") form...
            ╵              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

X [ERROR] [plugin angular-css-resource] Could not resolve "../fonts/fontawesome-webfont.woff?v=4.7.0"

    src/styles.scss:11384:162:
      11384 │ ...f2"), url("../fonts/fontawesome-webfont.woff?v=4.7.0") forma...
            ╵              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

X [ERROR] [plugin angular-css-resource] Could not resolve "../fonts/fontawesome-webfont.ttf?v=4.7.0"

    src/styles.scss:11384:227:
      11384 │ ...ff"), url("../fonts/fontawesome-webfont.ttf?v=4.7.0") format...
            ╵              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

X [ERROR] [plugin angular-css-resource] Could not resolve "../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular"

    src/styles.scss:11384:295:
      11384 │ ...pe"), url("../fonts/fontawesome-webfont.svg?v=4.7.0#fontawes...

Your Environment

Angular CLI: 15.0.4
Node: 16.13.2
Package Manager: npm 8.1.2
OS: win32 x64

Angular: 15.0.4
... animations, cli, common, compiler, compiler-cli, core, forms
... localize, platform-browser, platform-browser-dynamic, router
... service-worker

Package                         Version
---------------------------------------------------------
@angular-devkit/architect       0.1500.4
@angular-devkit/build-angular   15.0.4
@angular-devkit/core            15.0.4
@angular-devkit/schematics      15.0.4
@angular/cdk                    15.0.3
@schematics/angular             15.0.4
rxjs                            7.8.0
typescript                      4.8.4

Anything else relevant?

No response

clydin commented 1 year ago

Either the font-awesome package root import or the package's Sass variable $fa-font-path will need to be set to allow the fonts to be found. Previously the Sass deep import worked on its own due to Webpack specific behavior. The first option also has the advantage of not requiring the font-awesome Sass files to be processed on every application build.

Package root example:

@import 'font-awesome';

Variable example:

$fa-font-path: 'font-awesome/fonts';
@import 'font-awesome/scss/font-awesome';
llRandom commented 1 year ago

Thanks! It works for esbuild but it breaks regular build. Is it ok?

clydin commented 1 year ago

The first option should work in both (@import 'font-awesome';). The later should as well but currently doesn't for the default builder. A fix for that will require some additional investigation.

AStoker commented 1 year ago

Noticed the tags of this removed the browser-esbuild and changed to just the browser. Pretty sure this error is just with the browser-esbuild option though. The regular browser setup uses webpack and pulls in the resolve-url-loader which handles the hoisting of files and rewriting of urls. I've yet to discover (still looking) if the esbuild option has the equivalent plugin used, but if it's just using the basic sass compilation, that would be the problem here.

clydin commented 1 year ago

The defect here is with the Webpack-based build system. The following should work with the Webpack-based build system but does not:

$fa-font-path: 'font-awesome/fonts';
@import 'font-awesome/scss/font-awesome';

The Webpack-based build system also fails with just @import url(font-awesome/scss/font-awesome.scss);. Both of these work with the esbuild-based build system though.

However, the first option in the comment above (just @import 'font-awesome';) does work in both build systems and is the preferred option. It avoids the need to deep import into a package as well as manipulate Sass variables.

AStoker commented 1 year ago

I'm wondering if there's just a typo there, as with webpack builds what you're describing works (with a little tweak). For example, this works perfectly fine with the webpack builds (in fact, this is exactly what our production setup is doing)

@import '@fortawesome/fontawesome-pro/scss/fontawesome';

This is because the webpack builds when it can't find a relative file goes up directories looking for node_modules and trying to match, which it eventually will (assuming you have it installed) at the root level's node_modules directory.

Now, the reason why your @import url(...); isn't working is because Sass translates that as a plain css @import, not a sass one (https://sass-lang.com/documentation/at-rules/import#plain-css-imports), and therefore likely isn't trying to do any node_module searching.

I'm not sure where it's documented that @import 'font-awesome' is the preferred option, nor am I sure how that would feasibly work. When dealing with JS files, that works fine because the package.json has a main entry that describes the main entry point. However with Sass files, no such entry exists, so there's no way for the system to infer what the desired file is when you simply try to import based off the package name.

clydin commented 1 year ago

I'm not sure where it's documented that @import 'font-awesome' is the preferred option, nor am I sure how that would feasibly work. When dealing with JS files, that works fine because the package.json has a main entry that describes the main entry point. However with Sass files, no such entry exists, so there's no way for the system to infer what the desired file is when you simply try to import based off the package name.

There are several entry field values as well as exports conditions that are available for use with stylesheets. Both style and sass are available for use in both cases for build systems as well as other bundlers such as Vite. The Angular Material package has multiple in the exports condition form: https://github.com/angular/components/blob/7c48a36b5d153af55f42349bb3a80a3d950570e6/src/material/package-base.json

AStoker commented 1 year ago

Ah, thanks for clarifying the exports entry, forgot about that. However, with that being said, font-awesome doesn't have an exports entry in their package json. Nor can we always assume that a library would have an exports entry. Bringing this back full circle, we return to the difference between how the esbuild is working and how the historical browser build works. The browser build appropriately uses the resolve-url-loader to change urls in scss files, and this happens inside the webpack build. However, the esbuild (not webpack) does not make use of a library to handle deeply nested urls and how to resolve them (hence why I suggested that the esbuild tag be returned to this issue rather than removed, since it works fine with the regular browser build)

clydin commented 1 year ago

font-awesome does have a style entry field which is why it works as a bare specifier: https://github.com/FortAwesome/Font-Awesome/blob/afecf2af5d897b763e5e8e28d46aad2f710ccad6/js-packages/%40fortawesome/fontawesome-free/package.json#L53

AStoker commented 1 year ago

My oversight, I was looking for exports 😄 Regardless, the issue here is about handling urls, and isn't a webpack issue but rather an esbuild issue.

clydin commented 1 year ago

It is easy to miss a top-level field in a package.json. The newer exports field does make it much more convenient to discover what's available from within package.

mario-d-s commented 1 year ago

What does one have to do if one wants to use the solid variant of the FA font? That doesn't seem possible when using the bare specifier?

@clydin politely pinging you because the suggestion to use the package root import was yours originally and it does seem promising.

llRandom commented 11 months ago

is there a solution that would work for both esbuild and webpack? esbuild is now default app builder but karma build is still webpack-based and both reference styles so either one or the other would fail with suggested workaround

yasharsanaei commented 9 months ago

Im stuck at this stage. I don't know what to do with my setup. Any solution?