How do I use a custom font with Phoenix 1.6 and ESBuild?

I downloaded a font called “American Typewriter” and I now have a file named American_Typewriter_Regular.ttf in my priv/static/fonts directory.

But I can’t figure out how to get this file to work with my CSS.

At the top of my assets/css/app.css file I have the following lines:


@font-face {
  font-family: 'American Typewriter Regular';
  src: url('/fonts/American_Typewriter_Regular.ttf') format('ttf');
  font-weight: normal;
  font-style: normal
}

.my-logo {
  font-family: 'American Typewriter Regular';
  font-size: 28px;
}

What do I have to do to get ESBuild to use my font properly?

I’ve been reading ESBiuld docs and found this info:

You can @import other CSS files and reference image and font files with url() and esbuild will bundle everything together. Note that you will have to configure a loader for image and font files, since esbuild doesn’t have any pre-configured. Usually this is either the data URLloader or the external file loader.

So I guess my problem is that I’m not using a loader – but I don’t know exactly how to specify to ESBuild how to use a loader.

The docs tell me to set some configuration similar to this:

require('esbuild').buildSync({
  entryPoints: ['app.js'],
  bundle: true,
  loader: { '.png': 'dataurl' },
  outfile: 'out.js',
})

but I don’t know where to set this configuration.

TLDR: how do I use a font that I’ve downloaded and have saved in my fonts/ directory?

3 Likes

I answered (almost) the same question here:

Just change the extensions or follow the links :slight_smile:

1 Like

Wow, thanks much. Following you suggestion I have adjusted my config/dev.exs file and it now reads as follows:

watchers: [
    esbuild:
        {Esbuild, :install_and_run,
        [:default, ~w(--sourcemap=inline --watch --loader:.ttf=file)]}
]

I have also adjusted my mix.exs file and it now shows the following:

"assets.deploy": [
    "cmd --cd assets npm run deploy",
    "esbuild default --minify --loader:.ttf=file",
    "phx.digest"
]

But I’m still not getting the font to work on my website.\

Could my problem have something to do with the fact that I’m telling ESBuild to use a file loader, but in my app.css file I have specified a URL?

Also, it is not clear to me how the ESBuild file loader is supposed to know where to find the file. How does it know whether to look in priv/static/assets/fonts vs assets/css/fonts vs assets/fonts, etc?

1 Like

Could my problem have something to do with the fact that I’m telling ESBuild to use a file loader, but in my app.css file I have specified a URL?

Not really, what this loader does is copy the file to the output folder, if you used the dataURL it would convert the font to base64 and paste it at the start of the generated css.

Can you access the font file going straight to its url in the browser?

What exactly the error says?
Could not resolveor No loader is configured for ".ttf" files, if it is Could not resolve it means that the loader isn’t finding the file, in my referenced post the person put the font in priv and used a relative path like: background-image: url('../../priv/static/images/background.jpg');

1 Like

Yes, if I visit http://localhost:4000/fonts/American_Typewriter_Regular.ttf in my browser, then I get a popup saying “Do you want to allow downloads on “localhost”?” – which suggests to me that the browser can find the file and is prompting me to download it because displaying it is not an option.

I’m not getting any error, though – when I run mix phx.server it just runs normally. I can use my website as expected. The only problem is that the font is not showing up so I don’t get the cool American Typewriter look.

Can you try @import instead of @font-face?

ok, I’ve adjusted my assets/css/app.css file and it now has the following:

@import {
  font-family: 'American Typewriter';
  src: url('/fonts/American_Typewriter_Regular.ttf') format('ttf');
  font-weight: normal;
  font-style: normal;
}

This causes my VSCode editor to give me some red underlines and warnings, though. The font is still not showing up.

I have also tried different paths, such as:

  src: url('/fonts/American_Typewriter_Regular.ttf') format('ttf');
  src: url('fonts/American_Typewriter_Regular.ttf') format('ttf');
  src: url('/assets/fonts/American_Typewriter_Regular.ttf') format('ttf');
  src: url('/static/fonts/American_Typewriter_Regular.ttf') format('ttf');
  src: url('../fonts/American_Typewriter_Regular.ttf') format('ttf');
  src: url('static/assets/fonts/American_Typewriter_Regular.ttf') format('ttf');
  src: url('../assets/fonts/American_Typewriter_Regular.ttf') format('ttf');

I have the .ttf file stored in two places:

  1. priv/static/fonts/American_Typewriter_Regular.ttf
  2. assets/fonts/American_Typewriter_Regular.ttf

Forget about the @import, I was thinking of other stuff…

My last guess is to use ../../priv/static/fonts/American_Typewriter_Regular.ttf and hope for the best…

Ok, so I’ve made a repo here: GitHub - tadasajon/use_local_font: use a local font in Phoenix 1.6.2 using ESBuild

This was generated with mix phx.new use_local_font --no-ecto and it tries to do just one thing: use a local font.

I’ll obviously be delighted to receive a pull request from anyone.

1 Like

Ok, well, the font works fine for me in that repo that I just made. I used the following settings:

In my assets/css/app.css:

@font-face {
  font-family: 'American Typewriter';
  src: url('/fonts/American_Typewriter_Regular.ttf') format('ttf');
  font-weight: normal;
  font-style: normal;
}

.use-local-font {
  color: darkblue; 
  font-family: 'American Typewriter'

}

in my lib/use_local_font_web/templates/layout/root.html.heex file:

<div style="margin:16px; padding:16px;outline:4px solid red;font-size:28px;text-align:center;background-color:lightyellow;">
    <span>
        This text should be in the default font. &nbsp;&nbsp;&nbsp;&nbsp; Q
    </span>
    <br />
    <span class="use-local-font">
        This text should be in American Typewriter Regular font. &nbsp;&nbsp; Q
    </span>
</div>

in my mix.exs file:

"assets.deploy": ["esbuild default --minify --loader:.ttf=file", "phx.digest"]

in my config/dev.exs file:

watchers: [
    # Start the esbuild watcher by calling Esbuild.install_and_run(:default, args)
    esbuild:
      {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch --loader:.ttf=file)]}
  ]

1 Like

So I guess my problem has something to do with the way I have tailwind configured in my app, since it isn’t working in the setup I have in my real app, but it is working in this toy setup.

Wow, what a time suck this has been for me.

1 Like

It turns out I don’t need to adjust my mix.exs file or my config/dev.exs file for the local font to work in my toy example app. I can just drop the font in my fonts folder, reference it in my CSS file, and I’m done. So I can get rid of the stuff having to do with the file loader for .ttf files.

1 Like

Ok, so it turns out that I can entirely delete the priv/static/font directory and the assets/font directory and my toy app still has the American Typewriter font showing up. Maybe it is cached somewhere?

It seems like in my real app there is nothing I can do to get the American Typewriter font to work, and in my toy app there is nothing I can do to get it to stop working.

ok, so now I’ve commented out the @font-face rule in my CSS file and I’ve also deleted the assets/fonts directory and the static/fonts directory, which contained the American_Typewriter_Regular.ttf file.

But the font is showing up fine in my real app (not my toy app).

Before there was nothing I could do to make it work and now there is nothing I can do to break it. So I guess the font got cached somewhere.

But I want to make sure that the font works even if someone’s computer has never seen or heard of this font before, which is why I want to have a copy of the font bundled in my CSS. This appears to be hard to test because it is difficult to tell where a font is coming from.

I guess the lesson from all this is that playing around with fonts is just a stupid waste of time because there is no way to tell what is going on.

I can’t help with the esbuild setup but from this it sounds like you installed the font on your computer. I’d recommend uninstalling until you verify that you have your site working.

The solution I have come up with is to just skip all the ESBuild config stuff and manually convert the .ttf files to .woff2 and then just manually convert the .woff2 files to base64-encoded datauri strings that I manually include in my CSS file.

First, I use this tool to convert .ttf to .woff2: TTF to WOFF2 | CloudConvert

Then I use this tool to covert .woff2 to a base64-encoded datauri string: Woff2Base: Convert .woff2 to accurate Base64 css.

Then I create a /assets/css/fonts.css file and drop the @font-face rules in there, with the entire base64 string included, as follows:

@font-face { 
  font-family: "QuizDrill";
  src: url(data:application/octet-stream;base64,d09GMgABAAAAAHXIABIAAAAiNV/D/f09uyBBZB9W+3ovLSgYkiIYUScoOOMkzfm2/7LYcNpb5clKparZy9oQnYKYSgPwoISWRgs3iJBhX/+ib/4nZm3KxhqQXEcUACUh7T/8s/398//BtL3vL8qXMex/yq3LNP8q8SQUvyVQQT4d/DHVfj/7spNOX/8fzl6irz/pbkO/gdR55B/E35yUwY=);
}

Note that I have shortened the base64 string in this example because it is thousands (or tens of thousands) of characters long.

Then at the top of my /assets/css/app.css file I have the line:

@import "fonts";

This allows me to use these local fonts bundled with my CSS.

4 Likes

can you try this: Add external flag to esbuild for fonts and images (#4536) · phoenixframework/phoenix@19ec3c6 · GitHub

3 Likes

I recently converted to esbuild (after making an npm update that sent me back down the error rabbit hole), and I was getting these “mark the file as external” errors from esbuild and wondering how I mark the files as external, very excited to try this.

Thank you for sharing :heart:

1 Like

I am trying this and is not working.
It needs to put the fonts in some place?
or just convert to this Base64 and put on the CSS file ?

We had to remove the format for our code to work. So our CSS looks like this:

@font-face {
  font-family: 'Sigmar';
  src: url('../fonts/Sigmar-Regular.ttf');
}

Instead of:

@font-face {
  font-family: 'Sigmar';
  src: url('../fonts/Sigmar-Regular.ttf') format('ttf');
}

For some reason adding format to the url broke our font source.

I hope that helps anyone who might experience a similar issue.

3 Likes