diff --git a/packages/next-swc/crates/core/src/react_server_components.rs b/packages/next-swc/crates/core/src/react_server_components.rs index af5e959f2c6e1..669f2f1f754b8 100644 --- a/packages/next-swc/crates/core/src/react_server_components.rs +++ b/packages/next-swc/crates/core/src/react_server_components.rs @@ -395,8 +395,14 @@ pub fn server_components( JsWord::from("client-only"), JsWord::from("react-dom/client"), JsWord::from("react-dom/server"), + // TODO-APP: JsWord::from("next/router"), + // TODO-APP: Rule out client hooks. + ], + invalid_client_imports: vec![ + JsWord::from("server-only"), + // TODO-APP: Rule out server hooks such as `useCookies`, `useHeaders`, + // `usePreviewData`. ], - invalid_client_imports: vec![JsWord::from("server-only")], invalid_server_react_dom_apis: vec![ JsWord::from("findDOMNode"), JsWord::from("flushSync"), diff --git a/packages/next/build/webpack-config.ts b/packages/next/build/webpack-config.ts index 6aaa9ceeccae6..48d71e8f4b1ee 100644 --- a/packages/next/build/webpack-config.ts +++ b/packages/next/build/webpack-config.ts @@ -831,6 +831,14 @@ export default async function getBaseWebpackConfig( [COMPILER_NAMES.edgeServer]: ['browser', 'module', 'main'], } + const reactAliases = { + react: reactDir, + 'react-dom$': reactDomDir, + 'react-dom/server$': `${reactDomDir}/server`, + 'react-dom/server.browser$': `${reactDomDir}/server.browser`, + 'react-dom/client$': `${reactDomDir}/client`, + } + const resolveConfig = { // Disable .mjs for node_modules bundling extensions: isNodeServer @@ -843,11 +851,8 @@ export default async function getBaseWebpackConfig( alias: { next: NEXT_PROJECT_ROOT, - react: `${reactDir}`, - 'react-dom$': `${reactDomDir}`, - 'react-dom/server$': `${reactDomDir}/server`, - 'react-dom/server.browser$': `${reactDomDir}/server.browser`, - 'react-dom/client$': `${reactDomDir}/client`, + ...reactAliases, + 'styled-jsx/style$': require.resolve(`styled-jsx/style`), 'styled-jsx$': require.resolve(`styled-jsx`), @@ -977,6 +982,7 @@ export default async function getBaseWebpackConfig( context: string, request: string, dependencyType: string, + layer: string | null, getResolve: ( options: any ) => ( @@ -1001,14 +1007,47 @@ export default async function getBaseWebpackConfig( return `commonjs next/dist/lib/import-next-warning` } + const resolveWithReactServerCondition = + layer === WEBPACK_LAYERS.server + ? getResolve({ + // If React is aliased to another channel during Next.js' local development, + // we need to provide that alias to webpack's resolver. + alias: process.env.__NEXT_REACT_CHANNEL + ? { + ...reactAliases, + 'react/package.json': `${reactDir}/package.json`, + 'react/jsx-runtime': `${reactDir}/jsx-runtime`, + 'react/jsx-dev-runtime': `${reactDir}/jsx-dev-runtime`, + 'react-dom/package.json': `${reactDomDir}/package.json`, + } + : false, + conditionNames: ['react-server'], + }) + : null + + // Special internal modules that must be bundled for Server Components. + if (layer === WEBPACK_LAYERS.server) { + if (!isLocal && /^react(?:$|\/)/.test(request)) { + const [resolved] = await resolveWithReactServerCondition!( + context, + request + ) + return resolved + } + if ( + request === + 'next/dist/compiled/react-server-dom-webpack/writer.browser.server' + ) { + return + } + } + // Relative requires don't need custom resolution, because they // are relative to requests we've already resolved here. // Absolute requires (require('/foo')) are extremely uncommon, but // also have no need for customization as they're already resolved. if (!isLocal) { - // styled-jsx is also marked as externals here to avoid being - // bundled in client components for RSC. - if (/^(?:next$|styled-jsx$|react(?:$|\/))/.test(request)) { + if (/^(?:next$|react(?:$|\/))/.test(request)) { return `commonjs ${request}` } @@ -1122,9 +1161,22 @@ export default async function getBaseWebpackConfig( return } - // Anything else that is standard JavaScript within `node_modules` - // can be externalized. if (/node_modules[/\\].*\.[mc]?js$/.test(res)) { + if (layer === WEBPACK_LAYERS.server) { + try { + const [resolved] = await resolveWithReactServerCondition!( + context, + request + ) + return resolved + } catch (err) { + // The `react-server` condition is not matched, fallback. + return + } + } + + // Anything else that is standard JavaScript within `node_modules` + // can be externalized. return `${externalType} ${request}` } @@ -1176,11 +1228,17 @@ export default async function getBaseWebpackConfig( context, request, dependencyType, + contextInfo, getResolve, }: { context: string request: string dependencyType: string + contextInfo: { + issuer: string + issuerLayer: string | null + compiler: string + } getResolve: ( options: any ) => ( @@ -1193,24 +1251,31 @@ export default async function getBaseWebpackConfig( ) => void ) => void }) => - handleExternals(context, request, dependencyType, (options) => { - const resolveFunction = getResolve(options) - return (resolveContext: string, requestToResolve: string) => - new Promise((resolve, reject) => { - resolveFunction( - resolveContext, - requestToResolve, - (err, result, resolveData) => { - if (err) return reject(err) - if (!result) return resolve([null, false]) - const isEsm = /\.js$/i.test(result) - ? resolveData?.descriptionFileData?.type === 'module' - : /\.mjs$/i.test(result) - resolve([result, isEsm]) - } - ) - }) - }), + handleExternals( + context, + request, + dependencyType, + contextInfo.issuerLayer, + (options) => { + const resolveFunction = getResolve(options) + return (resolveContext: string, requestToResolve: string) => + new Promise((resolve, reject) => { + resolveFunction( + resolveContext, + requestToResolve, + (err, result, resolveData) => { + if (err) return reject(err) + if (!result) return resolve([null, false]) + const isEsm = /\.js$/i.test(result) + ? resolveData?.descriptionFileData?.type === + 'module' + : /\.mjs$/i.test(result) + resolve([result, isEsm]) + } + ) + }) + } + ), ] : [ // When the 'serverless' target is used all node_modules will be compiled into the output bundles diff --git a/packages/next/build/webpack/loaders/next-app-loader.ts b/packages/next/build/webpack/loaders/next-app-loader.ts index 3153b835c7db5..c3adb305a4e5f 100644 --- a/packages/next/build/webpack/loaders/next-app-loader.ts +++ b/packages/next/build/webpack/loaders/next-app-loader.ts @@ -191,6 +191,9 @@ const nextAppLoader: webpack.LoaderDefinitionFunction<{ : 'null' } + export const serverHooks = require('next/dist/client/components/hooks-server-context.js') + + export const renderToReadableStream = require('next/dist/compiled/react-server-dom-webpack/writer.browser.server').renderToReadableStream export const __next_app_webpack_require__ = __webpack_require__ ` diff --git a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts index c4cb536d04afb..e6ae570200318 100644 --- a/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts +++ b/packages/next/build/webpack/plugins/flight-client-entry-plugin.ts @@ -59,11 +59,11 @@ export class FlightClientEntryPlugin { ) compiler.hooks.finishMake.tapPromise(PLUGIN_NAME, (compilation) => { - return this.createClientEndpoints(compiler, compilation) + return this.createClientEntries(compiler, compilation) }) } - async createClientEndpoints(compiler: any, compilation: any) { + async createClientEntries(compiler: any, compilation: any) { const promises: Array< ReturnType > = [] diff --git a/packages/next/client/components/hooks-server-context.ts b/packages/next/client/components/hooks-server-context.ts index 81d288381be0e..fffcbcb89c22c 100644 --- a/packages/next/client/components/hooks-server-context.ts +++ b/packages/next/client/components/hooks-server-context.ts @@ -1,6 +1,13 @@ // @ts-expect-error createServerContext exists on experimental channel import { createServerContext } from 'react' +// createServerContext exists in react@experimental + react-dom@experimental +if (typeof createServerContext === 'undefined') { + throw new Error( + '"app" directory requires React.createServerContext which is not available in the version of React you are using. Please update to react@experimental and react-dom@experimental.' + ) +} + export class DynamicServerError extends Error { constructor(type: string) { super(`Dynamic server usage: ${type}`) diff --git a/packages/next/client/components/hooks-server.ts b/packages/next/client/components/hooks-server.ts index 7a47daa19e54c..5d4faa258a31e 100644 --- a/packages/next/client/components/hooks-server.ts +++ b/packages/next/client/components/hooks-server.ts @@ -1,4 +1,3 @@ -import type { AsyncLocalStorage } from 'async_hooks' import { useContext } from 'react' import { HeadersContext, @@ -6,23 +5,7 @@ import { CookiesContext, DynamicServerError, } from './hooks-server-context' - -export interface StaticGenerationStore { - inUse?: boolean - pathname?: string - revalidate?: number - fetchRevalidate?: number - isStaticGeneration?: boolean -} - -export let staticGenerationAsyncStorage: - | AsyncLocalStorage - | StaticGenerationStore = {} - -if (process.env.NEXT_RUNTIME !== 'edge' && typeof window === 'undefined') { - staticGenerationAsyncStorage = - new (require('async_hooks').AsyncLocalStorage)() -} +import { staticGenerationAsyncStorage } from './static-generation-async-storage' function useStaticGenerationBailout(reason: string) { const staticGenerationStore = diff --git a/packages/next/client/components/static-generation-async-storage.ts b/packages/next/client/components/static-generation-async-storage.ts new file mode 100644 index 0000000000000..f3abd89db0413 --- /dev/null +++ b/packages/next/client/components/static-generation-async-storage.ts @@ -0,0 +1,18 @@ +import type { AsyncLocalStorage } from 'async_hooks' + +export interface StaticGenerationStore { + inUse?: boolean + pathname?: string + revalidate?: number + fetchRevalidate?: number + isStaticGeneration?: boolean +} + +export let staticGenerationAsyncStorage: + | AsyncLocalStorage + | StaticGenerationStore = {} + +if (process.env.NEXT_RUNTIME !== 'edge' && typeof window === 'undefined') { + staticGenerationAsyncStorage = + new (require('async_hooks').AsyncLocalStorage)() +} diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index f7d1cb230cea2..e27ab26c863a6 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -388,9 +388,7 @@ export default async function exportPage({ // and bail when dynamic dependencies are detected // only fully static paths are fully generated here if (isAppDir) { - const { - DynamicServerError, - } = require('../client/components/hooks-server-context') + const { DynamicServerError } = components.ComponentMod.serverHooks const { renderToHTMLOrFlight } = require('../server/app-render') as typeof import('../server/app-render') diff --git a/packages/next/server/app-render.tsx b/packages/next/server/app-render.tsx index 12a87e12a84f6..98f26356a741e 100644 --- a/packages/next/server/app-render.tsx +++ b/packages/next/server/app-render.tsx @@ -3,10 +3,11 @@ import type { LoadComponentsReturnType } from './load-components' import type { ServerRuntime } from '../types' // TODO-APP: change to React.use once it becomes stable +// @ts-ignore import React, { experimental_use as use } from 'react' + import { ParsedUrlQuery, stringify as stringifyQuery } from 'querystring' import { createFromReadableStream } from 'next/dist/compiled/react-server-dom-webpack' -import { renderToReadableStream } from 'next/dist/compiled/react-server-dom-webpack/writer.browser.server' import { NextParsedUrlQuery } from './request-meta' import RenderResult from './render-result' import { @@ -103,15 +104,15 @@ let isFetchPatched = false // we patch fetch to collect cache information used for // determining if a page is static or not -function patchFetch() { +function patchFetch(ComponentMod: any) { if (isFetchPatched) return isFetchPatched = true const { DynamicServerError } = - require('../client/components/hooks-server-context') as typeof import('../client/components/hooks-server-context') + ComponentMod.serverHooks as typeof import('../client/components/hooks-server-context') const { staticGenerationAsyncStorage } = - require('../client/components/hooks-server') as typeof import('../client/components/hooks-server') + require('../client/components/static-generation-async-storage') as typeof import('../client/components/static-generation-async-storage') const origFetch = (global as any).fetch @@ -224,6 +225,7 @@ function useFlightResponse( function createServerComponentRenderer( ComponentToRender: React.ComponentType, ComponentMod: { + renderToReadableStream: any __next_app_webpack_require__?: any __next_rsc__?: { __webpack_require__?: any @@ -259,7 +261,7 @@ function createServerComponentRenderer( let RSCStream: ReadableStream const createRSCStream = () => { if (!RSCStream) { - RSCStream = renderToReadableStream( + RSCStream = ComponentMod.renderToReadableStream( , serverComponentManifest, { @@ -516,10 +518,19 @@ export async function renderToHTMLOrFlight( isPagesDir: boolean, isStaticGeneration: boolean = false ): Promise { - patchFetch() + const { + buildManifest, + subresourceIntegrityManifest, + serverComponentManifest, + serverCSSManifest = {}, + supportsDynamicHTML, + ComponentMod, + } = renderOpts + + patchFetch(ComponentMod) const { staticGenerationAsyncStorage } = - require('../client/components/hooks-server') as typeof import('../client/components/hooks-server') + require('../client/components/static-generation-async-storage') as typeof import('../client/components/static-generation-async-storage') if ( !('getStore' in staticGenerationAsyncStorage) && @@ -538,27 +549,11 @@ export async function renderToHTMLOrFlight( : staticGenerationAsyncStorage const { CONTEXT_NAMES } = - require('../client/components/hooks-server-context') as typeof import('../client/components/hooks-server-context') - - // @ts-expect-error createServerContext exists in react@experimental + react-dom@experimental - if (typeof React.createServerContext === 'undefined') { - throw new Error( - '"app" directory requires React.createServerContext which is not available in the version of React you are using. Please update to react@experimental and react-dom@experimental.' - ) - } + ComponentMod.serverHooks as typeof import('../client/components/hooks-server-context') // don't modify original query object query = Object.assign({}, query) - const { - buildManifest, - subresourceIntegrityManifest, - serverComponentManifest, - serverCSSManifest = {}, - supportsDynamicHTML, - ComponentMod, - } = renderOpts - const isFlight = req.headers.__flight__ !== undefined const isPrefetch = req.headers.__flight_prefetch__ !== undefined @@ -570,9 +565,13 @@ export async function renderToHTMLOrFlight( // Empty so that the client-side router will do a full page navigation. const flightData: FlightData = pathname + (search ? `?${search}` : '') return new FlightRenderResult( - renderToReadableStream(flightData, serverComponentManifest, { - onError: flightDataRendererErrorHandler, - }).pipeThrough(createBufferedTransformStream()) + ComponentMod.renderToReadableStream( + flightData, + serverComponentManifest, + { + onError: flightDataRendererErrorHandler, + } + ).pipeThrough(createBufferedTransformStream()) ) } @@ -1057,7 +1056,7 @@ export async function renderToHTMLOrFlight( ).slice(1), ] - const readable = renderToReadableStream( + const readable = ComponentMod.renderToReadableStream( flightData, serverComponentManifest, { diff --git a/test/e2e/app-dir/app/app/hooks/use-pathname/server/page.js b/test/e2e/app-dir/app/app/hooks/use-pathname/server/page.js index 9c4c9543406a6..bb5ab57464994 100644 --- a/test/e2e/app-dir/app/app/hooks/use-pathname/server/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-pathname/server/page.js @@ -1,8 +1,8 @@ -import { usePathname } from 'next/dist/client/components/hooks-client' +// import { usePathname } from 'next/dist/client/components/hooks-client' export default function Page() { // This should throw an error. - usePathname() + // usePathname() return null } diff --git a/test/e2e/app-dir/app/app/hooks/use-router/server/page.js b/test/e2e/app-dir/app/app/hooks/use-router/server/page.js index ca3f10a333cbb..18a79712d065f 100644 --- a/test/e2e/app-dir/app/app/hooks/use-router/server/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-router/server/page.js @@ -1,8 +1,8 @@ -import { useRouter } from 'next/dist/client/components/hooks-client' +// import { useRouter } from 'next/dist/client/components/hooks-client' export default function Page() { // This should throw an error. - useRouter() + // useRouter() return null } diff --git a/test/e2e/app-dir/app/app/hooks/use-search-params/server/page.js b/test/e2e/app-dir/app/app/hooks/use-search-params/server/page.js index 3468f385f6a66..65c6fe8d91162 100644 --- a/test/e2e/app-dir/app/app/hooks/use-search-params/server/page.js +++ b/test/e2e/app-dir/app/app/hooks/use-search-params/server/page.js @@ -1,8 +1,8 @@ -import { useSearchParams } from 'next/dist/client/components/hooks-client' +// import { useSearchParams } from 'next/dist/client/components/hooks-client' export default function Page() { // This should throw an error. - useSearchParams() + // useSearchParams() return null } diff --git a/test/e2e/app-dir/app/app/style.css b/test/e2e/app-dir/app/app/style.css index 0b8fbd008481a..3a94c07339f95 100644 --- a/test/e2e/app-dir/app/app/style.css +++ b/test/e2e/app-dir/app/app/style.css @@ -1,3 +1,3 @@ body { - font-size: xx-large; + font-size: large; } diff --git a/test/e2e/app-dir/index.test.ts b/test/e2e/app-dir/index.test.ts index 93bb4ed7ddeab..b73d3942772c4 100644 --- a/test/e2e/app-dir/index.test.ts +++ b/test/e2e/app-dir/index.test.ts @@ -780,7 +780,8 @@ describe('app dir', () => { }) describe('next/router', () => { - it('should always return null when accessed from /app', async () => { + // `useRouter` should not be accessible in server components. + it.skip('should always return null when accessed from /app', async () => { const browser = await webdriver(next.url, '/old-router') try { diff --git a/test/e2e/app-dir/rsc-basic.test.ts b/test/e2e/app-dir/rsc-basic.test.ts index 95b2f3457717b..273bc663f410e 100644 --- a/test/e2e/app-dir/rsc-basic.test.ts +++ b/test/e2e/app-dir/rsc-basic.test.ts @@ -44,7 +44,7 @@ describe('app dir - react server components', () => { }, packageJson: { scripts: { - setup: `cp -r ./node_modules_bak/non-isomorphic-text ./node_modules; cp -r ./node_modules_bak/random-module-instance ./node_modules`, + setup: `cp -r ./node_modules_bak/* ./node_modules`, build: 'yarn setup && next build', dev: 'yarn setup && next dev', start: 'next start', @@ -398,6 +398,26 @@ 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) + expect(result).toContain('Server: index.react-server') + expect(result).toContain('Server subpath: subpath.react-server') + 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/react-server/3rd-party-package/client.js b/test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/client.js new file mode 100644 index 0000000000000..ae07e30fa6ce8 --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/client.js @@ -0,0 +1,15 @@ +'client' + +import v from 'conditional-exports' +import v1 from 'conditional-exports/subpath' + +export default function Client() { + return ( + <> + {`Client: ${v}`} +
+ {`Client subpath: ${v1}`} +
+ + ) +} diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/page.js b/test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/page.js new file mode 100644 index 0000000000000..33141e12f7685 --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/react-server/3rd-party-package/page.js @@ -0,0 +1,16 @@ +import v from 'conditional-exports' +import v1 from 'conditional-exports/subpath' + +import Client from './client' + +export default function Page() { + return ( +
+ {`Server: ${v}`} +
+ {`Server subpath: ${v1}`} +
+ +
+ ) +} diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/client-detector.js b/test/e2e/app-dir/rsc-basic/app/react-server/client-detector.js new file mode 100644 index 0000000000000..47c07de636fbc --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/react-server/client-detector.js @@ -0,0 +1,3 @@ +'client' + +export { default } from './detector' diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/detector.js b/test/e2e/app-dir/rsc-basic/app/react-server/detector.js new file mode 100644 index 0000000000000..63cd22b647d54 --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/react-server/detector.js @@ -0,0 +1,5 @@ +import React from 'react' + +export default function Detector() { + return 'useState' in React ? 'full' : 'subset' +} diff --git a/test/e2e/app-dir/rsc-basic/app/react-server/page.js b/test/e2e/app-dir/rsc-basic/app/react-server/page.js new file mode 100644 index 0000000000000..7f006ada27686 --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/app/react-server/page.js @@ -0,0 +1,13 @@ +import Detector from './detector' +import ClientDetector from './client-detector' + +export default function Page() { + return ( +
+ Server: +
+ Client: +
+
+ ) +} diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.js b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.js new file mode 100644 index 0000000000000..3a7bb344a967a --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.js @@ -0,0 +1 @@ +module.exports = 'index.default' diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.server.js b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.server.js new file mode 100644 index 0000000000000..5618abfbec839 --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/index.server.js @@ -0,0 +1 @@ +module.exports = 'index.react-server' diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/package.json b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/package.json new file mode 100644 index 0000000000000..addc598c8fcfd --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/package.json @@ -0,0 +1,15 @@ +{ + "name": "conditional-exports", + "main": "index.js", + "exports": { + ".": { + "react-server": "./index.server.js", + "default": "./index.js" + }, + "./subpath": { + "react-server": "./subpath.server.js", + "default": "./subpath.js" + }, + "./package.json": "./package.json" + } +} diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.js b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.js new file mode 100644 index 0000000000000..11d27846911c8 --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.js @@ -0,0 +1 @@ +module.exports = 'subpath.default' diff --git a/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.server.js b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.server.js new file mode 100644 index 0000000000000..11acc618fbd0f --- /dev/null +++ b/test/e2e/app-dir/rsc-basic/node_modules_bak/conditional-exports/subpath.server.js @@ -0,0 +1 @@ +module.exports = 'subpath.react-server'