From 953a22249d5630dd2e7d107d8eb6d255ac9babea Mon Sep 17 00:00:00 2001 From: Jiachi Liu Date: Fri, 9 Dec 2022 12:42:15 +0100 Subject: [PATCH 1/2] Alias next/head to noop for rsc and add upgration warning --- packages/next/build/webpack-config.ts | 21 +++++++++++++++++++ packages/next/client/components/noop-head.tsx | 10 +++++++++ test/e2e/app-dir/head.test.ts | 14 +++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 packages/next/client/components/noop-head.tsx diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index fac7fe4b5a787c1..21209d17a4c18c4 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -98,6 +98,14 @@ const BABEL_CONFIG_FILES = [ 'babel.config.cjs', ] +function appDirIssuerLayer(layer: string) { + return ( + layer === WEBPACK_LAYERS.client || + layer === WEBPACK_LAYERS.server || + layer === WEBPACK_LAYERS.appClient + ) +} + export const getBabelConfigFile = async (dir: string) => { const babelConfigFile = await BABEL_CONFIG_FILES.reduce( async (memo: Promise, filename) => { @@ -1709,6 +1717,19 @@ export default async function getBaseWebpackConfig( : []), ...(hasServerComponents ? [ + // Alias next/head component to noop for RSC + { + test: codeCondition.test, + issuerLayer: appDirIssuerLayer, + resolve: { + alias: { + // Alias `next/dynamic` to React.lazy implementation for RSC + [require.resolve('next/head')]: require.resolve( + 'next/dist/client/components/noop-head' + ), + }, + }, + }, { // Alias react-dom for ReactDOM.preload usage. // Alias react for switching between default set and share subset. diff --git a/packages/next/client/components/noop-head.tsx b/packages/next/client/components/noop-head.tsx new file mode 100644 index 000000000000000..800d4b5406f7846 --- /dev/null +++ b/packages/next/client/components/noop-head.tsx @@ -0,0 +1,10 @@ +import { warnOnce } from '../../shared/lib/utils/warn-once' + +export default function NoopHead() { + if (process.env.NODE_ENV !== 'production') { + warnOnce( + `You're using \`next/head\` inside app directory, please migrate to \`head.js\`. Checkout https://beta.nextjs.org/docs/api-reference/file-conventions/head for details.` + ) + } + return null +} diff --git a/test/e2e/app-dir/head.test.ts b/test/e2e/app-dir/head.test.ts index c441577a54bf545..2eed5c95fd49c22 100644 --- a/test/e2e/app-dir/head.test.ts +++ b/test/e2e/app-dir/head.test.ts @@ -115,8 +115,22 @@ describe('app dir head', () => { }) it('should treat next/head as client components but not apply', async () => { + const errors = [] + next.on('stderr', (args) => { + errors.push(args) + }) const html = await renderViaHTTP(next.url, '/next-head') expect(html).not.toMatch(/legacy-head<\/title>/) + + if (globalThis.isNextDev) { + expect( + errors.some( + (output) => + output === + `You're using \`next/head\` inside app directory, please migrate to \`head.js\`. Checkout https://beta.nextjs.org/docs/api-reference/file-conventions/head for details.\n` + ) + ).toBe(true) + } }) } From ef016c1e3673870ba62640313cc450ed11233d92 Mon Sep 17 00:00:00 2001 From: Jiachi Liu <inbox@huozhi.im> Date: Fri, 9 Dec 2022 15:08:23 +0100 Subject: [PATCH 2/2] hoist log and add test for pure client case --- packages/next/client/components/noop-head.tsx | 11 ++++++----- test/e2e/app-dir/head.test.ts | 18 ++++++++++++++++++ .../app-dir/head/app/next-head/client-head.js | 9 +++++++++ .../app-dir/head/app/next-head/dynamic-head.js | 7 +++++++ test/e2e/app-dir/head/app/next-head/page.js | 2 ++ 5 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 test/e2e/app-dir/head/app/next-head/client-head.js create mode 100644 test/e2e/app-dir/head/app/next-head/dynamic-head.js diff --git a/packages/next/client/components/noop-head.tsx b/packages/next/client/components/noop-head.tsx index 800d4b5406f7846..42fbdb94f230f03 100644 --- a/packages/next/client/components/noop-head.tsx +++ b/packages/next/client/components/noop-head.tsx @@ -1,10 +1,11 @@ import { warnOnce } from '../../shared/lib/utils/warn-once' +if (process.env.NODE_ENV !== 'production') { + warnOnce( + `You're using \`next/head\` inside app directory, please migrate to \`head.js\`. Checkout https://beta.nextjs.org/docs/api-reference/file-conventions/head for details.` + ) +} + export default function NoopHead() { - if (process.env.NODE_ENV !== 'production') { - warnOnce( - `You're using \`next/head\` inside app directory, please migrate to \`head.js\`. Checkout https://beta.nextjs.org/docs/api-reference/file-conventions/head for details.` - ) - } return null } diff --git a/test/e2e/app-dir/head.test.ts b/test/e2e/app-dir/head.test.ts index 2eed5c95fd49c22..3b92efc47c4b40e 100644 --- a/test/e2e/app-dir/head.test.ts +++ b/test/e2e/app-dir/head.test.ts @@ -1,9 +1,11 @@ +import fs from 'fs-extra' import path from 'path' import cheerio from 'cheerio' import { createNext, FileRef } from 'e2e-utils' import { NextInstance } from 'test/lib/next-modes/base' import { renderViaHTTP } from 'next-test-utils' import webdriver from 'next-webdriver' +import escapeStringRegexp from 'escape-string-regexp' describe('app dir head', () => { if ((global as any).isNextDeploy) { @@ -130,6 +132,22 @@ describe('app dir head', () => { `You're using \`next/head\` inside app directory, please migrate to \`head.js\`. Checkout https://beta.nextjs.org/docs/api-reference/file-conventions/head for details.\n` ) ).toBe(true) + + const dynamicChunkPath = path.join( + next.testDir, + '.next', + 'static/chunks/_app-client_app_next-head_client-head_js.js' + ) + const content = await fs.readFile(dynamicChunkPath, 'utf-8') + expect(content).not.toMatch( + new RegExp(escapeStringRegexp(`next/dist/shared/lib/head.js`), 'm') + ) + expect(content).toMatch( + new RegExp( + escapeStringRegexp(`next/dist/client/components/noop-head.js`), + 'm' + ) + ) } }) } diff --git a/test/e2e/app-dir/head/app/next-head/client-head.js b/test/e2e/app-dir/head/app/next-head/client-head.js new file mode 100644 index 000000000000000..76f0091490fb33a --- /dev/null +++ b/test/e2e/app-dir/head/app/next-head/client-head.js @@ -0,0 +1,9 @@ +import Head from 'next/head' + +export default function ClientHead() { + return ( + <Head> + <meta content="dynamic" property="dynamic-head" /> + </Head> + ) +} diff --git a/test/e2e/app-dir/head/app/next-head/dynamic-head.js b/test/e2e/app-dir/head/app/next-head/dynamic-head.js new file mode 100644 index 000000000000000..b7be754b7b16c08 --- /dev/null +++ b/test/e2e/app-dir/head/app/next-head/dynamic-head.js @@ -0,0 +1,7 @@ +'use client' + +import dynamic from 'next/dynamic' + +const ClientHead = dynamic(() => import('./client-head'), { ssr: false }) + +export default ClientHead diff --git a/test/e2e/app-dir/head/app/next-head/page.js b/test/e2e/app-dir/head/app/next-head/page.js index e55823f8d945c87..f8ab168be047927 100644 --- a/test/e2e/app-dir/head/app/next-head/page.js +++ b/test/e2e/app-dir/head/app/next-head/page.js @@ -1,4 +1,5 @@ import Head from 'next/head' +import DynamicHead from './dynamic-head' export default function page() { return ( @@ -6,6 +7,7 @@ export default function page() { <Head> <title>legacy-head +

page

)