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

Font loader support in app #40898

Merged
merged 11 commits into from Sep 27, 2022
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
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
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