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

feat(fonts): experimental release #12775

Draft
wants to merge 59 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
6391469
feat: setup branch
florian-lefebvre Dec 18, 2024
fdb3141
Merge branch 'main' into feat/fonts
florian-lefebvre Jan 17, 2025
21b6f35
Merge branch 'main' into feat/fonts
florian-lefebvre Jan 21, 2025
95673fc
feat(fonts): config (#12777)
florian-lefebvre Jan 21, 2025
c6cd69c
Merge branch 'main' into feat/fonts
florian-lefebvre Jan 30, 2025
3c18360
feat(fonts): vite plugin (#13093)
florian-lefebvre Feb 7, 2025
338757d
Merge branch 'main' into feat/fonts
florian-lefebvre Feb 7, 2025
4899359
Merge branch 'main' into feat/fonts
florian-lefebvre Feb 10, 2025
c6bc00f
feat(fonts): rename component (#13207)
florian-lefebvre Feb 10, 2025
363faf9
feat(fonts): add more providers (#13208)
florian-lefebvre Feb 11, 2025
193a9c0
feat(fonts): local provider (#13209)
florian-lefebvre Feb 17, 2025
e97e42a
Merge branch 'main' into feat/fonts
florian-lefebvre Feb 17, 2025
e8aead8
feat(fonts): improve providers (#13263)
florian-lefebvre Feb 18, 2025
918ee30
feat(fonts): improve type handling (#13270)
florian-lefebvre Feb 20, 2025
8bc05a0
feat(fonts): improve cache (#13273)
florian-lefebvre Feb 20, 2025
287b789
refactor(fonts): url proxy (#13274)
florian-lefebvre Feb 20, 2025
868e2ab
feat(fonts): dev headers (#13275)
florian-lefebvre Feb 20, 2025
8e89e80
feat(fonts): better local provider (#13276)
florian-lefebvre Feb 21, 2025
c515886
Merge branch 'main' into feat/fonts
florian-lefebvre Feb 27, 2025
08eff8c
Merge branch 'main' into feat/fonts
florian-lefebvre Feb 27, 2025
4eef143
feat(fonts): fallbacks (#13331)
florian-lefebvre Mar 3, 2025
a84a8b5
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 3, 2025
d8e0743
feat(fonts): refactor loading logic (#13353)
florian-lefebvre Mar 4, 2025
f62a7b7
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 5, 2025
75b1f5b
feat(fonts): css vars (#13362)
florian-lefebvre Mar 5, 2025
0f3419c
feat(fonts): as prop (#13366)
florian-lefebvre Mar 10, 2025
4a784be
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 10, 2025
1f3fc18
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 12, 2025
7ce73a1
feat(fonts): resolve config todos (#13401)
florian-lefebvre Mar 12, 2025
784976d
feat(fonts): use capsize instead of fontaine (#13411)
florian-lefebvre Mar 13, 2025
f01739d
feat(fonts): family name validation (#13417)
florian-lefebvre Mar 13, 2025
22ed30c
fix: zod error path
florian-lefebvre Mar 14, 2025
ef6f9ce
fix(fonts): vite plugin cleanup (#13424)
florian-lefebvre Mar 14, 2025
b70d8c4
feat(fonts): allow disabling automatic fallback generation (#13442)
florian-lefebvre Mar 18, 2025
c3845c8
feat(fonts): add errors (#13425)
florian-lefebvre Mar 19, 2025
e42de26
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 20, 2025
93ffd30
feat(fonts): typegen for <Font /> family prop (#13465)
florian-lefebvre Mar 21, 2025
b6af656
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 21, 2025
2665ebc
Update packages/astro/src/core/errors/errors-data.ts
florian-lefebvre Mar 25, 2025
dc24bdd
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 25, 2025
b470183
Apply suggestions from code review
florian-lefebvre Mar 26, 2025
3fcdba0
fix(fonts): config family types for inferred provider (#13469)
florian-lefebvre Mar 26, 2025
100cd48
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 26, 2025
fedde31
Merge branch 'main' into feat/fonts
florian-lefebvre Mar 30, 2025
af0e0e1
fix(fonts): throw error on unsuccessful responses (#13517)
florian-lefebvre Mar 31, 2025
4250554
fix(fonts): read local fonts files to extract metrics (#13518)
florian-lefebvre Mar 31, 2025
d893932
feat(fonts): set default fallbacks (#13516)
florian-lefebvre Mar 31, 2025
6cd4c14
Merge branch 'main' into feat/fonts
florian-lefebvre Apr 1, 2025
d7b2b51
feat(fonts): update config shape (#13515)
florian-lefebvre Apr 2, 2025
0dbe842
feat(fonts): rename provider type helper (#13533)
florian-lefebvre Apr 2, 2025
78c88b2
feat(fonts): simpler logging (#13535)
florian-lefebvre Apr 2, 2025
0730611
fix(fonts): do not add quotes to family names (#13534)
florian-lefebvre Apr 2, 2025
95b9fef
Merge branch 'main' into feat/fonts
florian-lefebvre Apr 2, 2025
c22561c
feat(fonts): new cssVariable property (#13544)
florian-lefebvre Apr 3, 2025
b350235
Merge branch 'main' into feat/fonts
florian-lefebvre Apr 3, 2025
118a707
feat(fonts): update local family shape (#13553)
florian-lefebvre Apr 4, 2025
7d50c96
Merge branch 'main' into feat/fonts
florian-lefebvre Apr 4, 2025
0362529
feat(fonts): improve local font files deletion (#13559)
florian-lefebvre Apr 4, 2025
8ae9be0
feat(fonts): update local to support entrypoints src and techs (#13556)
florian-lefebvre Apr 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/happy-spies-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': minor
---

Adds first-party support for fonts
3 changes: 3 additions & 0 deletions packages/astro/client.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/// <reference path="./types/content.d.ts" />
/// <reference path="./types/actions.d.ts" />
/// <reference path="./types/env.d.ts" />
/// <reference path="./types/fonts.d.ts" />

interface ImportMetaEnv {
/**
Expand Down Expand Up @@ -54,6 +55,7 @@ declare module 'astro:assets' {
inferRemoteSize: typeof import('./dist/assets/utils/index.js').inferRemoteSize;
Image: typeof import('./components/Image.astro').default;
Picture: typeof import('./components/Picture.astro').default;
Font: typeof import('./components/Font.astro').default;
};

type ImgAttributes = import('./dist/type-utils.js').WithRequired<
Expand All @@ -73,6 +75,7 @@ declare module 'astro:assets' {
imageConfig,
Image,
Picture,
Font,
inferRemoteSize,
}: AstroAssets;
}
Expand Down
30 changes: 30 additions & 0 deletions packages/astro/components/Font.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';

// TODO: remove dynamic import when fonts are stabilized
const { fontsData } = await import('virtual:astro:assets/fonts/internal').catch(() => {
throw new AstroError(AstroErrorData.ExperimentalFontsNotEnabled);
});

interface Props {
family: import('astro:assets').FontFamily;
preload?: boolean;
}

const { family, preload = false } = Astro.props;
const data = fontsData.get(family);
if (!data) {
throw new AstroError({
...AstroErrorData.FontFamilyNotFound,
message: AstroErrorData.FontFamilyNotFound.message(family),
});
}
---

<style set:html={data.css}></style>
{
preload &&
data.preloadData.map(({ url, type }) => (
<link rel="preload" href={url} as="font" type={`font/${type}`} crossorigin />
))
}
6 changes: 5 additions & 1 deletion packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"./assets/endpoint/*": "./dist/assets/endpoint/*.js",
"./assets/services/sharp": "./dist/assets/services/sharp.js",
"./assets/services/noop": "./dist/assets/services/noop.js",
"./assets/fonts/providers/*": "./dist/assets/fonts/providers/entrypoints/*.js",
"./loaders": "./dist/content/loaders/index.js",
"./content/runtime": "./dist/content/runtime.js",
"./content/runtime-assets": "./dist/content/runtime-assets.js",
Expand Down Expand Up @@ -113,7 +114,7 @@
"test:e2e:match": "playwright test -g",
"test:e2e:chrome": "playwright test",
"test:e2e:firefox": "playwright test --config playwright.firefox.config.js",
"test:types": "tsc --project tsconfig.tests.json",
"test:types": "tsc --project test/types/tsconfig.json",
"test:unit": "astro-scripts test \"test/units/**/*.test.js\" --teardown ./test/units/teardown.js",
"test:integration": "astro-scripts test \"test/*.test.js\""
},
Expand All @@ -122,6 +123,8 @@
"@astrojs/internal-helpers": "workspace:*",
"@astrojs/markdown-remark": "workspace:*",
"@astrojs/telemetry": "workspace:*",
"@capsizecss/metrics": "^3.5.0",
"@capsizecss/unpack": "^2.4.0",
"@oslojs/encoding": "^1.1.0",
"@rollup/pluginutils": "^5.1.4",
"acorn": "^8.14.1",
Expand Down Expand Up @@ -164,6 +167,7 @@
"tinyglobby": "^0.2.12",
"tsconfck": "^3.1.5",
"ultrahtml": "^1.6.0",
"unifont": "^0.1.7",
"unist-util-visit": "^5.0.0",
"unstorage": "^1.15.0",
"vfile": "^6.0.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/src/actions/plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { addRollupInput } from '../core/build/add-rollup-input.js';
import type { BuildInternals } from '../core/build/internal.js';
import type { StaticBuildOptions } from '../core/build/types.js';
import { shouldAppendForwardSlash } from '../core/build/util.js';
import { getOutputDirectory } from '../prerender/utils.js';
import { getServerOutputDirectory } from '../prerender/utils.js';
import type { AstroSettings } from '../types/astro.js';
import {
ASTRO_ACTIONS_INTERNAL_MODULE_ID,
Expand Down Expand Up @@ -73,7 +73,7 @@ export function vitePluginActionsBuild(
chunk.type !== 'asset' &&
chunk.facadeModuleId === RESOLVED_ASTRO_ACTIONS_INTERNAL_MODULE_ID
) {
const outputDirectory = getOutputDirectory(opts.settings);
const outputDirectory = getServerOutputDirectory(opts.settings);
internals.astroActionsEntryPoint = new URL(chunkName, outputDirectory);
}
}
Expand Down
11 changes: 11 additions & 0 deletions packages/astro/src/assets/fonts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# fonts

The vite plugin orchestrates the fonts logic:

- Retrieves data from the config
- Initializes font providers
- Fetches fonts data
- In dev, serves a middleware that dynamically loads and caches fonts data
- In build, download fonts data (from cache if possible)

The `<Font />` component is the only aspect not managed in the vite plugin, since it's exported from `astro:assets`.
82 changes: 82 additions & 0 deletions packages/astro/src/assets/fonts/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { z } from 'zod';
import { GOOGLE_PROVIDER_NAME, LOCAL_PROVIDER_NAME } from './constants.js';

// TODO: jsdoc for everything, most of those end up in the public AstroConfig type

const weightSchema = z.union([z.string(), z.number()]);
const styleSchema = z.enum(['normal', 'italic', 'oblique']);

const familyPropertiesSchema = z.object({
weight: weightSchema,
style: styleSchema,
display: z.enum(['auto', 'block', 'swap', 'fallback', 'optional']).optional(),
unicodeRange: z.array(z.string()).nonempty().optional(),
stretch: z.string().optional(),
featureSettings: z.string().optional(),
variationSettings: z.string().optional(),
});

const fallbacksSchema = z.object({
fallbacks: z.array(z.string()).nonempty().optional(),
automaticFallback: z.boolean().optional(),
});

export const requiredFamilyAttributesSchema = z.object({
name: z.string(),
cssVariable: z.string(),
});

const entrypointSchema = z.union([z.string(), z.instanceof(URL)]);

export const fontProviderSchema = z
.object({
entrypoint: entrypointSchema,
config: z.record(z.string(), z.any()).optional(),
})
.strict();

export const localFontFamilySchema = requiredFamilyAttributesSchema
.merge(fallbacksSchema)
.merge(
z.object({
provider: z.literal(LOCAL_PROVIDER_NAME).optional(),
variants: z
.array(
familyPropertiesSchema.merge(
z
.object({
src: z
.array(
z.union([
entrypointSchema,
z.object({ url: entrypointSchema, tech: z.string().optional() }).strict(),
]),
)
.nonempty(),
// TODO: find a way to support subsets (through fontkit?)
})
.strict(),
),
)
.nonempty(),
}),
)
.strict();

export const remoteFontFamilySchema = requiredFamilyAttributesSchema
.merge(
familyPropertiesSchema.omit({
weight: true,
style: true,
}),
)
.merge(fallbacksSchema)
.merge(
z.object({
provider: z.union([z.literal(GOOGLE_PROVIDER_NAME), fontProviderSchema]).optional(),
weights: z.array(weightSchema).nonempty().optional(),
styles: z.array(styleSchema).nonempty().optional(),
subsets: z.array(z.string()).nonempty().optional(),
}),
)
.strict();
43 changes: 43 additions & 0 deletions packages/astro/src/assets/fonts/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { ResolvedRemoteFontFamily } from './types.js';

export const GOOGLE_PROVIDER_NAME = 'google';
export const LOCAL_PROVIDER_NAME = 'local';

export const BUILTIN_PROVIDERS = [GOOGLE_PROVIDER_NAME, LOCAL_PROVIDER_NAME] as const;

export const DEFAULTS = {
weights: ['400'],
styles: ['normal', 'italic'],
subsets: ['cyrillic-ext', 'cyrillic', 'greek-ext', 'greek', 'vietnamese', 'latin-ext', 'latin'],
// Technically serif is the browser default but most websites these days use sans-serif
fallbacks: ['sans-serif'],
automaticFallback: true,
} satisfies Partial<ResolvedRemoteFontFamily>;

export const VIRTUAL_MODULE_ID = 'virtual:astro:assets/fonts/internal';
export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;

// Requires a trailing slash
export const URL_PREFIX = '/_astro/fonts/';
export const CACHE_DIR = './fonts/';

export const FONT_TYPES = ['woff2', 'woff', 'otf', 'ttf', 'eot'] as const;

// Source: https://github.com/nuxt/fonts/blob/3a3eb6dfecc472242b3011b25f3fcbae237d0acc/src/module.ts#L55-L75
export const DEFAULT_FALLBACKS: Record<string, Array<string>> = {
serif: ['Times New Roman'],
'sans-serif': ['Arial'],
monospace: ['Courier New'],
cursive: [],
fantasy: [],
'system-ui': ['BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial'],
'ui-serif': ['Times New Roman'],
'ui-sans-serif': ['Arial'],
'ui-monospace': ['Courier New'],
'ui-rounded': [],
emoji: [],
math: [],
fangsong: [],
};

export const FONTS_TYPES_FILE = 'fonts.d.ts';
Loading