Skip to content

Commit

Permalink
change rsc detection
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi committed Sep 13, 2022
1 parent 90a3618 commit 450d747
Show file tree
Hide file tree
Showing 12 changed files with 86 additions and 94 deletions.
35 changes: 34 additions & 1 deletion packages/next/build/analysis/get-page-static-info.ts
Expand Up @@ -12,6 +12,7 @@ import * as Log from '../output/log'
import { SERVER_RUNTIME } from '../../lib/constants'
import { ServerRuntime } from 'next/types'
import { checkCustomRoutes } from '../../lib/load-custom-routes'
import { RSC_MODULE_TYPES } from '../../shared/lib/constants'

export interface MiddlewareConfig {
matchers: MiddlewareMatcher[]
Expand All @@ -27,9 +28,34 @@ export interface PageStaticInfo {
runtime?: ServerRuntime
ssg?: boolean
ssr?: boolean
rsc?: RSCModuleType
middleware?: Partial<MiddlewareConfig>
}

export type RSCModuleType = 'server' | 'client'
export function getRSCModuleType(swcAST: any): RSCModuleType {
const { body } = swcAST
// TODO-APP: optimize the directive detection
// Assume there're only "use strict" and "client" directives at top,
// so pick the 2 nodes
const firstTwoNodes = body.slice(0, 2)

let rscType: RSCModuleType = 'server'
for (const node of firstTwoNodes) {
if (
node.type === 'ExpressionStatement' &&
node.expression.type === 'StringLiteral'
) {
if (node.expression.value === RSC_MODULE_TYPES.client) {
// Detect client entry
rscType = 'client'
break
}
}
}
return rscType
}

/**
* Receives a parsed AST from SWC and checks if it belongs to a module that
* requires a runtime to be specified. Those are:
Expand Down Expand Up @@ -226,6 +252,7 @@ export async function getPageStaticInfo(params: {
if (/runtime|getStaticProps|getServerSideProps|matcher/.test(fileContent)) {
const swcAST = await parseModule(pageFilePath, fileContent)
const { ssg, ssr } = checkExports(swcAST)
const rsc = getRSCModuleType(swcAST)

// default / failsafe value for config
let config: any = {}
Expand Down Expand Up @@ -273,10 +300,16 @@ export async function getPageStaticInfo(params: {
return {
ssr,
ssg,
rsc,
...(middlewareConfig && { middleware: middlewareConfig }),
...(runtime && { runtime }),
}
}

return { ssr: false, ssg: false, runtime: nextConfig.experimental?.runtime }
return {
ssr: false,
ssg: false,
rsc: RSC_MODULE_TYPES.server,
runtime: nextConfig.experimental?.runtime,
}
}
16 changes: 7 additions & 9 deletions packages/next/build/entries.ts
Expand Up @@ -37,14 +37,12 @@ import { warn } from './output/log'
import {
isMiddlewareFile,
isMiddlewareFilename,
isServerComponentPage,
NestedMiddlewareError,
MiddlewareInServerlessTargetError,
} from './utils'
import { getPageStaticInfo } from './analysis/get-page-static-info'
import { normalizePathSep } from '../shared/lib/page-path/normalize-path-sep'
import { normalizePagePath } from '../shared/lib/page-path/normalize-page-path'
import { serverComponentRegex } from './webpack/loaders/utils'
import { ServerRuntime } from '../types'
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'
import { encodeMatchers } from './webpack/loaders/next-middleware-loader'
Expand Down Expand Up @@ -204,10 +202,7 @@ export function getEdgeServerEntry(opts: {
absolutePagePath: opts.absolutePagePath,
buildId: opts.buildId,
dev: opts.isDev,
isServerComponent: isServerComponentPage(
opts.config,
opts.absolutePagePath
),
isServerComponent: opts.isServerComponent,
page: opts.page,
stringifiedConfig: JSON.stringify(opts.config),
pagesType: opts.pagesType,
Expand Down Expand Up @@ -414,16 +409,19 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
nestedMiddleware.push(page)
}

const isServerComponent = serverComponentRegex.test(absolutePagePath)
const isInsideAppDir = appDir && absolutePagePath.startsWith(appDir)

const isInsideAppDir =
!!appDir &&
(absolutePagePath.startsWith(APP_DIR_ALIAS) ||
absolutePagePath.startsWith(appDir))
const staticInfo = await getPageStaticInfo({
nextConfig: config,
pageFilePath,
isDev,
page,
})

const isServerComponent = isInsideAppDir && staticInfo.rsc === 'server'

if (isMiddlewareFile(page)) {
middlewareMatchers = staticInfo.middleware?.matchers ?? [
{ regexp: '.*' },
Expand Down
24 changes: 9 additions & 15 deletions packages/next/build/index.ts
Expand Up @@ -96,7 +96,6 @@ import {
printTreeView,
copyTracedFiles,
isReservedPage,
isServerComponentPage,
} from './utils'
import getBaseWebpackConfig from './webpack-config'
import { PagesManifest } from './webpack/plugins/pages-manifest-plugin'
Expand Down Expand Up @@ -1267,21 +1266,16 @@ export default async function build(
)
: appPaths?.find((p) => p.startsWith(actualPage + '/page.'))

const pageRuntime =
const staticInfo =
pagesDir && pageType === 'pages' && pagePath
? (
await getPageStaticInfo({
pageFilePath: join(pagesDir, pagePath),
nextConfig: config,
})
).runtime
: undefined

if (hasServerComponents && pagePath) {
if (isServerComponentPage(config, pagePath)) {
isServerComponent = true
}
}
? await getPageStaticInfo({
pageFilePath: join(pagesDir, pagePath),
nextConfig: config,
})
: {}
const pageRuntime = staticInfo.runtime
isServerComponent =
pageType === 'app' && staticInfo.rsc === 'server'

if (
// Only calculate page static information if the page is not an
Expand Down
17 changes: 1 addition & 16 deletions packages/next/build/utils.ts
Expand Up @@ -1327,6 +1327,7 @@ export function detectConflictingPaths(
}
}

// TODO-APP: drop withoutRSCExtensions to use original page extensions
/**
* With RSC we automatically add .server and .client to page extensions. This
* function allows to remove them for cases where we just need to strip out
Expand All @@ -1338,22 +1339,6 @@ export function withoutRSCExtensions(pageExtensions: string[]): string[] {
)
}

export function isServerComponentPage(
nextConfig: NextConfigComplete,
filePath: string
): boolean {
if (!nextConfig.experimental.serverComponents) {
return false
}

const rawPageExtensions = withoutRSCExtensions(
nextConfig.pageExtensions || []
)
return rawPageExtensions.some((ext) => {
return filePath.endsWith(`.server.${ext}`)
})
}

export async function copyTracedFiles(
dir: string,
distDir: string,
Expand Down
4 changes: 0 additions & 4 deletions packages/next/build/webpack-config.ts
Expand Up @@ -685,10 +685,6 @@ export default async function getBaseWebpackConfig(
? withoutRSCExtensions(config.pageExtensions)
: config.pageExtensions

const serverComponentsRegex = new RegExp(
`\\.server\\.(${rawPageExtensions.join('|')})$`
)

const babelIncludeRegexes: RegExp[] = [
/next[\\/]dist[\\/]shared[\\/]lib/,
/next[\\/]dist[\\/]client/,
Expand Down
52 changes: 20 additions & 32 deletions packages/next/build/webpack/loaders/next-flight-loader/index.ts
@@ -1,6 +1,9 @@
import path from 'path'
import { RSC_CLIENT_ENTRY } from '../../../../shared/lib/constants'
import { checkExports } from '../../../analysis/get-page-static-info'
import { RSC_MODULE_TYPES } from '../../../../shared/lib/constants'
import {
checkExports,
getRSCModuleType,
} from '../../../analysis/get-page-static-info'
import { parse } from '../../../swc'

function transformClient(resourcePath: string): string {
Expand Down Expand Up @@ -31,50 +34,35 @@ const isPageOrLayoutFile = (filePath: string) => {

export default async function transformSource(
this: any,
source: string
): Promise<string> {
source: string,
sourceMap: any
) {
if (typeof source !== 'string') {
throw new Error('Expected source to have been transformed to a string.')
}

const { resourcePath } = this
const callback = this.async()
const buildInfo = (this as any)._module.buildInfo

const swcAST = await parse(source, {
filename: resourcePath,
isModule: 'unknown',
})
const isModule = swcAST.type === 'Module'
const { body } = swcAST
// TODO-APP: optimize the directive detection
// Assume there're only "use strict" and "<type>-entry" directives at top,
// so pick the 2 nodes
const firstTwoNodes = body.slice(0, 2)
const appDir = path.join(this.rootContext, 'app')
const isUnderAppDir = containsPath(appDir, this.resourcePath)

const rscType = getRSCModuleType(swcAST)

// Assign the RSC meta information to buildInfo.
buildInfo.rsc = { type: rscType }

const isModule = swcAST.type === 'Module'
const createError = (name: string) =>
new Error(
`${name} is not supported in client components.\nFrom: ${this.resourcePath}`
)

const appDir = path.join(this.rootContext, 'app')
const isUnderAppDir = containsPath(appDir, this.resourcePath)
const isResourcePageOrLayoutFile = isPageOrLayoutFile(this.resourcePath)

// Assign the RSC meta information to buildInfo.
buildInfo.rsc = {}
for (const node of firstTwoNodes) {
if (
node.type === 'ExpressionStatement' &&
node.expression.type === 'StringLiteral'
) {
if (node.expression.value === RSC_CLIENT_ENTRY) {
// Detect client entry
buildInfo.rsc.type = RSC_CLIENT_ENTRY
break
}
}
}

// If client entry has any gSSP/gSP data fetching methods, error
function errorForInvalidDataFetching(onError: (error: any) => void) {
if (isUnderAppDir && isResourcePageOrLayoutFile) {
Expand All @@ -88,12 +76,12 @@ export default async function transformSource(
}
}

if (buildInfo.rsc.type === RSC_CLIENT_ENTRY) {
if (buildInfo.rsc.type === RSC_MODULE_TYPES.client) {
errorForInvalidDataFetching(this.emitError)
const code = transformClient(this.resourcePath)
return code
return callback(null, code, sourceMap)
}

const code = transformServer(source, isModule)
return code
return callback(null, code, sourceMap)
}
Expand Up @@ -15,6 +15,7 @@ import { APP_DIR_ALIAS } from '../../../lib/constants'
import {
COMPILER_NAMES,
FLIGHT_SERVER_CSS_MANIFEST,
RSC_MODULE_TYPES,
} from '../../../shared/lib/constants'
import { FlightCSSManifest } from './flight-manifest-plugin'

Expand Down Expand Up @@ -240,7 +241,7 @@ export class FlightClientEntryPlugin {

const isCSS = regexCSS.test(modRequest)
const rscModuleType = mod.buildInfo.rsc?.type
const isClientComponent = rscModuleType === 'client-entry'
const isClientComponent = rscModuleType === RSC_MODULE_TYPES.client

if (isCSS) {
serverCSSImports[layoutOrPageRequest] =
Expand Down
4 changes: 2 additions & 2 deletions packages/next/build/webpack/plugins/flight-manifest-plugin.ts
Expand Up @@ -8,7 +8,7 @@
import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
import {
FLIGHT_MANIFEST,
RSC_CLIENT_ENTRY,
RSC_MODULE_TYPES,
} from '../../../shared/lib/constants'
import { relative } from 'path'

Expand Down Expand Up @@ -179,7 +179,7 @@ export class FlightManifestPlugin {
// That way we know by the type of dep whether to include.
// It also resolves conflicts when the same module is in multiple chunks.
const rscType = mod.buildInfo.rsc?.type
if (rscType !== RSC_CLIENT_ENTRY) return
if (rscType !== RSC_MODULE_TYPES.client) return

if (/\/(page|layout)\.(ts|js)x?$/.test(resource)) {
entryFilepath = resource
Expand Down
12 changes: 4 additions & 8 deletions packages/next/server/dev/hot-reloader.ts
Expand Up @@ -46,7 +46,6 @@ import { getProperError } from '../../lib/is-error'
import ws from 'next/dist/compiled/ws'
import { promises as fs } from 'fs'
import { getPageStaticInfo } from '../../build/analysis/get-page-static-info'
import { serverComponentRegex } from '../../build/webpack/loaders/utils'
import { UnwrapPromise } from '../../lib/coalesced-function'

function diff(a: Set<any>, b: Set<any>) {
Expand Down Expand Up @@ -598,27 +597,24 @@ export default class HotReloader {
}
}

const isServerComponent = isEntry
? serverComponentRegex.test(entryData.absolutePagePath)
: false

const isAppPath = !!this.appDir && bundlePath.startsWith('app/')
const staticInfo = isEntry
? await getPageStaticInfo({
pageFilePath: entryData.absolutePagePath,
nextConfig: this.config,
isDev: true,
})
: {}
const isServerComponent = isAppPath && staticInfo.rsc === 'server'

await runDependingOnPageType({
page,
pageRuntime: staticInfo.runtime,
onEdgeServer: () => {
// TODO-APP: verify if child entry should support.
if (!isEdgeServerCompilation || !isEntry) return
const isApp = this.appDir && bundlePath.startsWith('app/')
const appDirLoader =
isApp && this.appDir
isAppPath && this.appDir
? getAppEntry({
name: bundlePath,
appPaths: entryData.appPaths,
Expand Down Expand Up @@ -648,7 +644,7 @@ export default class HotReloader {
pages: this.pagesMapping,
isServerComponent,
appDirLoader,
pagesType: isApp ? 'app' : undefined,
pagesType: isAppPath ? 'app' : undefined,
}),
appDir: this.config.experimental.appDir,
})
Expand Down

0 comments on commit 450d747

Please sign in to comment.