diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 8a07941359b4e..b6f7be7f3d1e4 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -1586,6 +1586,21 @@ export default async function getBaseWebpackConfig( } as any, ] : []), + // Alias `next/dynamic` to React.lazy implementation for RSC + ...(hasServerComponents && appDir + ? [ + { + test: codeCondition.test, + include: [appDir], + resolve: { + alias: { + [require.resolve('next/dynamic')]: + 'next/dist/client/components/dynamic', + }, + }, + }, + ] + : []), ...(hasServerComponents && (isNodeServer || isEdgeServer) ? [ // RSC server compilation loaders diff --git a/packages/next/client/components/shared/dynamic/index.tsx b/packages/next/client/components/dynamic.tsx similarity index 85% rename from packages/next/client/components/shared/dynamic/index.tsx rename to packages/next/client/components/dynamic.tsx index f333b8fef6c24..4c3f9c8278198 100644 --- a/packages/next/client/components/shared/dynamic/index.tsx +++ b/packages/next/client/components/dynamic.tsx @@ -15,7 +15,5 @@ export type LoadableComponent

= React.ComponentType

export default function dynamic

( loader: Loader

): React.ComponentType

{ - const LazyLoadable = React.lazy(loader) - - return LazyLoadable + return React.lazy(loader) } diff --git a/packages/next/shared/lib/dynamic.tsx b/packages/next/shared/lib/dynamic.tsx index a3046ba882152..b79caf271dc4f 100644 --- a/packages/next/shared/lib/dynamic.tsx +++ b/packages/next/shared/lib/dynamic.tsx @@ -1,5 +1,3 @@ -'client' - import React from 'react' import Loadable from './loadable' diff --git a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js index 63dcccc63c1cf..2d0f36e76be8e 100644 --- a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js +++ b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-client.js @@ -1,6 +1,6 @@ 'client' -import dynamic from 'next/dist/client/components/shared/dynamic' +import dynamic from 'next/dynamic' const Dynamic = dynamic(() => import('../text-dynamic-client')) diff --git a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-server.js b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-server.js index 0d9406e90f4a0..665da40efa5d2 100644 --- a/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-server.js +++ b/test/e2e/app-dir/app/app/dashboard/index/dynamic-imports/dynamic-server.js @@ -1,4 +1,4 @@ -import dynamic from 'next/dist/client/components/shared/dynamic' +import dynamic from 'next/dynamic' const Dynamic = dynamic(() => import('../text-dynamic-server')) diff --git a/test/e2e/app-dir/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic.test.ts index 273f6dd1acd66..44af702ef11cd 100644 --- a/test/e2e/app-dir/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic.test.ts @@ -25,7 +25,7 @@ async function resolveStreamResponse(response: any, onData?: any) { return result } -describe('app dir - react server components', () => { +describe('app dir - rsc basics', () => { let next: NextInstance let distDir: string @@ -44,9 +44,8 @@ describe('app dir - react server components', () => { }, packageJson: { scripts: { - setup: `cp -r ./node_modules_bak/* ./node_modules`, - build: 'yarn setup && next build', - dev: 'yarn setup && next dev', + build: 'next build', + dev: 'next dev', start: 'next start', }, }, @@ -79,7 +78,7 @@ describe('app dir - react server components', () => { const inlineFlightContents = [] const $ = cheerio.load(homeHTML) - $('script').each((index, tag) => { + $('script').each((_index, tag) => { const content = $(tag).text() if (content) inlineFlightContents.push(content) }) @@ -244,20 +243,6 @@ describe('app dir - react server components', () => { expect(imageTag.attr('src')).toContain('data:image') }) - it('should handle external async module libraries correctly', async () => { - const clientHtml = await renderViaHTTP(next.url, '/external-imports/client') - const serverHtml = await renderViaHTTP(next.url, '/external-imports/server') - - expect(clientHtml).toContain('module type:esm-export') - expect(clientHtml).toContain('export named:named') - expect(clientHtml).toContain('export value:123') - expect(clientHtml).toContain('export array:4,5,6') - expect(clientHtml).toContain('export object:{x:1}') - - // support esm module on server side - expect(serverHtml).toContain('random-module-instance') - }) - it('should handle various kinds of exports correctly', async () => { const html = await renderViaHTTP(next.url, '/various-exports') const content = getNodeBySelector(html, 'body').text() @@ -399,45 +384,6 @@ describe('app dir - react server components', () => { ) }) - it('should resolve the subset react in server components based on the react-server condition', async () => { - await fetchViaHTTP(next.url, '/react-server').then(async (response) => { - const result = await resolveStreamResponse(response) - expect(result).toContain('Server: subset') - expect(result).toContain('Client: full') - }) - }) - - it('should resolve 3rd party package exports based on the react-server condition', async () => { - await fetchViaHTTP(next.url, '/react-server/3rd-party-package').then( - async (response) => { - const result = await resolveStreamResponse(response) - - // Package should be resolved based on the react-server condition, - // as well as package's internal & external dependencies. - expect(result).toContain( - 'Server: index.react-server:react.subset:dep.server' - ) - expect(result).toContain('Client: index.default:react.full:dep.default') - - // Subpath exports should be resolved based on the condition too. - expect(result).toContain('Server subpath: subpath.react-server') - expect(result).toContain('Client subpath: subpath.default') - } - ) - }) - - it('should be able to opt-out 3rd party packages being bundled in server components', async () => { - await fetchViaHTTP(next.url, '/react-server/optout').then( - async (response) => { - const result = await resolveStreamResponse(response) - expect(result).toContain('Server: index.default') - expect(result).toContain('Server subpath: subpath.default') - expect(result).toContain('Client: index.default') - expect(result).toContain('Client subpath: subpath.default') - } - ) - }) - if (!isNextDev) { it('should generate edge SSR manifests for Node.js', async () => { const distServerDir = path.join(distDir, 'server') diff --git a/test/e2e/app-dir/rsc-basic/app/shared/page.js b/test/e2e/app-dir/rsc-basic/app/shared/page.js index d61c03703de63..c0f3712250cbd 100644 --- a/test/e2e/app-dir/rsc-basic/app/shared/page.js +++ b/test/e2e/app-dir/rsc-basic/app/shared/page.js @@ -1,7 +1,7 @@ import ClientFromDirect from '../../components/client' import ClientFromShared from '../../components/shared' import SharedFromClient from '../../components/shared-client' -import Random from '../../components/random-module-instance' +// import Random from '../../components/random-module-instance' import Bar from '../../components/bar' export default function Page() { @@ -12,7 +12,7 @@ export default function Page() { // It's expected to have hydration mismatch here. return (

- + {/* */}

diff --git a/test/e2e/app-dir/rsc-basic/components/container.js b/test/e2e/app-dir/rsc-basic/components/container.js deleted file mode 100644 index ab63ebb0d37f0..0000000000000 --- a/test/e2e/app-dir/rsc-basic/components/container.js +++ /dev/null @@ -1,3 +0,0 @@ -export default function Container({ children }) { - return
{children}
-} diff --git a/test/e2e/app-dir/rsc-basic/components/random-module-instance.js b/test/e2e/app-dir/rsc-basic/components/random-module-instance.js deleted file mode 100644 index d3badf9b35ef0..0000000000000 --- a/test/e2e/app-dir/rsc-basic/components/random-module-instance.js +++ /dev/null @@ -1,7 +0,0 @@ -'client' - -import { random } from 'random-module-instance' - -export default function () { - return `node_modules instance from client module ${random}` -} diff --git a/test/e2e/app-dir/rsc-external.test.ts b/test/e2e/app-dir/rsc-external.test.ts new file mode 100644 index 0000000000000..0529deba1fc6a --- /dev/null +++ b/test/e2e/app-dir/rsc-external.test.ts @@ -0,0 +1,113 @@ +import path from 'path' +import { renderViaHTTP, fetchViaHTTP } from 'next-test-utils' +import { createNext, FileRef } from 'e2e-utils' +import { NextInstance } from 'test/lib/next-modes/base' + +async function resolveStreamResponse(response: any, onData?: any) { + let result = '' + onData = onData || (() => {}) + await new Promise((resolve) => { + response.body.on('data', (chunk) => { + result += chunk.toString() + onData(chunk.toString(), result) + }) + + response.body.on('end', resolve) + }) + return result +} + +describe('app dir - rsc external dependency', () => { + let next: NextInstance + + if ((global as any).isNextDeploy) { + it('should skip for deploy mode for now', () => {}) + return + } + + beforeAll(async () => { + next = await createNext({ + files: new FileRef(path.join(__dirname, './rsc-external')), + dependencies: { + react: 'experimental', + 'react-dom': 'experimental', + }, + packageJson: { + scripts: { + setup: `cp -r ./node_modules_bak/* ./node_modules`, + build: 'yarn setup && next build', + dev: 'yarn setup && next dev', + start: 'next start', + }, + }, + installCommand: 'yarn', + startCommand: (global as any).isNextDev ? 'yarn dev' : 'yarn start', + buildCommand: 'yarn build', + }) + }) + afterAll(() => next.destroy()) + + const { isNextDeploy } = global as any + const isReact17 = process.env.NEXT_TEST_REACT_VERSION === '^17' + if (isNextDeploy || isReact17) { + it('should skip tests for next-deploy and react 17', () => {}) + return + } + + it('should be able to opt-out 3rd party packages being bundled in server components', async () => { + await fetchViaHTTP(next.url, '/react-server/optout').then( + async (response) => { + const result = await resolveStreamResponse(response) + expect(result).toContain('Server: index.default') + expect(result).toContain('Server subpath: subpath.default') + expect(result).toContain('Client: index.default') + expect(result).toContain('Client subpath: subpath.default') + } + ) + }) + + it('should handle external async module libraries correctly', async () => { + const clientHtml = await renderViaHTTP(next.url, '/external-imports/client') + const serverHtml = await renderViaHTTP(next.url, '/external-imports/server') + const sharedHtml = await renderViaHTTP(next.url, '/shared-esm-dep') + + expect(clientHtml).toContain('module type:esm-export') + expect(clientHtml).toContain('export named:named') + expect(clientHtml).toContain('export value:123') + expect(clientHtml).toContain('export array:4,5,6') + expect(clientHtml).toContain('export object:{x:1}') + + // support esm module imports on server side, and indirect imports from shared components + expect(serverHtml).toContain('random-module-instance') + expect(sharedHtml).toContain( + 'node_modules instance from client module random-module-instance' + ) + }) + + it('should resolve the subset react in server components based on the react-server condition', async () => { + await fetchViaHTTP(next.url, '/react-server').then(async (response) => { + const result = await resolveStreamResponse(response) + expect(result).toContain('Server: subset') + expect(result).toContain('Client: full') + }) + }) + + it('should resolve 3rd party package exports based on the react-server condition', async () => { + await fetchViaHTTP(next.url, '/react-server/3rd-party-package').then( + async (response) => { + const result = await resolveStreamResponse(response) + + // Package should be resolved based on the react-server condition, + // as well as package's internal & external dependencies. + expect(result).toContain( + 'Server: index.react-server:react.subset:dep.server' + ) + expect(result).toContain('Client: index.default:react.full:dep.default') + + // Subpath exports should be resolved based on the condition too. + expect(result).toContain('Server subpath: subpath.react-server') + expect(result).toContain('Client subpath: subpath.default') + } + ) + }) +}) diff --git a/test/e2e/app-dir/rsc-basic/app/external-imports/client/page.js b/test/e2e/app-dir/rsc-external/app/external-imports/client/page.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/external-imports/client/page.js rename to test/e2e/app-dir/rsc-external/app/external-imports/client/page.js diff --git a/test/e2e/app-dir/rsc-basic/app/external-imports/server/page.js b/test/e2e/app-dir/rsc-external/app/external-imports/server/page.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/external-imports/server/page.js rename to test/e2e/app-dir/rsc-external/app/external-imports/server/page.js diff --git a/test/e2e/app-dir/rsc-external/app/layout.js b/test/e2e/app-dir/rsc-external/app/layout.js new file mode 100644 index 0000000000000..0c44d9a157467 --- /dev/null +++ b/test/e2e/app-dir/rsc-external/app/layout.js @@ -0,0 +1,8 @@ +export default function AppLayout({ children }) { + return ( + + + {children} + + ) +} diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/client.js b/test/e2e/app-dir/rsc-external/app/react-server/3rd-party-package/client.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/client.js rename to test/e2e/app-dir/rsc-external/app/react-server/3rd-party-package/client.js diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/page.js b/test/e2e/app-dir/rsc-external/app/react-server/3rd-party-package/page.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/page.js rename to test/e2e/app-dir/rsc-external/app/react-server/3rd-party-package/page.js diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/client-detector.js b/test/e2e/app-dir/rsc-external/app/react-server/client-detector.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/react-server/client-detector.js rename to test/e2e/app-dir/rsc-external/app/react-server/client-detector.js diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/detector.js b/test/e2e/app-dir/rsc-external/app/react-server/detector.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/react-server/detector.js rename to test/e2e/app-dir/rsc-external/app/react-server/detector.js diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/optout/client.js b/test/e2e/app-dir/rsc-external/app/react-server/optout/client.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/react-server/optout/client.js rename to test/e2e/app-dir/rsc-external/app/react-server/optout/client.js diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/optout/page.js b/test/e2e/app-dir/rsc-external/app/react-server/optout/page.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/react-server/optout/page.js rename to test/e2e/app-dir/rsc-external/app/react-server/optout/page.js diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/page.js b/test/e2e/app-dir/rsc-external/app/react-server/page.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/app/react-server/page.js rename to test/e2e/app-dir/rsc-external/app/react-server/page.js diff --git a/test/e2e/app-dir/rsc-external/app/shared-esm-dep/page.js b/test/e2e/app-dir/rsc-external/app/shared-esm-dep/page.js new file mode 100644 index 0000000000000..f15c5177d6b94 --- /dev/null +++ b/test/e2e/app-dir/rsc-external/app/shared-esm-dep/page.js @@ -0,0 +1,5 @@ +import Random from '../../components/random-module-instance' + +export default function Page() { + return +} diff --git a/test/e2e/app-dir/rsc-external/components/random-module-instance.js b/test/e2e/app-dir/rsc-external/components/random-module-instance.js new file mode 100644 index 0000000000000..5a44dd00b9910 --- /dev/null +++ b/test/e2e/app-dir/rsc-external/components/random-module-instance.js @@ -0,0 +1,7 @@ +'client' + +import { name } from 'random-module-instance' + +export default function () { + return `node_modules instance from client module ${name}` +} diff --git a/test/e2e/app-dir/rsc-external/next.config.js b/test/e2e/app-dir/rsc-external/next.config.js new file mode 100644 index 0000000000000..fadd1f407c87e --- /dev/null +++ b/test/e2e/app-dir/rsc-external/next.config.js @@ -0,0 +1,7 @@ +module.exports = { + reactStrictMode: true, + experimental: { + appDir: true, + serverComponentsExternalPackages: ['conditional-exports-optout'], + }, +} diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/index.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/index.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/index.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/index.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/index.server.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/index.server.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/index.server.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/index.server.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/package.json b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/package.json similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/package.json rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/package.json diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/subpath.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/subpath.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/subpath.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/subpath.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/subpath.server.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/subpath.server.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports-optout/subpath.server.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports-optout/subpath.server.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/dep.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/dep.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/dep.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/dep.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/dep.server.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/dep.server.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/dep.server.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/dep.server.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/index.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/index.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.server.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/index.server.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.server.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/index.server.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/package.json b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/package.json similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/package.json rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/package.json diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/subpath.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/subpath.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.server.js b/test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/subpath.server.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.server.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/conditional-exports/subpath.server.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/non-isomorphic-text/browser.js b/test/e2e/app-dir/rsc-external/node_modules_bak/non-isomorphic-text/browser.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/non-isomorphic-text/browser.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/non-isomorphic-text/browser.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/non-isomorphic-text/index.mjs b/test/e2e/app-dir/rsc-external/node_modules_bak/non-isomorphic-text/index.mjs similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/non-isomorphic-text/index.mjs rename to test/e2e/app-dir/rsc-external/node_modules_bak/non-isomorphic-text/index.mjs diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/non-isomorphic-text/package.json b/test/e2e/app-dir/rsc-external/node_modules_bak/non-isomorphic-text/package.json similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/non-isomorphic-text/package.json rename to test/e2e/app-dir/rsc-external/node_modules_bak/non-isomorphic-text/package.json diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/random-module-instance/index.js b/test/e2e/app-dir/rsc-external/node_modules_bak/random-module-instance/index.js similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/random-module-instance/index.js rename to test/e2e/app-dir/rsc-external/node_modules_bak/random-module-instance/index.js diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/random-module-instance/package.json b/test/e2e/app-dir/rsc-external/node_modules_bak/random-module-instance/package.json similarity index 100% rename from test/e2e/app-dir/rsc-basic/node_modules_bak/random-module-instance/package.json rename to test/e2e/app-dir/rsc-external/node_modules_bak/random-module-instance/package.json