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

Add support for font loaders #40746

Merged
merged 17 commits into from Sep 22, 2022
Merged
1 change: 1 addition & 0 deletions packages/next/build/entries.ts
Expand Up @@ -204,6 +204,7 @@ 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
4 changes: 4 additions & 0 deletions packages/next/build/index.ts
Expand Up @@ -59,6 +59,7 @@ import {
APP_BUILD_MANIFEST,
FLIGHT_SERVER_CSS_MANIFEST,
RSC_MODULE_TYPES,
FONT_LOADER_MANIFEST,
} from '../shared/lib/constants'
import { getSortedRoutes, isDynamicRoute } from '../shared/lib/router/utils'
import { __ApiPreviewProps } from '../server/api-utils'
Expand Down Expand Up @@ -828,6 +829,9 @@ export default async function build(
config.optimizeFonts ? path.join(serverDir, FONT_MANIFEST) : null,
BUILD_ID_FILE,
appDir ? path.join(serverDir, APP_PATHS_MANIFEST) : null,
config.experimental.fontLoaders
? path.join(serverDir, FONT_LOADER_MANIFEST)
: null,
]
.filter(nonNullable)
.map((file) => path.join(config.distDir, file)),
Expand Down
3 changes: 3 additions & 0 deletions packages/next/build/swc/options.js
Expand Up @@ -123,6 +123,9 @@ function getBaseSWCOptions({
isServer: !!isServerLayer,
}
: false,
fontLoaders:
nextConfig?.experimental?.fontLoaders &&
Object.keys(nextConfig.experimental.fontLoaders),
}
}

Expand Down
6 changes: 6 additions & 0 deletions packages/next/build/webpack-config.ts
Expand Up @@ -61,6 +61,7 @@ import loadJsConfig from './load-jsconfig'
import { loadBindings } from './swc'
import { AppBuildManifestPlugin } from './webpack/plugins/app-build-manifest-plugin'
import { SubresourceIntegrityPlugin } from './webpack/plugins/subresource-integrity-plugin'
import { FontLoaderManifestPlugin } from './webpack/plugins/font-loader-manifest-plugin'

const NEXT_PROJECT_ROOT = pathJoin(__dirname, '..', '..')
const NEXT_PROJECT_ROOT_DIST = pathJoin(NEXT_PROJECT_ROOT, 'dist')
Expand Down Expand Up @@ -1508,6 +1509,7 @@ export default async function getBaseWebpackConfig(
'next-middleware-asset-loader',
'next-middleware-wasm-loader',
'next-app-loader',
'next-font-loader',
].reduce((alias, loader) => {
// using multiple aliases to replace `resolveLoader.modules`
alias[loader] = path.join(__dirname, 'webpack', 'loaders', loader)
Expand Down Expand Up @@ -1838,6 +1840,7 @@ 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 @@ -1890,6 +1893,9 @@ export default async function getBaseWebpackConfig(
isClient &&
!!config.experimental.sri?.algorithm &&
new SubresourceIntegrityPlugin(config.experimental.sri.algorithm),
isClient &&
config.experimental.fontLoaders &&
new FontLoaderManifestPlugin(),
!dev &&
isClient &&
new (require('./webpack/plugins/telemetry-plugin').TelemetryPlugin)(
Expand Down
55 changes: 55 additions & 0 deletions packages/next/build/webpack/config/blocks/css/index.ts
Expand Up @@ -3,11 +3,13 @@ import { webpack } from 'next/dist/compiled/webpack/webpack'
import { loader, plugin } from '../../helpers'
import { ConfigurationContext, ConfigurationFn, pipe } from '../../utils'
import { getCssModuleLoader, getGlobalCssLoader } from './loaders'
import { getFontLoader } from './loaders/font-loader'
import {
getCustomDocumentError,
getGlobalImportError,
getGlobalModuleImportError,
getLocalModuleImportError,
getFontLoaderDocumentImportError,
} from './messages'
import { getPostCssPlugins } from './plugins'

Expand Down Expand Up @@ -199,6 +201,59 @@ export const css = curry(async function css(
})
)

// 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),
fontLoaderOptions,
]
)
: undefined

// Font loaders cannot be imported in _document.
fontLoaders?.forEach(([fontLoaderPath, fontLoaderOptions]) => {
fns.push(
loader({
oneOf: [
markRemovable({
test: fontLoaderPath,
// 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: getFontLoaderDocumentImportError(),
},
},
}),
],
})
)

// Matches the resolved font loaders noop files to run next-font-loader
fns.push(
loader({
oneOf: [
markRemovable({
sideEffects: false,
test: fontLoaderPath,
issuer: {
and: [
{
or: [ctx.rootDirectory, regexClientEntry],
},
],
not: [/node_modules/],
},
use: getFontLoader(ctx, lazyPostCSSInitializer, fontLoaderOptions),
}),
],
})
)
})

// 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
@@ -0,0 +1,70 @@
import { webpack } from 'next/dist/compiled/webpack/webpack'
import { ConfigurationContext } from '../../../utils'
import { getClientStyleLoader } from './client'
import { cssFileResolve } from './file-resolve'

export function getFontLoader(
ctx: ConfigurationContext,
postcss: any,
fontLoaderOptions: any
): webpack.RuleSetUseItem[] {
const loaders: webpack.RuleSetUseItem[] = []

if (ctx.isClient) {
// Add appropriate development mode or production mode style
// loader
loaders.push(
getClientStyleLoader({
isAppDir: !!ctx.experimental.appDir,
isDevelopment: ctx.isDevelopment,
assetPrefix: ctx.assetPrefix,
})
)
}

loaders.push({
loader: require.resolve('../../../../loaders/css-loader/src'),
options: {
postcss,
importLoaders: 1,
// Use CJS mode for backwards compatibility:
esModule: false,
url: (url: string, resourcePath: string) =>
cssFileResolve(url, resourcePath, ctx.experimental.urlImports),
import: (url: string, _: any, resourcePath: string) =>
cssFileResolve(url, resourcePath, ctx.experimental.urlImports),
modules: {
// Do not transform class names (CJS mode backwards compatibility):
exportLocalsConvention: 'asIs',
// Server-side (Node.js) rendering support:
exportOnlyLocals: ctx.isServer,
// Disallow global style exports so we can code-split CSS and
// not worry about loading order.
mode: 'pure',
getLocalIdent: (
_context: any,
_localIdentName: any,
exportName: string,
_options: any,
meta: any
) => {
// hash from next-font-loader
return `__${exportName}_${meta.fontFamilyHash}`
},
},
fontLoader: true,
},
})

loaders.push({
loader: 'next-font-loader',
options: {
isServer: ctx.isServer,
assetPrefix: ctx.assetPrefix,
fontLoaderOptions,
postcss,
},
})

return loaders
}
6 changes: 6 additions & 0 deletions packages/next/build/webpack/config/blocks/css/messages.ts
Expand Up @@ -31,3 +31,9 @@ export function getCustomDocumentError() {
'pages/_document.js'
)}. Please move global styles to ${chalk.cyan('pages/_app.js')}.`
}

export function getFontLoaderDocumentImportError() {
return `Font loaders ${chalk.bold('cannot')} be used within ${chalk.cyan(
'pages/_document.js'
)}.`
}
7 changes: 4 additions & 3 deletions packages/next/build/webpack/loaders/css-loader/src/index.js
Expand Up @@ -4,7 +4,6 @@
*/
import CssSyntaxError from './CssSyntaxError'
import Warning from '../../postcss-loader/src/Warning'
// import { icssParser, importParser, urlParser } from './plugins'
import { stringifyRequest } from '../../../stringify-request'

const moduleRegExp = /\.module\.\w+$/i
Expand Down Expand Up @@ -128,6 +127,7 @@ function normalizeOptions(rawOptions, loaderContext) {
: rawOptions.importLoaders,
esModule:
typeof rawOptions.esModule === 'undefined' ? true : rawOptions.esModule,
fontLoader: rawOptions.fontLoader,
}
}

Expand Down Expand Up @@ -169,10 +169,11 @@ export default async function loader(content, map, meta) {
const { icssParser, importParser, urlParser } = require('./plugins')

const replacements = []
const exports = []
// if it's a font loader next-font-loader will have exports that should be exported as is
const exports = options.fontLoader ? meta.exports : []

if (shouldUseModulesPlugins(options)) {
plugins.push(...getModulesPlugins(options, this))
plugins.push(...getModulesPlugins(options, this, meta))
}

const importPluginImports = []
Expand Down
18 changes: 12 additions & 6 deletions packages/next/build/webpack/loaders/css-loader/src/utils.js
Expand Up @@ -135,7 +135,7 @@ function shouldUseIcssPlugin(options) {
return options.icss === true || Boolean(options.modules)
}

function getModulesPlugins(options, loaderContext) {
function getModulesPlugins(options, loaderContext, meta) {
const {
mode,
getLocalIdent,
Expand All @@ -154,11 +154,17 @@ function getModulesPlugins(options, loaderContext) {
extractImports(),
modulesScope({
generateScopedName(exportName) {
return getLocalIdent(loaderContext, localIdentName, exportName, {
context: localIdentContext,
hashPrefix: localIdentHashPrefix,
regExp: localIdentRegExp,
})
return getLocalIdent(
loaderContext,
localIdentName,
exportName,
{
context: localIdentContext,
hashPrefix: localIdentHashPrefix,
regExp: localIdentRegExp,
},
meta
)
},
exportGlobals: options.modules.exportGlobals,
}),
Expand Down
Expand Up @@ -15,6 +15,7 @@ export type EdgeSSRLoaderQuery = {
appDirLoader?: string
pagesType?: 'app' | 'pages' | 'root'
sriEnabled: boolean
hasFontLoaders: boolean
}

export default async function edgeSSRLoader(this: any) {
Expand All @@ -32,6 +33,7 @@ export default async function edgeSSRLoader(this: any) {
appDirLoader: appDirLoaderBase64,
pagesType,
sriEnabled,
hasFontLoaders,
} = this.getOptions()

const appDirLoader = Buffer.from(
Expand Down Expand Up @@ -103,6 +105,9 @@ export default async function edgeSSRLoader(this: any) {
const subresourceIntegrityManifest = ${
sriEnabled ? 'self.__SUBRESOURCE_INTEGRITY_MANIFEST' : 'undefined'
}
const fontLoaderManifest = ${
hasFontLoaders ? 'self.__FONT_LOADER_MANIFEST' : 'undefined'
}

const render = getRender({
pageType,
Expand All @@ -122,6 +127,7 @@ export default async function edgeSSRLoader(this: any) {
subresourceIntegrityManifest,
config: ${stringifiedConfig},
buildId: ${JSON.stringify(buildId)},
fontLoaderManifest,
})

export const ComponentMod = pageMod
Expand Down
Expand Up @@ -2,6 +2,7 @@ import type { NextConfig } from '../../../../server/config-shared'
import type { DocumentType, AppType } from '../../../../shared/lib/utils'
import type { BuildManifest } from '../../../../server/get-page-files'
import type { ReactLoadableManifest } from '../../../../server/load-components'
import type { FontLoaderManifest } from '../../plugins/font-loader-manifest-plugin'

import WebServer from '../../../../server/web-server'
import {
Expand All @@ -28,6 +29,7 @@ export function getRender({
serverCSSManifest,
config,
buildId,
fontLoaderManifest,
}: {
pagesType?: 'app' | 'pages' | 'root'
dev: boolean
Expand All @@ -47,13 +49,15 @@ export function getRender({
appServerMod: any
config: NextConfig
buildId: string
fontLoaderManifest: FontLoaderManifest
}) {
const isAppPath = pagesType === 'app'
const baseLoadComponentResult = {
dev,
buildManifest,
reactLoadableManifest,
subresourceIntegrityManifest,
fontLoaderManifest,
Document,
App: appMod?.default as AppType,
}
Expand Down