Resolve asset to public path in `bud.config.js`/JavaScript

bud.path(...) and bud.relPath(...) can be used to resolve the path to an asset (including aliases) in the bud.config.js/JavaScript. How can path to the public path with hash resolved?

Alternatively, can bud resolve aliases/paths used in wpjson.settings; set(...), notably typography.fontFamilies for the WordPress Web Fonts API:

    .wpjson
      .settings(theme =>
        theme
          .set("typography.fontFamilies", [
            {
              "fontFamily": "\"Some self-hosted (e.g. licensed) font\", Arial, sans-serif",
              "slug": "tertiary",
              "name": "Self-hosted",
              "fontFace": [
                {
                  "fontFamily":  "Self hosted",
                  "fontWeight":  "normal",
                  "fontStyle":   "normal",
                  "fontStretch": "normal",
                  "src": [
                    "file:@fonts/licensed/self-hosted/file.woff",
                    "file:@fonts/licensed/self-hosted/file.woff2",
                  ]
                }
              ]
            }
          ])
      )

Those asset paths (@fonts/...) are not resolved (also not the @fonts alias).

1 Like

The problem we’re going to bump into is that those files don’t actually exist in time for bud.path to resolve them. And, I don’t really want to start going down the road of interpreting the data provided too much because I don’t spend a lot of time with the API and don’t want to be in a position where we have to respond to changes upstream.

Still, worth thinking about. I get the need.

I think the easiest way to handle this right now is to register a function with bud.after to read the manifest and update the data accordingly:

export default async (app) => {
  app.after(async () => {
    const manifest = await app.fs.read(`public/manifest.json`);
    const data = app.container(await app.fs.read(`theme.json`));

    data.set(`settings.typography.fontFamilies.[0].fontFace.[0].src`, [
      `file:${manifest[`fonts/example.woff`]}`,
    ]);

    await app.fs.write(`theme.json`, data.all());
  });
}
  • You don’t have to use the container API if you prefer to handle it with ES6 or a different tool.
  • app.fs.read will give you an object if you ask it for the contents of a json file
  • app.fs.write will handle stringifying the data for you
  • unless you specify otherwise, app.fs will always resolve relative to project root.
  • If you need the publicPath you can get it from app.publicPath()
2 Likes

Edit: I should more RTFM: bud.assets | bud.js

Follow up:

public/ has to be prefixed to the paths (in the manifest)

export default async (app) => {
  app.after(async () => {
    const manifest = await app.fs.read(`public/manifest.json`);
    const data = app.container(await app.fs.read(`theme.json`));

    data.set(`settings.typography.fontFamilies.[0].fontFace.[0].src`, [
      `file:./public/${manifest[`fonts/example.woff`]}`,
    ]);

    await app.fs.write(`theme.json`, data.all());
  });
}

@kellymears: Follow up on this:
On recent bud this approach does not appear to work as before, the font file is not included in manifest.json anymore and not resolved.

Minimal example of a Sage 10 theme that should use a font file with the WordPress Web Fonts API (in theme.json):

  1. git clone https://github.com/strarsis/sage10-wp-font-api
  2. cd sage10-wp-font-api
  3. nvm use
  4. npm install
  5. npm run build
    Note that in theme.json in the font fontFace definition the src is not correctly resolved to the font asset file.

The font file should be treated as an asset and resolved in the generated theme.json in a Web Fonts API font definition.

OK, I found out the difference, but I also made a mistake in the example:

  1. The font asset path needs to be passed resolvedly (app.path(...)).
.assets([app.path('@fonts/Roboto-Regular-subset.woff2')]);
  1. In the last step where the path to font in generated theme.json is replaced, just the font filename has to be used (as listed in the generated manifest.json), not with its complete path.
file:./public/${manifest[`Roboto-Regular-subset.woff2`]}`,

I adjusted the repository accordingly.

Edit: I have to use @roots/bud* 6.12.2, higher versions result in the assets not being added to manifest, as app.path(...) resolves the bud-sage @fonts alias incorrectly, the resulting path does not lead to the actual file.

1 Like

Thanks @strarsis for your insights. I tried to make this config more dynamic and ended now with:

export default async (app) => {

  /* ... */

  .useTailwindColors(true)
  // We don't want to use fonts configured in tailwind.config.js., so we disable
  // .useTailwindFontFamily()
  .useTailwindFontSize();

  /**
   * Set up fonts
   */
  app.setPath('@fonts', '@src/fonts');

  // Define font families
  const fontFamilies = {
    'barlow-condensed': {
      fontFamily: 'Barlow Condensed, sans-serif',
      slug: 'sans',
      name: 'Barlow Condensed',
      fontFace: [
        {
          fontFamily: 'Barlow Condensed',
          fontStyle: 'normal',
          fontWeight: 500,
          fontDisplay: 'swap',
          src: [
            '@fonts/barlow-condensed/files/barlow-condensed-latin-ext-500-normal.woff2',
            '@fonts/barlow-condensed/files/barlow-condensed-latin-500-normal.woff2'
          ],
        },
        {
          fontFamily: 'Barlow Condensed',
          fontStyle: 'italic',
          fontWeight: 500,
          fontDisplay: 'swap',
          src: [
            '@fonts/barlow-condensed/files/barlow-condensed-latin-ext-500-italic.woff2',
            '@fonts/barlow-condensed/files/barlow-condensed-latin-500-italic.woff2'
          ],
        },
      ]
    },
    'abril-fatface': {
      fontFamily: 'Abril Fatface, serif',
      slug: 'serif',
      name: 'Abril Fatface',
      fontFace: [
        {
          fontFamily: 'Abril Fatface',
          fontWeight: 400,
          fontDisplay: 'swap',
          src: [
            '@fonts/abril-fatface/files/abril-fatface-latin-ext-400-normal.woff2',
            '@fonts/abril-fatface/files/abril-fatface-latin-400-normal.woff2'
          ],
        }
      ],
    },
  };

  // Set font families in theme.json
  app.wpjson.settings(theme => theme.set('typography.fontFamilies', Object.values(fontFamilies)));

  // Copy all font files from @fontsource to @fonts
  for (const [id, font] of Object.entries(fontFamilies)) {
    await app.fs.copy(
      app.path(`@modules/@fontsource/${id}`),
      app.path(`@fonts/${id}`),
      {
        overwrite: true,
      }
    );
  }

  // Copy all font files to public directory
  app.assets(
    Object.values(fontFamilies)
      .flatMap(({ fontFace }) => fontFace)
      .flatMap(({ src }) => src)
      .map(src => app.path(src))
  );

  // Update theme.json with public paths
  app
    .after(async () => {
      const manifest = await app.fs.read(`public/manifest.json`);
      const data = app.container(await app.fs.read(`theme.json`));
      const fonts = data.get(`settings.typography.fontFamilies`);

      fonts.forEach((font, fontIndex) => {
        font.fontFace.forEach((face, faceIndex) => {
          data.set(
            `settings.typography.fontFamilies.[${fontIndex}].fontFace.[${faceIndex}].src`,
            face.src.map((src) => `file:./public/${manifest[src.split('/').pop()]}`)
          );
        });
      });

      await app.fs.write(`theme.json`, data.all());
    });
};

Maybe this could be simplified by a custom bud.js extensions for font handling, where we just config the fontFamilies object.

1 Like

@dsturm: Yes, a bud extension would be great! In practically all projects I have to use custom fonts.