-
-
Notifications
You must be signed in to change notification settings - Fork 2.9k
feat(fonts): refactor #13653
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
Merged
Merged
feat(fonts): refactor #13653
Changes from 71 commits
Commits
Show all changes
75 commits
Select commit
Hold shift + click to select a range
71f7820
feat(fonts): generate fallbacks for all faces
florian-lefebvre 5d89da8
fix: avoid duplicates
florian-lefebvre 1feaf67
feat: experiment
florian-lefebvre 4d68367
feat: experiment
florian-lefebvre 58aa0c1
feat: experiment
florian-lefebvre 693a2b6
feat: experiment
florian-lefebvre 1945164
feat: experiment
florian-lefebvre 35d670c
feat: experiment
florian-lefebvre 3d42f0e
feat: experiment
florian-lefebvre d7a8b46
feat: experiment
florian-lefebvre b6cfd54
feat: experiment
florian-lefebvre ea56a48
feat: experiment
florian-lefebvre caf9539
feat: experiment
florian-lefebvre ba76199
feat: experiment
florian-lefebvre d6af004
feat: experiment
florian-lefebvre d2d573a
feat: experiment
florian-lefebvre 5151bc2
feat: experiment
florian-lefebvre c240c7d
feat: experiment
florian-lefebvre 00e85ca
Merge branch 'main' into feat/fonts-fallbacks-for-all-faces
florian-lefebvre 2ab9768
Merge branch 'feat/fonts-fallbacks-for-all-faces' into feat/fonts-ref…
florian-lefebvre 4e8b2c0
feat: experiment
florian-lefebvre cc58199
feat: experiment
florian-lefebvre 265e768
Merge branch 'main' into feat/fonts-refactor
florian-lefebvre fd4faf4
feat: experiment
florian-lefebvre 2ab5ce2
feat: experiment
florian-lefebvre 8f88ca0
feat: experiment
florian-lefebvre 77140e6
feat: experiment
florian-lefebvre ffed015
feat: experiment
florian-lefebvre 01191d0
feat: experiment
florian-lefebvre 2afd050
feat: experiment
florian-lefebvre a103ad9
feat: experiment
florian-lefebvre a6e7155
feat: experiment
florian-lefebvre b5476ba
feat: experiment
florian-lefebvre 6822ef7
feat: experiment
florian-lefebvre 1e1ac84
feat: deep sort
florian-lefebvre 6c2b4ea
feat: move stuff around
florian-lefebvre 6ce6f3c
feat: handle space in family name
florian-lefebvre 001fc6a
feat: jsdocs
florian-lefebvre 8b437db
feat: move type
florian-lefebvre 4268776
chore: comments
florian-lefebvre 0a74196
feat: simplify
florian-lefebvre f18a270
Merge branch 'main' into feat/fonts-refactor
florian-lefebvre 42fe62b
Merge branch 'main' into feat/fonts-refactor
florian-lefebvre edeb949
feat: refactor
florian-lefebvre 07e0e65
feat: refactor create url proxy
florian-lefebvre 5c146ae
feat: improve type
florian-lefebvre 51d59ca
feat: use functions instead of classes
florian-lefebvre 837cd94
chore: comment
florian-lefebvre c6e22de
feat: refactor
florian-lefebvre 2f87326
chore: clean
florian-lefebvre 3d92628
feat: refactor
florian-lefebvre 957215c
chore: comment
florian-lefebvre 3da9498
feat: work on test
florian-lefebvre bd10d34
feat: work on test
florian-lefebvre ed2c282
feat: work on test
florian-lefebvre 9517bc3
feat: work on test
florian-lefebvre 3b2af11
feat: work on test
florian-lefebvre fb84109
feat: work on test
florian-lefebvre 30c528f
feat: work on test
florian-lefebvre 9cf8025
feat: work on test
florian-lefebvre ab2a1ee
feat: work on test
florian-lefebvre 3e9a7e1
feat: work on test
florian-lefebvre 6bbf14b
feat: work on test
florian-lefebvre 6f60e6d
feat: work on test
florian-lefebvre c461fad
feat: work on test
florian-lefebvre 2ce8b6b
feat: work on test
florian-lefebvre d7e89f6
feat: optimize fonts
florian-lefebvre 54fdac5
Merge branch 'main' into feat/fonts-refactor
florian-lefebvre 895c58a
feat: public warn
florian-lefebvre 518ae7c
chore: changeset
florian-lefebvre c864483
fix: test
florian-lefebvre 06e974b
Merge branch 'main' into feat/fonts-refactor
florian-lefebvre 7e6a9d1
feedback
florian-lefebvre bcbfb77
fix: test
florian-lefebvre 41b8c37
Discard changes to packages/astro/test/fixtures/fonts/astro.config.mjs
florian-lefebvre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'astro': patch | ||
| --- | ||
|
|
||
| Reduces the amount of preloaded files for the local provider when using the experimental fonts API |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| 'astro': patch | ||
| --- | ||
|
|
||
| Fixes a case where invalid CSS was emitted when using an experimental fonts API family name containing a space |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| /* eslint-disable @typescript-eslint/no-empty-object-type */ | ||
| import type { AstroFontProvider, FontType, PreloadData, ResolvedFontProvider } from './types.js'; | ||
| import type * as unifont from 'unifont'; | ||
| import type { FontFaceMetrics, GenericFallbackName } from './types.js'; | ||
| import type { CollectedFontForMetrics } from './logic/optimize-fallbacks.js'; | ||
|
|
||
| export interface Hasher { | ||
| hashString: (input: string) => string; | ||
| hashObject: (input: Record<string, any>) => string; | ||
| } | ||
|
|
||
| export interface RemoteFontProviderModResolver { | ||
| resolve: (id: string) => Promise<any>; | ||
| } | ||
|
|
||
| export interface RemoteFontProviderResolver { | ||
| resolve: (provider: AstroFontProvider) => Promise<ResolvedFontProvider>; | ||
| } | ||
|
|
||
| export interface LocalProviderUrlResolver { | ||
| resolve: (input: string) => string; | ||
| } | ||
|
|
||
| type SingleErrorInput<TType extends string, TData extends Record<string, any>> = { | ||
| type: TType; | ||
| data: TData; | ||
| cause: unknown; | ||
| }; | ||
|
|
||
| export type ErrorHandlerInput = | ||
| | SingleErrorInput< | ||
| 'cannot-load-font-provider', | ||
| { | ||
| entrypoint: string; | ||
| } | ||
| > | ||
| | SingleErrorInput<'unknown-fs-error', {}> | ||
| | SingleErrorInput<'cannot-fetch-font-file', { url: string }> | ||
| | SingleErrorInput<'cannot-extract-font-type', { url: string }>; | ||
|
|
||
| export interface ErrorHandler { | ||
| handle: (input: ErrorHandlerInput) => Error; | ||
| } | ||
|
|
||
| export interface UrlProxy { | ||
| proxy: (input: { | ||
| url: string; | ||
| collectPreload: boolean; | ||
| data: Partial<unifont.FontFaceData>; | ||
| }) => string; | ||
| } | ||
|
|
||
| export interface UrlProxyContentResolver { | ||
| resolve: (url: string) => string; | ||
| } | ||
|
|
||
| export interface DataCollector { | ||
| collect: (input: { | ||
| originalUrl: string; | ||
| hash: string; | ||
| data: Partial<unifont.FontFaceData>; | ||
| preload: PreloadData | null; | ||
| }) => void; | ||
| } | ||
|
|
||
| export type CssProperties = Record<string, string | undefined>; | ||
|
|
||
| export interface CssRenderer { | ||
| generateFontFace: (family: string, properties: CssProperties) => string; | ||
| generateCssVariable: (key: string, values: Array<string>) => string; | ||
| } | ||
|
|
||
| export interface FontMetricsResolver { | ||
| getMetrics: (name: string, font: CollectedFontForMetrics) => Promise<FontFaceMetrics>; | ||
| generateFontFace: (input: { | ||
| metrics: FontFaceMetrics; | ||
| fallbackMetrics: FontFaceMetrics; | ||
| name: string; | ||
| font: string; | ||
| properties: CssProperties; | ||
| }) => string; | ||
| } | ||
|
|
||
| export interface SystemFallbacksProvider { | ||
| getLocalFonts: (fallback: GenericFallbackName) => Array<string> | null; | ||
| getMetricsForLocalFont: (family: string) => FontFaceMetrics; | ||
| } | ||
|
|
||
| export interface FontFetcher { | ||
| fetch: (hash: string, url: string) => Promise<Buffer>; | ||
| } | ||
|
|
||
| export interface FontTypeExtractor { | ||
| extract: (url: string) => FontType; | ||
| } |
50 changes: 50 additions & 0 deletions
50
packages/astro/src/assets/fonts/implementations/css-renderer.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import type { CssProperties, CssRenderer } from '../definitions.js'; | ||
|
|
||
| export function renderFontFace(properties: CssProperties, minify: boolean): string { | ||
| // Line return | ||
| const lr = minify ? '' : `\n`; | ||
| // Space | ||
| const sp = minify ? '' : ' '; | ||
|
|
||
| return `@font-face${sp}{${lr}${Object.entries(properties) | ||
| .filter(([, value]) => Boolean(value)) | ||
| .map(([key, value]) => `${sp}${sp}${key}:${sp}${value};`) | ||
| .join(lr)}${lr}}${lr}`; | ||
| } | ||
|
|
||
| export function renderCssVariable(key: string, values: Array<string>, minify: boolean): string { | ||
| // Line return | ||
| const lr = minify ? '' : `\n`; | ||
florian-lefebvre marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // Space | ||
| const sp = minify ? '' : ' '; | ||
|
|
||
| return `:root${sp}{${lr}${sp}${sp}${key}:${sp}${values.map((v) => handleValueWithSpaces(v)).join(`,${sp}`)};${lr}}${lr}`; | ||
| } | ||
|
|
||
| export function withFamily(family: string, properties: CssProperties): CssProperties { | ||
| return { | ||
| 'font-family': handleValueWithSpaces(family), | ||
| ...properties, | ||
| }; | ||
| } | ||
|
|
||
| const SPACE_RE = /\s/; | ||
|
|
||
| /** If the value contains spaces (which would be incorrectly interpreted), we wrap it in quotes. */ | ||
| export function handleValueWithSpaces(value: string): string { | ||
| if (SPACE_RE.test(value)) { | ||
| return JSON.stringify(value); | ||
| } | ||
| return value; | ||
| } | ||
|
|
||
| export function createMinifiableCssRenderer({ minify }: { minify: boolean }): CssRenderer { | ||
| return { | ||
| generateFontFace(family, properties) { | ||
| return renderFontFace(withFamily(family, properties), minify); | ||
| }, | ||
| generateCssVariable(key, values) { | ||
| return renderCssVariable(key, values, minify); | ||
| }, | ||
| }; | ||
| } | ||
25 changes: 25 additions & 0 deletions
25
packages/astro/src/assets/fonts/implementations/data-collector.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import type { DataCollector } from '../definitions.js'; | ||
| import type { CreateUrlProxyParams } from '../types.js'; | ||
|
|
||
| export function createDataCollector({ | ||
| hasUrl, | ||
| saveUrl, | ||
| savePreload, | ||
| saveFontData, | ||
| }: Omit<CreateUrlProxyParams, 'local'>): DataCollector { | ||
| return { | ||
| collect({ originalUrl, hash, preload, data }) { | ||
| if (!hasUrl(hash)) { | ||
| saveUrl(hash, originalUrl); | ||
| if (preload) { | ||
| savePreload(preload); | ||
| } | ||
| } | ||
| saveFontData({ | ||
| hash, | ||
| url: originalUrl, | ||
| data, | ||
| }); | ||
| }, | ||
| }; | ||
| } |
34 changes: 34 additions & 0 deletions
34
packages/astro/src/assets/fonts/implementations/error-handler.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { AstroError, AstroErrorData } from '../../../core/errors/index.js'; | ||
| import type { ErrorHandler, ErrorHandlerInput } from '../definitions.js'; | ||
|
|
||
| function getProps(input: ErrorHandlerInput): ConstructorParameters<typeof AstroError>[0] { | ||
| if (input.type === 'cannot-load-font-provider') { | ||
| return { | ||
| ...AstroErrorData.CannotLoadFontProvider, | ||
| message: AstroErrorData.CannotLoadFontProvider.message(input.data.entrypoint), | ||
| }; | ||
| } else if (input.type === 'unknown-fs-error') { | ||
| return AstroErrorData.UnknownFilesystemError; | ||
| } else if (input.type === 'cannot-fetch-font-file') { | ||
| return { | ||
| ...AstroErrorData.CannotFetchFontFile, | ||
| message: AstroErrorData.CannotFetchFontFile.message(input.data.url), | ||
| }; | ||
| } else if (input.type === 'cannot-extract-font-type') { | ||
| return { | ||
| ...AstroErrorData.CannotExtractFontType, | ||
| message: AstroErrorData.CannotExtractFontType.message(input.data.url), | ||
| }; | ||
| } | ||
| input satisfies never; | ||
| // Should never happen but TS isn't happy | ||
| return AstroErrorData.UnknownError; | ||
| } | ||
|
|
||
| export function createAstroErrorHandler(): ErrorHandler { | ||
| return { | ||
| handle(input) { | ||
| return new AstroError(getProps(input), { cause: input.cause }); | ||
| }, | ||
| }; | ||
| } |
41 changes: 41 additions & 0 deletions
41
packages/astro/src/assets/fonts/implementations/font-fetcher.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import type { Storage } from 'unstorage'; | ||
| import type { ErrorHandler, FontFetcher } from '../definitions.js'; | ||
| import { cache } from '../utils.js'; | ||
| import { isAbsolute } from 'node:path'; | ||
|
|
||
| export function createCachedFontFetcher({ | ||
| storage, | ||
| errorHandler, | ||
| fetch, | ||
| readFile, | ||
| }: { | ||
| storage: Storage; | ||
| errorHandler: ErrorHandler; | ||
| fetch: (url: string) => Promise<Response>; | ||
| readFile: (url: string) => Promise<Buffer>; | ||
| }): FontFetcher { | ||
| return { | ||
| async fetch(hash, url) { | ||
| return await cache(storage, hash, async () => { | ||
| try { | ||
| if (isAbsolute(url)) { | ||
| return await readFile(url); | ||
| } | ||
| // TODO: find a way to pass headers | ||
| // https://github.com/unjs/unifont/issues/143 | ||
| const response = await fetch(url); | ||
| if (!response.ok) { | ||
| throw new Error(`Response was not successful, received status code ${response.status}`); | ||
| } | ||
| return Buffer.from(await response.arrayBuffer()); | ||
| } catch (cause) { | ||
| throw errorHandler.handle({ | ||
| type: 'cannot-fetch-font-file', | ||
| data: { url }, | ||
| cause, | ||
| }); | ||
| } | ||
| }); | ||
| }, | ||
| }; | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The folders
implementations/andlogic/have an unusual name. We usually name the folders by feature, but in this case, it's hard to understand which "feature" belongs to. Maybe consider a main folder calledfonts/and then have everything there