Dev Service (#56442)

This replaces the `(global as any)._nextDevHandlers` invocation with references to a specific service instance while also removing the module scoped `devInstances`. This ensures that correct types are used.

This was done while changing the `match` parameter in `ensurePage` to `definition` which didn't cause a typescript error (it should have).
This commit is contained in:
Wyatt Johnson 2023-10-05 11:45:00 -06:00 committed by GitHub
parent d21025cc3a
commit a44b4f85b5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 203 additions and 191 deletions

20
.github/CODEOWNERS vendored
View file

@ -28,13 +28,13 @@
# Tooling & Telemetry
/packages/next/src/build/ @timneutkens @ijjk @shuding @huozhi @ztanner @feedthejim @vercel/web-tooling
/packages/next/src/server/lib/router-utils/setup-dev.ts @timneutkens @ijjk @shuding @huozhi @feedthejim @ztanner @wyattjoh @vercel/web-tooling
/packages/next/src/telemetry/ @timneutkens @ijjk @shuding @padmaia
/packages/next-swc/ @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
Cargo.toml @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
Cargo.lock @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/.cargo/config.toml @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/.config/nextest.toml @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/test/build-turbopack-tests-manifest.js @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/test/turbopack-tests-manifest.json @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/packages/next/src/build/ @timneutkens @ijjk @shuding @huozhi @ztanner @feedthejim @vercel/web-tooling
/packages/next/src/server/lib/router-utils/setup-dev-bundler.ts @timneutkens @ijjk @shuding @huozhi @feedthejim @ztanner @wyattjoh @vercel/web-tooling
/packages/next/src/telemetry/ @timneutkens @ijjk @shuding @padmaia
/packages/next-swc/ @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
Cargo.toml @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
Cargo.lock @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/.cargo/config.toml @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/.config/nextest.toml @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/test/build-turbopack-tests-manifest.js @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling
/test/turbopack-tests-manifest.json @timneutkens @ijjk @shuding @huozhi @vercel/web-tooling

View file

@ -3,7 +3,7 @@ import type { UrlObject } from 'url'
import type { Duplex } from 'stream'
import type { webpack } from 'next/dist/compiled/webpack/webpack'
import type getBaseWebpackConfig from '../../build/webpack-config'
import type { RouteMatch } from '../future/route-matches/route-match'
import type { RouteDefinition } from '../future/route-definitions/route-definition'
import type { Project, Update as TurbopackUpdate } from '../../build/swc'
import type { VersionInfo } from './parse-version-info'
@ -134,13 +134,13 @@ export interface NextJsHotReloaderInterface {
page,
clientOnly,
appPaths,
match,
definition,
isApp,
}: {
page: string
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
isApp?: boolean
match?: RouteMatch
definition: RouteDefinition | undefined
}): Promise<void>
}

View file

@ -4,6 +4,7 @@ import type { Duplex } from 'stream'
import type { Telemetry } from '../../telemetry/storage'
import type { IncomingMessage, ServerResponse } from 'http'
import type { UrlObject } from 'url'
import type { RouteDefinition } from '../future/route-definitions/route-definition'
import { webpack, StringXor } from 'next/dist/compiled/webpack/webpack'
import { getOverlayMiddleware } from 'next/dist/compiled/@next/react-dev-overlay/dist/middleware'
@ -60,7 +61,6 @@ import ws from 'next/dist/compiled/ws'
import { existsSync, promises as fs } from 'fs'
import { UnwrapPromise } from '../../lib/coalesced-function'
import { getRegistry } from '../../lib/helpers/get-registry'
import { RouteMatch } from '../future/route-matches/route-match'
import { parseVersionInfo, VersionInfo } from './parse-version-info'
import { isAPIRoute } from '../../lib/is-api-route'
import { getRouteLoaderEntry } from '../../build/webpack/loaders/next-route-loader'
@ -1466,14 +1466,14 @@ export default class HotReloader implements NextJsHotReloaderInterface {
page,
clientOnly,
appPaths,
match,
definition,
isApp,
}: {
page: string
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
isApp?: boolean
match?: RouteMatch
definition?: RouteDefinition
}): Promise<void> {
// Make sure we don't re-build or dispose prebuilt pages
if (page !== '/_error' && BLOCKED_PAGES.indexOf(page) !== -1) {
@ -1490,7 +1490,7 @@ export default class HotReloader implements NextJsHotReloaderInterface {
page,
clientOnly,
appPaths,
match,
definition,
isApp,
})
}

View file

@ -8,12 +8,13 @@ import type { UrlWithParsedQuery } from 'url'
import type { BaseNextRequest, BaseNextResponse } from '../base-http'
import type { FallbackMode, MiddlewareRoutingItem } from '../base-server'
import type { FunctionComponent } from 'react'
import type { RouteMatch } from '../future/route-matches/route-match'
import type { RouteDefinition } from '../future/route-definitions/route-definition'
import type { RouteMatcherManager } from '../future/route-matcher-managers/route-matcher-manager'
import type {
NextParsedUrlQuery,
NextUrlWithParsedQuery,
} from '../request-meta'
import type { DevBundlerService } from '../lib/dev-bundler-service'
import fs from 'fs'
import { Worker } from 'next/dist/compiled/jest-worker'
@ -80,6 +81,11 @@ export interface Options extends ServerOptions {
* Tells of Next.js is running from the `next dev` command
*/
isNextDevCommand?: boolean
/**
* Interface to the development bundler.
*/
bundlerService: DevBundlerService
}
export default class DevServer extends Server {
@ -92,15 +98,12 @@ export default class DevServer extends Server {
private actualInstrumentationHookFile?: string
private middleware?: MiddlewareRoutingItem
private originalFetch: typeof fetch
private readonly bundlerService: DevBundlerService
private staticPathsCache: LRUCache<
string,
UnwrapPromise<ReturnType<DevServer['getStaticPaths']>>
>
private invokeDevMethod({ method, args }: { method: string; args: any[] }) {
return (global as any)._nextDevHandlers[method](this.dir, ...args)
}
protected staticPathsWorker?: { [key: string]: any } & {
loadStaticPaths: typeof import('./static-paths-worker').loadStaticPaths
}
@ -140,6 +143,7 @@ export default class DevServer extends Server {
Error.stackTraceLimit = 50
} catch {}
super({ ...options, dev: true })
this.bundlerService = options.bundlerService
this.originalFetch = global.fetch
this.renderOpts.dev = true
this.renderOpts.appDirDevErrorLogger = (err: any) =>
@ -190,7 +194,7 @@ export default class DevServer extends Server {
const ensurer: RouteEnsurer = {
ensure: async (match) => {
await this.ensurePage({
match,
definition: match.definition,
page: match.definition.page,
clientOnly: false,
})
@ -487,10 +491,7 @@ export default class DevServer extends Server {
err?: unknown,
type?: 'unhandledRejection' | 'uncaughtException' | 'warning' | 'app-dir'
): Promise<void> {
await this.invokeDevMethod({
method: 'logErrorWithOriginalStack',
args: [err, type],
})
await this.bundlerService.logErrorWithOriginalStack(err, type)
}
protected getPagesManifest(): PagesManifest | undefined {
@ -534,6 +535,7 @@ export default class DevServer extends Server {
return this.ensurePage({
page: this.actualMiddlewareFile!,
clientOnly: false,
definition: undefined,
})
}
@ -543,6 +545,7 @@ export default class DevServer extends Server {
(await this.ensurePage({
page: this.actualInstrumentationHookFile!,
clientOnly: false,
definition: undefined,
})
.then(() => true)
.catch(() => false))
@ -570,7 +573,12 @@ export default class DevServer extends Server {
page: string
appPaths: string[] | null
}) {
return this.ensurePage({ page, appPaths, clientOnly: false })
return this.ensurePage({
page,
appPaths,
clientOnly: false,
definition: undefined,
})
}
generateRoutes(_dev?: boolean) {
@ -723,12 +731,9 @@ export default class DevServer extends Server {
page: string
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
match?: RouteMatch
definition: RouteDefinition | undefined
}): Promise<void> {
await this.invokeDevMethod({
method: 'ensurePage',
args: [opts],
})
await this.bundlerService.ensurePage(opts)
}
protected async findPageComponents({
@ -759,6 +764,7 @@ export default class DevServer extends Server {
page,
appPaths,
clientOnly: false,
definition: undefined,
})
}
@ -785,17 +791,11 @@ export default class DevServer extends Server {
}
protected async getFallbackErrorComponents(): Promise<LoadComponentsReturnType | null> {
await this.invokeDevMethod({
method: 'getFallbackErrorComponents',
args: [],
})
await this.bundlerService.getFallbackErrorComponents()
return await loadDefaultErrorComponents(this.distDir)
}
async getCompilationError(page: string): Promise<any> {
return await this.invokeDevMethod({
method: 'getCompilationError',
args: [page],
})
return await this.bundlerService.getCompilationError(page)
}
}

View file

@ -33,7 +33,6 @@ import {
COMPILER_NAMES,
RSC_MODULE_TYPES,
} from '../../shared/lib/constants'
import { RouteMatch } from '../future/route-matches/route-match'
import { HMR_ACTIONS_SENT_TO_BROWSER } from './hot-reloader-types'
import HotReloader from './hot-reloader-webpack'
import { isAppPageRouteDefinition } from '../future/route-definitions/app-page-route-definition'
@ -677,13 +676,13 @@ export function onDemandEntryHandler({
page,
clientOnly,
appPaths,
match,
definition,
isApp,
}: {
page: string
clientOnly: boolean
appPaths: ReadonlyArray<string> | null
match: Pick<RouteMatch, 'definition'> | undefined
definition: RouteDefinition | undefined
isApp: boolean | undefined
}): Promise<void> {
const stalledTime = 60
@ -694,11 +693,11 @@ export function onDemandEntryHandler({
}, stalledTime * 1000)
try {
let definition: Pick<RouteDefinition, 'filename' | 'bundlePath' | 'page'>
if (match?.definition) {
definition = match.definition
let route: Pick<RouteDefinition, 'filename' | 'bundlePath' | 'page'>
if (definition) {
route = definition
} else {
definition = await findPagePathData(
route = await findPagePathData(
rootDir,
page,
nextConfig.pageExtensions,
@ -707,18 +706,18 @@ export function onDemandEntryHandler({
)
}
const isInsideAppDir = !!appDir && definition.filename.startsWith(appDir)
const isInsideAppDir = !!appDir && route.filename.startsWith(appDir)
if (typeof isApp === 'boolean' && isApp !== isInsideAppDir) {
Error.stackTraceLimit = 15
throw new Error(
`Ensure bailed, found path "${
definition.page
route.page
}" does not match ensure type (${isApp ? 'app' : 'pages'})`
)
}
const pageBundleType = getPageBundleType(definition.bundlePath)
const pageBundleType = getPageBundleType(route.bundlePath)
const addEntry = (
compilerType: CompilerNameValues
): {
@ -726,11 +725,7 @@ export function onDemandEntryHandler({
newEntry: boolean
shouldInvalidate: boolean
} => {
const entryKey = getEntryKey(
compilerType,
pageBundleType,
definition.page
)
const entryKey = getEntryKey(compilerType, pageBundleType, route.page)
if (
curEntries[entryKey] &&
// there can be an overlap in the entryKey for the instrumentation hook file and a page named the same
@ -758,9 +753,9 @@ export function onDemandEntryHandler({
curEntries[entryKey] = {
type: EntryTypes.ENTRY,
appPaths,
absolutePagePath: definition.filename,
request: definition.filename,
bundlePath: definition.bundlePath,
absolutePagePath: route.filename,
request: route.filename,
bundlePath: route.bundlePath,
dispose: false,
lastActiveTime: Date.now(),
status: ADDED,
@ -774,7 +769,7 @@ export function onDemandEntryHandler({
const staticInfo = await getStaticInfoIncludingLayouts({
page,
pageFilePath: definition.filename,
pageFilePath: route.filename,
isInsideAppDir,
pageExtensions: nextConfig.pageExtensions,
isDev: true,
@ -787,7 +782,7 @@ export function onDemandEntryHandler({
isInsideAppDir && staticInfo.rsc !== RSC_MODULE_TYPES.client
runDependingOnPageType({
page: definition.page,
page: route.page,
pageRuntime: staticInfo.runtime,
pageType: pageBundleType,
onClient: () => {
@ -802,11 +797,11 @@ export function onDemandEntryHandler({
const edgeServerEntry = getEntryKey(
COMPILER_NAMES.edgeServer,
pageBundleType,
definition.page
route.page
)
if (
curEntries[edgeServerEntry] &&
!isInstrumentationHookFile(definition.page)
!isInstrumentationHookFile(route.page)
) {
// Runtime switched from edge to server
delete curEntries[edgeServerEntry]
@ -820,11 +815,11 @@ export function onDemandEntryHandler({
const serverEntry = getEntryKey(
COMPILER_NAMES.server,
pageBundleType,
definition.page
route.page
)
if (
curEntries[serverEntry] &&
!isInstrumentationHookFile(definition.page)
!isInstrumentationHookFile(route.page)
) {
// Runtime switched from server to edge
delete curEntries[serverEntry]
@ -839,9 +834,7 @@ export function onDemandEntryHandler({
const hasNewEntry = addedValues.some((entry) => entry.newEntry)
if (hasNewEntry) {
reportTrigger(
!clientOnly && hasNewEntry ? `${definition.page}` : definition.page
)
reportTrigger(!clientOnly && hasNewEntry ? `${route.page}` : route.page)
}
if (entriesThatShouldBeInvalidated.length > 0) {
@ -883,18 +876,12 @@ export function onDemandEntryHandler({
page: string
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
match?: RouteMatch
definition?: RouteDefinition
isApp?: boolean
}
// Make sure that we won't have multiple invalidations ongoing concurrently.
const batcher = Batcher.create<
Omit<EnsurePageOptions, 'match'> & {
definition?: RouteDefinition
},
void,
string
>({
const batcher = Batcher.create<EnsurePageOptions, void, string>({
// The cache key here is composed of the elements that affect the
// compilation, namely, the page, whether it's client only, and whether
// it's an app page. This ensures that we don't have multiple compilations
@ -913,26 +900,28 @@ export function onDemandEntryHandler({
page,
clientOnly,
appPaths = null,
match,
definition,
isApp,
}: EnsurePageOptions) {
// If the route is actually an app page route, then we should have access
// to the app route match, and therefore, the appPaths from it.
if (
!appPaths &&
match?.definition &&
isAppPageRouteDefinition(match.definition)
) {
appPaths = match.definition.appPaths
// to the app route definition, and therefore, the appPaths from it.
if (!appPaths && definition && isAppPageRouteDefinition(definition)) {
appPaths = definition.appPaths
}
// Wrap the invocation of the ensurePageImpl function in the pending
// wrapper, which will ensure that we don't have multiple compilations
// for the same page happening concurrently.
return batcher.batch(
{ page, clientOnly, appPaths, definition: match?.definition, isApp },
{ page, clientOnly, appPaths, definition, isApp },
async () => {
await ensurePageImpl({ page, clientOnly, appPaths, match, isApp })
await ensurePageImpl({
page,
clientOnly,
appPaths,
definition,
isApp,
})
}
)
},

View file

@ -0,0 +1,74 @@
import type { IncomingMessage } from 'http'
import type { DevBundler } from './router-utils/setup-dev-bundler'
import type { WorkerRequestHandler } from './types'
import { createRequestResponseMocks } from './mock-request'
/**
* The DevBundlerService provides an interface to perform tasks with the
* bundler while in development.
*/
export class DevBundlerService {
constructor(
private readonly bundler: DevBundler,
private readonly handler: WorkerRequestHandler
) {}
public ensurePage: typeof this.bundler.hotReloader.ensurePage = async (
definition
) => {
// TODO: remove after ensure is pulled out of server
return await this.bundler.hotReloader.ensurePage(definition)
}
public logErrorWithOriginalStack: typeof this.bundler.logErrorWithOriginalStack =
async (...args) => {
return await this.bundler.logErrorWithOriginalStack(...args)
}
public async getFallbackErrorComponents() {
await this.bundler.hotReloader.buildFallbackError()
// Build the error page to ensure the fallback is built too.
// TODO: See if this can be moved into hotReloader or removed.
await this.bundler.hotReloader.ensurePage({
page: '/_error',
clientOnly: false,
definition: undefined,
})
}
public async getCompilationError(page: string) {
const errors = await this.bundler.hotReloader.getCompilationErrors(page)
if (!errors) return
// Return the very first error we found.
return errors[0]
}
public async revalidate({
urlPath,
revalidateHeaders,
opts: revalidateOpts,
}: {
urlPath: string
revalidateHeaders: IncomingMessage['headers']
opts: any
}) {
const mocked = createRequestResponseMocks({
url: urlPath,
headers: revalidateHeaders,
})
await this.handler(mocked.req, mocked.res)
await mocked.res.hasStreamed
if (
mocked.res.getHeader('x-nextjs-cache') !== 'REVALIDATED' &&
!(mocked.res.statusCode === 404 && revalidateOpts.unstable_onlyGenerated)
) {
throw new Error(`Invalid response ${mocked.res.statusCode}`)
}
return {}
}
}

View file

@ -1,4 +1,5 @@
import type { NextServer, RequestHandler } from '../next'
import type { DevBundlerService } from './dev-bundler-service'
import next from '../next'
import { PropagateToWorkersField } from './router-utils/types'
@ -79,6 +80,7 @@ async function initializeImpl(opts: {
experimentalHttpsServer: boolean
_ipcPort?: string
_ipcKey?: string
bundlerService: DevBundlerService | undefined
}) {
const type = process.env.__NEXT_PRIVATE_RENDER_WORKER
if (type) {

View file

@ -1,7 +1,6 @@
import type { IncomingMessage } from 'http'
// this must come first as it includes require hooks
import type { WorkerRequestHandler, WorkerUpgradeHandler } from './types'
import type { DevBundler } from './router-utils/setup-dev-bundler'
// This is required before other imports to ensure the require hook is setup.
import '../node-polyfill-fetch'
@ -20,8 +19,6 @@ import { findPagesDir } from '../../lib/find-pages-dir'
import { setupFsCheck } from './router-utils/filesystem'
import { proxyRequest } from './router-utils/proxy-request'
import { isAbortError, pipeReadable } from '../pipe-readable'
import { createRequestResponseMocks } from './mock-request'
import { UnwrapPromise } from '../../lib/coalesced-function'
import { getResolveRoutes } from './router-utils/resolve-routes'
import { NextUrlWithParsedQuery, getRequestMeta } from '../request-meta'
import { pathHasPrefix } from '../../shared/lib/router/utils/path-has-prefix'
@ -35,7 +32,7 @@ import {
PHASE_DEVELOPMENT_SERVER,
PERMANENT_REDIRECT_STATUS,
} from '../../shared/lib/constants'
import type { NextJsHotReloaderInterface } from '../dev/hot-reloader-types'
import { DevBundlerService } from './dev-bundler-service'
const debug = setupDebug('next:router-server:main')
@ -48,11 +45,6 @@ export type RenderServer = Pick<
| 'propagateServerField'
>
const devInstances: Record<
string,
UnwrapPromise<ReturnType<typeof import('./router-utils/setup-dev').setupDev>>
> = {}
export interface LazyRenderServerInstance {
instance?: RenderServer
}
@ -100,11 +92,9 @@ export async function initialize(opts: {
const renderServer: LazyRenderServerInstance = {}
let devInstance:
| UnwrapPromise<
ReturnType<typeof import('./router-utils/setup-dev').setupDev>
>
| undefined
let developmentBundler: DevBundler | undefined
let devBundlerService: DevBundlerService | undefined
if (opts.dev) {
const telemetry = new Telemetry({
@ -112,10 +102,10 @@ export async function initialize(opts: {
})
const { pagesDir, appDir } = findPagesDir(opts.dir)
const { setupDev } =
(await require('./router-utils/setup-dev')) as typeof import('./router-utils/setup-dev')
const { setupDevBundler } =
require('./router-utils/setup-dev-bundler') as typeof import('./router-utils/setup-dev-bundler')
devInstance = await setupDev({
developmentBundler = await setupDevBundler({
// Passed here but the initialization of this object happens below, doing the initialization before the setupDev call breaks.
renderServer,
appDir,
@ -128,74 +118,15 @@ export async function initialize(opts: {
turbo: !!process.env.TURBOPACK,
port: opts.port,
})
devInstances[opts.dir] = devInstance
;(global as any)._nextDevHandlers = {
async ensurePage(
dir: string,
match: Parameters<NextJsHotReloaderInterface['ensurePage']>[0]
) {
const curDevInstance = devInstances[dir]
// TODO: remove after ensure is pulled out of server
return await curDevInstance?.hotReloader.ensurePage(match)
},
async logErrorWithOriginalStack(dir: string, ...args: any[]) {
const curDevInstance = devInstances[dir]
// @ts-ignore
return await curDevInstance?.logErrorWithOriginalStack(...args)
},
async getFallbackErrorComponents(dir: string) {
const curDevInstance = devInstances[dir]
await curDevInstance.hotReloader.buildFallbackError()
// Build the error page to ensure the fallback is built too.
// TODO: See if this can be moved into hotReloader or removed.
await curDevInstance.hotReloader.ensurePage({
page: '/_error',
clientOnly: false,
})
},
async getCompilationError(dir: string, page: string) {
const curDevInstance = devInstances[dir]
const errors = await curDevInstance?.hotReloader?.getCompilationErrors(
page
)
if (!errors) return
// Return the very first error we found.
return errors[0]
},
async revalidate(
dir: string,
{
urlPath,
revalidateHeaders,
opts: revalidateOpts,
}: {
urlPath: string
revalidateHeaders: IncomingMessage['headers']
opts: any
}
) {
const mocked = createRequestResponseMocks({
url: urlPath,
headers: revalidateHeaders,
})
const curRequestHandler = requestHandlers[dir]
// eslint-disable-next-line @typescript-eslint/no-use-before-define
await curRequestHandler(mocked.req, mocked.res)
await mocked.res.hasStreamed
if (
mocked.res.getHeader('x-nextjs-cache') !== 'REVALIDATED' &&
!(
mocked.res.statusCode === 404 &&
revalidateOpts.unstable_onlyGenerated
)
) {
throw new Error(`Invalid response ${mocked.res.statusCode}`)
}
return {}
},
} as any
devBundlerService = new DevBundlerService(
developmentBundler,
// The request handler is assigned below, this allows us to create a lazy
// reference to it.
(req, res) => {
return requestHandlers[opts.dir](req, res)
}
)
}
renderServer.instance =
@ -209,9 +140,10 @@ export async function initialize(opts: {
dev: !!opts.dev,
server: opts.server,
isNodeDebugging: !!opts.isNodeDebugging,
serverFields: devInstance?.serverFields || {},
serverFields: developmentBundler?.serverFields || {},
experimentalTestProxy: !!opts.experimentalTestProxy,
experimentalHttpsServer: !!opts.experimentalHttpsServer,
bundlerService: devBundlerService,
}
// pre-initialize workers
@ -221,7 +153,7 @@ export async function initialize(opts: {
type: 'uncaughtException' | 'unhandledRejection',
err: Error | undefined
) => {
await devInstance?.logErrorWithOriginalStack(err, type)
await developmentBundler?.logErrorWithOriginalStack(err, type)
}
process.on('uncaughtException', logError.bind(null, 'uncaughtException'))
@ -233,7 +165,7 @@ export async function initialize(opts: {
opts,
renderServer.instance,
renderServerOpts,
devInstance?.ensureMiddleware
developmentBundler?.ensureMiddleware
)
const requestHandlerImpl: WorkerRequestHandler = async (req, res) => {
@ -328,7 +260,7 @@ export async function initialize(opts: {
}
// handle hot-reloader first
if (devInstance) {
if (developmentBundler) {
const origUrl = req.url || '/'
if (config.basePath && pathHasPrefix(origUrl, config.basePath)) {
@ -336,7 +268,7 @@ export async function initialize(opts: {
}
const parsedUrl = url.parse(req.url || '/')
const hotReloaderResult = await devInstance.hotReloader.run(
const hotReloaderResult = await developmentBundler.hotReloader.run(
req,
res,
parsedUrl
@ -367,7 +299,7 @@ export async function initialize(opts: {
return
}
if (devInstance && matchedOutput?.type === 'devVirtualFsItem') {
if (developmentBundler && matchedOutput?.type === 'devVirtualFsItem') {
const origUrl = req.url || '/'
if (config.basePath && pathHasPrefix(origUrl, config.basePath)) {
@ -379,7 +311,7 @@ export async function initialize(opts: {
res.setHeader(key, resHeaders[key])
}
}
const result = await devInstance.requestHandler(req, res)
const result = await developmentBundler.requestHandler(req, res)
if (result.finished) {
return
@ -571,7 +503,7 @@ export async function initialize(opts: {
}
const appNotFound = opts.dev
? devInstance?.serverFields.hasAppNotFound
? developmentBundler?.serverFields.hasAppNotFound
: await fsChecker.getItem('/_not-found')
res.statusCode = 404
@ -640,9 +572,9 @@ export async function initialize(opts: {
// console.error(_err);
})
if (opts.dev && devInstance) {
if (opts.dev && developmentBundler) {
if (req.url?.includes(`/_next/webpack-hmr`)) {
return devInstance.hotReloader.onHMR(req, socket, head)
return developmentBundler.hotReloader.onHMR(req, socket, head)
}
}

View file

@ -1065,6 +1065,7 @@ async function startWatcher(opts: SetupOpts) {
.ensurePage({
page: decodedPagePath,
clientOnly: false,
definition: undefined,
})
.catch(console.error)
}
@ -1166,10 +1167,10 @@ async function startWatcher(opts: SetupOpts) {
// Unused parameters
// clientOnly,
// appPaths,
match,
definition,
isApp,
}) {
let page = match?.definition?.pathname ?? inputPage
let page = definition?.pathname ?? inputPage
if (page === '/_error') {
if (globalEntries.app) {
@ -1218,7 +1219,7 @@ async function startWatcher(opts: SetupOpts) {
curEntries.get(page) ??
curEntries.get(
normalizeAppPath(
normalizeMetadataRoute(match?.definition?.page ?? inputPage)
normalizeMetadataRoute(definition?.page ?? inputPage)
)
)
@ -1457,6 +1458,7 @@ async function startWatcher(opts: SetupOpts) {
clientOnly: false,
page: item.itemPath,
isApp: item.type === 'appFile',
definition: undefined,
})
}
})
@ -2236,12 +2238,13 @@ async function startWatcher(opts: SetupOpts) {
return hotReloader.ensurePage({
page: serverFields.actualMiddlewareFile,
clientOnly: false,
definition: undefined,
})
},
}
}
export async function setupDev(opts: SetupOpts) {
export async function setupDevBundler(opts: SetupOpts) {
const isSrcDir = path
.relative(opts.dir, opts.pagesDir || opts.appDir || '')
.startsWith('src')
@ -2266,3 +2269,5 @@ export async function setupDev(opts: SetupOpts) {
)
return result
}
export type DevBundler = Awaited<ReturnType<typeof setupDevBundler>>

View file

@ -1,5 +1,8 @@
import type { Options as DevServerOptions } from './dev/next-dev-server'
import type { NodeRequestHandler } from './next-server'
import type {
NodeRequestHandler,
Options as ServerOptions,
} from './next-server'
import type { UrlWithParsedQuery } from 'url'
import type { NextConfigComplete } from './config-shared'
import type { IncomingMessage, ServerResponse } from 'http'
@ -34,10 +37,15 @@ const getServerImpl = async () => {
return ServerImpl
}
export type NextServerOptions = Partial<DevServerOptions> & {
preloadedConfig?: NextConfigComplete
internal_setStandaloneConfig?: boolean
}
export type NextServerOptions = Omit<
ServerOptions | DevServerOptions,
// This is assigned in this server abstraction.
'conf'
> &
Partial<Pick<ServerOptions | DevServerOptions, 'conf'>> & {
preloadedConfig?: NextConfigComplete
internal_setStandaloneConfig?: boolean
}
export interface RequestHandler {
(
@ -153,7 +161,9 @@ export class NextServer {
return (server as any).close()
}
private async createServer(options: DevServerOptions): Promise<Server> {
private async createServer(
options: ServerOptions | DevServerOptions
): Promise<Server> {
let ServerImplementation: typeof Server
if (options.dev) {
ServerImplementation = require('./dev/next-dev-server').default