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

Adding experimentalAdjustFallback feature to font optimization #40185

Merged
merged 28 commits into from Sep 16, 2022
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
cc484c5
Adding experimentalAdjustFallback feture to font optimization
janicklas-ralph Sep 2, 2022
c413060
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 6, 2022
203fde2
Fix lint
janicklas-ralph Sep 6, 2022
6cb2099
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 8, 2022
051f486
Removing serverless tests since target: serverless is being deprecated
janicklas-ralph Sep 8, 2022
1f4cfa6
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 8, 2022
be23651
Removing serverless tests since target: serverless is being deprecated
janicklas-ralph Sep 8, 2022
3a44f45
Adding font override test case
janicklas-ralph Sep 8, 2022
5f31377
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 8, 2022
8097ac8
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 13, 2022
680ea2a
Addressing review comments. Fixing failing tests
janicklas-ralph Sep 13, 2022
eda0789
Adding serverless tests back. Fixed serverless tests
janicklas-ralph Sep 13, 2022
f9c39c3
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 13, 2022
8cb7714
update font-metrics to JSON
ijjk Sep 14, 2022
16e2df4
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 15, 2022
0b2da36
Merge branch 'font-override' of github.com:janicklas-ralph/next.js in…
janicklas-ralph Sep 15, 2022
9ef2d1c
Fix types
janicklas-ralph Sep 15, 2022
cb2837f
Fix config schema
janicklas-ralph Sep 15, 2022
4d55860
Making optimizefonts backward compatible, work with either bool or ob…
janicklas-ralph Sep 15, 2022
883492c
Fix json import error
janicklas-ralph Sep 15, 2022
c5cbe14
usMerge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 15, 2022
92b9491
Changed the Fontconfig types to handle boolean values for backward co…
janicklas-ralph Sep 15, 2022
a682cf5
t Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 15, 2022
2e803f8
Separating the experimental config out of optimizeFonts flag
janicklas-ralph Sep 16, 2022
490be99
Merge branch 'canary' of github.com:vercel/next.js into font-override
janicklas-ralph Sep 16, 2022
1c82ede
Fix lint
janicklas-ralph Sep 16, 2022
d2a3318
Merge branch 'canary' into font-override
ijjk Sep 16, 2022
1afdc33
tweak config a bit
ijjk Sep 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/next/build/webpack-config.ts
Expand Up @@ -177,7 +177,7 @@ export function getDefineEnv({
'process.env.__NEXT_STRICT_MODE': JSON.stringify(config.reactStrictMode),
'process.env.__NEXT_REACT_ROOT': JSON.stringify(hasReactRoot),
'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
config.optimizeFonts && !dev
!dev && config.optimizeFonts
),
'process.env.__NEXT_OPTIMIZE_CSS': JSON.stringify(
config.experimental.optimizeCss && !dev
Expand Down Expand Up @@ -1779,6 +1779,7 @@ export default async function getBaseWebpackConfig(
}
return new FontStylesheetGatheringPlugin({
isLikeServerless,
optimizeFonts: config.optimizeFonts,
})
})(),
new WellKnownErrorsPlugin(),
Expand Down
Expand Up @@ -284,7 +284,7 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
const isPreviewMode = previewData !== false

if (process.env.__NEXT_OPTIMIZE_FONTS) {
renderOpts.optimizeFonts = true
renderOpts.optimizeFonts = process.env.__NEXT_OPTIMIZE_FONTS
/**
* __webpack_require__.__NEXT_FONT_MANIFEST__ is added by
* font-stylesheet-gathering-plugin
Expand Down
Expand Up @@ -5,7 +5,9 @@ import {
} from 'next/dist/compiled/webpack/webpack'
import {
getFontDefinitionFromNetwork,
getFontOverrideCss,
FontManifest,
FontConfig,
} from '../../../server/font-utils'
import postcss from 'postcss'
import minifier from 'next/dist/compiled/cssnano-simple'
Expand Down Expand Up @@ -52,9 +54,17 @@ export class FontStylesheetGatheringPlugin {
gatheredStylesheets: Array<string> = []
manifestContent: FontManifest = []
isLikeServerless: boolean
optimizeFonts: FontConfig

constructor({ isLikeServerless }: { isLikeServerless: boolean }) {
constructor({
isLikeServerless,
optimizeFonts,
}: {
isLikeServerless: boolean
optimizeFonts: FontConfig
}) {
this.isLikeServerless = isLikeServerless
this.optimizeFonts = optimizeFonts
}

private parserHandler = (
Expand Down Expand Up @@ -186,7 +196,9 @@ export class FontStylesheetGatheringPlugin {
this.manifestContent
)};
// Enable feature:
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true);`
process.env.__NEXT_OPTIMIZE_FONTS = ${JSON.stringify(
this.optimizeFonts
)};`
}
)
}
Expand All @@ -212,7 +224,11 @@ export class FontStylesheetGatheringPlugin {

this.manifestContent = []
for (let promiseIndex in fontDefinitionPromises) {
const css = await fontDefinitionPromises[promiseIndex]
let css = await fontDefinitionPromises[promiseIndex]

if (this.optimizeFonts?.experimentalAdjustFallbacks) {
css += getFontOverrideCss(fontStylesheets[promiseIndex], css)
}

if (css) {
try {
Expand Down
24 changes: 13 additions & 11 deletions packages/next/export/worker.ts
@@ -1,5 +1,5 @@
import type { ComponentType } from 'react'
import type { FontManifest } from '../server/font-utils'
import type { FontManifest, FontConfig } from '../server/font-utils'
import type { GetStaticProps } from '../types'
import type { IncomingMessage, ServerResponse } from 'http'
import type { DomainLocale, NextConfigComplete } from '../server/config-shared'
Expand Down Expand Up @@ -59,7 +59,7 @@ interface ExportPageInput {
serverRuntimeConfig: { [key: string]: any }
subFolders?: boolean
serverless: boolean
optimizeFonts: boolean
optimizeFonts: FontConfig
optimizeCss: any
disableOptimizedLoading: any
parentSpanId: any
Expand All @@ -82,7 +82,7 @@ interface RenderOpts {
ampPath?: string
ampValidatorPath?: string
ampSkipValidation?: boolean
optimizeFonts?: boolean
optimizeFonts?: FontConfig
disableOptimizedLoading?: boolean
optimizeCss?: any
fontManifest?: FontManifest
Expand Down Expand Up @@ -340,9 +340,10 @@ export default async function exportPage({
optimizeCss,
disableOptimizedLoading,
distDir,
fontManifest: optimizeFonts
? requireFontManifest(distDir, serverless)
: null,
fontManifest:
optimizeFonts && optimizeFonts.inlineFonts
? requireFontManifest(distDir, serverless)
: null,
locale: locale!,
locales: renderOpts.locales!,
},
Expand Down Expand Up @@ -401,8 +402,8 @@ export default async function exportPage({
* `process.env.__NEXT_OPTIMIZE_FONTS`.
* TODO(prateekbh@): Remove this when experimental.optimizeFonts are being cleaned up.
*/
if (optimizeFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true)
if (optimizeFonts && optimizeFonts.inlineFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(optimizeFonts)
}
if (optimizeCss) {
process.env.__NEXT_OPTIMIZE_CSS = JSON.stringify(true)
Expand All @@ -415,9 +416,10 @@ export default async function exportPage({
optimizeFonts,
optimizeCss,
disableOptimizedLoading,
fontManifest: optimizeFonts
? requireFontManifest(distDir, serverless)
: null,
fontManifest:
optimizeFonts && optimizeFonts.inlineFonts
? requireFontManifest(distDir, serverless)
: null,
locale: locale as string,
}
renderResult = await renderMethod(
Expand Down
6 changes: 3 additions & 3 deletions packages/next/pages/_document.tsx
Expand Up @@ -420,7 +420,7 @@ export class Head extends Component<HeadProps> {
)
})

if (process.env.NODE_ENV !== 'development' && optimizeFonts) {
if (process.env.NODE_ENV !== 'development' && optimizeFonts?.inlineFonts) {
cssLinkElements = this.makeStylesheetInert(
cssLinkElements
) as ReactElement[]
Expand Down Expand Up @@ -657,7 +657,7 @@ export class Head extends Component<HeadProps> {

if (
process.env.NODE_ENV !== 'development' &&
optimizeFonts &&
optimizeFonts?.inlineFonts &&
!(process.env.NEXT_RUNTIME !== 'edge' && inAmpMode)
) {
children = this.makeStylesheetInert(children)
Expand Down Expand Up @@ -754,7 +754,7 @@ export class Head extends Component<HeadProps> {
/>

{children}
{optimizeFonts && <meta name="next-font-preconnect" />}
{optimizeFonts?.inlineFonts && <meta name="next-font-preconnect" />}

{process.env.NEXT_RUNTIME !== 'edge' && inAmpMode && (
<>
Expand Down
11 changes: 7 additions & 4 deletions packages/next/server/base-server.ts
Expand Up @@ -2,7 +2,7 @@ import type { __ApiPreviewProps } from './api-utils'
import type { CustomRoutes } from '../lib/load-custom-routes'
import type { DomainLocale } from './config'
import type { DynamicRoutes, PageChecker, Route } from './router'
import type { FontManifest } from './font-utils'
import type { FontManifest, FontConfig } from './font-utils'
import type { LoadComponentsReturnType } from './load-components'
import type { RouteMatch } from '../shared/lib/router/utils/route-matcher'
import type { MiddlewareRouteMatch } from '../shared/lib/router/utils/middleware-route-matcher'
Expand Down Expand Up @@ -201,7 +201,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
customServer?: boolean
ampOptimizerConfig?: { [key: string]: any }
basePath: string
optimizeFonts: boolean
optimizeFonts: FontConfig
images: ImageConfigComplete
fontManifest?: FontManifest
disableOptimizedLoading?: boolean
Expand Down Expand Up @@ -381,9 +381,11 @@ export default abstract class Server<ServerOptions extends Options = Options> {
ampOptimizerConfig: this.nextConfig.experimental.amp?.optimizer,
basePath: this.nextConfig.basePath,
images: this.nextConfig.images,
optimizeFonts: !!this.nextConfig.optimizeFonts && !dev,
optimizeFonts: this.nextConfig.optimizeFonts,
fontManifest:
this.nextConfig.optimizeFonts && !dev
this.nextConfig.optimizeFonts &&
this.nextConfig.optimizeFonts.inlineFonts &&
!dev
? this.getFontManifest()
: undefined,
optimizeCss: this.nextConfig.experimental.optimizeCss,
Expand Down Expand Up @@ -1194,6 +1196,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
locale,
locales,
defaultLocale,
optimizeFonts: this.renderOpts.optimizeFonts,
optimizeCss: this.renderOpts.optimizeCss,
nextScriptWorkers: this.renderOpts.nextScriptWorkers,
distDir: this.distDir,
Expand Down
17 changes: 16 additions & 1 deletion packages/next/server/config-schema.ts
Expand Up @@ -563,7 +563,22 @@ const configSchema = {
type: 'object',
},
optimizeFonts: {
type: 'boolean',
oneOf: [
{
type: 'boolean',
},
{
properties: {
inlineFonts: {
type: 'boolean',
},
experimentalAdjustFallbacks: {
type: 'boolean',
},
},
type: 'object',
},
] as any,
},
output: {
// automatic typing doesn't like enum
Expand Down
12 changes: 9 additions & 3 deletions packages/next/server/config-shared.ts
Expand Up @@ -355,11 +355,14 @@ export interface NextConfig extends Record<string, any> {
/**
* By default, Next.js will automatically inline font CSS at build time
*
* @default true
* @default { inlineFonts: true }
* @since version 10.2
* @see [Font Optimization](https://nextjs.org/docs/basic-features/font-optimization)
*/
optimizeFonts?: boolean
optimizeFonts?: {
janicklas-ralph marked this conversation as resolved.
Show resolved Hide resolved
inlineFonts: boolean
experimentalAdjustFallbacks?: boolean
}

/**
* The Next.js runtime is Strict Mode-compliant.
Expand Down Expand Up @@ -523,7 +526,10 @@ export const defaultConfig: NextConfig = {
trailingSlash: false,
i18n: null,
productionBrowserSourceMaps: false,
optimizeFonts: true,
optimizeFonts: {
inlineFonts: true,
experimentalAdjustFallbacks: false,
},
webpack5: undefined,
excludeDefaultMomentLocales: true,
serverRuntimeConfig: {},
Expand Down
68 changes: 67 additions & 1 deletion packages/next/server/font-utils.ts
@@ -1,5 +1,10 @@
import * as Log from '../build/output/log'
import { GOOGLE_FONT_PROVIDER } from '../shared/lib/constants'
import {
GOOGLE_FONT_PROVIDER,
DEFAULT_SERIF_FONT,
DEFAULT_SANS_SERIF_FONT,
} from '../shared/lib/constants'
const googleFontsMetrics = require('./google-font-metrics.json')
const https = require('https')

const CHROME_UA =
Expand All @@ -11,6 +16,11 @@ export type FontManifest = Array<{
content: string
}>

export type FontConfig = {
experimentalAdjustFallbacks?: boolean
inlineFonts: boolean
}

function isGoogleFont(url: string): boolean {
return url.startsWith(GOOGLE_FONT_PROVIDER)
}
Expand Down Expand Up @@ -77,3 +87,59 @@ export function getFontDefinitionFromManifest(
})?.content || ''
)
}

function parseGoogleFontName(css: string): Array<string> {
const regex = /font-family: ([^;]*)/g
const matches = css.matchAll(regex)
const fontNames = new Set<string>()

for (let font of matches) {
const fontFamily = font[1].replace(/^['"]|['"]$/g, '')
fontNames.add(fontFamily)
}

return [...fontNames]
}

function calculateOverrideCSS(font: string, fontMetrics: any) {
const fontName = font.toLowerCase().trim().replace(/ /g, '-')
const fontKey = font.toLowerCase().trim().replace(/ /g, '')
janicklas-ralph marked this conversation as resolved.
Show resolved Hide resolved
const { category, ascentOverride, descentOverride, lineGapOverride } =
fontMetrics[fontKey]
const fallbackFont =
category === 'serif' ? DEFAULT_SERIF_FONT : DEFAULT_SANS_SERIF_FONT
const ascent = (ascentOverride * 100).toFixed(2)
const descent = (descentOverride * 100).toFixed(2)
const lineGap = (lineGapOverride * 100).toFixed(2)

return `
@font-face {
font-family: "${fontName}-fallback";
ascent-override: ${ascent}%;
descent-override: ${descent}%;
line-gap-override: ${lineGap}%;
src: local("${fallbackFont}");
}
`
}

export function getFontOverrideCss(url: string, css: string) {
if (!isGoogleFont(url)) {
return ''
}

try {
const fontNames = parseGoogleFontName(css)
const fontMetrics = googleFontsMetrics

const fontCss = fontNames.reduce((cssStr, fontName) => {
cssStr += calculateOverrideCSS(fontName, fontMetrics)
return cssStr
}, '')

return fontCss
} catch (e) {
console.log('Error getting font override values - ', e)
return ''
}
}