Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom app for switchable runtime #35666

Merged
merged 17 commits into from Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 25 additions & 4 deletions packages/next/build/entries.ts
Expand Up @@ -19,7 +19,12 @@ import { ClientPagesLoaderOptions } from './webpack/loaders/next-client-pages-lo
import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader'
import { LoadedEnvFiles } from '@next/env'
import { parse } from '../build/swc'
import { isCustomErrorPage, isFlightPage, isReservedPage } from './utils'
import {
getRawPageExtensions,
isCustomErrorPage,
isFlightPage,
isReservedPage,
} from './utils'
import { ssrEntries } from './webpack/plugins/middleware-plugin'
import {
MIDDLEWARE_RUNTIME_WEBPACK,
Expand All @@ -32,7 +37,14 @@ export type PagesMapping = {
}

export function getPageFromPath(pagePath: string, extensions: string[]) {
let page = pagePath.replace(new RegExp(`\\.+(${extensions.join('|')})$`), '')
const rawExtensions = getRawPageExtensions(extensions)
const pickedExtensions = pagePath.includes('/_app.server.')
? rawExtensions
: extensions
let page = pagePath.replace(
new RegExp(`\\.+(${pickedExtensions.join('|')})$`),
''
)
page = page.replace(/\\/g, '/').replace(/\/index$/, '')
return page === '' ? '/' : page
}
Expand Down Expand Up @@ -86,13 +98,20 @@ export function createPagesMapping(
// allow falling back to the correct source file so
// that HMR can work properly when a file is added/removed
if (isDev) {
if (hasServerComponents) {
pages['/_app.server'] = `${PAGES_DIR_ALIAS}/_app.server`
}
pages['/_app'] = `${PAGES_DIR_ALIAS}/_app`
pages['/_error'] = `${PAGES_DIR_ALIAS}/_error`
pages['/_document'] = `${PAGES_DIR_ALIAS}/_document`
} else {
if (hasServerComponents) {
pages['/_app.server'] =
pages['/_app.server'] || 'next/dist/pages/_app.server'
}
pages['/_app'] = pages['/_app'] || 'next/dist/pages/_app'
pages['/_error'] = pages['/_error'] || 'next/dist/pages/_error'
pages['/_document'] = pages['/_document'] || `next/dist/pages/_document`
pages['/_document'] = pages['/_document'] || 'next/dist/pages/_document'
}
return pages
}
Expand Down Expand Up @@ -229,6 +248,7 @@ export async function createEntrypoints(

const defaultServerlessOptions = {
absoluteAppPath: pages['/_app'],
absoluteAppServerPath: pages['/_app.server'],
absoluteDocumentPath: pages['/_document'],
absoluteErrorPath: pages['/_error'],
absolute404Path: pages['/404'] || '',
Expand Down Expand Up @@ -320,6 +340,7 @@ export async function createEntrypoints(
} else if (
isLikeServerless &&
page !== '/_app' &&
page !== '/_app.server' &&
page !== '/_document' &&
!isEdgeRuntime
) {
Expand All @@ -333,7 +354,7 @@ export async function createEntrypoints(
)}!`
}

if (page === '/_document') {
if (page === '/_document' || page === '/_app.server') {
return
}

Expand Down
10 changes: 8 additions & 2 deletions packages/next/build/utils.ts
Expand Up @@ -144,6 +144,11 @@ export async function printTreeView(
]

const hasCustomApp = await findPageFile(pagesDir, '/_app', pageExtensions)
const hasCustomAppServer = await findPageFile(
pagesDir,
'/_app.server',
pageExtensions
)

pageInfos.set('/404', {
...(pageInfos.get('/404') || pageInfos.get('/_error')),
Expand All @@ -170,7 +175,8 @@ export async function printTreeView(
!(
e === '/_document' ||
e === '/_error' ||
(!hasCustomApp && e === '/_app')
(!hasCustomApp && e === '/_app') ||
(!hasCustomAppServer && e === '/_app.server')
)
)
.sort((a, b) => a.localeCompare(b))
Expand All @@ -192,7 +198,7 @@ export async function printTreeView(
(pageInfo?.ssgPageDurations?.reduce((a, b) => a + (b || 0), 0) || 0)

const symbol =
item === '/_app'
item === '/_app' || item === '/_app.server'
? ' '
: item.endsWith('/_middleware')
? 'ƒ'
Expand Down
14 changes: 9 additions & 5 deletions packages/next/build/webpack-config.ts
Expand Up @@ -477,9 +477,6 @@ export default async function getBaseWebpackConfig(
const serverComponentsRegex = new RegExp(
`\\.server\\.(${rawPageExtensions.join('|')})$`
)
const clientComponentsRegex = new RegExp(
`\\.client\\.(${rawPageExtensions.join('|')})$`
)

const babelIncludeRegexes: RegExp[] = [
/next[\\/]dist[\\/]shared[\\/]lib/,
Expand Down Expand Up @@ -554,12 +551,19 @@ export default async function getBaseWebpackConfig(

if (dev) {
customAppAliases[`${PAGES_DIR_ALIAS}/_app`] = [
...config.pageExtensions.reduce((prev, ext) => {
...rawPageExtensions.reduce((prev, ext) => {
prev.push(path.join(pagesDir, `_app.${ext}`))
return prev
}, [] as string[]),
'next/dist/pages/_app.js',
]
customAppAliases[`${PAGES_DIR_ALIAS}/_app.server`] = [
...rawPageExtensions.reduce((prev, ext) => {
prev.push(path.join(pagesDir, `_app.server.${ext}`))
return prev
}, [] as string[]),
'next/dist/pages/_app.server.js',
]
customAppAliases[`${PAGES_DIR_ALIAS}/_error`] = [
...config.pageExtensions.reduce((prev, ext) => {
prev.push(path.join(pagesDir, `_error.${ext}`))
Expand Down Expand Up @@ -1509,7 +1513,7 @@ export default async function getBaseWebpackConfig(
}),
hasServerComponents &&
!isServer &&
new FlightManifestPlugin({ dev, clientComponentsRegex }),
new FlightManifestPlugin({ dev, pageExtensions: rawPageExtensions }),
!dev &&
!isServer &&
new TelemetryPlugin(
Expand Down
Expand Up @@ -115,7 +115,7 @@ export default async function transformSource(
const names: string[] = []
await parseModuleInfo(resourcePath, transformedSource, names)

// next.js/packages/next/<component>.js
// Next.js built-in client components
if (/[\\/]next[\\/](link|image)\.js$/.test(resourcePath)) {
names.push('default')
}
Expand Down
28 changes: 10 additions & 18 deletions packages/next/build/webpack/loaders/next-flight-server-loader.ts
@@ -1,26 +1,25 @@
import { parse } from '../../swc'
import { getRawPageExtensions } from '../../utils'
import { buildExports } from './utils'

const imageExtensions = ['jpg', 'jpeg', 'png', 'webp', 'avif']

const createClientComponentFilter = (pageExtensions: string[]) => {
export const createClientComponentFilter = (pageExtensions: string[]) => {
// Special cases for Next.js APIs that are considered as client components:
// - .client.[ext]
// - next/link, next/image
// - next built-in client components
// - .[imageExt]
const regex = new RegExp(
'(' +
`\\.client(\\.(${pageExtensions.join('|')}))?|` +
`next/link|next/image|` +
`next/(link|image)(\\.js)?|` +
`\\.(${imageExtensions.join('|')})` +
')$'
)

return (importSource: string) => regex.test(importSource)
}

const createServerComponentFilter = (pageExtensions: string[]) => {
export const createServerComponentFilter = (pageExtensions: string[]) => {
const regex = new RegExp(`\\.server(\\.(${pageExtensions.join('|')}))?$`)
return (importSource: string) => regex.test(importSource)
}
Expand Down Expand Up @@ -166,15 +165,8 @@ export default async function transformSource(
throw new Error('Expected source to have been transformed to a string.')
}

// We currently assume that all components are shared components (unsuffixed)
// from node_modules.
if (resourcePath.includes('/node_modules/')) {
return source
}

const rawRawPageExtensions = getRawPageExtensions(pageExtensions)
const isServerComponent = createServerComponentFilter(rawRawPageExtensions)
const isClientComponent = createClientComponentFilter(rawRawPageExtensions)
const isServerComponent = createServerComponentFilter(pageExtensions)
const isClientComponent = createClientComponentFilter(pageExtensions)

if (!isClientCompilation) {
// We only apply the loader to server components, or shared components that
Expand Down Expand Up @@ -206,19 +198,19 @@ export default async function transformSource(
*
* Server compilation output:
* (The content of the Server Component module will be kept.)
* export const __next_rsc__ = { __webpack_require__, _: () => { ... } }
* export const __next_rsc__ = { __webpack_require__, _: () => { ... }, server: true }
*
* Client compilation output:
* (The content of the Server Component module will be removed.)
* export const __next_rsc__ = { __webpack_require__, _: () => { ... } }
* export const __next_rsc__ = { __webpack_require__, _: () => { ... }, server: false }
*/

const rscExports: any = {
__next_rsc__: `{
__webpack_require__,
_: () => {\n${imports}\n}
_: () => {\n${imports}\n},
server: ${isServerComponent(resourcePath) ? 'true' : 'false'}
}`,
__next_rsc_server__: isServerComponent(resourcePath) ? 'true' : 'false',
}

if (isClientCompilation) {
Expand Down
Expand Up @@ -7,6 +7,7 @@ export default async function middlewareSSRLoader(this: any) {
buildId,
absolutePagePath,
absoluteAppPath,
absoluteAppServerPath,
absoluteDocumentPath,
absolute500Path,
absoluteErrorPath,
Expand All @@ -16,11 +17,15 @@ export default async function middlewareSSRLoader(this: any) {

const stringifiedPagePath = stringifyRequest(this, absolutePagePath)
const stringifiedAppPath = stringifyRequest(this, absoluteAppPath)
const stringifiedAppServerPath = absoluteAppServerPath
? stringifyRequest(this, absoluteAppServerPath)
: null

const stringifiedErrorPath = stringifyRequest(this, absoluteErrorPath)
const stringifiedDocumentPath = stringifyRequest(this, absoluteDocumentPath)
const stringified500Path = absolute500Path
? stringifyRequest(this, absolute500Path)
: 'null'
: null

const transformed = `
import { adapter } from 'next/dist/server/web/adapter'
Expand All @@ -31,9 +36,15 @@ export default async function middlewareSSRLoader(this: any) {
import Document from ${stringifiedDocumentPath}

const appMod = require(${stringifiedAppPath})
const appServerMod = ${
stringifiedAppServerPath ? `require(${stringifiedAppServerPath})` : 'null'
}
const pageMod = require(${stringifiedPagePath})
const errorMod = require(${stringifiedErrorPath})
const error500Mod = ${stringified500Path} ? require(${stringified500Path}) : null
const error500Mod = ${
stringified500Path ? `require(${stringified500Path})` : 'null'
}


const buildManifest = self.__BUILD_MANIFEST
const reactLoadableManifest = self.__REACT_LOADABLE_MANIFEST
Expand All @@ -44,7 +55,7 @@ export default async function middlewareSSRLoader(this: any) {
page: ${JSON.stringify(page)},
buildId: ${JSON.stringify(buildId)},
}

const render = getRender({
dev: ${dev},
page: ${JSON.stringify(page)},
Expand All @@ -56,6 +67,7 @@ export default async function middlewareSSRLoader(this: any) {
buildManifest,
reactLoadableManifest,
serverComponentManifest: ${isServerComponent} ? rscManifest : null,
appServerMod,
config: ${stringifiedConfig},
buildId: ${JSON.stringify(buildId)},
})
Expand Down
Expand Up @@ -27,6 +27,7 @@ export function getRender({
serverComponentManifest,
config,
buildId,
appServerMod,
}: {
dev: boolean
page: string
Expand All @@ -37,7 +38,8 @@ export function getRender({
Document: DocumentType
buildManifest: BuildManifest
reactLoadableManifest: ReactLoadableManifest
serverComponentManifest: any | null
serverComponentManifest: any
appServerMod: any
config: NextConfig
buildId: string
}) {
Expand All @@ -48,6 +50,7 @@ export function getRender({
Document,
App: appMod.default as AppType,
AppMod: appMod,
AppServerMod: appServerMod,
}

const server = new WebServer({
Expand Down
Expand Up @@ -18,6 +18,7 @@ export type ServerlessLoaderQuery = {
distDir: string
absolutePagePath: string
absoluteAppPath: string
absoluteAppServerPath: string
absoluteDocumentPath: string
absoluteErrorPath: string
absolute404Path: string
Expand Down
Expand Up @@ -65,7 +65,7 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
_params?: any
) {
let Component
let App
let AppMod
let config
let Document
let Error
Expand All @@ -78,7 +78,7 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
getServerSideProps,
getStaticPaths,
Component,
App,
AppMod,
config,
{ default: Document },
{ default: Error },
Expand All @@ -103,8 +103,9 @@ export function getPageHandler(ctx: ServerlessHandlerCtx) {
setLazyProp({ req: req as any }, 'cookies', getCookieParser(req.headers))

const options = {
App,
AppMod,
Document,
ComponentMod: { default: Component },
buildManifest,
getStaticProps,
getServerSideProps,
Expand Down