Font optimization (#21676)

Enable font optimization by default
This commit is contained in:
Janicklas Ralph 2021-04-05 10:47:03 -07:00 committed by GitHub
parent bf629f9427
commit 8fdcc52854
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 78 additions and 60 deletions

View file

@ -453,7 +453,7 @@ export default async function build(
BUILD_MANIFEST,
PRERENDER_MANIFEST,
REACT_LOADABLE_MANIFEST,
config.experimental.optimizeFonts
config.optimizeFonts
? path.join(
isLikeServerless ? SERVERLESS_DIRECTORY : SERVER_DIRECTORY,
FONT_MANIFEST

View file

@ -1055,7 +1055,7 @@ export default async function getBaseWebpackConfig(
config.experimental.reactMode
),
'process.env.__NEXT_OPTIMIZE_FONTS': JSON.stringify(
config.experimental.optimizeFonts && !dev
config.optimizeFonts && !dev
),
'process.env.__NEXT_OPTIMIZE_IMAGES': JSON.stringify(
config.experimental.optimizeImages
@ -1168,7 +1168,7 @@ export default async function getBaseWebpackConfig(
distDir,
}),
new ProfilingPlugin(),
config.experimental.optimizeFonts &&
config.optimizeFonts &&
!dev &&
isServer &&
(function () {
@ -1252,7 +1252,7 @@ export default async function getBaseWebpackConfig(
plugins: config.experimental.plugins,
reactStrictMode: config.reactStrictMode,
reactMode: config.experimental.reactMode,
optimizeFonts: config.experimental.optimizeFonts,
optimizeFonts: config.optimizeFonts,
optimizeImages: config.experimental.optimizeImages,
optimizeCss: config.experimental.optimizeCss,
scrollRestoration: config.experimental.scrollRestoration,

View file

@ -16,20 +16,16 @@ import {
OPTIMIZED_FONT_PROVIDERS,
} from '../../../next-server/lib/constants'
async function minifyCss(css: string): Promise<string> {
return new Promise((resolve) =>
postcss([
minifier({
excludeAll: true,
discardComments: true,
normalizeWhitespace: { exclude: false },
}),
])
.process(css, { from: undefined })
.then((res) => {
resolve(res.css)
})
)
function minifyCss(css: string): Promise<string> {
return postcss([
minifier({
excludeAll: true,
discardComments: true,
normalizeWhitespace: { exclude: false },
}),
])
.process(css, { from: undefined })
.then((res) => res.css)
}
export class FontStylesheetGatheringPlugin {
@ -173,11 +169,14 @@ export class FontStylesheetGatheringPlugin {
this.manifestContent = []
for (let promiseIndex in fontDefinitionPromises) {
const css = await fontDefinitionPromises[promiseIndex]
const content = await minifyCss(css)
this.manifestContent.push({
url: this.gatheredStylesheets[promiseIndex],
content,
})
if (css) {
const content = await minifyCss(css)
this.manifestContent.push({
url: this.gatheredStylesheets[promiseIndex],
content,
})
}
}
if (!isWebpack5) {
compilation.assets[FONT_MANIFEST] = new sources.RawSource(

View file

@ -534,7 +534,7 @@ export default async function exportApp(
subFolders,
buildExport: options.buildExport,
serverless: isTargetLikeServerless(nextConfig.target),
optimizeFonts: nextConfig.experimental.optimizeFonts,
optimizeFonts: nextConfig.optimizeFonts,
optimizeImages: nextConfig.experimental.optimizeImages,
optimizeCss: nextConfig.experimental.optimizeCss,
parentSpanId: pageExportSpan.id,

View file

@ -93,6 +93,7 @@ export const defaultConfig: NextConfig = {
trailingSlash: false,
i18n: null,
productionBrowserSourceMaps: false,
optimizeFonts: true,
experimental: {
cpus: Math.max(
1,
@ -105,7 +106,6 @@ export const defaultConfig: NextConfig = {
reactMode: 'legacy',
workerThreads: false,
pageEnv: false,
optimizeFonts: false,
optimizeImages: false,
optimizeCss: false,
scrollRestoration: false,

View file

@ -1,3 +1,4 @@
import * as Log from '../../build/output/log'
const https = require('https')
const CHROME_UA =
@ -10,24 +11,28 @@ export type FontManifest = Array<{
}>
function getFontForUA(url: string, UA: string): Promise<String> {
return new Promise((resolve) => {
return new Promise((resolve, reject) => {
let rawData: any = ''
https.get(
url,
{
headers: {
'user-agent': UA,
https
.get(
url,
{
headers: {
'user-agent': UA,
},
},
},
(res: any) => {
res.on('data', (chunk: any) => {
rawData += chunk
})
res.on('end', () => {
resolve(rawData.toString('utf8'))
})
}
)
(res: any) => {
res.on('data', (chunk: any) => {
rawData += chunk
})
res.on('end', () => {
resolve(rawData.toString('utf8'))
})
}
)
.on('error', (e: Error) => {
reject(e)
})
})
}
@ -39,8 +44,16 @@ export async function getFontDefinitionFromNetwork(
* The order of IE -> Chrome is important, other wise chrome starts loading woff1.
* CSS cascading 🤷.
*/
result += await getFontForUA(url, IE_UA)
result += await getFontForUA(url, CHROME_UA)
try {
result += await getFontForUA(url, IE_UA)
result += await getFontForUA(url, CHROME_UA)
} catch (e) {
Log.warn(
`Failed to download the stylesheet for ${url}. Skipped optimizing this font.`
)
return ''
}
return result
}

View file

@ -205,9 +205,9 @@ export default class Server {
ampOptimizerConfig: this.nextConfig.experimental.amp?.optimizer,
basePath: this.nextConfig.basePath,
images: JSON.stringify(this.nextConfig.images),
optimizeFonts: !!this.nextConfig.experimental.optimizeFonts && !dev,
optimizeFonts: !!this.nextConfig.optimizeFonts && !dev,
fontManifest:
this.nextConfig.experimental.optimizeFonts && !dev
this.nextConfig.optimizeFonts && !dev
? requireFontManifest(this.distDir, this._isLikeServerless)
: null,
optimizeImages: !!this.nextConfig.experimental.optimizeImages,
@ -272,9 +272,9 @@ export default class Server {
/**
* This sets environment variable to be used at the time of SSR by head.tsx.
* Using this from process.env allows targetting both serverless and SSR by calling
* Using this from process.env allows targeting both serverless and SSR by calling
* `process.env.__NEXT_OPTIMIZE_IMAGES`.
* TODO(atcastle@): Remove this when experimental.optimizeImages are being clened up.
* TODO(atcastle@): Remove this when experimental.optimizeImages are being cleaned up.
*/
if (this.renderOpts.optimizeFonts) {
process.env.__NEXT_OPTIMIZE_FONTS = JSON.stringify(true)

View file

@ -398,7 +398,7 @@ export class Head extends Component<
})
head = cssPreloads.concat(otherHeadElements)
}
let children = this.props.children
let children = React.Children.toArray(this.props.children).filter(Boolean)
// show a warning if Head contains <title> (only in development)
if (process.env.NODE_ENV !== 'production') {
children = React.Children.map(children, (child: any) => {

View file

@ -99,7 +99,7 @@ describe('Build Output', () => {
expect(parseFloat(indexFirstLoad)).toBeCloseTo(65.3, 1)
expect(indexFirstLoad.endsWith('kB')).toBe(true)
expect(parseFloat(err404Size) - 3.7).toBeLessThanOrEqual(0)
expect(parseFloat(err404Size)).toBeCloseTo(3.7, 1)
expect(err404Size.endsWith('kB')).toBe(true)
expect(parseFloat(err404FirstLoad)).toBeCloseTo(68.5, 0)

View file

@ -19,6 +19,7 @@ export default class MyDocument extends Document {
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Voces"
/>
{false && <script data-href="test"></script>}
</Head>
<body>
<Main />

View file

@ -10,6 +10,7 @@ import {
initNextServerScript,
} from 'next-test-utils'
import fs from 'fs-extra'
import cheerio from 'cheerio'
jest.setTimeout(1000 * 60 * 2)
@ -54,13 +55,21 @@ function runTests() {
it('should pass nonce to the inlined font definition', async () => {
const html = await renderViaHTTP(appPort, '/nonce')
const $ = cheerio.load(html)
expect(await fsExists(builtPage('font-manifest.json'))).toBe(true)
expect(html).toContain(
'<link rel="stylesheet" nonce="VmVyY2Vs" data-href="https://fonts.googleapis.com/css2?family=Modak"/>'
const link = $(
'link[rel="stylesheet"][data-href="https://fonts.googleapis.com/css2?family=Modak"]'
)
expect(html).toMatch(
/<style data-href="https:\/\/fonts\.googleapis\.com\/css2\?family=Modak" nonce="VmVyY2Vs">.*<\/style>/
const nonce = link.attr('nonce')
const style = $(
'style[data-href="https://fonts.googleapis.com/css2?family=Modak"]'
)
const styleNonce = style.attr('nonce')
expect(link).toBeDefined()
expect(nonce).toBe('VmVyY2Vs')
expect(styleNonce).toBe('VmVyY2Vs')
})
it('should inline the google fonts for static pages with Next/Head', async () => {
@ -117,11 +126,7 @@ function runTests() {
describe('Font optimization for SSR apps', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
`module.exports = { experimental: {optimizeFonts: true} }`,
'utf8'
)
await fs.writeFile(nextConfig, `module.exports = {}`, 'utf8')
if (fs.pathExistsSync(join(appDir, '.next'))) {
await fs.remove(join(appDir, '.next'))
@ -140,7 +145,7 @@ describe('Font optimization for serverless apps', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
`module.exports = { target: 'serverless', experimental: {optimizeFonts: true} }`,
`module.exports = { target: 'serverless' }`,
'utf8'
)
await nextBuild(appDir)
@ -157,7 +162,7 @@ describe('Font optimization for emulated serverless apps', () => {
beforeAll(async () => {
await fs.writeFile(
nextConfig,
`module.exports = { target: 'experimental-serverless-trace', experimental: {optimizeFonts: true} }`,
`module.exports = { target: 'experimental-serverless-trace' }`,
'utf8'
)
await nextBuild(appDir)