Fontsource fonts imports via Bud - path can't be resolved during yarn build

Hi folks,

We are in the process of a Sage 9 to 10 upgrade. We use Fontsource fonts (self-hosted Google fonts), installed via NPM/Yarn but am struggling to get them to work.

Previously on Sage 9, they were included as follows:

  1. installed via NPM
  2. imported specific styles & weights in theme CSS file
@import "@fontsource/exo-2/300.css";
@import "@fontsource/exo-2/300-italic.css";
@import "@fontsource/exo-2/400.css";
// etc...
  1. Within each of these CSS files (inside node_modules) are a series of .woff imports, eg
@font-face {
  font-family: 'Exo 2';
  font-style: normal;
  font-display: swap;
  font-weight: 300;
  src: url('./files/exo-2-cyrillic-ext-300-normal.woff2') format('woff2'), url('./files/exo-2-all-300-normal.woff') format('woff');
  unicode-range: U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F;
}

When I tried to compile, the path to the .woff files couldn’t be resolved so I had to create an entry in my webpack.mix.js file to copy the node_modules/@fontsource/exo-2/files/*.woff files from node_modules to the dist directory, eg:

mix.copyDirectory('node_modules/@fontsource/exo-2/files', 'dist/styles/files');
  1. I also had to add processCssUrls: false as a Mix option for the URLs to resolve:
mix
  .autoload({ jquery: ["$", "window.$", "window.jQuery", "jQuery", "jquery"] })
  .options({ processCssUrls: false })

In Sage 10 with Bud 6.7.3, I’ve done the equivalent of steps 1-3 above. The following in bud.config.js handles the file copy

await app.fs.copy("node_modules/@fontsource/exo-2/files", "public/css/frontend/files")

I’m seeing the .woff files in public/css/{entryPointName}/files

But I’m getting

Can't resolve './files/exo-2-all-300-normal.woff' in './resources/styles/frontend'

ie it’s trying to get the font files from the theme’s resources directory rather than public.

I haven’t done anything equivalent to the Mix option of processCssUrls and can’t find anything in the Bud docs. I was wondering if anyone knew how to do this? (or a better way, generally speaking, for including @fontsource fonts!)

Thanks in advance

IMHO the WordPress Web Fonts API (theme.json) should be used for this:

1 Like

Agreed. I have a template where the fonts get written into the css file, due to some requirements.
It’s quite a simple process - if you are looking for that, I could share the code with you.

1 Like

I’d love to take a look @frizzant - thank you :slight_smile:

@strarsis I’m not familiar with the WP Web Fonts API. We’re not using FSE/block editor - does that matter?

The theme.json is a new, declarative way for configuring a theme.
Most of its settings are block- and FSE-related, but not all of them and it also works with non-block and -FSE themes:

The WordPress Web Fonts API can also be used without a theme.json. It is a new core feature for declaring fonts in WordPress.

1 Like

Sage@9 used sass and resolve-url-loader. I’m not sure, but you might have more luck getting this to play nicely by installing the sass extension.

I say that because it doesn’t look like fontsource plays all that nicely with postcss out-of-the-box. See this stalled issue:

There is a relatively simple way to get you unblocked, regardless. Maybe not perfect, but it works. Happy to make revisions on our end if they are sensible and don’t complicate things for the majority of users.

Anyway, almost all of the (non sass) guides on the fontsource website involve doing the font imports with javascript, so that’s how we’ll do it. Any CSS imported like this will be bundled in the app.css file – it isn’t applied with JS or anything wacky like that.

app.js

import '@fontsource/exo-2/300.css';
import '@fontsource/exo-2/300-italic.css';
import '@fontsource/exo-2/400.css';

// app code

import.meta.webpackHot?.accept(console.error);

This is close but font URLs that are too big for inlining still reference the node_modules path.

I think the copying approach is a great idea, but for me it’s easier to reason about all this if we just copy the files directly before webpack is even invoked.

bud.fs has a pretty nice copy utility (from fs-jetpack) we can use:

bud.config.js

export default async (app) => {
  await app.fs.copy(
    app.path(`@modules/@fontsource/exo-2`),
    app.path(`@src/fonts/vendor/@fontsource/exo-2`),
    {overwrite: true},
  )

  app.alias({
    '@fontsource/exo-2': app.path(`@src/fonts/vendor/@fontsource/exo-2`),
  })

  // ...config

Last thing is to alias @fontsource/exo-2 to our copy target. Now when webpack finds that package reference it will source the modules from our vendored files.

And now we should be all set:

2 Likes

Thanks @strarsis

@kellymears thanks for being so generous with your time to provide that advice. I tried out your suggestions, and it’s definitely promising but something interesting is happening.

My bud.config.js contains your code exactly.

When I yarn build, the build is successful and the fonts do indeed get copied from node_modules/@fontsource/exo-2 to app/themes/my-theme/resources/fonts/vendor/@fontsource/exo-2. These in turn get copied to app/themes/my-theme/public/fonts/vendor/@fontsource/exo-2.

The font files are inside a subdirectory called files inside exo-2

The Fontsource CSS is imported via JS and is, as you asserted, included in the app.css file (or in my case frontend.css).

However, the fonts aren’t rendering on the frontend so I did a bit of digging…

In the compiled stylesheet, the font includes look like this:

@font-face {
    font-display: swap;
    font-family: Exo\ 2;
    font-style: normal;
    font-weight: 300;
    src: url(/wp-content/themes/my-theme/public/fonts/vendor/@fontsource/exo-2/files/exo-2-cyrillic-ext-300-normal.d146ff.woff2) format("woff2"),url(/wp-content/themes/my-theme/public/fonts/vendor/@fontsource/exo-2/files/exo-2-all-300-normal.029bb3.woff) format("woff");
    unicode-range: u+0460-052f,u+1c80-1c88,u+20b4,u+2de0-2dff,u+a640-a69f,u+fe2e-fe2f
}

The URL is using wp-content as the content directory. I’m using Bedrock, so it should be app. From what I can see, most things use relative paths - including manifest.json - so I’m not sure where this is coming from. Any ideas would be appreciated!

It must be an environment variable. The public path can be set with APP_PUBLIC_PATH.

If you run yarn bud doctor it will list out the found env values. bud.js will pick up on any .env file in the path to Sage (so, the env value may be sourced from bedrock root).

You could override it in the sage directory by creating a .env file and setting APP_PUBLIC_PATH=/app/themes/my-theme/public/ in it. But, in this case it’s probably better to track the value down and change it since it doesn’t seem to be accurate.

The only instances of wp-content in the bud repo are from ./examples and ./test – so you can be assured this default isn’t coming from bud.js itself:

1 Like

Thanks again for the info @kellymears

Firstly - adding a .env to the theme and defining the APP_PUBLIC_PATH does indeed “fix” the issue - the path is reflected correctly in the compiled frontend.css (app.css) file. Fonts are rendered on the site. But it feels a bit odd having to do that - so I will keep on digging a bit.

Let’s acknowledge first that the Bud config you provided above @kellymears worked really well! Thank you again.

But I’m reasonably sure wp-content isn’t being specified anywhere in the site. All other asset paths - images, scripts, styles - correctly use app. It only seems to be the rewrite of the url() call in the Fontsource CSS files that, without the theme .env, references wp-content in the path.

BTW - I don’t see any env vars after running yarn bud doctor - even after adding the .env to the theme.

yarn run v1.22.19
$ /Users/me/Web/my-site/web/app/themes/my-theme/node_modules/.bin/bud doctor
âś… bud.js generated configuration

âś… webpack validated configuration

Registered configurations
- bud.config.js ./bud.config.js

✨  Done in 1.02s.

I’m wondering if Bud is looking for this, whether APP_PUBLIC_PATH should be defined somewhere by default, and as I’ve upgraded just the theme within a Bedrock project, I might’ve missed something. Ie an undefined constant is defaulting to wp-content. I’ve compared with the main branch of Bedrock so I’m a bit stumped. If you have any thoughts - I’d appreciate them, but this isn’t a huge issue now and besides, I think this is probably a “me” thing.

I missed that you were using 6.7.3. The new doctor stuff was released after that. My b!

The public path gets sourced from env early on in yarn bud commands.

Whether or not that gets triggered it can also be specifically set using bud.setPublicPath.

Our general advice is to set the public path explicitly (especially when using dynamic imports). Sage default config sets it here:

https://github.com/roots/sage/blob/d1e1c8ea906be04a62a34f272db72123c05c5537/bud.config.js#L45-L49

1 Like

Hi @kellymears

Well, embarrassingly, it was the setPublicPath() in bud.config.js that was set to wp-content/... I was so blinkered towards the copying/aliasing that I completely missed it :slight_smile:

So all in all, a great solution! Thanks again for your support through it!