From e9bc9eb448914e2b84dfbac126329c2acdbb3289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Born=C3=B6?= Date: Sat, 22 Oct 2022 03:08:37 +0200 Subject: [PATCH] Font loader default config (#41628) Adds default config if @next/font is a dependency. Warns instead of errors when subsets is missing. [slack ref](https://vercel.slack.com/archives/C03KED0D4N7/p1666226599615489?thread_ts=1666225686.389179&cid=C03KED0D4N7) ## 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 --- packages/font/google/index.js | 8 +++- packages/font/local/index.js | 8 +++- packages/font/src/google/loader.ts | 17 +++++-- packages/font/src/local/utils.ts | 2 +- .../webpack/loaders/next-font-loader/index.ts | 22 ++++++++- packages/next/lib/get-package-version.ts | 2 +- packages/next/server/config-shared.ts | 2 +- packages/next/server/config.ts | 46 ++++++++++++++++++- test/e2e/app-dir/next-font/fonts/index.js | 6 +-- .../next-font/fonts/{ => test}/font5.woff2 | 0 .../next-font/fonts/{ => test}/font6.woff2 | 0 test/e2e/app-dir/next-font/next.config.js | 5 -- .../font-loader-in-document-error.test.ts | 3 -- .../font-loader-in-document/next.config.js | 10 ---- test/unit/google-font-loader.test.ts | 15 ------ 15 files changed, 99 insertions(+), 47 deletions(-) rename test/e2e/app-dir/next-font/fonts/{ => test}/font5.woff2 (100%) rename test/e2e/app-dir/next-font/fonts/{ => test}/font6.woff2 (100%) delete mode 100644 test/e2e/next-font/font-loader-in-document/next.config.js diff --git a/packages/font/google/index.js b/packages/font/google/index.js index f3d7e7c80ab0a..b86140fd452a3 100644 --- a/packages/font/google/index.js +++ b/packages/font/google/index.js @@ -1 +1,7 @@ -throw new Error('@next/font/google is not correctly configured') +let message = '@next/font/google failed to run or is incorrectly configured.' +if (process.env.NODE_ENV === 'development') { + message += + '\nIf you just installed `@next/font`, please try restarting `next dev` and resaving your file.' +} + +throw new Error(message) diff --git a/packages/font/local/index.js b/packages/font/local/index.js index bc2f598aaa23d..c2eda14c0128f 100644 --- a/packages/font/local/index.js +++ b/packages/font/local/index.js @@ -1 +1,7 @@ -throw new Error('@next/font/local is not correctly configured') +let message = '@next/font/local failed to run or is incorrectly configured.' +if (process.env.NODE_ENV === 'development') { + message += + '\nIf you just installed `@next/font`, please try restarting `next dev` and resaving your file.' +} + +throw new Error(message) diff --git a/packages/font/src/google/loader.ts b/packages/font/src/google/loader.ts index 758815be8c599..173318093543b 100644 --- a/packages/font/src/google/loader.ts +++ b/packages/font/src/google/loader.ts @@ -1,6 +1,10 @@ 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' +// @ts-ignore +import chalk from 'next/dist/compiled/chalk' import { fetchCSSFromGoogleFonts, fetchFontFile, @@ -19,10 +23,15 @@ const downloadGoogleFonts: FontLoader = async ({ emitFontFile, }) => { if (!config?.subsets) { - throw new Error( - 'Please specify subsets for `@next/font/google` in your `next.config.js`' + Log.warn( + `${chalk.bold('@next/font/google')} is missing ${chalk.bold( + 'options.subsets' + )} in your ${chalk.bold( + 'next.config.js' + )}. Please specify subsets, otherwise no fonts will be preloaded.` ) } + const subsets = config?.subsets || [] const { fontFamily, @@ -63,7 +72,7 @@ const downloadGoogleFonts: FontLoader = async ({ if (googleFontFileUrl) { fontFiles.push({ googleFontFileUrl, - preloadFontFile: !!preload && config.subsets.includes(currentSubset), + preloadFontFile: !!preload && subsets.includes(currentSubset), }) } } @@ -121,7 +130,7 @@ const downloadGoogleFonts: FontLoader = async ({ sizeAdjust: `${sizeAdjust}%`, } } catch { - console.error( + Log.error( `Failed to find font override values for font \`${fontFamily}\`` ) } diff --git a/packages/font/src/local/utils.ts b/packages/font/src/local/utils.ts index 541517da28f3c..90cdf807e15d1 100644 --- a/packages/font/src/local/utils.ts +++ b/packages/font/src/local/utils.ts @@ -60,7 +60,7 @@ export function validateData(functionName: string, data: any): FontOptions { throw new Error(`Unexpected file \`${src}\``) } - const family = /.+\/(.+?)\./.exec(src)![1] + const family = /(.*\/)?(.+?)\.(woff|woff2|eot|ttf|otf)$/.exec(src)![2] if (Array.isArray(declarations)) { declarations.forEach((declaration) => { diff --git a/packages/next/build/webpack/loaders/next-font-loader/index.ts b/packages/next/build/webpack/loaders/next-font-loader/index.ts index 6edc12b4dddc6..de340cddd0d55 100644 --- a/packages/next/build/webpack/loaders/next-font-loader/index.ts +++ b/packages/next/build/webpack/loaders/next-font-loader/index.ts @@ -1,10 +1,12 @@ import type { FontLoader } from '../../../../font' +import { promises as fs } from 'fs' import path from 'path' import loaderUtils from 'next/dist/compiled/loader-utils3' import postcssFontLoaderPlugn from './postcss-font-loader' import { promisify } from 'util' import chalk from 'next/dist/compiled/chalk' +import { CONFIG_FILES } from '../../../../shared/lib/constants' export default async function nextFontLoader(this: any) { const fontLoaderSpan = this.currentTraceSpan.traceChild('next-font-loader') @@ -17,6 +19,24 @@ export default async function nextFontLoader(this: any) { postcss: getPostcss, } = this.getOptions() + const nextConfigPaths = CONFIG_FILES.map((config) => + path.join(this.rootContext, config) + ) + // Add next.config.js as a dependency, loaders must rerun in case options changed + await Promise.all( + nextConfigPaths.map(async (configPath) => { + const hasConfig = await fs.access(configPath).then( + () => true, + () => false + ) + if (hasConfig) { + this.addDependency(configPath) + } else { + this.addMissingDependency(configPath) + } + }) + ) + const emitFontFile = (content: Buffer, ext: string, preload: boolean) => { const opts = { context: this.rootContext, content } const interpolatedName = loaderUtils.interpolateName( @@ -55,7 +75,7 @@ export default async function nextFontLoader(this: any) { path.dirname( path.join(this.rootContext, relativeFilePathFromRoot) ), - src + src.startsWith('.') ? src : `./${src}` ), fs: this.fs, }) diff --git a/packages/next/lib/get-package-version.ts b/packages/next/lib/get-package-version.ts index a924adc2cabb1..636faf7eb215d 100644 --- a/packages/next/lib/get-package-version.ts +++ b/packages/next/lib/get-package-version.ts @@ -10,7 +10,7 @@ type PackageJsonDependencies = { let cachedDeps: Promise -function getDependencies({ +export function getDependencies({ cwd, }: { cwd: string diff --git a/packages/next/server/config-shared.ts b/packages/next/server/config-shared.ts index 942f5d41e514e..9e81357dab704 100644 --- a/packages/next/server/config-shared.ts +++ b/packages/next/server/config-shared.ts @@ -162,7 +162,7 @@ export interface ExperimentalConfig { // A list of packages that should always be transpiled and bundled in the server transpilePackages?: string[] - fontLoaders?: [{ loader: string; options?: any }] + fontLoaders?: Array<{ loader: string; options?: any }> webVitalsAttribution?: Array } diff --git a/packages/next/server/config.ts b/packages/next/server/config.ts index 83ee128ac16fd..2a3d9de917428 100644 --- a/packages/next/server/config.ts +++ b/packages/next/server/config.ts @@ -24,6 +24,7 @@ import { } from '../shared/lib/image-config' import { loadEnvConfig } from '@next/env' import { gte as semverGte } from 'next/dist/compiled/semver' +import { getDependencies } from '../lib/get-package-version' export { DomainLocale, NextConfig, normalizeConfig } from './config-shared' @@ -77,6 +78,46 @@ export function setHttpClientAndAgentOptions(options: NextConfig) { ;(global as any).__NEXT_HTTPS_AGENT = new HttpsAgent(options.httpAgentOptions) } +async function setFontLoaderDefaults(config: NextConfigComplete, dir: string) { + if (config.experimental?.fontLoaders) return + + // Add @next/font loaders by default if they're installed + const hasNextFontDependency = ( + await getDependencies({ + cwd: dir, + }) + ).dependencies['@next/font'] + + if (hasNextFontDependency) { + const googleFontLoader = { + loader: '@next/font/google', + } + const localFontLoader = { + loader: '@next/font/local', + } + if (!config.experimental) { + config.experimental = {} + } + if (!config.experimental.fontLoaders) { + config.experimental.fontLoaders = [] + } + if ( + !config.experimental.fontLoaders.find( + ({ loader }: any) => loader === '@next/font/goggle' + ) + ) { + config.experimental.fontLoaders.push(googleFontLoader) + } + if ( + !config.experimental.fontLoaders.find( + ({ loader }: any) => loader === '@next/font/local' + ) + ) { + config.experimental.fontLoaders.push(localFontLoader) + } + } +} + function assignDefaults(dir: string, userConfig: { [key: string]: any }) { const configFileName = userConfig.configFileName if (typeof userConfig.exportTrailingSlash !== 'undefined') { @@ -835,12 +876,14 @@ export default async function loadConfig( : canonicalBase) || '' } - return assignDefaults(dir, { + const completeConfig = assignDefaults(dir, { configOrigin: relative(dir, path), configFile: path, configFileName, ...userConfig, }) as NextConfigComplete + await setFontLoaderDefaults(completeConfig, dir) + return completeConfig } else { const configBaseName = basename(CONFIG_FILES[0], extname(CONFIG_FILES[0])) const nonJsPath = findUp.sync( @@ -869,5 +912,6 @@ export default async function loadConfig( ) as NextConfigComplete completeConfig.configFileName = configFileName setHttpClientAndAgentOptions(completeConfig) + await setFontLoaderDefaults(completeConfig, dir) return completeConfig } diff --git a/test/e2e/app-dir/next-font/fonts/index.js b/test/e2e/app-dir/next-font/fonts/index.js index 55074a79d13cc..d9d8746396cef 100644 --- a/test/e2e/app-dir/next-font/fonts/index.js +++ b/test/e2e/app-dir/next-font/fonts/index.js @@ -1,7 +1,7 @@ import localFont from '@next/font/local' export const font1 = localFont({ src: './font1.woff2', variable: '--font-1' }) -export const font2 = localFont({ src: './font2.woff2', variable: '--font-2' }) +export const font2 = localFont({ src: 'font2.woff2', variable: '--font-2' }) export const font3 = localFont({ src: './font3.woff2', weight: '900', @@ -9,8 +9,8 @@ export const font3 = localFont({ }) export const font4 = localFont({ src: './font4.woff2', weight: '100' }) export const font5 = localFont({ - src: './font5.woff2', + src: './test/font5.woff2', style: 'italic', preload: false, }) -export const font6 = localFont({ src: './font6.woff2' }) +export const font6 = localFont({ src: 'test/font6.woff2' }) diff --git a/test/e2e/app-dir/next-font/fonts/font5.woff2 b/test/e2e/app-dir/next-font/fonts/test/font5.woff2 similarity index 100% rename from test/e2e/app-dir/next-font/fonts/font5.woff2 rename to test/e2e/app-dir/next-font/fonts/test/font5.woff2 diff --git a/test/e2e/app-dir/next-font/fonts/font6.woff2 b/test/e2e/app-dir/next-font/fonts/test/font6.woff2 similarity index 100% rename from test/e2e/app-dir/next-font/fonts/font6.woff2 rename to test/e2e/app-dir/next-font/fonts/test/font6.woff2 diff --git a/test/e2e/app-dir/next-font/next.config.js b/test/e2e/app-dir/next-font/next.config.js index fd841b325c948..cfa3ac3d7aa94 100644 --- a/test/e2e/app-dir/next-font/next.config.js +++ b/test/e2e/app-dir/next-font/next.config.js @@ -1,10 +1,5 @@ module.exports = { experimental: { appDir: true, - fontLoaders: [ - { - loader: '@next/font/local', - }, - ], }, } diff --git a/test/e2e/next-font/font-loader-in-document-error.test.ts b/test/e2e/next-font/font-loader-in-document-error.test.ts index 7e6d39cd5d610..8c4dc13951665 100644 --- a/test/e2e/next-font/font-loader-in-document-error.test.ts +++ b/test/e2e/next-font/font-loader-in-document-error.test.ts @@ -17,9 +17,6 @@ describe('font-loader-in-document-error', () => { next = await createNext({ files: { pages: new FileRef(join(__dirname, 'font-loader-in-document/pages')), - 'next.config.js': new FileRef( - join(__dirname, 'font-loader-in-document/next.config.js') - ), }, dependencies: { '@next/font': 'canary', diff --git a/test/e2e/next-font/font-loader-in-document/next.config.js b/test/e2e/next-font/font-loader-in-document/next.config.js deleted file mode 100644 index 3f4a776b7f409..0000000000000 --- a/test/e2e/next-font/font-loader-in-document/next.config.js +++ /dev/null @@ -1,10 +0,0 @@ -module.exports = { - experimental: { - fontLoaders: [ - { - loader: '@next/font/google', - options: { subsets: ['latin'] }, - }, - ], - }, -} diff --git a/test/unit/google-font-loader.test.ts b/test/unit/google-font-loader.test.ts index 8d69df45b676d..4b15723270d3d 100644 --- a/test/unit/google-font-loader.test.ts +++ b/test/unit/google-font-loader.test.ts @@ -201,21 +201,6 @@ describe('@next/font/google loader', () => { `) }) - test('Missing config with subsets', async () => { - await expect( - loader({ - functionName: 'Inter', - data: [], - config: undefined, - emitFontFile: jest.fn(), - resolve: jest.fn(), - fs: {} as any, - }) - ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Please specify subsets for \`@next/font/google\` in your \`next.config.js\`"` - ) - }) - test('Missing function name', async () => { await expect( loader({