Skip to content

Commit

Permalink
Fix @next/font imports from outside of the root directory (#42678)
Browse files Browse the repository at this point in the history
Similar to #42106. Make
`@next/font` works as expected when using `transpilePackages`.

Also makes sure `@next/font` is auto-configured correctly when in a
monorepo.

## 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 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)


Co-authored-by: JJ Kasper <jj@jjsweb.site>
  • Loading branch information
Hannes Bornö and ijjk committed Nov 9, 2022
1 parent c962f2d commit 584d842
Show file tree
Hide file tree
Showing 17 changed files with 74 additions and 77 deletions.
4 changes: 2 additions & 2 deletions errors/babel-font-loader-conflict.md
@@ -1,8 +1,8 @@
# Babel and Font loader conflict
# Babel and `@next/font` conflict

#### Why This Error Occurred

You have tried to use `experimental.fontLoaders` 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 `experimental.fontLoaders`.
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
5 changes: 5 additions & 0 deletions packages/next/build/babel/loader/get-config.ts
Expand Up @@ -121,6 +121,10 @@ function getPlugins(
{ type: 'plugin' }
)
: null
const nextFontUnsupported = createConfigItem(
[require('../plugins/next-font-unsupported')],
{ type: 'plugin' }
)

return [
reactRefreshItem,
Expand All @@ -130,6 +134,7 @@ function getPlugins(
transformDefineItem,
nextSsgItem,
commonJsItem,
nextFontUnsupported,
].filter(Boolean)
}

Expand Down
23 changes: 23 additions & 0 deletions packages/next/build/babel/plugins/next-font-unsupported.ts
@@ -0,0 +1,23 @@
import { NodePath, PluginObj, types } from 'next/dist/compiled/babel/core'

export default function NextPageDisallowReExportAllExports(): PluginObj<any> {
return {
visitor: {
ImportDeclaration(path: NodePath<types.ImportDeclaration>) {
if (
['@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`
)
;(err as any).code = 'BABEL_PARSE_ERROR'
;(err as any).loc =
path.node.loc?.start ?? path.node.loc?.end ?? path.node.loc
throw err
}
},
},
}
}
10 changes: 0 additions & 10 deletions packages/next/build/webpack-config.ts
Expand Up @@ -662,16 +662,6 @@ export default async function getBaseWebpackConfig(
loggedIgnoredCompilerOptions = true
}

if (!useSWCLoader && babelConfigFile && config.experimental.fontLoaders) {
Log.error(
`"experimental.fontLoaders" is enabled which requires SWC although Babel is being used due to custom babel config being present "${path.relative(
dir,
babelConfigFile
)}".\nSee more info here: https://nextjs.org/docs/messages/babel-font-loader-conflict`
)
process.exit(1)
}

const getBabelLoader = () => {
return {
loader: require.resolve('./babel/loader/index'),
Expand Down
27 changes: 0 additions & 27 deletions packages/next/build/webpack/config/blocks/css/index.ts
Expand Up @@ -11,7 +11,6 @@ import {
getGlobalModuleImportError,
getLocalModuleImportError,
getFontLoaderDocumentImportError,
getFontLoaderImportError,
} from './messages'
import { getPostCssPlugins } from './plugins'

Expand All @@ -25,8 +24,6 @@ const regexCssModules = /\.module\.css$/
// RegExps for Syntactically Awesome Style Sheets
const regexSassGlobal = /(?<!\.module)\.(scss|sass)$/
const regexSassModules = /\.module\.(scss|sass)$/
// Also match the virtual client entry which doesn't have file path
const regexClientEntry = /^$/

/**
* Mark a rule as removable if built-in CSS support is disabled
Expand Down Expand Up @@ -218,35 +215,11 @@ export const css = curry(async function css(
markRemovable({
sideEffects: false,
test: fontLoaderPath,
issuer: {
and: [
{
or: [ctx.rootDirectory, regexClientEntry],
},
],
not: [/node_modules/],
},
use: getFontLoader(ctx, lazyPostCSSInitializer, fontLoaderOptions),
}),
],
})
)

fns.push(
loader({
oneOf: [
markRemovable({
test: fontLoaderPath,
use: {
loader: 'error-loader',
options: {
reason: getFontLoaderImportError(),
},
},
}),
],
})
)
})

// CSS cannot be imported in _document. This comes before everything because
Expand Down
6 changes: 0 additions & 6 deletions packages/next/build/webpack/config/blocks/css/messages.ts
Expand Up @@ -37,9 +37,3 @@ export function getFontLoaderDocumentImportError() {
'cannot'
)} be used within ${chalk.cyan('pages/_document.js')}.`
}

export function getFontLoaderImportError() {
return `Font loader error:\nFont loaders ${chalk.bold(
'cannot'
)} be used from within ${chalk.bold('node_modules')}.`
}
25 changes: 13 additions & 12 deletions packages/next/server/config.ts
Expand Up @@ -24,7 +24,6 @@ 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'

Expand Down Expand Up @@ -93,15 +92,17 @@ export function setHttpClientAndAgentOptions(config: {
)
}

async function setFontLoaderDefaults(config: NextConfigComplete, dir: string) {
// Add @next/font loaders by default if they're installed
const hasNextFontDependency = (
await getDependencies({
cwd: dir,
})
).dependencies['@next/font']
function setFontLoaderDefaults(config: NextConfigComplete) {
try {
// eslint-disable-next-line import/no-extraneous-dependencies
const nextFontVersion = require('@next/font/package.json').version
const nextVersion = require('next/package.json').version
if (nextFontVersion !== nextVersion) {
Log.warn(
`Different versions of @next/font (${nextFontVersion}) and next (${nextVersion}) detected. This may lead to unexpected behavior.`
)
}

if (hasNextFontDependency) {
const googleFontLoader = {
loader: '@next/font/google',
}
Expand All @@ -128,7 +129,7 @@ async function setFontLoaderDefaults(config: NextConfigComplete, dir: string) {
) {
config.experimental.fontLoaders.push(localFontLoader)
}
}
} catch {}
}

function assignDefaults(dir: string, userConfig: { [key: string]: any }) {
Expand Down Expand Up @@ -906,7 +907,7 @@ export default async function loadConfig(
configFileName,
...userConfig,
}) as NextConfigComplete
await setFontLoaderDefaults(completeConfig, dir)
setFontLoaderDefaults(completeConfig)
return completeConfig
} else {
const configBaseName = basename(CONFIG_FILES[0], extname(CONFIG_FILES[0]))
Expand Down Expand Up @@ -936,6 +937,6 @@ export default async function loadConfig(
) as NextConfigComplete
completeConfig.configFileName = configFileName
setHttpClientAndAgentOptions(completeConfig)
await setFontLoaderDefaults(completeConfig, dir)
setFontLoaderDefaults(completeConfig)
return completeConfig
}
11 changes: 11 additions & 0 deletions test/e2e/app-dir/rsc-external.test.ts
Expand Up @@ -30,6 +30,7 @@ describe('app dir - rsc external dependency', () => {
next = await createNext({
files: new FileRef(path.join(__dirname, './rsc-external')),
dependencies: {
'@next/font': 'canary',
react: 'latest',
'react-dom': 'latest',
swr: '2.0.0-rc.0',
Expand Down Expand Up @@ -155,4 +156,14 @@ describe('app dir - rsc external dependency', () => {
)
).toBe('rgb(255, 0, 0)')
})

it('should handle external @next/font', async () => {
const browser = await webdriver(next.url, '/font')

expect(
await browser.eval(
`window.getComputedStyle(document.querySelector('p')).fontFamily`
)
).toMatch(/^__myFont_.{6}, __myFont_Fallback_.{6}$/)
})
})
5 changes: 5 additions & 0 deletions test/e2e/app-dir/rsc-external/app/font/page.js
@@ -0,0 +1,5 @@
import { myFont } from 'font'

export default function Page() {
return <p className={myFont.className}>Hello world</p>
}
2 changes: 1 addition & 1 deletion test/e2e/app-dir/rsc-external/next.config.js
Expand Up @@ -3,6 +3,6 @@ module.exports = {
experimental: {
appDir: true,
serverComponentsExternalPackages: ['conditional-exports-optout'],
transpilePackages: ['untranspiled-module', 'css'],
transpilePackages: ['untranspiled-module', 'css', 'font'],
},
}
3 changes: 3 additions & 0 deletions test/e2e/app-dir/rsc-external/node_modules_bak/font/index.ts
@@ -0,0 +1,3 @@
import localFont from '@next/font/local'

export const myFont = localFont({ src: './my-font.woff2' })
Binary file not shown.
@@ -0,0 +1,4 @@
{
"name": "font",
"main": "index.ts"
}
10 changes: 0 additions & 10 deletions test/e2e/next-font/babel/next.config.js

This file was deleted.

Expand Up @@ -2,19 +2,13 @@ import { createNext, FileRef } from 'e2e-utils'
import { NextInstance } from 'test/lib/next-modes/base'
import { join } from 'path'

describe('@next/font/google babel', () => {
const isNextStart = (global as any).isNextStart
describe('@next/fon babel unsupported', () => {
let next: NextInstance

if (!isNextStart) {
it('should only run on next start', () => {})
return
}

beforeAll(async () => {
next = await createNext({
skipStart: true,
files: new FileRef(join(__dirname, 'babel')),
files: new FileRef(join(__dirname, 'babel-unsupported')),
dependencies: {
'@next/font': 'canary',
},
Expand All @@ -27,7 +21,7 @@ describe('@next/font/google babel', () => {
'next build failed with code/signal 1'
)
expect(next.cliOutput).toMatch(
/"experimental.fontLoaders" is enabled which requires SWC although Babel is being used due to custom babel config being present ".babelrc"./
/"@next\/font" requires SWC although Babel is being used due to a custom babel config being present./
)
})
})
File renamed without changes.
@@ -1,3 +1,7 @@
import { Inter } from '@next/font/google'

console.log(Inter)

export default function Page() {
return <p>Hello world</p>
}

0 comments on commit 584d842

Please sign in to comment.