add NEXT_TRIGGER_URL env var to show request triggering a compilation (#58762)

### What?

Shows the actual request url that triggered a compilation in the console
message

### Why?

makes it easier to find accidental compilation

### How?



Closes PACK-2017
This commit is contained in:
Tobias Koppers 2023-11-27 09:14:20 +01:00 committed by GitHub
parent 7874ad2659
commit 4d330a8500
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 110 additions and 30 deletions

View file

@ -40,6 +40,7 @@ type BuildStatusStore = {
server: WebpackStatus
edgeServer: WebpackStatus
trigger: string | undefined
url: string | undefined
amp: AmpPageStatus
}
@ -111,7 +112,7 @@ let serverWasLoading = true
let edgeServerWasLoading = false
buildStore.subscribe((state) => {
const { amp, client, server, edgeServer, trigger } = state
const { amp, client, server, edgeServer, trigger, url } = state
const { appUrl } = consoleStore.getState()
@ -123,6 +124,7 @@ buildStore.subscribe((state) => {
// If it takes more than 3 seconds to compile, mark it as loading status
loading: true,
trigger,
url,
} as OutputState,
true
)
@ -230,6 +232,7 @@ export function watchCompilers(
server: { loading: true },
edgeServer: { loading: true },
trigger: 'initial',
url: undefined,
})
function tapCompiler(
@ -273,6 +276,7 @@ export function watchCompilers(
buildStore.setState({
client: status,
trigger: undefined,
url: undefined,
})
} else {
buildStore.setState({
@ -290,6 +294,7 @@ export function watchCompilers(
buildStore.setState({
server: status,
trigger: undefined,
url: undefined,
})
} else {
buildStore.setState({
@ -307,6 +312,7 @@ export function watchCompilers(
buildStore.setState({
edgeServer: status,
trigger: undefined,
url: undefined,
})
} else {
buildStore.setState({
@ -317,7 +323,7 @@ export function watchCompilers(
}
const internalSegments = ['[[...__metadata_id__]]', '[__metadata_id__]']
export function reportTrigger(trigger: string) {
export function reportTrigger(trigger: string, url?: string) {
for (const segment of internalSegments) {
if (trigger.includes(segment)) {
trigger = trigger.replace(segment, '')
@ -330,5 +336,6 @@ export function reportTrigger(trigger: string) {
buildStore.setState({
trigger,
url,
})
}

View file

@ -16,6 +16,7 @@ export type OutputState =
| {
loading: true
trigger: string | undefined
url: string | undefined
}
| {
loading: false
@ -51,6 +52,7 @@ function hasStoreChanged(nextStore: OutputState) {
let startTime = 0
let trigger = '' // default, use empty string for trigger
let triggerUrl: string | undefined = undefined
let loadingLogTimer: NodeJS.Timeout | null = null
let traceSpan: Span | null = null
@ -66,6 +68,7 @@ store.subscribe((state) => {
if (state.loading) {
if (state.trigger) {
trigger = state.trigger
triggerUrl = state.url
if (trigger !== 'initial') {
traceSpan = trace('compile-path', undefined, {
trigger: trigger,
@ -73,7 +76,15 @@ store.subscribe((state) => {
if (!loadingLogTimer) {
// Only log compiling if compiled is not finished in 3 seconds
loadingLogTimer = setTimeout(() => {
Log.wait(`Compiling ${trigger} ...`)
if (
triggerUrl &&
triggerUrl !== trigger &&
process.env.NEXT_TRIGGER_URL
) {
Log.wait(`Compiling ${trigger} (${triggerUrl}) ...`)
} else {
Log.wait(`Compiling ${trigger} ...`)
}
}, MAX_LOG_SKIP_DURATION)
}
}

View file

@ -343,6 +343,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
sriEnabled?: boolean
appPaths?: ReadonlyArray<string> | null
shouldEnsure?: boolean
url?: string
}): Promise<FindComponentsResult | null>
protected abstract getFontManifest(): FontManifest | undefined
protected abstract getPrerenderManifest(): PrerenderManifest
@ -3006,7 +3007,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
}
protected abstract getMiddleware(): MiddlewareRoutingItem | undefined
protected abstract getFallbackErrorComponents(): Promise<LoadComponentsReturnType | null>
protected abstract getFallbackErrorComponents(
url?: string
): Promise<LoadComponentsReturnType | null>
protected abstract getRoutesManifest(): NormalizedRouteManifest | undefined
private async renderToResponseImpl(
@ -3249,6 +3252,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
params: {},
isAppPath: true,
shouldEnsure: true,
url: ctx.req.url,
})
using404Page = result !== null
}
@ -3261,6 +3265,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
isAppPath: false,
// Ensuring can't be done here because you never "match" a 404 route.
shouldEnsure: true,
url: ctx.req.url,
})
using404Page = result !== null
}
@ -3283,6 +3288,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
// Ensuring can't be done here because you never "match" a 500
// route.
shouldEnsure: true,
url: ctx.req.url,
})
}
}
@ -3296,6 +3302,7 @@ export default abstract class Server<ServerOptions extends Options = Options> {
// Ensuring can't be done here because you never "match" an error
// route.
shouldEnsure: true,
url: ctx.req.url,
})
statusPage = '/_error'
}
@ -3376,7 +3383,9 @@ export default abstract class Server<ServerOptions extends Options = Options> {
this.logError(renderToHtmlError)
}
res.statusCode = 500
const fallbackComponents = await this.getFallbackErrorComponents()
const fallbackComponents = await this.getFallbackErrorComponents(
ctx.req.url
)
if (fallbackComponents) {
// There was an error, so use it's definition from the route module

View file

@ -148,11 +148,13 @@ export interface NextJsHotReloaderInterface {
appPaths,
definition,
isApp,
url,
}: {
page: string
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
isApp?: boolean
definition: RouteDefinition | undefined
url?: string
}): Promise<void>
}

View file

@ -313,7 +313,7 @@ export default class HotReloader implements NextJsHotReloaderInterface {
if (page === '/_error' || BLOCKED_PAGES.indexOf(page) === -1) {
try {
await this.ensurePage({ page, clientOnly: true })
await this.ensurePage({ page, clientOnly: true, url: req.url })
} catch (error) {
return await renderScriptError(pageBundleRes, getProperError(error))
}
@ -1471,12 +1471,14 @@ export default class HotReloader implements NextJsHotReloaderInterface {
appPaths,
definition,
isApp,
url,
}: {
page: string
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
isApp?: boolean
definition?: RouteDefinition
url?: string
}): Promise<void> {
// Make sure we don't re-build or dispose prebuilt pages
if (page !== '/_error' && BLOCKED_PAGES.indexOf(page) !== -1) {
@ -1494,6 +1496,7 @@ export default class HotReloader implements NextJsHotReloaderInterface {
appPaths,
definition,
isApp,
url,
})
}
}

View file

@ -198,11 +198,12 @@ export default class DevServer extends Server {
const { pagesDir, appDir } = findPagesDir(this.dir)
const ensurer: RouteEnsurer = {
ensure: async (match) => {
ensure: async (match, pathname) => {
await this.ensurePage({
definition: match.definition,
page: match.definition.page,
clientOnly: false,
url: pathname,
})
},
}
@ -559,11 +560,12 @@ export default class DevServer extends Server {
return this.hasPage(this.actualMiddlewareFile!)
}
protected async ensureMiddleware() {
protected async ensureMiddleware(url: string) {
return this.ensurePage({
page: this.actualMiddlewareFile!,
clientOnly: false,
definition: undefined,
url,
})
}
@ -597,15 +599,18 @@ export default class DevServer extends Server {
protected async ensureEdgeFunction({
page,
appPaths,
url,
}: {
page: string
appPaths: string[] | null
url: string
}) {
return this.ensurePage({
page,
appPaths,
clientOnly: false,
definition: undefined,
url,
})
}
@ -762,6 +767,7 @@ export default class DevServer extends Server {
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
definition: RouteDefinition | undefined
url?: string
}): Promise<void> {
await this.bundlerService.ensurePage(opts)
}
@ -773,6 +779,7 @@ export default class DevServer extends Server {
isAppPath,
appPaths = null,
shouldEnsure,
url,
}: {
page: string
query: NextParsedUrlQuery
@ -781,6 +788,7 @@ export default class DevServer extends Server {
sriEnabled?: boolean
appPaths?: ReadonlyArray<string> | null
shouldEnsure: boolean
url?: string
}): Promise<FindComponentsResult | null> {
await this.ready?.promise
@ -796,6 +804,7 @@ export default class DevServer extends Server {
appPaths,
clientOnly: false,
definition: undefined,
url,
})
}
@ -812,6 +821,7 @@ export default class DevServer extends Server {
params,
isAppPath,
shouldEnsure,
url,
})
} catch (err) {
if ((err as any).code !== 'ENOENT') {
@ -821,8 +831,10 @@ export default class DevServer extends Server {
}
}
protected async getFallbackErrorComponents(): Promise<LoadComponentsReturnType | null> {
await this.bundlerService.getFallbackErrorComponents()
protected async getFallbackErrorComponents(
url?: string
): Promise<LoadComponentsReturnType | null> {
await this.bundlerService.getFallbackErrorComponents(url)
return await loadDefaultErrorComponents(this.distDir)
}

View file

@ -678,11 +678,13 @@ export function onDemandEntryHandler({
appPaths,
definition,
isApp,
url,
}: {
page: string
appPaths: ReadonlyArray<string> | null
definition: RouteDefinition | undefined
isApp: boolean | undefined
url?: string
}): Promise<void> {
const stalledTime = 60
const stalledEnsureTimeout = setTimeout(() => {
@ -834,7 +836,7 @@ export function onDemandEntryHandler({
if (hasNewEntry) {
const routePage = isApp ? route.page : normalizeAppPath(route.page)
reportTrigger(routePage)
reportTrigger(routePage, url)
}
if (entriesThatShouldBeInvalidated.length > 0) {
@ -877,6 +879,7 @@ export function onDemandEntryHandler({
appPaths?: ReadonlyArray<string> | null
definition?: RouteDefinition
isApp?: boolean
url?: string
}
// Make sure that we won't have multiple invalidations ongoing concurrently.
@ -900,6 +903,7 @@ export function onDemandEntryHandler({
appPaths = null,
definition,
isApp,
url,
}: EnsurePageOptions) {
// If the route is actually an app page route, then we should have access
// to the app route definition, and therefore, the appPaths from it.
@ -916,6 +920,7 @@ export function onDemandEntryHandler({
appPaths,
definition,
isApp,
url,
})
})
},

View file

@ -9,7 +9,7 @@ import { cyan } from '../../../lib/picocolors'
import type { RouteMatcher } from '../route-matchers/route-matcher'
export interface RouteEnsurer {
ensure(match: RouteMatch): Promise<void>
ensure(match: RouteMatch, pathname: string): Promise<void>
}
export class DevRouteMatcherManager extends DefaultRouteMatcherManager {
@ -74,7 +74,7 @@ export class DevRouteMatcherManager extends DefaultRouteMatcherManager {
for await (const development of super.matchAll(pathname, options)) {
// We're here, which means that we haven't seen this match yet, so we
// should try to ensure it and recompile the production matcher.
await this.ensurer.ensure(development)
await this.ensurer.ensure(development, pathname)
await this.production.reload()
// Iterate over the production matches again, this time we should be able

View file

@ -26,7 +26,7 @@ export class DevBundlerService {
return await this.bundler.logErrorWithOriginalStack(...args)
}
public async getFallbackErrorComponents() {
public async getFallbackErrorComponents(url?: string) {
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.
@ -34,6 +34,7 @@ export class DevBundlerService {
page: '/_error',
clientOnly: false,
definition: undefined,
url,
})
}

View file

@ -49,7 +49,7 @@ export function getResolveRoutes(
opts: Parameters<typeof initialize>[0],
renderServer: RenderServer,
renderServerOpts: Parameters<RenderServer['initialize']>[0],
ensureMiddleware?: () => Promise<void>
ensureMiddleware?: (url?: string) => Promise<void>
) {
type Route = {
/**
@ -446,7 +446,7 @@ export function getResolveRoutes(
// @ts-expect-error BaseNextRequest stuff
match?.(parsedUrl.pathname, req, parsedUrl.query) &&
(!ensureMiddleware ||
(await ensureMiddleware?.()
(await ensureMiddleware?.(req.url)
.then(() => true)
.catch(() => false)))
) {

View file

@ -452,7 +452,11 @@ async function startWatcher(opts: SetupOpts) {
const buildingIds = new Set()
const readyIds = new Set()
function startBuilding(id: string, forceRebuild: boolean = false) {
function startBuilding(
id: string,
requestUrl: string | undefined,
forceRebuild: boolean = false
) {
if (!forceRebuild && readyIds.has(id)) {
return () => {}
}
@ -461,6 +465,7 @@ async function startWatcher(opts: SetupOpts) {
{
loading: true,
trigger: id,
url: requestUrl,
} as OutputState,
true
)
@ -1144,7 +1149,11 @@ async function startWatcher(opts: SetupOpts) {
false,
middleware.endpoint,
async () => {
const finishBuilding = startBuilding('middleware', true)
const finishBuilding = startBuilding(
'middleware',
undefined,
true
)
await processMiddleware()
await propagateServerField(
'actualMiddlewareFile',
@ -1248,6 +1257,7 @@ async function startWatcher(opts: SetupOpts) {
page: denormalizedPagePath,
clientOnly: false,
definition: undefined,
url: req.url,
})
.catch(console.error)
}
@ -1355,11 +1365,12 @@ async function startWatcher(opts: SetupOpts) {
// appPaths,
definition,
isApp,
url: requestUrl,
}) {
let page = definition?.pathname ?? inputPage
if (page === '/_error') {
let finishBuilding = startBuilding(page)
let finishBuilding = startBuilding(page, requestUrl)
try {
if (globalEntries.app) {
const writtenEndpoint = await processResult(
@ -1458,7 +1469,7 @@ async function startWatcher(opts: SetupOpts) {
)
}
finishBuilding = startBuilding(buildingKey)
finishBuilding = startBuilding(buildingKey, requestUrl)
try {
if (globalEntries.app) {
const writtenEndpoint = await processResult(
@ -1546,7 +1557,7 @@ async function startWatcher(opts: SetupOpts) {
// since this can happen when app pages make
// api requests to page API routes.
finishBuilding = startBuilding(buildingKey)
finishBuilding = startBuilding(buildingKey, requestUrl)
const writtenEndpoint = await processResult(
page,
await route.endpoint.writeToDisk()
@ -1571,7 +1582,7 @@ async function startWatcher(opts: SetupOpts) {
break
}
case 'app-page': {
finishBuilding = startBuilding(buildingKey)
finishBuilding = startBuilding(buildingKey, requestUrl)
const writtenEndpoint = await processResult(
page,
await route.htmlEndpoint.writeToDisk()
@ -1622,7 +1633,7 @@ async function startWatcher(opts: SetupOpts) {
break
}
case 'app-route': {
finishBuilding = startBuilding(buildingKey)
finishBuilding = startBuilding(buildingKey, requestUrl)
const writtenEndpoint = await processResult(
page,
await route.endpoint.writeToDisk()
@ -2479,12 +2490,13 @@ async function startWatcher(opts: SetupOpts) {
requestHandler,
logErrorWithOriginalStack,
async ensureMiddleware() {
async ensureMiddleware(requestUrl?: string) {
if (!serverFields.actualMiddlewareFile) return
return hotReloader.ensurePage({
page: serverFields.actualMiddlewareFile,
clientOnly: false,
definition: undefined,
url: requestUrl,
})
},
}

View file

@ -626,6 +626,7 @@ export default class NextNodeServer extends BaseServer {
query,
params,
isAppPath,
url,
}: {
page: string
query: NextParsedUrlQuery
@ -636,6 +637,7 @@ export default class NextNodeServer extends BaseServer {
sriEnabled?: boolean
appPaths?: ReadonlyArray<string> | null
shouldEnsure: boolean
url?: string
}): Promise<FindComponentsResult | null> {
return getTracer().trace(
NextNodeServerSpan.findPageComponents,
@ -651,6 +653,7 @@ export default class NextNodeServer extends BaseServer {
query,
params,
isAppPath,
url,
})
)
}
@ -660,11 +663,13 @@ export default class NextNodeServer extends BaseServer {
query,
params,
isAppPath,
url: _url,
}: {
page: string
query: NextParsedUrlQuery
params: Params
isAppPath: boolean
url?: string
}): Promise<FindComponentsResult | null> {
const pagePaths: string[] = [page]
if (query.amp) {
@ -977,6 +982,7 @@ export default class NextNodeServer extends BaseServer {
clientOnly: boolean
appPaths?: ReadonlyArray<string> | null
match?: RouteMatch
url?: string
}): Promise<void> {
throw new Error(
'Invariant: ensurePage can only be called on the development server'
@ -1288,6 +1294,7 @@ export default class NextNodeServer extends BaseServer {
await this.ensurePage({
page: notFoundPathname,
clientOnly: false,
url: req.url,
}).catch(() => {})
}
@ -1453,10 +1460,11 @@ export default class NextNodeServer extends BaseServer {
* It will make sure that the root middleware or an edge function has been compiled
* so that we can run it.
*/
protected async ensureMiddleware() {}
protected async ensureMiddleware(_url?: string) {}
protected async ensureEdgeFunction(_params: {
page: string
appPaths: string[] | null
url?: string
}) {}
/**
@ -1523,7 +1531,7 @@ export default class NextNodeServer extends BaseServer {
return { finished: false }
}
await this.ensureMiddleware()
await this.ensureMiddleware(params.request.url)
const middlewareInfo = this.getEdgeFunctionInfo({
page: middleware.page,
middleware: true,
@ -1634,7 +1642,7 @@ export default class NextNodeServer extends BaseServer {
this.stripInternalHeaders(req)
try {
await this.ensureMiddleware()
await this.ensureMiddleware(req.url)
result = await this.runMiddleware({
request: req,
@ -1796,7 +1804,11 @@ export default class NextNodeServer extends BaseServer {
const { query, page, match } = params
if (!match)
await this.ensureEdgeFunction({ page, appPaths: params.appPaths })
await this.ensureEdgeFunction({
page,
appPaths: params.appPaths,
url: params.req.url,
})
edgeInfo = this.getEdgeFunctionInfo({
page,
middleware: false,
@ -1903,7 +1915,9 @@ export default class NextNodeServer extends BaseServer {
return serverDistDir
}
protected async getFallbackErrorComponents(): Promise<LoadComponentsReturnType | null> {
protected async getFallbackErrorComponents(
_url?: string
): Promise<LoadComponentsReturnType | null> {
// Not implemented for production use cases, this is implemented on the
// development server.
return null

View file

@ -280,11 +280,13 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
page,
query,
params,
url: _url,
}: {
page: string
query: NextParsedUrlQuery
params: Params | null
isAppPath: boolean
url?: string
}) {
const result = await this.serverOptions.webServerConfig.loadComponent(page)
if (!result) return null
@ -342,7 +344,9 @@ export default class NextWebServer extends BaseServer<WebServerOptions> {
// The web server does not support web sockets.
}
protected async getFallbackErrorComponents(): Promise<LoadComponentsReturnType | null> {
protected async getFallbackErrorComponents(
_url?: string
): Promise<LoadComponentsReturnType | null> {
// The web server does not need to handle fallback errors in production.
return null
}