Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fonts #1039

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open

Fonts #1039

wants to merge 16 commits into from

Conversation

florian-lefebvre
Copy link
Member

Summary

Have first-party support for fonts in Astro:

// astro config
export default defineConfig({
	fonts: {
		families: ["Roboto", "Lato"]
	}
})
---
// layouts/Layout.astro
import { Font } from "astro:fonts"
---
<head>
	<Font family="Inter" preload />
	<Font family="Lato" />
	<style>
		h1 {
			font-family: var(--astro-font-inter);
		}
		p {
			font-family: var(--astro-font-lato);
		}
	</style>
</head>

Links

@florian-lefebvre florian-lefebvre changed the title [WIP] Fonts Fonts Oct 22, 2024
@florian-lefebvre florian-lefebvre marked this pull request as ready for review October 22, 2024 14:39
@florian-lefebvre florian-lefebvre mentioned this pull request Oct 22, 2024
@lilnasy
Copy link
Contributor

lilnasy commented Oct 22, 2024

How does preloading pick the src when the font provides multiple files for multiple unicode ranges? See IBM Plex Mono on font source for example.

What's the reasoning behind considering subsetting a non-goal? It helps a lot when the font participates in LCP and is trivial to do when using google fonts directly. Moving to astro fonts in that case would be a downgrade.

@florian-lefebvre
Copy link
Member Author

How does preloading pick the src when the font provides multiple files for multiple unicode ranges? See IBM Plex Mono on font source for example.

I think we can't choose the src to pick automatically, so that would preload everything? Not sure, have ideas in mind?

What's the reasoning behind considering subsetting a non-goal? It helps a lot when the font participates in LCP and is trivial to do when using google fonts directly. Moving to astro fonts in that case would be a downgrade.

Only automatic subsetting is a non goal (eg. by analyzing the static content), subsetting is actually supported. I'll clarify the non goal

proposals/0052-fonts.md Outdated Show resolved Hide resolved
proposals/0052-fonts.md Show resolved Hide resolved
<Font family='Lato' />
<style>
h1 {
font-family: var(--astro-font-inter);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's gonna be important to make sure those variables are accessible correctly to things like Tailwind. It's a bit unclear with this API because the Tailwind CSS would be imported in the frontmatter, whereas the fonts are in the document.

Perhaps Vite will re-order it to make it work, not sure.

Copy link
Member Author

@florian-lefebvre florian-lefebvre Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well since it's just some css, I don't think it will cause any issues? In Tailwind 3, you'd have to reference like so:

import defaultTheme from 'tailwindcss/defaultTheme'

export default {
  theme: {
    extend: {
      fontFamily: {
        sans: ['var(--astro-font-inter)', ...defaultTheme.fontFamily.sans],
      },
    }
  }
}

provider: "adobe",
weights: [200, 700],
styles: ["italic"],
subsets: [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would love an example with opentype fonts features

Copy link
Member Author

@florian-lefebvre florian-lefebvre Oct 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess that would be one key per setting? Something like the following:

{
	fontKerning: "'kern' 1",
	fontVariantAlternates: "1"
	// etc...
}

MDN docs: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_fonts/OpenType_fonts_guide

myCustomFontProvider(),
],
defaults: {
provider: "adobe",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how would you know the name of the provider here? Will it be... typed (👀) automatically based on what you put in providers?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! Thanks to defineConfig() being generic now, we can do a lot of cool stuff here. Providers would be easily typed

fontProviders.adobe({ apiKey: process.env.ADOBE_FONTS_API_KEY }),
myCustomFontProvider(),
],
defaults: {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think defaults make this a nicer API overall, but I wonder how required it is, do people really have that much fonts that setting defaults is required 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit I rarely use a lot of fonts but I guess that could be the case for more complex sites. IMO people will probably want this for setting a default provider in most cases, so why not support all the other properties I suppose!


#### Definition

A provider allows to retrieve font faces data from a font family name from a given CDN or abstraction. It's a [unifont](https://github.com/unjs/unifont) provider.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think some people could have a concern with us exposing a third-party "API" like that in the config, but we already do for syntax highlighting themes, so I think it's okay. Plus, the default providers unifont provide are probably enough for 99.999% of people anyway.


| Path | Example (weight) | Advantage | Downside |
| --------- | ------------------- | --------------------- | --------------------------------------------------------------------------------- |
| Minimal | Only include `400` | Lightweight | People will probably struggle by expecting all weights to be available by default |
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm team just 400, Google Fonts also gives you only 400 when you unselect the "give all options" setting


#### Why this API?

1. **Coherent API**: a few things in Astro are using this pattern, namely integrations and vite plugins. It's simple to author as a library author, easy to use as a user
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with this statement, this API is used only for integrations and vite plugins.

For example: middleware, actions, and content collections (we could consider them an exception though) don't follow this pattern. They use a known file to collect users' APIs. Same thing for @astrojs/db (but that's integration).

I think that our configuration should inject as less runtime as possible, use good defaults, and maybe the custom providers could follow the same pattern we use with actions and middleware.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image services, which are probably the most similar thing do work this way

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably I missed something, please correct me if I misunderstood what you mean. Based on this docs page (https://docs.astro.build/en/reference/image-service-reference/#user-configuration), the image service uses an entry point API, which is different from the one proposed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, apologies, I misunderstood what you meant. Thought this was mostly about where they're configured.

Image services do use an entrypoint, but they're configured in the Astro config, when they have settings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The main advantage of having objects in the config directly for providers is typing support, which imo is super important

Copy link
Member

@ematipico ematipico Oct 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My major concern is about the providers and the way they are injected. The rest is fine :)

Copy link
Member Author

@florian-lefebvre florian-lefebvre Oct 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright so I guess you'd prefer such an API, am I right?

defineConfig({
	fonts: {
		providers: [fontProviders.adobe({ apiKey: "..." })]
		// equal to { name: "adobe", entrypoint: "astro/assets/fonts/adobe", config: { apiKey: "..." } }
	}
})

Seems doable

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants