Webpack via Embroider: importing through CSS vs. JS

Hey,

I’m trying to load a font through a 3rd party package called @fontsource/inter in an “embroidered” app. That means Webpack is used to pull in the required assets.

Here is the piece that I added to the Webpack configuration:

webpackConfig: { 
  module: {
    rules: [
      {
        test: /\.(woff|woff2)$/,
        type: 'asset/resource',
        generator: {
          filename: 'fonts/[name].[hash][ext][query]',
        },
      },
    ],
  },
}

Here is the relevant CSS file which defines the font faces that could then be used:

/* node_modules/@fontsource/inter/variable.css */
@font-face {
  font-family: 'InterVariable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url('./files/inter-cyrillic-variable-wghtOnly-normal.woff2') format('woff2');
  unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}  

Now, I think there are two ways to import this file (and, with that import, let Webpack know that asset is needed).

  1. Using a JS import

If I add the following line to app/app.js, then everything works wonderfully:

// app/app.js
import '@fontsource/inter/variable.css';
  1. Using a CSS import

The other way would be to import the CSS file in a CSS file via @import:

/* app/styles/app.css */
@import "@fontsource/inter/variable.css";

However, this approach doesn’t work.

There are two differences in the output.

First, with the the CSS import Webpack copies the contents of the imported file, node_modules/@fontsource/inter/variable.css verbatim to the output css.

Since the font-face definition contains a relative url, the browser tries to fetch the font from http://localhost:4200/assets/files/inter-latin-variable-wghtOnly-normal.woff2 as URLs seem to be relative to /assets. It fails, as the font file is not copied to that location.

The other difference is that the Webpack config rule I added for the woff fonts seems to have only been kicked off with the JavaScript fonts. The font files are added to dist/assets/fontsin the output and are referred in dist/chunk-md5-here.js.

As it seems to me importing a CSS file is more intuitive via CSS, I’d like to understand whether this should work – and whether something could be done about it.

Thank you!

Does it help if you do this?

-@import "@fontsource/inter/variable.css";
+@import "~@fontsource/inter/variable.css";

The place to investigate further is the css-loader documentation & options:

Our default settings are here:

And you can override them by passing cssLoaderOptions to @embroider/webpack:

compatBuild(app, Webpack, {
  packagerOptions: {
    cssLoaderOptions: {
      // customize css-loader here
    }
  }
});

By default css-loader supports a ~ syntax to say that an @import refers to an NPM package. In my opinion that’s a weird default choice and we should instead be striving to make a path passed to CSS @import mean the same thing as a path passed to JS import. I’d be happy to change the embroider defaults to make this behave more consistently.

1 Like

Hey Ed,

Thank you for your help.

When I change the import to @import "~@fontsource/inter/variable.css";, I get the following error:

Build Error (PostcssCompiler)

Failed to find '~@fontsource/inter/variable.css'
  in [
    <app-root>/app/styles
  ]

I got the same error when following the docs to the tee (T?) and wrote the import as @import url("~@fontsource/inter/variable.css").

It seems like webpack doesn’t realize it’s an npm package and considers it a relative import.

With my limited knowledge of Embroider, this would make sense to me also because if the builder can be swapped out (so that Ember devs will be able to use Vite, Snowpack, esbuild, etc. with Embroider) than the less builder specific config we have, the better.