Add support for fallback font and css variable for font/local (#40990)
Adds support for generating a fallback font by providing font override metrics for the given local font. Also adds support for providing a CSS variable name that then can be accessed through the `.variable` export, it contains the hashed font family name. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) Co-authored-by: JJ Kasper <jj@jjsweb.site>
This commit is contained in:
parent
a89d760d60
commit
299f392d7b
15 changed files with 316 additions and 165 deletions
|
@ -1,8 +1,8 @@
|
|||
import type { FontLoader } from 'next/font'
|
||||
import type { AdjustFontFallback, FontLoader } from 'next/font'
|
||||
// @ts-ignore
|
||||
import fetch from 'next/dist/compiled/node-fetch'
|
||||
// @ts-ignore
|
||||
import { calculateOverrideCSS } from 'next/dist/server/font-utils'
|
||||
import { calculateOverrideValues } from 'next/dist/server/font-utils'
|
||||
import {
|
||||
fetchCSSFromGoogleFonts,
|
||||
getFontAxes,
|
||||
|
@ -97,20 +97,36 @@ const downloadGoogleFonts: FontLoader = async ({
|
|||
}
|
||||
|
||||
// Add fallback font
|
||||
let adjustFontFallbackMetrics: AdjustFontFallback | undefined
|
||||
if (adjustFontFallback) {
|
||||
try {
|
||||
updatedCssResponse += calculateOverrideCSS(
|
||||
fontFamily,
|
||||
require('next/dist/server/google-font-metrics.json')
|
||||
const { ascent, descent, lineGap, fallbackFont } =
|
||||
calculateOverrideValues(
|
||||
fontFamily,
|
||||
require('next/dist/server/google-font-metrics.json')
|
||||
)
|
||||
adjustFontFallbackMetrics = {
|
||||
fallbackFont,
|
||||
ascentOverride: ascent,
|
||||
descentOverride: descent,
|
||||
lineGapOverride: lineGap,
|
||||
}
|
||||
} catch {
|
||||
console.error(
|
||||
`Failed to find font override values for font \`${fontFamily}\``
|
||||
)
|
||||
} catch (e) {
|
||||
console.log('Error getting font override values - ', e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
css: updatedCssResponse,
|
||||
fallbackFonts: fallback,
|
||||
weight: weight === 'variable' ? undefined : weight,
|
||||
style,
|
||||
variable: `--next-font-${fontFamily.toLowerCase().replace(/ /g, '-')}${
|
||||
weight !== 'variable' ? `-${weight}` : ''
|
||||
}${style === 'italic' ? `-italic` : ''}`,
|
||||
adjustFontFallback: adjustFontFallbackMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import type { FontModule } from 'next/font'
|
||||
import type { AdjustFontFallback, FontModule } from 'next/font'
|
||||
type Display = 'auto' | 'block' | 'swap' | 'fallback' | 'optional'
|
||||
type LocalFont = {
|
||||
src: string | Array<{ file: string; unicodeRange: string }>
|
||||
|
@ -8,15 +8,18 @@ type LocalFont = {
|
|||
style?: string
|
||||
fallback?: string[]
|
||||
preload?: boolean
|
||||
variable?: string
|
||||
|
||||
ascentOverride?: string
|
||||
descentOverride?: string
|
||||
fontStretch?: string
|
||||
fontVariant?: string
|
||||
fontFeatureSettings?: string
|
||||
fontVariationSettings?: string
|
||||
ascentOverride?: string
|
||||
descentOverride?: string
|
||||
lineGapOverride?: string
|
||||
sizeAdjust?: string
|
||||
|
||||
adjustFontFallback?: AdjustFontFallback
|
||||
}
|
||||
|
||||
export default function localFont(options: LocalFont): FontModule {
|
||||
|
|
|
@ -18,12 +18,14 @@ const fetchFonts: FontLoader = async ({
|
|||
style,
|
||||
fallback,
|
||||
preload,
|
||||
variable,
|
||||
ascentOverride,
|
||||
descentOverride,
|
||||
lineGapOverride,
|
||||
fontStretch,
|
||||
fontFeatureSettings,
|
||||
sizeAdjust,
|
||||
adjustFontFallback,
|
||||
} = validateData(functionName, data)
|
||||
|
||||
const fontFaces = await Promise.all(
|
||||
|
@ -61,6 +63,10 @@ ${fontFaceProperties
|
|||
return {
|
||||
css: fontFaces.join('\n'),
|
||||
fallbackFonts: fallback,
|
||||
weight,
|
||||
style,
|
||||
variable,
|
||||
adjustFontFallback,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { AdjustFontFallback } from 'next/font'
|
||||
|
||||
const allowedDisplayValues = ['auto', 'block', 'swap', 'fallback', 'optional']
|
||||
|
||||
const formatValues = (values: string[]) =>
|
||||
|
@ -24,6 +26,7 @@ type FontOptions = {
|
|||
style?: string
|
||||
fallback?: string[]
|
||||
preload: boolean
|
||||
variable?: string
|
||||
ascentOverride?: string
|
||||
descentOverride?: string
|
||||
fontStretch?: string
|
||||
|
@ -32,6 +35,7 @@ type FontOptions = {
|
|||
fontVariationSettings?: string
|
||||
lineGapOverride?: string
|
||||
sizeAdjust?: string
|
||||
adjustFontFallback?: AdjustFontFallback
|
||||
}
|
||||
export function validateData(functionName: string, data: any): FontOptions {
|
||||
if (functionName) {
|
||||
|
@ -44,6 +48,7 @@ export function validateData(functionName: string, data: any): FontOptions {
|
|||
style,
|
||||
fallback,
|
||||
preload = true,
|
||||
variable,
|
||||
ascentOverride,
|
||||
descentOverride,
|
||||
fontStretch,
|
||||
|
@ -52,6 +57,7 @@ export function validateData(functionName: string, data: any): FontOptions {
|
|||
fontVariationSettings,
|
||||
lineGapOverride,
|
||||
sizeAdjust,
|
||||
adjustFontFallback,
|
||||
} = data[0] || ({} as any)
|
||||
|
||||
if (!allowedDisplayValues.includes(display)) {
|
||||
|
@ -100,6 +106,7 @@ export function validateData(functionName: string, data: any): FontOptions {
|
|||
style,
|
||||
fallback,
|
||||
preload,
|
||||
variable,
|
||||
ascentOverride,
|
||||
descentOverride,
|
||||
fontStretch,
|
||||
|
@ -108,5 +115,6 @@ export function validateData(functionName: string, data: any): FontOptions {
|
|||
fontVariationSettings,
|
||||
lineGapOverride,
|
||||
sizeAdjust,
|
||||
adjustFontFallback,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,18 +43,21 @@ export default async function nextFontLoader(this: any) {
|
|||
this.resourcePath,
|
||||
'../loader.js'
|
||||
)).default
|
||||
let { css, fallbackFonts } = await fontLoader({
|
||||
functionName,
|
||||
data,
|
||||
config: fontLoaderOptions,
|
||||
emitFontFile,
|
||||
resolve: (src: string) =>
|
||||
promisify(this.resolve)(
|
||||
path.dirname(path.join(this.rootContext, relativeFilePathFromRoot)),
|
||||
src
|
||||
),
|
||||
fs: this.fs,
|
||||
})
|
||||
let { css, fallbackFonts, adjustFontFallback, weight, style, variable } =
|
||||
await fontLoader({
|
||||
functionName,
|
||||
data,
|
||||
config: fontLoaderOptions,
|
||||
emitFontFile,
|
||||
resolve: (src: string) =>
|
||||
promisify(this.resolve)(
|
||||
path.dirname(
|
||||
path.join(this.rootContext, relativeFilePathFromRoot)
|
||||
),
|
||||
src
|
||||
),
|
||||
fs: this.fs,
|
||||
})
|
||||
|
||||
const { postcss } = await getPostcss()
|
||||
|
||||
|
@ -68,7 +71,15 @@ export default async function nextFontLoader(this: any) {
|
|||
)
|
||||
// Add CSS classes, exports and make the font-family localy scoped by turning it unguessable
|
||||
const result = await postcss(
|
||||
postcssFontLoaderPlugn(exports, fontFamilyHash, fallbackFonts)
|
||||
postcssFontLoaderPlugn({
|
||||
exports,
|
||||
fontFamilyHash,
|
||||
fallbackFonts,
|
||||
weight,
|
||||
style,
|
||||
adjustFontFallback,
|
||||
variable,
|
||||
})
|
||||
).process(css, {
|
||||
from: undefined,
|
||||
})
|
||||
|
@ -79,9 +90,14 @@ export default async function nextFontLoader(this: any) {
|
|||
version: result.processor.version,
|
||||
root: result.root,
|
||||
}
|
||||
callback(null, result.css, null, { exports, ast, fontFamilyHash })
|
||||
callback(null, result.css, null, {
|
||||
exports,
|
||||
ast,
|
||||
fontFamilyHash,
|
||||
})
|
||||
} catch (err: any) {
|
||||
err.stack = false
|
||||
err.message = `Font loader error:\n${err.message}`
|
||||
err.message += `
|
||||
|
||||
${chalk.cyan(`Location: ${relativeFilePathFromRoot}`)}`
|
||||
|
|
|
@ -1,26 +1,38 @@
|
|||
import type { AdjustFontFallback } from '../../../../font'
|
||||
import postcss, { Declaration } from 'postcss'
|
||||
|
||||
const postcssFontLoaderPlugn = (
|
||||
exports: { name: any; value: any }[],
|
||||
fontFamilyHash: string,
|
||||
fallbackFonts: string[] = []
|
||||
) => {
|
||||
const postcssFontLoaderPlugn = ({
|
||||
exports,
|
||||
fontFamilyHash,
|
||||
fallbackFonts = [],
|
||||
adjustFontFallback,
|
||||
variable,
|
||||
weight,
|
||||
style,
|
||||
}: {
|
||||
exports: { name: any; value: any }[]
|
||||
fontFamilyHash: string
|
||||
fallbackFonts?: string[]
|
||||
adjustFontFallback?: AdjustFontFallback
|
||||
variable?: string
|
||||
weight?: string
|
||||
style?: string
|
||||
}) => {
|
||||
return {
|
||||
postcssPlugin: 'postcss-font-loader',
|
||||
Once(root: any) {
|
||||
const fontFamilies: string[] = []
|
||||
let rawFamily: string | undefined
|
||||
let fontWeight: string | undefined
|
||||
let fontStyle: string | undefined
|
||||
let fontFamily: string | undefined
|
||||
|
||||
const normalizeFamily = (family: string) => {
|
||||
return family.replace(/['"]/g, '')
|
||||
}
|
||||
|
||||
const formatFamily = (family: string) => {
|
||||
if (family[0] === "'" || family[0] === '"') {
|
||||
family = family.slice(1, family.length - 1)
|
||||
}
|
||||
// Turn the font family unguessable to make it localy scoped
|
||||
return `'__${family.replace(/ /g, '_')}_${fontFamilyHash}'`
|
||||
}
|
||||
|
||||
// Hash font-family name
|
||||
for (const node of root.nodes) {
|
||||
if (node.type === 'atrule' && node.name === 'font-face') {
|
||||
const familyNode = node.nodes.find(
|
||||
|
@ -30,52 +42,85 @@ const postcssFontLoaderPlugn = (
|
|||
continue
|
||||
}
|
||||
|
||||
if (!rawFamily) {
|
||||
let family: string = familyNode.value
|
||||
if (family[0] === "'" || family[0] === '"') {
|
||||
family = family.slice(1, family.length - 1)
|
||||
}
|
||||
rawFamily = family
|
||||
}
|
||||
const formattedFamily = formatFamily(familyNode.value)
|
||||
familyNode.value = formattedFamily
|
||||
const currentFamily = normalizeFamily(familyNode.value)
|
||||
|
||||
if (fontFamilies.includes(formattedFamily)) {
|
||||
continue
|
||||
}
|
||||
fontFamilies.push(formattedFamily)
|
||||
|
||||
// Only extract weight and style from first encountered family, the rest will treated as fallbacks
|
||||
if (fontFamilies.length > 1) {
|
||||
continue
|
||||
if (!fontFamily) {
|
||||
fontFamily = currentFamily
|
||||
} else if (fontFamily !== currentFamily) {
|
||||
throw new Error(
|
||||
`Font family mismatch, expected ${fontFamily} but got ${currentFamily}`
|
||||
)
|
||||
}
|
||||
|
||||
// Extract weight and style from first encountered @font-face
|
||||
const weight = node.nodes.find(
|
||||
(decl: Declaration) => decl.prop === 'font-weight'
|
||||
)
|
||||
|
||||
// Skip if the value includes ' ', then it's a range of possible values
|
||||
if (weight && !weight.value.includes(' ')) {
|
||||
fontWeight = weight.value
|
||||
}
|
||||
|
||||
const style = node.nodes.find(
|
||||
(decl: Declaration) => decl.prop === 'font-style'
|
||||
)
|
||||
// Skip if the value includes ' ', then it's a range of possible values
|
||||
if (style && !style.value.includes(' ')) {
|
||||
fontStyle = style.value
|
||||
}
|
||||
familyNode.value = formatFamily(fontFamily)
|
||||
}
|
||||
}
|
||||
|
||||
const [mainFontFamily, ...adjustFontFallbacks] = fontFamilies
|
||||
// If fallback fonts were provided from the font loader, they should be used before the adjustFontFallbacks
|
||||
if (!fontFamily) {
|
||||
throw new Error('Font loaders must have exactly one font family')
|
||||
}
|
||||
|
||||
// Add fallback font with override values
|
||||
let adjustFontFallbackFamily: string | undefined
|
||||
if (adjustFontFallback) {
|
||||
adjustFontFallbackFamily = formatFamily(`${fontFamily} Fallback`)
|
||||
const fallbackFontFace = postcss.atRule({ name: 'font-face' })
|
||||
const {
|
||||
fallbackFont,
|
||||
ascentOverride,
|
||||
descentOverride,
|
||||
lineGapOverride,
|
||||
sizeAdjust,
|
||||
} = adjustFontFallback
|
||||
fallbackFontFace.nodes = [
|
||||
new postcss.Declaration({
|
||||
prop: 'font-family',
|
||||
value: adjustFontFallbackFamily,
|
||||
}),
|
||||
new postcss.Declaration({
|
||||
prop: 'src',
|
||||
value: `local("${fallbackFont}")`,
|
||||
}),
|
||||
...(ascentOverride
|
||||
? [
|
||||
new postcss.Declaration({
|
||||
prop: 'ascent-override',
|
||||
value: ascentOverride,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(descentOverride
|
||||
? [
|
||||
new postcss.Declaration({
|
||||
prop: 'descent-override',
|
||||
value: descentOverride,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(lineGapOverride
|
||||
? [
|
||||
new postcss.Declaration({
|
||||
prop: 'line-gap-override',
|
||||
value: lineGapOverride,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(sizeAdjust
|
||||
? [
|
||||
new postcss.Declaration({
|
||||
prop: 'size-adjust',
|
||||
value: sizeAdjust,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
]
|
||||
root.nodes.push(fallbackFontFace)
|
||||
}
|
||||
|
||||
const formattedFontFamilies = [
|
||||
mainFontFamily,
|
||||
formatFamily(fontFamily),
|
||||
...fallbackFonts,
|
||||
...adjustFontFallbacks,
|
||||
...(adjustFontFallbackFamily ? [adjustFontFallbackFamily] : []),
|
||||
].join(', ')
|
||||
// Add class with family, weight and style
|
||||
const classRule = new postcss.Rule({ selector: '.className' })
|
||||
|
@ -84,19 +129,19 @@ const postcssFontLoaderPlugn = (
|
|||
prop: 'font-family',
|
||||
value: formattedFontFamilies,
|
||||
}),
|
||||
...(fontWeight
|
||||
...(weight
|
||||
? [
|
||||
new postcss.Declaration({
|
||||
prop: 'font-weight',
|
||||
value: fontWeight,
|
||||
value: weight,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
...(fontStyle
|
||||
...(style
|
||||
? [
|
||||
new postcss.Declaration({
|
||||
prop: 'font-style',
|
||||
value: fontStyle,
|
||||
value: style,
|
||||
}),
|
||||
]
|
||||
: []),
|
||||
|
@ -104,26 +149,24 @@ const postcssFontLoaderPlugn = (
|
|||
root.nodes.push(classRule)
|
||||
|
||||
// Add class that defines a variable with the font family
|
||||
const varialbeRule = new postcss.Rule({ selector: '.variable' })
|
||||
varialbeRule.nodes = [
|
||||
new postcss.Declaration({
|
||||
prop: rawFamily
|
||||
? `--next-font-${rawFamily.toLowerCase().replace(/ /g, '-')}${
|
||||
fontWeight ? `-${fontWeight}` : ''
|
||||
}${fontStyle === 'italic' ? `-${fontStyle}` : ''}`
|
||||
: '',
|
||||
value: formattedFontFamilies,
|
||||
}),
|
||||
]
|
||||
root.nodes.push(varialbeRule)
|
||||
if (variable) {
|
||||
const varialbeRule = new postcss.Rule({ selector: '.variable' })
|
||||
varialbeRule.nodes = [
|
||||
new postcss.Declaration({
|
||||
prop: variable,
|
||||
value: formattedFontFamilies,
|
||||
}),
|
||||
]
|
||||
root.nodes.push(varialbeRule)
|
||||
}
|
||||
|
||||
// Export @font-face values as is
|
||||
exports.push({
|
||||
name: 'style',
|
||||
value: {
|
||||
fontFamily: formattedFontFamilies,
|
||||
fontWeight: fontWeight && Number(fontWeight),
|
||||
fontStyle,
|
||||
fontWeight: weight && Number(weight),
|
||||
fontStyle: style,
|
||||
},
|
||||
})
|
||||
},
|
||||
|
|
|
@ -173,6 +173,16 @@ function formatWebpackMessages(json, verbose) {
|
|||
const formattedErrors = json.errors.map(function (message) {
|
||||
let importTraceNote
|
||||
|
||||
if (
|
||||
message &&
|
||||
message.message &&
|
||||
/Font loader error:/.test(message.message)
|
||||
) {
|
||||
return message.message.slice(
|
||||
message.message.indexOf('Font loader error:')
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Shall we use invisible characters in the original error
|
||||
// message as meta information?
|
||||
if (message && message.message && /NEXT_RSC_ERR_/.test(message.message)) {
|
||||
|
|
19
packages/next/font/index.d.ts
vendored
19
packages/next/font/index.d.ts
vendored
|
@ -1,9 +1,17 @@
|
|||
export type FontModule = {
|
||||
className: string
|
||||
variable: string
|
||||
variable?: string
|
||||
style: { fontFamily: string; fontWeight?: number; fontStyle?: string }
|
||||
}
|
||||
|
||||
export type AdjustFontFallback = {
|
||||
fallbackFont: string
|
||||
ascentOverride?: string
|
||||
descentOverride?: string
|
||||
lineGapOverride?: string
|
||||
sizeAdjust?: string
|
||||
}
|
||||
|
||||
export type FontLoader = (options: {
|
||||
functionName: string
|
||||
data: any[]
|
||||
|
@ -11,4 +19,11 @@ export type FontLoader = (options: {
|
|||
emitFontFile: (content: Buffer, ext: string, preload: boolean) => string
|
||||
resolve: (src: string) => string
|
||||
fs: any
|
||||
}) => Promise<{ css: string; fallbackFonts?: string[] }>
|
||||
}) => Promise<{
|
||||
css: string
|
||||
fallbackFonts?: string[]
|
||||
variable?: string
|
||||
adjustFontFallback?: AdjustFontFallback
|
||||
weight?: string
|
||||
style?: string
|
||||
}>
|
||||
|
|
|
@ -98,8 +98,7 @@ function parseGoogleFontName(css: string): Array<string> {
|
|||
return [...fontNames]
|
||||
}
|
||||
|
||||
export function calculateOverrideCSS(font: string, fontMetrics: any) {
|
||||
const fontName = font.toLowerCase().trim().replace(/ /g, '-')
|
||||
export function calculateOverrideValues(font: string, fontMetrics: any) {
|
||||
const fontKey = font.toLowerCase().trim().replace(/ /g, '')
|
||||
const { category, ascentOverride, descentOverride, lineGapOverride } =
|
||||
fontMetrics[fontKey]
|
||||
|
@ -109,6 +108,22 @@ export function calculateOverrideCSS(font: string, fontMetrics: any) {
|
|||
const descent = (descentOverride * 100).toFixed(2)
|
||||
const lineGap = (lineGapOverride * 100).toFixed(2)
|
||||
|
||||
return {
|
||||
ascent,
|
||||
descent,
|
||||
lineGap,
|
||||
fallbackFont,
|
||||
}
|
||||
}
|
||||
|
||||
function calculateOverrideCSS(font: string, fontMetrics: any) {
|
||||
const fontName = font.toLowerCase().trim().replace(/ /g, '-')
|
||||
|
||||
const { ascent, descent, lineGap, fallbackFont } = calculateOverrideValues(
|
||||
font,
|
||||
fontMetrics
|
||||
)
|
||||
|
||||
return `
|
||||
@font-face {
|
||||
font-family: "${fontName}-fallback";
|
||||
|
|
|
@ -55,7 +55,6 @@ describe('app dir next-font', () => {
|
|||
// Comp
|
||||
expect(JSON.parse($('#root-comp').text())).toEqual({
|
||||
className: expect.stringMatching(/^__className_.{6}$/),
|
||||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(/^'__font3_.{6}'$/),
|
||||
fontStyle: 'italic',
|
||||
|
@ -80,7 +79,6 @@ describe('app dir next-font', () => {
|
|||
// layout
|
||||
expect(JSON.parse($('#client-layout').text())).toEqual({
|
||||
className: expect.stringMatching(/^__className_.{6}$/),
|
||||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(/^'__font4_.{6}'$/),
|
||||
fontWeight: 100,
|
||||
|
@ -89,7 +87,6 @@ describe('app dir next-font', () => {
|
|||
// page
|
||||
expect(JSON.parse($('#client-page').text())).toEqual({
|
||||
className: expect.stringMatching(/^__className_.{6}$/),
|
||||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(/^'__font5_.{6}'$/),
|
||||
fontStyle: 'italic',
|
||||
|
@ -98,7 +95,6 @@ describe('app dir next-font', () => {
|
|||
// Comp
|
||||
expect(JSON.parse($('#client-comp').text())).toEqual({
|
||||
className: expect.stringMatching(/^__className_.{6}$/),
|
||||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(/^'__font6_.{6}'$/),
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import localFont from '@next/font/local'
|
||||
|
||||
export const font1 = localFont({ src: './font1.woff2' })
|
||||
export const font2 = localFont({ src: './font2.woff2' })
|
||||
export const font1 = localFont({ src: './font1.woff2', variable: '--font-1' })
|
||||
export const font2 = localFont({ src: './font2.woff2', variable: '--font-2' })
|
||||
export const font3 = localFont({
|
||||
src: './font3.woff2',
|
||||
weight: '900',
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { Fira_Code, Albert_Sans, Inter, Roboto } from '@next/font/google'
|
||||
import localFont from '@next/font/local'
|
||||
|
||||
const firaCode = Fira_Code()
|
||||
const albertSans = Albert_Sans({
|
||||
variant: 'variable-italic',
|
||||
|
@ -10,6 +12,11 @@ const roboto = Roboto({
|
|||
display: 'swap',
|
||||
preload: true,
|
||||
})
|
||||
const myFont = localFont({
|
||||
src: '../fonts/my-font.woff2',
|
||||
preload: false,
|
||||
variable: '--my-font',
|
||||
})
|
||||
|
||||
export default function WithFonts() {
|
||||
return (
|
||||
|
@ -73,6 +80,21 @@ export default function WithFonts() {
|
|||
>
|
||||
Without variables
|
||||
</div>
|
||||
|
||||
{/* Local font */}
|
||||
<div
|
||||
id="variables-local-font"
|
||||
className={myFont.variable}
|
||||
style={{ fontFamily: 'var(--my-font)' }}
|
||||
>
|
||||
With variables
|
||||
</div>
|
||||
<div
|
||||
id="without-variables-local-font"
|
||||
style={{ fontFamily: 'var(--my-font)' }}
|
||||
>
|
||||
Without variables
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,16 @@ const myFont1 = localFont({
|
|||
src: '../fonts/my-font.woff2',
|
||||
style: 'italic',
|
||||
weight: '100',
|
||||
fallback: ['system-ui'],
|
||||
})
|
||||
const myFont2 = localFont({
|
||||
src: '../fonts/my-other-font.woff',
|
||||
preload: false,
|
||||
variable: '--my-font',
|
||||
adjustFontFallback: {
|
||||
fallbackFont: 'Arial',
|
||||
sizeAdjust: '120%',
|
||||
},
|
||||
})
|
||||
|
||||
export default function WithFonts() {
|
||||
|
|
|
@ -46,9 +46,8 @@ describe('@next/font/google', () => {
|
|||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(
|
||||
/^'__Open_Sans_.{6}', '__open-sans-fallback_.{6}'$/
|
||||
/^'__Open_Sans_.{6}', '__Open_Sans_Fallback_.{6}'$/
|
||||
),
|
||||
fontStyle: 'normal',
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -58,9 +57,8 @@ describe('@next/font/google', () => {
|
|||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(
|
||||
/^'__Open_Sans_.{6}', '__open-sans-fallback_.{6}'$/
|
||||
/^'__Open_Sans_.{6}', '__Open_Sans_Fallback_.{6}'$/
|
||||
),
|
||||
fontStyle: 'normal',
|
||||
},
|
||||
})
|
||||
|
||||
|
@ -70,9 +68,8 @@ describe('@next/font/google', () => {
|
|||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(
|
||||
/^'__Inter_.{6}', '__inter-fallback_.{6}'$/
|
||||
/^'__Inter_.{6}', '__Inter_Fallback_.{6}'$/
|
||||
),
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 900,
|
||||
},
|
||||
})
|
||||
|
@ -81,7 +78,7 @@ describe('@next/font/google', () => {
|
|||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(
|
||||
/^'__Roboto_.{6}', '__roboto-fallback_.{6}'$/
|
||||
/^'__Roboto_.{6}', '__Roboto_Fallback_.{6}'$/
|
||||
),
|
||||
fontStyle: 'italic',
|
||||
fontWeight: 100,
|
||||
|
@ -95,31 +92,31 @@ describe('@next/font/google', () => {
|
|||
|
||||
// _app.js
|
||||
expect(JSON.parse($('#app-open-sans').text())).toEqual({
|
||||
className: '__className_f32d04',
|
||||
variable: '__variable_f32d04',
|
||||
className: expect.stringMatching(/__className_.{6}/),
|
||||
variable: expect.stringMatching(/__variable_.{6}/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(
|
||||
/^'__Open_Sans_.{6}', '__open-sans-fallback_.{6}'$/
|
||||
/^'__Open_Sans_.{6}', '__Open_Sans_Fallback_.{6}'$/
|
||||
),
|
||||
fontStyle: 'normal',
|
||||
},
|
||||
})
|
||||
|
||||
// with-local-fonts.js
|
||||
expect(JSON.parse($('#first-local-font').text())).toEqual({
|
||||
className: '__className_410624',
|
||||
variable: '__variable_410624',
|
||||
className: expect.stringMatching(/__className_.{6}/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(/^'__my-font_.{6}'$/),
|
||||
fontFamily: expect.stringMatching(/^'__my-font_.{6}', system-ui$/),
|
||||
fontStyle: 'italic',
|
||||
fontWeight: 100,
|
||||
},
|
||||
})
|
||||
expect(JSON.parse($('#second-local-font').text())).toEqual({
|
||||
className: '__className_3ff726',
|
||||
variable: '__variable_3ff726',
|
||||
className: expect.stringMatching(/^__className_.{6}$/),
|
||||
variable: expect.stringMatching(/^__variable_.{6}$/),
|
||||
style: {
|
||||
fontFamily: expect.stringMatching(/^'__my-other-font_.{6}'$/),
|
||||
fontFamily: expect.stringMatching(
|
||||
/^'__my-other-font_.{6}', '__my-other-font_Fallback_.{6}'$/
|
||||
),
|
||||
},
|
||||
})
|
||||
})
|
||||
|
@ -134,7 +131,7 @@ describe('@next/font/google', () => {
|
|||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#app-open-sans")).fontFamily'
|
||||
)
|
||||
).toMatch(/^__Open_Sans_.{6}, __open-sans-fallback_.{6}$/)
|
||||
).toMatch(/^__Open_Sans_.{6}, __Open_Sans_Fallback_.{6}$/)
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#app-open-sans")).fontWeight'
|
||||
|
@ -151,7 +148,7 @@ describe('@next/font/google', () => {
|
|||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#with-fonts-open-sans")).fontFamily'
|
||||
)
|
||||
).toMatch(/^__Open_Sans_.{6}, __open-sans-fallback_.{6}$/)
|
||||
).toMatch(/^__Open_Sans_.{6}, __Open_Sans_Fallback_.{6}$/)
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#with-fonts-open-sans")).fontWeight'
|
||||
|
@ -178,7 +175,7 @@ describe('@next/font/google', () => {
|
|||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#comp-with-fonts-inter")).fontFamily'
|
||||
)
|
||||
).toMatch(/^__Inter_.{6}, __inter-fallback_.{6}$/)
|
||||
).toMatch(/^__Inter_.{6}, __Inter_Fallback_.{6}$/)
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#comp-with-fonts-inter")).fontWeight'
|
||||
|
@ -194,7 +191,7 @@ describe('@next/font/google', () => {
|
|||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#comp-with-fonts-roboto")).fontFamily'
|
||||
)
|
||||
).toMatch(/^__Roboto_.{6}, __roboto-fallback_.{6}$/)
|
||||
).toMatch(/^__Roboto_.{6}, __Roboto_Fallback_.{6}$/)
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#comp-with-fonts-roboto")).fontWeight'
|
||||
|
@ -211,7 +208,7 @@ describe('@next/font/google', () => {
|
|||
const browser = await webdriver(next.url, '/variables')
|
||||
|
||||
// Fira Code Variable
|
||||
const firaCodeRegex = /^__Fira_Code_.{6}, __fira-code-fallback_.{6}$/
|
||||
const firaCodeRegex = /^__Fira_Code_.{6}, __Fira_Code_Fallback_.{6}$/
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#variables-fira-code")).fontFamily'
|
||||
|
@ -237,7 +234,7 @@ describe('@next/font/google', () => {
|
|||
).not.toMatch(albertSansItalicRegex)
|
||||
|
||||
// Inter 900
|
||||
const inter900Regex = /^__Inter_.{6}, __inter-fallback_.{6}$/
|
||||
const inter900Regex = /^__Inter_.{6}, __Inter_Fallback_.{6}$/
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#variables-inter-900")).fontFamily'
|
||||
|
@ -250,7 +247,7 @@ describe('@next/font/google', () => {
|
|||
).not.toMatch(inter900Regex)
|
||||
|
||||
// Roboto 100 Italic
|
||||
const roboto100ItalicRegex = /^__Roboto_.{6}, __roboto-fallback_.{6}$/
|
||||
const roboto100ItalicRegex = /^__Roboto_.{6}, __Roboto_Fallback_.{6}$/
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#variables-roboto-100-italic")).fontFamily'
|
||||
|
@ -261,6 +258,19 @@ describe('@next/font/google', () => {
|
|||
'getComputedStyle(document.querySelector("#without-variables-roboto-100-italic")).fontFamily'
|
||||
)
|
||||
).not.toMatch(roboto100ItalicRegex)
|
||||
|
||||
// Local font
|
||||
const localFontRegex = /^__my-font_.{6}$/
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#variables-local-font")).fontFamily'
|
||||
)
|
||||
).toMatch(localFontRegex)
|
||||
expect(
|
||||
await browser.eval(
|
||||
'getComputedStyle(document.querySelector("#without-variables-local-font")).fontFamily'
|
||||
)
|
||||
).not.toMatch(localFontRegex)
|
||||
})
|
||||
|
||||
test('page using fallback fonts', async () => {
|
||||
|
@ -272,7 +282,7 @@ describe('@next/font/google', () => {
|
|||
'getComputedStyle(document.querySelector("#with-fallback-fonts-classname")).fontFamily'
|
||||
)
|
||||
).toMatch(
|
||||
/^__Open_Sans_.{6}, system-ui, Arial, __open-sans-fallback_.{6}$/
|
||||
/^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/
|
||||
)
|
||||
|
||||
// .style
|
||||
|
@ -281,7 +291,7 @@ describe('@next/font/google', () => {
|
|||
'getComputedStyle(document.querySelector("#with-fallback-fonts-style")).fontFamily'
|
||||
)
|
||||
).toMatch(
|
||||
/^__Open_Sans_.{6}, system-ui, Arial, __open-sans-fallback_.{6}$/
|
||||
/^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/
|
||||
)
|
||||
|
||||
// .variable
|
||||
|
@ -290,7 +300,7 @@ describe('@next/font/google', () => {
|
|||
'getComputedStyle(document.querySelector("#with-fallback-fonts-variable")).fontFamily'
|
||||
)
|
||||
).toMatch(
|
||||
/^__Open_Sans_.{6}, system-ui, Arial, __open-sans-fallback_.{6}$/
|
||||
/^__Open_Sans_.{6}, system-ui, Arial, __Open_Sans_Fallback_.{6}$/
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -93,7 +93,7 @@ describe('@next/font/google loader', () => {
|
|||
ok: true,
|
||||
text: async () => '',
|
||||
})
|
||||
const { css, fallbackFonts } = await loader({
|
||||
const { adjustFontFallback, fallbackFonts } = await loader({
|
||||
functionName: 'Inter',
|
||||
data: [],
|
||||
config: { subsets: [] },
|
||||
|
@ -101,17 +101,12 @@ describe('@next/font/google loader', () => {
|
|||
resolve: jest.fn(),
|
||||
fs: {} as any,
|
||||
})
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"
|
||||
@font-face {
|
||||
font-family: \\"inter-fallback\\";
|
||||
ascent-override: 96.88%;
|
||||
descent-override: 24.15%;
|
||||
line-gap-override: 0.00%;
|
||||
src: local(\\"Arial\\");
|
||||
}
|
||||
"
|
||||
`)
|
||||
expect(adjustFontFallback).toEqual({
|
||||
ascentOverride: '96.88',
|
||||
descentOverride: '24.15',
|
||||
fallbackFont: 'Arial',
|
||||
lineGapOverride: '0.00',
|
||||
})
|
||||
expect(fallbackFonts).toBeUndefined()
|
||||
})
|
||||
|
||||
|
@ -120,7 +115,7 @@ describe('@next/font/google loader', () => {
|
|||
ok: true,
|
||||
text: async () => '',
|
||||
})
|
||||
const { css, fallbackFonts } = await loader({
|
||||
const { fallbackFonts, adjustFontFallback } = await loader({
|
||||
functionName: 'Source_Code_Pro',
|
||||
data: [],
|
||||
config: { subsets: [] },
|
||||
|
@ -128,17 +123,12 @@ describe('@next/font/google loader', () => {
|
|||
resolve: jest.fn(),
|
||||
fs: {} as any,
|
||||
})
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"
|
||||
@font-face {
|
||||
font-family: \\"source-code-pro-fallback\\";
|
||||
ascent-override: 98.40%;
|
||||
descent-override: 27.30%;
|
||||
line-gap-override: 0.00%;
|
||||
src: local(\\"Arial\\");
|
||||
}
|
||||
"
|
||||
`)
|
||||
expect(adjustFontFallback).toEqual({
|
||||
ascentOverride: '98.40',
|
||||
descentOverride: '27.30',
|
||||
fallbackFont: 'Arial',
|
||||
lineGapOverride: '0.00',
|
||||
})
|
||||
expect(fallbackFonts).toBeUndefined()
|
||||
})
|
||||
|
||||
|
@ -147,7 +137,7 @@ describe('@next/font/google loader', () => {
|
|||
ok: true,
|
||||
text: async () => '',
|
||||
})
|
||||
const { css, fallbackFonts } = await loader({
|
||||
const { adjustFontFallback, fallbackFonts } = await loader({
|
||||
functionName: 'Fraunces',
|
||||
data: [{ fallback: ['Abc', 'Def'] }],
|
||||
config: { subsets: [] },
|
||||
|
@ -155,17 +145,12 @@ describe('@next/font/google loader', () => {
|
|||
resolve: jest.fn(),
|
||||
fs: {} as any,
|
||||
})
|
||||
expect(css).toMatchInlineSnapshot(`
|
||||
"
|
||||
@font-face {
|
||||
font-family: \\"fraunces-fallback\\";
|
||||
ascent-override: 97.80%;
|
||||
descent-override: 25.50%;
|
||||
line-gap-override: 0.00%;
|
||||
src: local(\\"Times New Roman\\");
|
||||
}
|
||||
"
|
||||
`)
|
||||
expect(adjustFontFallback).toEqual({
|
||||
ascentOverride: '97.80',
|
||||
descentOverride: '25.50',
|
||||
fallbackFont: 'Times New Roman',
|
||||
lineGapOverride: '0.00',
|
||||
})
|
||||
expect(fallbackFonts).toEqual(['Abc', 'Def'])
|
||||
})
|
||||
|
||||
|
|
Loading…
Reference in a new issue