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

feat: add instrumentation hook #46002

Merged
merged 27 commits into from
Feb 20, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/next/src/build/build-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const NextBuildContext: Partial<{
mappedRootPaths: {
[page: string]: string
}
hasInstrumentationHook: boolean

// misc fields
telemetryPlugin: TelemetryPlugin
Expand Down
18 changes: 18 additions & 0 deletions packages/next/src/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ROOT_DIR_ALIAS,
APP_DIR_ALIAS,
WEBPACK_LAYERS,
INSTRUMENTATION_HOOK_FILENAME,
} from '../lib/constants'
import { isAPIRoute } from '../lib/is-api-route'
import { isEdgeRuntime } from '../lib/is-edge-runtime'
Expand All @@ -36,6 +37,7 @@ import { warn } from './output/log'
import {
isMiddlewareFile,
isMiddlewareFilename,
isInstrumentationHookFile,
NestedMiddlewareError,
} from './utils'
import { getPageStaticInfo } from './analysis/get-page-static-info'
Expand Down Expand Up @@ -148,6 +150,7 @@ export interface CreateEntrypointsParams {
appDir?: string
appPaths?: Record<string, string>
pageExtensions: string[]
hasInstrumentationHook?: boolean
}

export function getEdgeServerEntry(opts: {
Expand All @@ -163,6 +166,7 @@ export function getEdgeServerEntry(opts: {
middleware?: Partial<MiddlewareConfig>
pagesType: 'app' | 'pages' | 'root'
appDirLoader?: string
hasInstrumentationHook?: boolean
}) {
if (
opts.pagesType === 'app' &&
Expand Down Expand Up @@ -200,6 +204,13 @@ export function getEdgeServerEntry(opts: {
return `next-edge-function-loader?${stringify(loaderParams)}!`
}

if (isInstrumentationHookFile(opts.page)) {
return {
import: opts.page,
filename: `edge-${INSTRUMENTATION_HOOK_FILENAME}.js`,
}
}

const loaderParams: EdgeSSRLoaderQuery = {
absolute500Path: opts.pages['/500'] || '',
absoluteAppPath: opts.pages['/_app'],
Expand Down Expand Up @@ -267,7 +278,13 @@ export async function runDependingOnPageType<T>(params: {
onServer: () => T
page: string
pageRuntime: ServerRuntime
pageType?: 'app' | 'pages' | 'root'
feedthejim marked this conversation as resolved.
Show resolved Hide resolved
}): Promise<void> {
if (params.pageType === 'root' && isInstrumentationHookFile(params.page)) {
await Promise.all([params.onServer(), params.onEdgeServer()])
return
}

if (isMiddlewareFile(params.page)) {
await params.onEdgeServer()
return
Expand Down Expand Up @@ -404,6 +421,7 @@ export async function createEntrypoints(params: CreateEntrypointsParams) {
await runDependingOnPageType({
page,
pageRuntime: staticInfo.runtime,
pageType: pagesType,
onClient: () => {
if (isServerComponent || isInsideAppDir) {
// We skip the initial entries for server component pages and let the
Expand Down
22 changes: 21 additions & 1 deletion packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
PUBLIC_DIR_MIDDLEWARE_CONFLICT,
MIDDLEWARE_FILENAME,
PAGES_DIR_ALIAS,
INSTRUMENTATION_HOOK_FILENAME,
} from '../lib/constants'
import { fileExists } from '../lib/file-exists'
import { findPagesDir } from '../lib/find-pages-dir'
Expand Down Expand Up @@ -513,11 +514,30 @@ export default async function build(
`^${MIDDLEWARE_FILENAME}\\.(?:${config.pageExtensions.join('|')})$`
)

const instrumentationHookDetectionRegExp = new RegExp(
`^${INSTRUMENTATION_HOOK_FILENAME}\\.(?:${config.pageExtensions.join(
'|'
)})$`
)

const rootDir = path.join((pagesDir || appDir)!, '..')
const instrumentationHookEnabled = Boolean(
config.experimental.instrumentationHook
)
const rootPaths = (
await flatReaddir(rootDir, middlewareDetectionRegExp)
await flatReaddir(rootDir, [
middlewareDetectionRegExp,
...(instrumentationHookEnabled
? [instrumentationHookDetectionRegExp]
: []),
])
).map((absoluteFile) => absoluteFile.replace(dir, ''))

const hasInstrumentationHook = rootPaths.some((p) =>
p.includes(INSTRUMENTATION_HOOK_FILENAME)
)
NextBuildContext.hasInstrumentationHook = hasInstrumentationHook

// needed for static exporting since we want to replace with HTML
// files

Expand Down
24 changes: 24 additions & 0 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
SERVER_PROPS_GET_INIT_PROPS_CONFLICT,
SERVER_PROPS_SSG_CONFLICT,
MIDDLEWARE_FILENAME,
INSTRUMENTATION_HOOK_FILENAME,
} from '../lib/constants'
import { MODERN_BROWSERSLIST_TARGET } from '../shared/lib/constants'
import prettyBytes from '../lib/pretty-bytes'
Expand Down Expand Up @@ -286,6 +287,13 @@ export function isMiddlewareFilename(file?: string) {
return file === MIDDLEWARE_FILENAME || file === `src/${MIDDLEWARE_FILENAME}`
}

export function isInstrumentationHookFilename(file?: string) {
return (
file === INSTRUMENTATION_HOOK_FILENAME ||
file === `src/${INSTRUMENTATION_HOOK_FILENAME}`
)
}

export interface PageInfo {
isHybridAmp?: boolean
size: number
Expand Down Expand Up @@ -1840,6 +1848,22 @@ export function isMiddlewareFile(file: string) {
)
}

export function isInstrumentationHookFile(file: string) {
return (
file === `/${INSTRUMENTATION_HOOK_FILENAME}` ||
file === `/src/${INSTRUMENTATION_HOOK_FILENAME}`
)
}

export function getPossibleInstrumentationHookFilenames(
folder: string,
extensions: string[]
) {
return extensions.map((extension) =>
path.join(folder, `${INSTRUMENTATION_HOOK_FILENAME}.${extension}`)
)
}

export function getPossibleMiddlewareFilenames(
folder: string,
extensions: string[]
Expand Down
10 changes: 6 additions & 4 deletions packages/next/src/build/webpack-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,32 +60,34 @@ async function webpackBuildImpl(): Promise<{
const nextBuildSpan = NextBuildContext.nextBuildSpan!
const buildSpinner = NextBuildContext.buildSpinner
const dir = NextBuildContext.dir!
const config = NextBuildContext.config!

const runWebpackSpan = nextBuildSpan.traceChild('run-webpack-compiler')
const entrypoints = await nextBuildSpan
.traceChild('create-entrypoints')
.traceAsyncFn(() =>
createEntrypoints({
buildId: NextBuildContext.buildId!,
config: NextBuildContext.config!,
config: config,
envFiles: NextBuildContext.loadedEnvFiles!,
isDev: false,
rootDir: dir,
pageExtensions: NextBuildContext.config!.pageExtensions!,
pageExtensions: config.pageExtensions!,
pagesDir: NextBuildContext.pagesDir!,
appDir: NextBuildContext.appDir!,
pages: NextBuildContext.mappedPages!,
appPaths: NextBuildContext.mappedAppPages!,
previewMode: NextBuildContext.previewProps!,
rootPaths: NextBuildContext.mappedRootPaths!,
hasInstrumentationHook: NextBuildContext.hasInstrumentationHook!,
})
)

const commonWebpackOptions = {
isServer: false,
buildId: NextBuildContext.buildId!,
config: NextBuildContext.config!,
target: NextBuildContext.config!.target!,
config: config,
target: config.target!,
appDir: NextBuildContext.appDir!,
pagesDir: NextBuildContext.pagesDir!,
rewrites: NextBuildContext.rewrites!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default async function edgeSSRLoader(this: any) {
import { getRender } from 'next/dist/esm/build/webpack/loaders/next-edge-ssr-loader/render'

enhanceGlobals()

const pageType = ${JSON.stringify(pagesType)}
${
isAppDir
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { NextConfig } from '../../../../server/config-shared'
import type { NextConfigComplete } from '../../../../server/config-shared'

import type { DocumentType, AppType } from '../../../../shared/lib/utils'
import type { BuildManifest } from '../../../../server/get-page-files'
Expand Down Expand Up @@ -48,7 +48,7 @@ export function getRender({
serverComponentManifest: any
serverCSSManifest: any
appServerMod: any
config: NextConfig
config: NextConfigComplete
buildId: string
fontLoaderManifest: FontLoaderManifest
}) {
Expand Down
14 changes: 12 additions & 2 deletions packages/next/src/build/webpack/plugins/middleware-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import { Telemetry } from '../../../telemetry/storage'
import { traceGlobals } from '../../../trace/shared'
import { EVENT_BUILD_FEATURE_USAGE } from '../../../telemetry/events'
import { normalizeAppPath } from '../../../shared/lib/router/utils/app-paths'
import { INSTRUMENTATION_HOOK_FILENAME } from '../../../lib/constants'
import { NextBuildContext } from '../../build-context'

export interface EdgeFunctionDefinition {
env: string[]
Expand Down Expand Up @@ -90,7 +92,9 @@ function isUsingIndirectEvalAndUsedByExports(args: {
function getEntryFiles(
entryFiles: string[],
meta: EntryMetadata,
opts: { sriEnabled: boolean }
opts: {
sriEnabled: boolean
}
) {
const files: string[] = []
if (meta.edgeSSR) {
Expand Down Expand Up @@ -121,6 +125,10 @@ function getEntryFiles(
)

files.push(`server/${FONT_LOADER_MANIFEST}.js`)
feedthejim marked this conversation as resolved.
Show resolved Hide resolved

if (NextBuildContext!.hasInstrumentationHook) {
files.push(`server/edge-${INSTRUMENTATION_HOOK_FILENAME}.js`)
}
}

files.push(
Expand All @@ -134,7 +142,9 @@ function getEntryFiles(
function getCreateAssets(params: {
compilation: webpack.Compilation
metadataByEntry: Map<string, EntryMetadata>
opts: { sriEnabled: boolean }
opts: {
sriEnabled: boolean
}
}) {
const { compilation, metadataByEntry, opts } = params
return (assets: any) => {
Expand Down
65 changes: 57 additions & 8 deletions packages/next/src/cli/next-dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { fileExists } from '../lib/file-exists'
import Watchpack from 'next/dist/compiled/watchpack'
import stripAnsi from 'next/dist/compiled/strip-ansi'
import { warn } from '../build/output/log'
import { getPossibleInstrumentationHookFilenames } from '../build/utils'

let isTurboSession = false
let sessionStopHandled = false
Expand Down Expand Up @@ -513,8 +514,12 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit

return execArgv
}
let childProcessExitUnsub: (() => void) | null = null

const setupFork = (env?: NodeJS.ProcessEnv, newDir?: string) => {
childProcessExitUnsub?.()
childProcess?.kill()

const startDir = dir
const [, script, ...nodeArgs] = process.argv
let shouldFilter = false
Expand Down Expand Up @@ -580,10 +585,11 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit
}
}
childProcess?.addListener('exit', callback)
return () => childProcess?.removeListener('exit', callback)
childProcessExitUnsub = () =>
childProcess?.removeListener('exit', callback)
}

let childProcessExitUnsub = setupFork()
setupFork()

config = await loadConfig(
PHASE_DEVELOPMENT_SERVER,
Expand All @@ -594,10 +600,8 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit
)

const handleProjectDirRename = (newDir: string) => {
childProcessExitUnsub()
childProcess?.kill()
process.chdir(newDir)
childProcessExitUnsub = setupFork(
setupFork(
{
...Object.keys(process.env).reduce((newEnv, key) => {
newEnv[key] = process.env[key]?.replace(dir, newDir)
Expand All @@ -610,21 +614,66 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit
}
const parentDir = path.join('/', dir, '..')
const watchedEntryLength = parentDir.split('/').length + 1
const previousItems = new Set()
const previousItems = new Set<string>()

const instrumentationFilePaths = !!config.experimental.instrumentationHook
? getPossibleInstrumentationHookFilenames(dir, config.pageExtensions!)
: []

const wp = new Watchpack({
ignored: (entry: string) => {
// watch only one level
return !(entry.split('/').length <= watchedEntryLength)
return (
!(entry.split('/').length <= watchedEntryLength) &&
!instrumentationFilePaths.includes(entry)
)
},
})

wp.watch({ directories: [parentDir], startTime: 0 })
let instrumentationFileLastModified: number | undefined = undefined

wp.on('aggregated', () => {
const knownFiles = wp.getTimeInfoEntries()
const newFiles: string[] = []
let hasPagesApp = false
// check if the `instrumentation.js` has changed
// if it has we need to restart the server
const instrumentationFile = [...knownFiles.keys()].find((key) =>
instrumentationFilePaths.includes(key)
)

if (instrumentationFile) {
const instrumentationFileModified =
knownFiles.get(instrumentationFile)?.timestamp

if (
instrumentationFileLastModified !== undefined &&
instrumentationFileLastModified !== instrumentationFileModified
) {
warn(
`The instrumentation file has changed, restarting the server to apply changes.`
)
setupFork()
} else {
instrumentationFileLastModified = instrumentationFileModified
if (previousItems.size !== 0) {
warn(
'An instrumentation file was added, restarting the server to apply changes.'
)
setupFork()
}
}
} else if (
[...previousItems.keys()].find((key) =>
instrumentationFilePaths.includes(key)
)
) {
warn(
`The instrumentation file has been removed, restarting the server to apply changes.`
)
instrumentationFileLastModified = undefined
setupFork()
}

// if the dir still exists nothing to check
try {
Expand Down
4 changes: 4 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import type { ServerRuntime } from '../../types'
export const MIDDLEWARE_FILENAME = 'middleware'
export const MIDDLEWARE_LOCATION_REGEXP = `(?:src/)?${MIDDLEWARE_FILENAME}`

// Pattern to detect instrumentation hooks file
export const INSTRUMENTATION_HOOK_FILENAME = 'instrumentation'
export const INSTRUMENTATION_HOOKS_LOCATION_REGEXP = `(?:src/)?${INSTRUMENTATION_HOOK_FILENAME}`

// Because on Windows absolute paths in the generated code can break because of numbers, eg 1 in the path,
// we have to use a private alias
export const PAGES_DIR_ALIAS = 'private-next-pages'
Expand Down
Loading