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

perf: create an experimental bundled version of the next server #52206

Merged
Show file tree
Hide file tree
Changes from 14 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
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@
},
{
"files": ["packages/**"],
"excludedFiles": ["packages/next/taskfile.js"],
"rules": {
"no-shadow": ["warn", { "builtinGlobals": false }],
"import/no-extraneous-dependencies": [
Expand Down
28 changes: 25 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
"debug.javascript.unmapMissingSources": true,

"files.exclude": {
"**/node_modules": false,
"node_modules": true,
"*[!test]**/node_modules": true
},

Expand Down Expand Up @@ -82,5 +80,29 @@
"language": "markdown",
"scheme": "file"
}
]
],
"workbench.colorCustomizations": {
feedthejim marked this conversation as resolved.
Show resolved Hide resolved
"activityBar.activeBackground": "#3399ff",
"activityBar.background": "#3399ff",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#bf0060",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#e7e7e799",
"editorGroup.border": "#3399ff",
"panel.border": "#3399ff",
"sash.hoverBorder": "#3399ff",
"sideBar.border": "#3399ff",
"statusBar.background": "#007fff",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#3399ff",
"statusBarItem.remoteBackground": "#007fff",
"statusBarItem.remoteForeground": "#e7e7e7",
"tab.activeBorder": "#3399ff",
"titleBar.activeBackground": "#007fff",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#007fff99",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.color": "#007fff"
}
14 changes: 13 additions & 1 deletion packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ import { createClientRouterFilter } from '../lib/create-client-router-filter'
import { createValidFileMatcher } from '../server/lib/find-page-file'
import { startTypeChecking } from './type-check'
import { generateInterceptionRoutesRewrites } from '../lib/generate-interception-routes-rewrites'
import { baseOverrides, experimentalOverrides } from '../server/require-hook'

export type SsgRoute = {
initialRevalidateSeconds: number | false
Expand Down Expand Up @@ -1961,8 +1962,19 @@ export default async function build(
'next/dist/server/lib/render-server-standalone'
)
: null,
require.resolve(
'next/dist/compiled/minimal-next-server/next-server-cached.js'
),
require.resolve(
'next/dist/compiled/minimal-next-server/next-server.js'
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have this behind an experimental config so that we only trace the relevant one as we shouldn't need to continue tracing the full next-server if we're using the minimal-next-server.

...Object.values(baseOverrides).map((override) =>
require.resolve(override)
),
...Object.values(experimentalOverrides).map((override) =>
require.resolve(override)
),
].filter(nonNullable)

// ensure we trace any dependencies needed for custom
// incremental cache handler
if (incrementalCacheHandlerPath) {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
LoadComponentsReturnType,
} from '../server/load-components'
import { trace } from '../trace'
import { setHttpClientAndAgentOptions } from '../server/config'
import { setHttpClientAndAgentOptions } from '../server/setup-http-agent-env'
import { recursiveDelete } from '../lib/recursive-delete'
import { Sema } from 'next/dist/compiled/async-sema'
import { denormalizePagePath } from '../shared/lib/page-path/denormalize-page-path'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { clearModuleContext } from '../../../server/web/sandbox'
import { realpathSync } from '../../../lib/realpath'
import path from 'path'
import isError from '../../../lib/is-error'
import { clearManifestCache } from '../../../server/load-manifest'

type Compiler = webpack.Compiler
type WebpackPluginInstance = webpack.WebpackPluginInstance
Expand Down Expand Up @@ -43,6 +44,10 @@ export function deleteCache(filePath: string) {
if ((global as any)._nextDeleteCache) {
;(global as any)._nextDeleteCache(filePath)
}

// try to clear it from the fs cache
clearManifestCache(filePath)

try {
filePath = realpathSync(filePath)
} catch (e) {
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/export/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { requireFontManifest } from '../server/require'
import { normalizeLocalePath } from '../shared/lib/i18n/normalize-locale-path'
import { trace } from '../trace'
import { isInAmpMode } from '../shared/lib/amp-mode'
import { setHttpClientAndAgentOptions } from '../server/config'
import { setHttpClientAndAgentOptions } from '../server/setup-http-agent-env'
import RenderResult from '../server/render-result'
import isError from '../lib/is-error'
import { addRequestMeta } from '../server/request-meta'
Expand Down
14 changes: 10 additions & 4 deletions packages/next/src/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -755,9 +755,15 @@ export async function normalizeConfig(phase: string, config: any) {
export function validateConfig(userConfig: NextConfig): {
errors?: Array<any> | null
} {
const configValidator = require('next/dist/next-config-validate.js')
configValidator(userConfig)
return {
errors: configValidator.errors,
if (process.env.NEXT_MINIMAL) {
return {
errors: [],
}
} else {
const configValidator = require('next/dist/next-config-validate.js')
configValidator(userConfig)
return {
errors: configValidator.errors,
}
}
}
25 changes: 1 addition & 24 deletions packages/next/src/server/config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { existsSync } from 'fs'
import { basename, extname, join, relative, isAbsolute, resolve } from 'path'
import { pathToFileURL } from 'url'
import { Agent as HttpAgent } from 'http'
import { Agent as HttpsAgent } from 'https'
import findUp from 'next/dist/compiled/find-up'
import chalk from '../lib/chalk'
import * as Log from '../build/output/log'
Expand All @@ -21,6 +19,7 @@ import { ImageConfig, imageConfigDefault } from '../shared/lib/image-config'
import { loadEnvConfig, updateInitialEnv } from '@next/env'
import { flushAndExit } from '../telemetry/flush-and-exit'
import { findRootDir } from '../lib/find-root'
import { setHttpClientAndAgentOptions } from './setup-http-agent-env'

export { DomainLocale, NextConfig, normalizeConfig } from './config-shared'

Expand All @@ -43,28 +42,6 @@ const experimentalWarning = execOnce(
}
)

export function setHttpClientAndAgentOptions(config: {
httpAgentOptions?: NextConfig['httpAgentOptions']
}) {
if ((globalThis as any).__NEXT_HTTP_AGENT) {
// We only need to assign once because we want
// to reuse the same agent for all requests.
return
}

if (!config) {
throw new Error('Expected config.httpAgentOptions to be an object')
}

;(globalThis as any).__NEXT_HTTP_AGENT_OPTIONS = config.httpAgentOptions
;(globalThis as any).__NEXT_HTTP_AGENT = new HttpAgent(
config.httpAgentOptions
)
;(globalThis as any).__NEXT_HTTPS_AGENT = new HttpsAgent(
config.httpAgentOptions
)
}

export function warnOptionHasBeenMovedOutOfExperimental(
config: NextConfig,
oldKey: string,
Expand Down
2 changes: 1 addition & 1 deletion packages/next/src/server/dev/static-paths-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
GenerateParams,
} from '../../build/utils'
import { loadComponents } from '../load-components'
import { setHttpClientAndAgentOptions } from '../config'
import { setHttpClientAndAgentOptions } from '../setup-http-agent-env'
import { IncrementalCache } from '../lib/incremental-cache'
import * as serverHooks from '../../client/components/hooks-server-context'
import { staticGenerationAsyncStorage } from '../../client/components/static-generation-async-storage'
Expand Down
19 changes: 12 additions & 7 deletions packages/next/src/server/load-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { BuildManifest } from './get-page-files'
import { interopDefault } from '../lib/interop-default'
import { getTracer } from './lib/trace/tracer'
import { LoadComponentsSpan } from './lib/trace/constants'

import { loadManifest } from './load-manifest'
export type ManifestItem = {
id: number | string
files: string[]
Expand Down Expand Up @@ -80,10 +80,13 @@ async function loadDefaultErrorComponentsImpl(
/**
* Load manifest file with retries, defaults to 3 attempts.
*/
async function loadManifest<T>(manifestPath: string, attempts = 3): Promise<T> {
async function loadManifestWithRetries<T>(
manifestPath: string,
attempts = 3
): Promise<T> {
while (true) {
try {
return require(manifestPath)
return loadManifest(manifestPath)
} catch (err) {
attempts--
if (attempts <= 0) throw err
Expand Down Expand Up @@ -121,15 +124,17 @@ async function loadComponentsImpl({
clientReferenceManifest,
serverActionsManifest,
] = await Promise.all([
loadManifest<BuildManifest>(join(distDir, BUILD_MANIFEST)),
loadManifest<ReactLoadableManifest>(join(distDir, REACT_LOADABLE_MANIFEST)),
loadManifestWithRetries<BuildManifest>(join(distDir, BUILD_MANIFEST)),
loadManifestWithRetries<ReactLoadableManifest>(
join(distDir, REACT_LOADABLE_MANIFEST)
),
hasServerComponents
? loadManifest<ClientReferenceManifest>(
? loadManifestWithRetries<ClientReferenceManifest>(
join(distDir, 'server', CLIENT_REFERENCE_MANIFEST + '.json')
)
: undefined,
hasServerComponents
? loadManifest(
? loadManifestWithRetries(
join(distDir, 'server', SERVER_REFERENCE_MANIFEST + '.json')
).catch(() => null)
: null,
Expand Down
23 changes: 23 additions & 0 deletions packages/next/src/server/load-manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { readFileSync } from 'fs'

const cache = new Map<string, any>()

export function loadManifest(path: string, shouldCache: boolean = true) {
const cached = shouldCache && cache.get(path)

if (cached) {
return cached
}

const manifest = JSON.parse(readFileSync(path, 'utf8'))

if (shouldCache) {
cache.set(path, manifest)
}

return manifest
}

export function clearManifestCache(path: string): boolean {
return cache.delete(path)
}
33 changes: 20 additions & 13 deletions packages/next/src/server/next-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ import ResponseCache from './response-cache'
import { IncrementalCache } from './lib/incremental-cache'
import { normalizeAppPath } from '../shared/lib/router/utils/app-paths'

import { setHttpClientAndAgentOptions } from './config'
import { setHttpClientAndAgentOptions } from './setup-http-agent-env'
import { RouteKind } from './future/route-kind'

import { PagesAPIRouteMatch } from './future/route-matches/pages-api-route-match'
Expand All @@ -111,6 +111,7 @@ import { createRequestResponseMocks } from './lib/mock-request'
import chalk from 'next/dist/compiled/chalk'
import { NEXT_RSC_UNION_QUERY } from '../client/components/app-router-headers'
import { signalFromNodeRequest } from './web/spec-extension/adapters/next-request'
import { loadManifest } from './load-manifest'

export * from './base-server'

Expand Down Expand Up @@ -269,7 +270,7 @@ export default class NextNodeServer extends BaseServer {
}).catch(() => {})
}

if (this.isRouterWorker) {
if (this.isRouterWorker && !process.env.NEXT_MINIMAL) {
this.renderWorkers = {}
this.renderWorkerOpts = {
port: this.port || 0,
Expand Down Expand Up @@ -435,14 +436,13 @@ export default class NextNodeServer extends BaseServer {
}

protected getPagesManifest(): PagesManifest | undefined {
return require(join(this.serverDistDir, PAGES_MANIFEST))
return loadManifest(join(this.serverDistDir, PAGES_MANIFEST))
}

protected getAppPathsManifest(): PagesManifest | undefined {
if (!this.hasAppDir) return undefined

const appPathsManifestPath = join(this.serverDistDir, APP_PATHS_MANIFEST)
return require(appPathsManifestPath)
return loadManifest(join(this.serverDistDir, APP_PATHS_MANIFEST))
}

protected async hasPage(pathname: string): Promise<boolean> {
Expand Down Expand Up @@ -1165,15 +1165,20 @@ export default class NextNodeServer extends BaseServer {

protected getServerComponentManifest() {
if (!this.hasAppDir) return undefined
return require(join(
this.distDir,
'server',
CLIENT_REFERENCE_MANIFEST + '.json'
))

try {
return loadManifest(
join(this.distDir, 'server', CLIENT_REFERENCE_MANIFEST + '.json')
)
} catch (e) {
return undefined
}
}

protected getNextFontManifest() {
return require(join(this.distDir, 'server', `${NEXT_FONT_MANIFEST}.json`))
return loadManifest(
join(this.distDir, 'server', NEXT_FONT_MANIFEST + '.json')
)
}

protected async getFallback(page: string): Promise<string> {
Expand Down Expand Up @@ -2751,13 +2756,15 @@ export default class NextNodeServer extends BaseServer {
}
return this._cachedPreviewManifest
}
const manifest = require(join(this.distDir, PRERENDER_MANIFEST))

const manifest = loadManifest(join(this.distDir, PRERENDER_MANIFEST))

return (this._cachedPreviewManifest = manifest)
}

protected getRoutesManifest() {
return getTracer().trace(NextNodeServerSpan.getRoutesManifest, () =>
require(join(this.distDir, ROUTES_MANIFEST))
loadManifest(join(this.distDir, ROUTES_MANIFEST))
)
}

Expand Down
6 changes: 5 additions & 1 deletion packages/next/src/server/node-environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@ if (typeof (globalThis as any).AsyncLocalStorage !== 'function') {
}

if (typeof (globalThis as any).WebSocket !== 'function') {
;(globalThis as any).WebSocket = require('next/dist/compiled/ws').WebSocket
Object.defineProperty(globalThis, 'WebSocket', {
get() {
return require('next/dist/compiled/ws').WebSocket
},
})
}
14 changes: 6 additions & 8 deletions packages/next/src/server/node-polyfill-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,12 @@
* Polyfills `FormData` and `Blob` in the Node.js runtime.
*/

if (!(global as any).FormData) {
const { FormData } =
require('next/dist/compiled/@edge-runtime/ponyfill') as typeof import('next/dist/compiled/@edge-runtime/ponyfill')
;(global as any).FormData = FormData
if (!global.FormData) {
const { FormData } = require('next/dist/compiled/@edge-runtime/ponyfill')
global.FormData = FormData
}

if (!(global as any).Blob) {
const { Blob } =
require('next/dist/compiled/@edge-runtime/ponyfill') as typeof import('next/dist/compiled/@edge-runtime/ponyfill')
;(global as any).Blob = Blob
if (!global.Blob) {
const { Blob } = require('next/dist/compiled/@edge-runtime/ponyfill')
global.Blob = Blob
}