Skip to content

Commit

Permalink
Add next/font import (#45891)
Browse files Browse the repository at this point in the history
Enables using `next/font` by adding `@next/font` as a dependency and
reexporting its loaders.

Always generates the `font-loader-manifest` as we can't know beforehand
if the user intends to use `next/font` or not.

Also adds telemetry for `next/font` usage.

The tests are updated to use `next/font`. But `@next/font` is tested in
`test/e2e/next-font/index.test.ts` and `test/e2e/app-dir/next-font` as
well to ensure it doesn't break.

Fixes NEXT-351

## 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 authored Feb 16, 2023
1 parent 0484dc4 commit 435eca3
Show file tree
Hide file tree
Showing 126 changed files with 1,793 additions and 1,285 deletions.
4 changes: 2 additions & 2 deletions errors/babel-font-loader-conflict.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Babel and `@next/font` conflict
# Babel and `next/font` conflict

#### Why This Error Occurred

You have tried to use `@next/font` with a custom babel config. When your application has a custom babel config you opt-out of the Next.js Compiler which is required to use `@next/font`.
You have tried to use `next/font` with a custom babel config. When your application has a custom babel config you opt-out of the Next.js Compiler which is required to use `next/font`.

#### Possible Ways to Fix It

Expand Down
2 changes: 1 addition & 1 deletion packages/font/src/google/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function validateData(
const subsets = callSubsets ?? config?.subsets ?? []

if (functionName === '') {
nextFontError(`@next/font/google has no default export`)
nextFontError(`next/font/google has no default export`)
}

const fontFamily = functionName.replace(/_/g, ' ')
Expand Down
2 changes: 1 addition & 1 deletion packages/font/src/local/loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-ignore
// eslint-disable-next-line import/no-extraneous-dependencies
import fontFromBuffer from '@next/font/dist/fontkit'
import fontFromBuffer from '../fontkit'
import type { AdjustFontFallback, FontLoader } from 'next/font'

import { promisify } from 'util'
Expand Down
2 changes: 1 addition & 1 deletion packages/font/src/local/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type FontOptions = {
}
export function validateData(functionName: string, fontData: any): FontOptions {
if (functionName) {
nextFontError(`@next/font/local has no named exports`)
nextFontError(`next/font/local has no named exports`)
}
let {
src,
Expand Down
1 change: 1 addition & 0 deletions packages/next/font/google/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from 'next/dist/compiled/@next/font/dist/google'
3 changes: 0 additions & 3 deletions packages/next/font/google/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
throw new Error(
'You tried to import `next/font/google`, did you mean `@next/font/google`?\nRead more: https://nextjs.org/docs/basic-features/font-optimization'
)
1 change: 1 addition & 0 deletions packages/next/font/google/target.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* target file for webpack loader */
3 changes: 0 additions & 3 deletions packages/next/font/index.js

This file was deleted.

1 change: 1 addition & 0 deletions packages/next/font/local/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'next/dist/compiled/@next/font/dist/local'
3 changes: 0 additions & 3 deletions packages/next/font/local/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
throw new Error(
'You tried to import `next/font/local`, did you mean `@next/font/local`?\nRead more: https://nextjs.org/docs/basic-features/font-optimization'
)
1 change: 1 addition & 0 deletions packages/next/font/local/target.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* target file for webpack loader */
11 changes: 7 additions & 4 deletions packages/next/src/build/babel/plugins/next-font-unsupported.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ export default function NextPageDisallowReExportAllExports(): PluginObj<any> {
visitor: {
ImportDeclaration(path: NodePath<types.ImportDeclaration>) {
if (
['@next/font/local', '@next/font/google'].includes(
path.node.source.value
)
[
'@next/font/local',
'@next/font/google',
'next/font/local',
'next/font/google',
].includes(path.node.source.value)
) {
const err = new SyntaxError(
`"@next/font" requires SWC although Babel is being used due to a custom babel config being present.\nRead more: https://nextjs.org/docs/messages/babel-font-loader-conflict`
`"next/font" requires SWC although Babel is being used due to a custom babel config being present.\nRead more: https://nextjs.org/docs/messages/babel-font-loader-conflict`
)
;(err as any).code = 'BABEL_PARSE_ERROR'
;(err as any).loc =
Expand Down
1 change: 0 additions & 1 deletion packages/next/src/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,6 @@ export function getEdgeServerEntry(opts: {
pagesType: opts.pagesType,
appDirLoader: Buffer.from(opts.appDirLoader || '').toString('base64'),
sriEnabled: !opts.isDev && !!opts.config.experimental.sri?.algorithm,
hasFontLoaders: !!opts.config.experimental.fontLoaders,
}

return {
Expand Down
8 changes: 2 additions & 6 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -931,12 +931,8 @@ export default async function build(
: null,
BUILD_ID_FILE,
appDir ? path.join(SERVER_DIRECTORY, APP_PATHS_MANIFEST) : null,
...(config.experimental.fontLoaders
? [
path.join(SERVER_DIRECTORY, FONT_LOADER_MANIFEST + '.js'),
path.join(SERVER_DIRECTORY, FONT_LOADER_MANIFEST + '.json'),
]
: []),
path.join(SERVER_DIRECTORY, FONT_LOADER_MANIFEST + '.js'),
path.join(SERVER_DIRECTORY, FONT_LOADER_MANIFEST + '.json'),
]
.filter(nonNullable)
.map((file) => path.join(config.distDir, file)),
Expand Down
2 changes: 2 additions & 0 deletions packages/next/src/build/jest/jest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export default function nextJest(options: { dir?: string } = {}) {

// Handle @next/font
'@next/font/(.*)': require.resolve('./__mocks__/nextFontMock.js'),
// Handle next/font
'next/font/(.*)': require.resolve('./__mocks__/nextFontMock.js'),

// custom config comes last to ensure the above rules are matched,
// fixes the case where @pages/(.*) -> src/pages/$! doesn't break
Expand Down
16 changes: 9 additions & 7 deletions packages/next/src/build/swc/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,14 +250,16 @@ any) {
hasServerComponents,
isServerLayer,
})
baseOptions.fontLoaders = {
fontLoaders: [
'next/font/local',
'next/font/google',

if (nextConfig?.experimental?.fontLoaders && relativeFilePathFromRoot) {
baseOptions.fontLoaders = {
fontLoaders: nextConfig.experimental.fontLoaders.map(
({ loader }: any) => loader
),
relativeFilePathFromRoot,
}
// TODO: remove this in the next major version
'@next/font/local',
'@next/font/google',
],
relativeFilePathFromRoot,
}

const isNextDist = nextDistPath.test(filename)
Expand Down
10 changes: 0 additions & 10 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1373,13 +1373,6 @@ export default async function getBaseWebpackConfig(
},
}

const fontLoaderTargets =
config.experimental.fontLoaders &&
config.experimental.fontLoaders.map(({ loader }) => {
const resolved = require.resolve(loader)
return path.join(resolved, '../target.css')
})

let webpackConfig: webpack.Configuration = {
parallelism: Number(process.env.NEXT_WEBPACK_PARALLELISM) || undefined,
...(isNodeServer ? { externalsPresets: { node: true } } : {}),
Expand Down Expand Up @@ -2111,7 +2104,6 @@ export default async function getBaseWebpackConfig(
new MiddlewarePlugin({
dev,
sriEnabled: !dev && !!config.experimental.sri?.algorithm,
hasFontLoaders: !!config.experimental.fontLoaders,
}),
isClient &&
new BuildManifestPlugin({
Expand Down Expand Up @@ -2176,10 +2168,8 @@ export default async function getBaseWebpackConfig(
!!config.experimental.sri?.algorithm &&
new SubresourceIntegrityPlugin(config.experimental.sri.algorithm),
isClient &&
fontLoaderTargets &&
new FontLoaderManifestPlugin({
appDirEnabled: !!config.experimental.appDir,
fontLoaderTargets,
}),
!dev &&
isClient &&
Expand Down
73 changes: 47 additions & 26 deletions packages/next/src/build/webpack/config/blocks/css/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import path from 'path'
import curry from 'next/dist/compiled/lodash.curry'
import { webpack } from 'next/dist/compiled/webpack/webpack'
import { loader, plugin } from '../../helpers'
Expand Down Expand Up @@ -181,32 +180,54 @@ export const css = curry(async function css(

const fns: ConfigurationFn[] = []

// Resolve the configured font loaders, the resolved files are noop files that next-font-loader will match
let fontLoaders: [string, string][] | undefined = ctx.experimental.fontLoaders
? ctx.experimental.fontLoaders.map(({ loader: fontLoader, options }) => [
path.join(require.resolve(fontLoader), '../target.css'),
options,
])
: undefined
const googleLoader = require.resolve(
'next/dist/compiled/@next/font/google/loader'
)
const localLoader = require.resolve(
'next/dist/compiled/@next/font/local/loader'
)
const googleLoaderOptions =
ctx.experimental?.fontLoaders?.find(
(loaderConfig) => loaderConfig.loader === '@next/font/google'
)?.options ?? {}
const fontLoaders: Array<[string | RegExp, string, any?]> = [
[
require.resolve('next/font/google/target.css'),
googleLoader,
googleLoaderOptions,
],
[require.resolve('next/font/local/target.css'), localLoader],

// TODO: remove this in the next major version
[
/node_modules[\\/]@next[\\/]font[\\/]google[\\/]target.css/,
googleLoader,
googleLoaderOptions,
],
[/node_modules[\\/]@next[\\/]font[\\/]local[\\/]target.css/, localLoader],
]

fontLoaders?.forEach(([fontLoaderPath, fontLoaderOptions]) => {
// Matches the resolved font loaders noop files to run next-font-loader
fns.push(
loader({
oneOf: [
markRemovable({
sideEffects: false,
test: fontLoaderPath,
use: getNextFontLoader(
ctx,
lazyPostCSSInitializer,
fontLoaderOptions
),
}),
],
})
)
})
fontLoaders.forEach(
([fontLoaderTarget, fontLoaderPath, fontLoaderOptions]) => {
// Matches the resolved font loaders noop files to run next-font-loader
fns.push(
loader({
oneOf: [
markRemovable({
sideEffects: false,
test: fontLoaderTarget,
use: getNextFontLoader(
ctx,
lazyPostCSSInitializer,
fontLoaderPath,
fontLoaderOptions
),
}),
],
})
)
}
)

// CSS cannot be imported in _document. This comes before everything because
// global CSS nor CSS modules work in said file.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { cssFileResolve } from './file-resolve'
export function getNextFontLoader(
ctx: ConfigurationContext,
postcss: any,
fontLoaderPath: string,
fontLoaderOptions: any
): webpack.RuleSetUseItem[] {
const loaders: webpack.RuleSetUseItem[] = []
Expand Down Expand Up @@ -62,6 +63,7 @@ export function getNextFontLoader(
isDev: ctx.isDevelopment,
isServer: ctx.isServer,
assetPrefix: ctx.assetPrefix,
fontLoaderPath,
fontLoaderOptions,
postcss,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ export type EdgeSSRLoaderQuery = {
appDirLoader?: string
pagesType: 'app' | 'pages' | 'root'
sriEnabled: boolean
hasFontLoaders: boolean
}

/*
Expand Down Expand Up @@ -45,7 +44,6 @@ export default async function edgeSSRLoader(this: any) {
appDirLoader: appDirLoaderBase64,
pagesType,
sriEnabled,
hasFontLoaders,
} = this.getOptions()

const appDirLoader = Buffer.from(
Expand Down Expand Up @@ -127,9 +125,7 @@ export default async function edgeSSRLoader(this: any) {
const subresourceIntegrityManifest = ${
sriEnabled ? 'self.__SUBRESOURCE_INTEGRITY_MANIFEST' : 'undefined'
}
const fontLoaderManifest = ${
hasFontLoaders ? 'self.__FONT_LOADER_MANIFEST' : 'undefined'
}
const fontLoaderManifest = self.__FONT_LOADER_MANIFEST
const render = getRender({
pageType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export default async function nextFontLoader(this: any) {
isDev,
isServer,
assetPrefix,
fontLoaderPath,
fontLoaderOptions,
postcss: getPostcss,
} = this.getOptions()
Expand Down Expand Up @@ -82,10 +83,7 @@ export default async function nextFontLoader(this: any) {
}

try {
const fontLoader: FontLoader = require(path.join(
this.resourcePath,
'../loader.js'
)).default
const fontLoader: FontLoader = require(fontLoaderPath).default
let { css, fallbackFonts, adjustFontFallback, weight, style, variable } =
await fontLoader({
functionName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,20 @@ export type FontLoaderManifest = {
}
const PLUGIN_NAME = 'FontLoaderManifestPlugin'

const fontLoaderTargets = [
require.resolve('next/font/google/target.css'),
require.resolve('next/font/local/target.css'),
// TODO: remove this in the next major version
/node_modules\/@next\/font\/google\/target\.css\?{.+}$/,
/node_modules\/@next\/font\/local\/target\.css\?{.+}$/,
]

// Creates a manifest of all fonts that should be preloaded given a route
export class FontLoaderManifestPlugin {
private appDirEnabled: boolean
private fontLoaderTargets: string[]

constructor(options: {
appDirEnabled: boolean
fontLoaderTargets: string[]
}) {
constructor(options: { appDirEnabled: boolean }) {
this.appDirEnabled = options.appDirEnabled
this.fontLoaderTargets = options.fontLoaderTargets
}

apply(compiler: webpack.Compiler) {
Expand All @@ -36,8 +39,10 @@ export class FontLoaderManifestPlugin {
compilation.hooks.finishModules.tap(PLUGIN_NAME, (modules) => {
const modulesArr = Array.from(modules)
fontLoaderModules = modulesArr.filter((mod: any) =>
this.fontLoaderTargets.some((fontLoaderTarget) =>
mod.userRequest?.startsWith(`${fontLoaderTarget}?`)
fontLoaderTargets.some((fontLoaderTarget) =>
typeof fontLoaderTarget === 'string'
? mod.userRequest?.startsWith(`${fontLoaderTarget}?`)
: fontLoaderTarget.test(mod.userRequest)
)
)
})
Expand Down
Loading

0 comments on commit 435eca3

Please sign in to comment.