Skip to content

Commit

Permalink
Refactor next/font/google and add additional comments (vercel#46692)
Browse files Browse the repository at this point in the history
Some refactoring in `next/font/google`. Also adds comments to places I
felt some extra context would be helpful for understanding the code.

## Bug

- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Errors have a helpful link attached, see
[`contributing.md`](https://github.com/vercel/next.js/blob/canary/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`
- [ ]
[e2e](https://github.com/vercel/next.js/blob/canary/contributing/core/testing.md#writing-tests-for-nextjs)
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`](https://github.com/vercel/next.js/blob/canary/contributing.md)

## Documentation / Examples

- [ ] Make sure the linting passes by running `pnpm build && pnpm lint`
- [ ] The "examples guidelines" are followed from [our contributing
doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md)
  • Loading branch information
hanneslund committed Mar 2, 2023
1 parent ad6961d commit 2a737e7
Show file tree
Hide file tree
Showing 3 changed files with 178 additions and 62 deletions.
97 changes: 38 additions & 59 deletions packages/font/src/google/loader.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import type { AdjustFontFallback, FontLoader } from 'next/font'
// @ts-ignore
import { calculateSizeAdjustValues } from 'next/dist/server/font-utils'
// @ts-ignore
import * as Log from 'next/dist/build/output/log'
import {
fetchCSSFromGoogleFonts,
fetchFontFile,
findFontFilesInCss,
getFallbackFontOverrideMetrics,
getFontAxes,
getUrl,
validateData,
} from './utils'
import { nextFontError } from '../utils'

const cssCache = new Map<string, Promise<string>>()
const fontCache = new Map<string, any>()
const cssCache = new Map<string, string | null>()
const fontCache = new Map<string, Buffer | null>()

// regexp is based on https://github.com/sindresorhus/escape-string-regexp
const reHasRegExp = /[|\\{}()[\]^$+*?.-]/
Expand All @@ -27,7 +27,7 @@ function escapeStringRegexp(str: string) {
return str
}

const downloadGoogleFonts: FontLoader = async ({
const nextFontGoogleFontLoader: FontLoader = async ({
functionName,
data,
config,
Expand All @@ -49,35 +49,20 @@ const downloadGoogleFonts: FontLoader = async ({
subsets,
} = validateData(functionName, data, config)

// Validate and get the font axes required to generated the URL
const fontAxes = getFontAxes(
fontFamily,
weights,
styles,
selectedVariableAxes
)

// Generate the Google Fonts URL from the font family, axes and display value
const url = getUrl(fontFamily, fontAxes, display)

// Find fallback font metrics
let adjustFontFallbackMetrics: AdjustFontFallback | undefined
if (adjustFontFallback) {
try {
const { ascent, descent, lineGap, fallbackFont, sizeAdjust } =
calculateSizeAdjustValues(
require('next/dist/server/google-font-metrics.json')[fontFamily]
)
adjustFontFallbackMetrics = {
fallbackFont,
ascentOverride: `${ascent}%`,
descentOverride: `${descent}%`,
lineGapOverride: `${lineGap}%`,
sizeAdjust: `${sizeAdjust}%`,
}
} catch {
Log.error(
`Failed to find font override values for font \`${fontFamily}\``
)
}
}
// Get precalculated fallback font metrics, used to generate the fallback font CSS
const adjustFontFallbackMetrics: AdjustFontFallback | undefined =
adjustFontFallback ? getFallbackFontOverrideMetrics(fontFamily) : undefined

const result = {
fallbackFonts: fallback,
Expand All @@ -91,62 +76,48 @@ const downloadGoogleFonts: FontLoader = async ({
}

try {
/**
* Hacky way to make sure the fetch is only done once.
* Otherwise both the client and server compiler would fetch the CSS.
* The reason we need to return the actual CSS from both the server and client is because a hash is generated based on the CSS content.
*/
const hasCachedCSS = cssCache.has(url)
// Fetch CSS from Google Fonts or get it from the cache
let fontFaceDeclarations = hasCachedCSS
? cssCache.get(url)
: await fetchCSSFromGoogleFonts(url, fontFamily).catch(() => null)
if (!hasCachedCSS) {
cssCache.set(url, fontFaceDeclarations)
cssCache.set(url, fontFaceDeclarations ?? null)
} else {
cssCache.delete(url)
}
if (fontFaceDeclarations === null) {
if (fontFaceDeclarations == null) {
nextFontError(`Failed to fetch \`${fontFamily}\` from Google Fonts.`)
}

// CSS Variables may be set on a body tag, ignore them to keep the CSS module pure
fontFaceDeclarations = fontFaceDeclarations.split('body {')[0]

// Find font files to download
const fontFiles: Array<{
googleFontFileUrl: string
preloadFontFile: boolean
}> = []
let currentSubset = ''
for (const line of fontFaceDeclarations.split('\n')) {
// Each @font-face has the subset above it in a comment
const newSubset = /\/\* (.+?) \*\//.exec(line)?.[1]
if (newSubset) {
currentSubset = newSubset
} else {
const googleFontFileUrl = /src: url\((.+?)\)/.exec(line)?.[1]
if (
googleFontFileUrl &&
!fontFiles.some(
(foundFile) => foundFile.googleFontFileUrl === googleFontFileUrl
)
) {
fontFiles.push({
googleFontFileUrl,
preloadFontFile: !!preload && subsets.includes(currentSubset),
})
}
}
}
// Find font files to download, provide the array of subsets we want to preload if preloading is enabled
const fontFiles = findFontFilesInCss(
fontFaceDeclarations,
preload ? subsets : undefined
)

// Download font files
// Download the font files extracted from the CSS
const downloadedFiles = await Promise.all(
fontFiles.map(async ({ googleFontFileUrl, preloadFontFile }) => {
const hasCachedFont = fontCache.has(googleFontFileUrl)
// Download the font file or get it from cache
const fontFileBuffer = hasCachedFont
? fontCache.get(googleFontFileUrl)
: await fetchFontFile(googleFontFileUrl).catch(() => null)
if (!hasCachedFont) {
fontCache.set(googleFontFileUrl, fontFileBuffer)
fontCache.set(googleFontFileUrl, fontFileBuffer ?? null)
} else {
fontCache.delete(googleFontFileUrl)
}
if (fontFileBuffer === null) {
if (fontFileBuffer == null) {
nextFontError(`Failed to fetch \`${fontFamily}\` from Google Fonts.`)
}

Expand All @@ -166,7 +137,15 @@ const downloadGoogleFonts: FontLoader = async ({
})
)

// Replace @font-face sources with self-hosted files
/**
* Replace the @font-face sources with the self-hosted files we just downloaded to .next/static/media
*
* E.g.
* @font-face {
* font-family: 'Inter';
* src: url(https://fonts.gstatic.com/...) -> url(/_next/static/media/_.woff2)
* }
*/
let updatedCssResponse = fontFaceDeclarations
for (const { googleFontFileUrl, selfHostedFileUrl } of downloadedFiles) {
updatedCssResponse = updatedCssResponse.replace(
Expand Down Expand Up @@ -212,4 +191,4 @@ const downloadGoogleFonts: FontLoader = async ({
}
}

export default downloadGoogleFonts
export default nextFontGoogleFontLoader
Loading

0 comments on commit 2a737e7

Please sign in to comment.