Skip to content

Commit

Permalink
Font loader support in app (vercel#40898)
Browse files Browse the repository at this point in the history
Makes sure font loader CSS ends up correctly in the Flight Manifest and Flight Client Entries.

## 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)
  • Loading branch information
Hannes Bornö authored and BowlingX committed Oct 5, 2022
1 parent bf32cbf commit efd89dc
Show file tree
Hide file tree
Showing 38 changed files with 467 additions and 84 deletions.
1 change: 1 addition & 0 deletions packages/font/google/target.css
@@ -0,0 +1 @@
/* target file for webpack loader */
1 change: 1 addition & 0 deletions packages/font/local/target.css
@@ -0,0 +1 @@
/* target file for webpack loader */
Expand Up @@ -48,7 +48,7 @@ impl<'a> FontImportsGenerator<'a> {
return Some(ImportDecl {
src: Box::new(Str {
value: JsWord::from(format!(
"{}?{}",
"{}/target.css?{}",
font_function.loader,
values.join(";")
)),
Expand Down
@@ -1,5 +1,5 @@
import firaCode from "@next/font/google?pages/test.tsx;Abel";
import inter from "@next/font/google?pages/test.tsx;Inter";
import firaCode from "@next/font/google/target.css?pages/test.tsx;Abel";
import inter from "@next/font/google/target.css?pages/test.tsx;Inter";
import React from 'react';
export { firaCode };
export { inter };
@@ -1,4 +1,4 @@
import inter1 from '@next/font/google?pages/test.tsx;Inter;{"variant":"400"}';
import inter2 from '@next/font/google?pages/test.tsx;Inter;{"variant":"400"}';
import inter1 from '@next/font/google/target.css?pages/test.tsx;Inter;{"variant":"400"}';
import inter2 from '@next/font/google/target.css?pages/test.tsx;Inter;{"variant":"400"}';
var i = 10;
var i2 = 20;
@@ -1,7 +1,7 @@
import a from "@next/font/google?pages/test.tsx;ABeeZee;{}";
import a from "@next/font/google?pages/test.tsx;ABeeZee;{}";
import a from "@next/font/google?pages/test.tsx;ABeeZee;{}";
import a from "@next/font/google?pages/test.tsx;ABeeZee;{}";
import a from "@next/font/google/target.css?pages/test.tsx;ABeeZee;{}";
import a from "@next/font/google/target.css?pages/test.tsx;ABeeZee;{}";
import a from "@next/font/google/target.css?pages/test.tsx;ABeeZee;{}";
import a from "@next/font/google/target.css?pages/test.tsx;ABeeZee;{}";
const a = fn({
10: 'hello'
});
Expand Down
@@ -1,2 +1,2 @@
import inter from "@next/font/google?pages/test.tsx;Inter;{};[]";
import inter from "@next/font/google/target.css?pages/test.tsx;Inter;{};[]";
const a = fn(...{}, ...[]);
@@ -1 +1 @@
import font from 'cool-fonts?pages/test.tsx;;{"prop":true}';
import font from 'cool-fonts/target.css?pages/test.tsx;;{"prop":true}';
@@ -1,5 +1,5 @@
import firaCode from "@next/font/google?pages/test.tsx;Abel";
import inter from "@next/font/google?pages/test.tsx;Inter";
import firaCode from "@next/font/google/target.css?pages/test.tsx;Abel";
import inter from "@next/font/google/target.css?pages/test.tsx;Inter";
import React from 'react';
export { firaCode };
export { inter };
@@ -1,5 +1,5 @@
import firaCode from "@next/font/google?pages/test.tsx;Abel";
import inter from "@next/font/google?pages/test.tsx;Inter";
import firaCode from "@next/font/google/target.css?pages/test.tsx;Abel";
import inter from "@next/font/google/target.css?pages/test.tsx;Inter";
import React from 'react';
export { firaCode };
export default inter;
@@ -1,3 +1,3 @@
import firaCode from '@next/font/google?pages/test.tsx;Fira_Code;{"fallback":["system-ui",{"key":false},[]],"key":{"key2":{}},"preload":true,"variant":"400"}';
import firaCode from '@next/font/google/target.css?pages/test.tsx;Fira_Code;{"fallback":["system-ui",{"key":false},[]],"key":{"key2":{}},"preload":true,"variant":"400"}';
import React from 'react';
console.log(firaCode);
@@ -1,2 +1,2 @@
import acme1 from 'cool-fonts?pages/test.tsx;Acme;{"variant":"400"}';
import acme1 from 'cool-fonts/target.css?pages/test.tsx;Acme;{"variant":"400"}';
import React from 'react';
@@ -1 +1 @@
import geo from '@next/font/google?pages/test.tsx;Geo;"test";[1.0];{"a":2.0};3.0';
import geo from '@next/font/google/target.css?pages/test.tsx;Geo;"test";[1.0];{"a":2.0};3.0';
@@ -1,3 +1,3 @@
import inter from '@next/font/google?pages/test.tsx;Inter;{"display":"swap","variant":"900"}';
import inter from '@next/font/google?pages/test.tsx;Inter;{"display":"swap","variant":"900"}';
import inter from '@next/font/google/target.css?pages/test.tsx;Inter;{"display":"swap","variant":"900"}';
import inter from '@next/font/google/target.css?pages/test.tsx;Inter;{"display":"swap","variant":"900"}';
import React from 'react';
@@ -1,3 +1,3 @@
import inter from '@next/font/google?pages/test.tsx;Inter;{"variant":"900"}';
import fira from 'cool-fonts?pages/test.tsx;Fira_Code;{"display":"swap","variant":"400"}';
import inter from '@next/font/google/target.css?pages/test.tsx;Inter;{"variant":"900"}';
import fira from 'cool-fonts/target.css?pages/test.tsx;Fira_Code;{"display":"swap","variant":"400"}';
import React from 'react';
@@ -1,3 +1,3 @@
import firaCode from '@next/font/google?pages/test.tsx;Fira_Code;{"fallback":["system-ui"],"variant":"400"}';
import inter from '@next/font/google?pages/test.tsx;Inter;{"display":"swap","variant":"900"}';
import firaCode from '@next/font/google/target.css?pages/test.tsx;Fira_Code;{"fallback":["system-ui"],"variant":"400"}';
import inter from '@next/font/google/target.css?pages/test.tsx;Inter;{"display":"swap","variant":"900"}';
import React from 'react';
@@ -1,3 +1,3 @@
import inter from '@next/font/google?pages/test.tsx;Inter;{"variant":"900"}';
import fira from '@next/font/google?pages/test.tsx;Fira_Code;{"display":"swap","variant":"400"}';
import inter from '@next/font/google/target.css?pages/test.tsx;Inter;{"variant":"900"}';
import fira from '@next/font/google/target.css?pages/test.tsx;Fira_Code;{"display":"swap","variant":"400"}';
import React from 'react';
@@ -1 +1 @@
import fira from "@next/font/google?pages/test.tsx;Fira_Code";
import fira from "@next/font/google/target.css?pages/test.tsx;Fira_Code";
9 changes: 9 additions & 0 deletions packages/next/build/webpack-config.ts
Expand Up @@ -1152,6 +1152,13 @@ export default async function getBaseWebpackConfig(
},
}

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

let webpackConfig: webpack.Configuration = {
parallelism: Number(process.env.NEXT_WEBPACK_PARALLELISM) || undefined,
// @ts-ignore
Expand Down Expand Up @@ -1883,10 +1890,12 @@ export default async function getBaseWebpackConfig(
(isClient
? new FlightManifestPlugin({
dev,
fontLoaderTargets,
})
: new FlightClientEntryPlugin({
dev,
isEdgeServer,
fontLoaderTargets,
})),
!dev &&
isClient &&
Expand Down
45 changes: 23 additions & 22 deletions packages/next/build/webpack/config/blocks/css/index.ts
@@ -1,3 +1,4 @@
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 @@ -180,32 +181,11 @@ export const css = curry(async function css(

const fns: ConfigurationFn[] = []

// CSS cannot be imported in _document. This comes before everything because
// global CSS nor CSS modules work in said file.
fns.push(
loader({
oneOf: [
markRemovable({
test: regexLikeCss,
// Use a loose regex so we don't have to crawl the file system to
// find the real file name (if present).
issuer: /pages[\\/]_document\./,
use: {
loader: 'error-loader',
options: {
reason: getCustomDocumentError(),
},
},
}),
],
})
)

// 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
? Object.entries(ctx.experimental.fontLoaders).map(
([fontLoader, fontLoaderOptions]: any) => [
require.resolve(fontLoader),
path.join(require.resolve(fontLoader), '../target.css'),
fontLoaderOptions,
]
)
Expand Down Expand Up @@ -254,6 +234,27 @@ export const css = curry(async function css(
)
})

// CSS cannot be imported in _document. This comes before everything because
// global CSS nor CSS modules work in said file.
fns.push(
loader({
oneOf: [
markRemovable({
test: regexLikeCss,
// Use a loose regex so we don't have to crawl the file system to
// find the real file name (if present).
issuer: /pages[\\/]_document\./,
use: {
loader: 'error-loader',
options: {
reason: getCustomDocumentError(),
},
},
}),
],
})
)

// CSS Modules support must be enabled on the server and client so the class
// names are available for SSR or Prerendering.
if (ctx.experimental.appDir && !ctx.isProduction) {
Expand Down
12 changes: 10 additions & 2 deletions packages/next/build/webpack/plugins/flight-client-entry-plugin.ts
Expand Up @@ -23,6 +23,7 @@ import { isClientComponentModule } from '../loaders/utils'
interface Options {
dev: boolean
isEdgeServer: boolean
fontLoaderTargets?: string[]
}

const PLUGIN_NAME = 'ClientEntryPlugin'
Expand All @@ -38,10 +39,12 @@ const flightCSSManifest: FlightCSSManifest = {}
export class FlightClientEntryPlugin {
dev: boolean
isEdgeServer: boolean
fontLoaderTargets?: string[]

constructor(options: Options) {
this.dev = options.dev
this.isEdgeServer = options.isEdgeServer
this.fontLoaderTargets = options.fontLoaderTargets
}

apply(compiler: webpack.Compiler) {
Expand Down Expand Up @@ -229,12 +232,17 @@ export class FlightClientEntryPlugin {
// Request could be undefined or ''
if (!rawRequest) return

const isFontLoader = this.fontLoaderTargets?.some((fontLoaderTarget) =>
mod.userRequest.startsWith(`${fontLoaderTarget}?`)
)
const modRequest: string | undefined =
!rawRequest.endsWith('.css') &&
!rawRequest.startsWith('.') &&
!rawRequest.startsWith('/') &&
!rawRequest.startsWith(APP_DIR_ALIAS)
? rawRequest
? isFontLoader
? mod.userRequest
: rawRequest
: mod.resourceResolveData?.path

// Ensure module is not walked again if it's already been visited
Expand All @@ -249,7 +257,7 @@ export class FlightClientEntryPlugin {
}
visitedBySegment[layoutOrPageRequest].add(modRequest)

const isCSS = regexCSS.test(modRequest)
const isCSS = isFontLoader || regexCSS.test(modRequest)
const isClientComponent = isClientComponentModule(mod)

if (isCSS) {
Expand Down
8 changes: 8 additions & 0 deletions packages/next/build/webpack/plugins/flight-manifest-plugin.ts
Expand Up @@ -19,6 +19,7 @@ import { isClientComponentModule } from '../loaders/utils'

interface Options {
dev: boolean
fontLoaderTargets?: string[]
}

/**
Expand Down Expand Up @@ -101,9 +102,11 @@ export function traverseModules(

export class FlightManifestPlugin {
dev: Options['dev'] = false
fontLoaderTargets?: Options['fontLoaderTargets']

constructor(options: Options) {
this.dev = options.dev
this.fontLoaderTargets = options.fontLoaderTargets
}

apply(compiler: webpack.Compiler) {
Expand Down Expand Up @@ -144,6 +147,7 @@ export class FlightManifestPlugin {
__edge_ssr_module_mapping__: {},
}
const dev = this.dev
const fontLoaderTargets = this.fontLoaderTargets

const clientRequestsSet = new Set()

Expand All @@ -168,7 +172,11 @@ export class FlightManifestPlugin {
id: ModuleId,
mod: webpack.NormalModule
) {
const isFontLoader = fontLoaderTargets?.some((fontLoaderTarget) =>
mod.resource?.startsWith(`${fontLoaderTarget}?`)
)
const isCSSModule =
isFontLoader ||
mod.resource?.endsWith('.css') ||
mod.type === 'css/mini-extract' ||
(!!mod.loaders &&
Expand Down

0 comments on commit efd89dc

Please sign in to comment.