Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
"resolutions": {
"@nuxt/fonts>fontaine": "latest",
"fontaine": "workspace:*",
"fontless": "workspace:*"
"fontless": "workspace:*",
"unifont": "0.6.0"
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
Expand Down
2 changes: 1 addition & 1 deletion packages/fontless/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ fontless({

// Default font settings
defaults: {
preload: true,
preload: { subsets: ['latin'] }, // select preload fonts by subset
weights: [400, 700],
styles: ['normal', 'italic'],
fallbacks: {
Expand Down
6 changes: 6 additions & 0 deletions packages/fontless/examples/tailwind/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ export default defineConfig({
tailwindcss(),
fontless({
provider: 'google',
families: [
{
name: 'Geist',
preload: { subsets: ['latin'] },
}
]
}),
],
})
2 changes: 1 addition & 1 deletion packages/fontless/examples/vanilla-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"private": true,
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "vite build",
"preview": "vite preview"
},
"devDependencies": {
Expand Down
19 changes: 13 additions & 6 deletions packages/fontless/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,7 @@ export interface FontFamilyOverrides {
name: string
/** Inject `@font-face` regardless of usage in project. */
global?: boolean
/**
* Enable or disable adding preload links to the initially rendered HTML.
* This is true by default for the highest priority format unless a font is subsetted (to avoid over-preloading).
*/
preload?: boolean
preload?: PreloadOption

// TODO:
// as?: string
Expand All @@ -66,6 +62,17 @@ export interface FontFamilyManualOverride extends FontFamilyOverrides, RawFontFa

type ProviderOption = ((options: any) => Provider) | string | false

/**
* Enable adding preload links to the initially rendered HTML.
* With `subsets`, you can specify which subsets to preload.
* @default false
* @example { subsets: ['latin'] }
*/
type PreloadOption
= | boolean
| { subsets: string[] }
| ((fontFamily: string, font: FontFaceData) => boolean)

export interface FontlessOptions {
/**
* Specify overrides for individual font families.
Expand All @@ -85,7 +92,7 @@ export interface FontlessOptions {
*/
families?: Array<FontFamilyManualOverride | FontFamilyProviderOverride>
defaults?: Partial<{
preload: boolean
preload?: PreloadOption
weights: Array<string | number>
styles: ResolveFontOptions['styles']
subsets: ResolveFontOptions['subsets']
Expand Down
11 changes: 10 additions & 1 deletion packages/fontless/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ export interface FontFamilyInjectionPluginOptions {
resolveFontFace: (fontFamily: string, fallbackOptions?: { fallbacks: string[], generic?: GenericCSSFamily }) => Awaitable<undefined | FontFaceResolution>
dev: boolean
processCSSVariables?: boolean | 'font-prefixed-only'
/** @deprecated use `filterFontsToPreload` instead */
shouldPreload: (fontFamily: string, font: FontFaceData) => boolean
filterFontsToPreload?: (fontFamily: string, fonts: FontFaceData[]) => FontFaceData[]
fontsToPreload: Map<string, Set<string>>
}

Expand Down Expand Up @@ -96,8 +98,15 @@ export async function transformCSS(options: FontFamilyInjectionPluginOptions, co
let insertFontFamilies = false

const [topPriorityFont] = result.fonts.sort((a, b) => (a.meta?.priority || 0) - (b.meta?.priority || 0))
const fontsToPreload: FontFaceData[] = []
if (topPriorityFont && options.shouldPreload(fontFamily, topPriorityFont)) {
const fontToPreload = topPriorityFont.src.find((s): s is RemoteFontSource => 'url' in s)?.url
fontsToPreload.push(topPriorityFont)
}
if (options.filterFontsToPreload) {
fontsToPreload.push(...options.filterFontsToPreload(fontFamily, result.fonts))
}
for (const font of fontsToPreload) {
const fontToPreload = font.src.find((s): s is RemoteFontSource => 'url' in s)?.url
if (fontToPreload) {
const urls = options.fontsToPreload.get(id) || new Set()
options.fontsToPreload.set(id, urls.add(fontToPreload))
Expand Down
18 changes: 16 additions & 2 deletions packages/fontless/src/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,23 @@ export function fontless(_options?: FontlessOptions): Plugin {

cssTransformOptions = {
processCSSVariables: options.processCSSVariables,
shouldPreload(fontFamily, _fontFace) {
shouldPreload: () => false,
filterFontsToPreload(fontFamily, fonts) {
const override = options.families?.find(f => f.name === fontFamily)
return override?.preload ?? options.defaults?.preload ?? false
const preload = override?.preload ?? options.defaults?.preload
// pick by priority (old behavior)
if (preload === true) {
return fonts.sort((a, b) => (a.meta?.priority || 0) - (b.meta?.priority || 0)).slice(0, 1)
}
// filter by function
if (typeof preload === 'function') {
return fonts.filter(f => preload(fontFamily, f))
}
// filter by subset
if (preload && 'subsets' in preload) {
return fonts.filter(f => f.meta?.subset && preload.subsets.includes(f.meta.subset))
}
return []
},
fontsToPreload: new Map(),
dev: config.mode === 'development',
Expand Down
5 changes: 5 additions & 0 deletions packages/fontless/test/e2e.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ describe.each(fixtures)('e2e %s', (fixture) => {
const woff2 = content.indexOf('format(woff2)')
expect(woff >= 0 && woff2 >= 0).toBe(true)
expect(woff).lessThan(woff2)
const html = files.find(file => file.endsWith('.html'))!
const htmlContent = await readFile(join(outputDir!, html), 'utf-8')
expect(htmlContent).toContain('rel="preload" as="font"')
expect(htmlContent).toContain('.woff2"') // woff2 is preloaded
expect(htmlContent).not.toContain('.woff"') // woff is not preloaded
}
}

Expand Down
13 changes: 3 additions & 10 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading