2021-08-16 21:29:11 +02:00
|
|
|
import nodePath from 'path'
|
2021-09-23 23:22:14 +02:00
|
|
|
import { Span } from '../../../trace'
|
|
|
|
import { spans } from './profiling-plugin'
|
|
|
|
import isError from '../../../lib/is-error'
|
2021-10-18 19:01:02 +02:00
|
|
|
import {
|
|
|
|
nodeFileTrace,
|
|
|
|
NodeFileTraceReasons,
|
|
|
|
} from 'next/dist/compiled/@vercel/nft'
|
2021-09-23 23:22:14 +02:00
|
|
|
import { TRACE_OUTPUT_VERSION } from '../../../shared/lib/constants'
|
2021-10-07 01:46:46 +02:00
|
|
|
import { webpack, sources } from 'next/dist/compiled/webpack/webpack'
|
2021-10-24 23:04:26 +02:00
|
|
|
import type { webpack5 } from 'next/dist/compiled/webpack/webpack'
|
2021-10-18 19:01:02 +02:00
|
|
|
import {
|
|
|
|
NODE_ESM_RESOLVE_OPTIONS,
|
|
|
|
NODE_RESOLVE_OPTIONS,
|
2021-10-28 10:14:09 +02:00
|
|
|
resolveExternal,
|
2021-10-18 19:01:02 +02:00
|
|
|
} from '../../webpack-config'
|
|
|
|
import { NextConfigComplete } from '../../../server/config-shared'
|
2021-08-16 21:29:11 +02:00
|
|
|
|
|
|
|
const PLUGIN_NAME = 'TraceEntryPointsPlugin'
|
|
|
|
const TRACE_IGNORES = [
|
2021-09-15 22:00:52 +02:00
|
|
|
'**/*/next/dist/server/next.js',
|
|
|
|
'**/*/next/dist/bin/next',
|
2021-08-16 21:29:11 +02:00
|
|
|
]
|
|
|
|
|
|
|
|
function getModuleFromDependency(
|
|
|
|
compilation: any,
|
|
|
|
dep: any
|
2021-10-24 23:04:26 +02:00
|
|
|
): webpack5.Module & { resource?: string } {
|
2021-10-07 01:46:46 +02:00
|
|
|
return compilation.moduleGraph.getModule(dep)
|
2021-08-16 21:29:11 +02:00
|
|
|
}
|
|
|
|
|
2021-10-30 00:39:51 +02:00
|
|
|
function getFilesMapFromReasons(
|
|
|
|
fileList: Set<string>,
|
2021-12-14 17:41:10 +01:00
|
|
|
reasons: NodeFileTraceReasons,
|
|
|
|
ignoreFn?: (file: string, parent?: string) => Boolean
|
2021-10-30 00:39:51 +02:00
|
|
|
) {
|
|
|
|
// this uses the reasons tree to collect files specific to a
|
|
|
|
// certain parent allowing us to not have to trace each parent
|
|
|
|
// separately
|
|
|
|
const parentFilesMap = new Map<string, Set<string>>()
|
|
|
|
|
|
|
|
function propagateToParents(
|
|
|
|
parents: Set<string>,
|
|
|
|
file: string,
|
|
|
|
seen = new Set<string>()
|
|
|
|
) {
|
|
|
|
for (const parent of parents || []) {
|
|
|
|
if (!seen.has(parent)) {
|
|
|
|
seen.add(parent)
|
|
|
|
let parentFiles = parentFilesMap.get(parent)
|
|
|
|
|
|
|
|
if (!parentFiles) {
|
|
|
|
parentFiles = new Set()
|
|
|
|
parentFilesMap.set(parent, parentFiles)
|
|
|
|
}
|
2021-12-14 17:41:10 +01:00
|
|
|
|
|
|
|
if (!ignoreFn?.(file, parent)) {
|
|
|
|
parentFiles.add(file)
|
|
|
|
}
|
2021-10-30 00:39:51 +02:00
|
|
|
const parentReason = reasons.get(parent)
|
|
|
|
|
|
|
|
if (parentReason?.parents) {
|
|
|
|
propagateToParents(parentReason.parents, file, seen)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const file of fileList!) {
|
|
|
|
const reason = reasons!.get(file)
|
2022-03-18 22:18:24 +01:00
|
|
|
const isInitial =
|
|
|
|
reason?.type.length === 1 && reason.type.includes('initial')
|
2021-10-30 00:39:51 +02:00
|
|
|
|
|
|
|
if (
|
|
|
|
!reason ||
|
|
|
|
!reason.parents ||
|
2022-03-18 22:18:24 +01:00
|
|
|
(isInitial && reason.parents.size === 0)
|
2021-10-30 00:39:51 +02:00
|
|
|
) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
propagateToParents(reason.parents, file)
|
|
|
|
}
|
|
|
|
return parentFilesMap
|
|
|
|
}
|
|
|
|
|
2021-10-24 23:04:26 +02:00
|
|
|
export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
|
2021-08-16 21:29:11 +02:00
|
|
|
private appDir: string
|
2021-11-04 10:23:28 +01:00
|
|
|
private tracingRoot: string
|
2021-10-18 19:01:02 +02:00
|
|
|
private entryTraces: Map<string, Set<string>>
|
2021-08-16 21:29:11 +02:00
|
|
|
private excludeFiles: string[]
|
2021-10-18 19:01:02 +02:00
|
|
|
private esmExternals?: NextConfigComplete['experimental']['esmExternals']
|
|
|
|
private staticImageImports?: boolean
|
2021-08-16 21:29:11 +02:00
|
|
|
|
|
|
|
constructor({
|
|
|
|
appDir,
|
|
|
|
excludeFiles,
|
2021-10-18 19:01:02 +02:00
|
|
|
esmExternals,
|
|
|
|
staticImageImports,
|
2021-11-04 10:23:28 +01:00
|
|
|
outputFileTracingRoot,
|
2021-08-16 21:29:11 +02:00
|
|
|
}: {
|
|
|
|
appDir: string
|
|
|
|
excludeFiles?: string[]
|
2021-10-18 19:01:02 +02:00
|
|
|
staticImageImports: boolean
|
2021-11-04 10:23:28 +01:00
|
|
|
outputFileTracingRoot?: string
|
2021-10-18 19:01:02 +02:00
|
|
|
esmExternals?: NextConfigComplete['experimental']['esmExternals']
|
2021-08-16 21:29:11 +02:00
|
|
|
}) {
|
|
|
|
this.appDir = appDir
|
|
|
|
this.entryTraces = new Map()
|
2021-10-18 19:01:02 +02:00
|
|
|
this.esmExternals = esmExternals
|
2021-08-16 21:29:11 +02:00
|
|
|
this.excludeFiles = excludeFiles || []
|
2021-10-18 19:01:02 +02:00
|
|
|
this.staticImageImports = staticImageImports
|
2021-11-04 10:23:28 +01:00
|
|
|
this.tracingRoot = outputFileTracingRoot || appDir
|
2021-08-16 21:29:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Here we output all traced assets and webpack chunks to a
|
|
|
|
// ${page}.js.nft.json file
|
2021-10-30 00:39:51 +02:00
|
|
|
async createTraceAssets(
|
|
|
|
compilation: any,
|
|
|
|
assets: any,
|
|
|
|
span: Span,
|
|
|
|
readlink: any,
|
2021-11-05 02:09:37 +01:00
|
|
|
stat: any
|
2021-10-30 00:39:51 +02:00
|
|
|
) {
|
2021-08-16 21:29:11 +02:00
|
|
|
const outputPath = compilation.outputOptions.path
|
|
|
|
|
2021-10-30 00:39:51 +02:00
|
|
|
await span.traceChild('create-trace-assets').traceAsyncFn(async () => {
|
|
|
|
const entryFilesMap = new Map<any, Set<string>>()
|
|
|
|
const chunksToTrace = new Set<string>()
|
|
|
|
|
2021-09-14 18:13:11 +02:00
|
|
|
for (const entrypoint of compilation.entrypoints.values()) {
|
|
|
|
const entryFiles = new Set<string>()
|
2021-08-16 21:29:11 +02:00
|
|
|
|
2021-09-14 18:13:11 +02:00
|
|
|
for (const chunk of entrypoint
|
|
|
|
.getEntrypointChunk()
|
|
|
|
.getAllReferencedChunks()) {
|
|
|
|
for (const file of chunk.files) {
|
2021-10-30 00:39:51 +02:00
|
|
|
const filePath = nodePath.join(outputPath, file)
|
|
|
|
chunksToTrace.add(filePath)
|
|
|
|
entryFiles.add(filePath)
|
2021-09-14 18:13:11 +02:00
|
|
|
}
|
|
|
|
for (const file of chunk.auxiliaryFiles) {
|
2021-10-30 00:39:51 +02:00
|
|
|
const filePath = nodePath.join(outputPath, file)
|
|
|
|
chunksToTrace.add(filePath)
|
|
|
|
entryFiles.add(filePath)
|
2021-09-14 18:13:11 +02:00
|
|
|
}
|
2021-08-16 21:29:11 +02:00
|
|
|
}
|
2021-10-30 00:39:51 +02:00
|
|
|
entryFilesMap.set(entrypoint, entryFiles)
|
|
|
|
}
|
|
|
|
|
|
|
|
const result = await nodeFileTrace([...chunksToTrace], {
|
2021-11-04 10:23:28 +01:00
|
|
|
base: this.tracingRoot,
|
2021-10-30 00:39:51 +02:00
|
|
|
processCwd: this.appDir,
|
|
|
|
readFile: async (path) => {
|
|
|
|
if (chunksToTrace.has(path)) {
|
|
|
|
const source =
|
|
|
|
assets[
|
|
|
|
nodePath.relative(outputPath, path).replace(/\\/g, '/')
|
|
|
|
]?.source?.()
|
|
|
|
if (source) return source
|
|
|
|
}
|
|
|
|
try {
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
|
|
;(
|
|
|
|
compilation.inputFileSystem
|
|
|
|
.readFile as typeof import('fs').readFile
|
|
|
|
)(path, (err, data) => {
|
|
|
|
if (err) return reject(err)
|
|
|
|
resolve(data)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
if (isError(e) && (e.code === 'ENOENT' || e.code === 'EISDIR')) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
throw e
|
|
|
|
}
|
|
|
|
},
|
|
|
|
readlink,
|
|
|
|
stat,
|
|
|
|
ignore: [...TRACE_IGNORES, ...this.excludeFiles],
|
|
|
|
mixedModules: true,
|
|
|
|
})
|
|
|
|
const reasons = result.reasons
|
|
|
|
const fileList = result.fileList
|
|
|
|
result.esmFileList.forEach((file) => fileList.add(file))
|
|
|
|
|
|
|
|
const parentFilesMap = getFilesMapFromReasons(fileList, reasons)
|
|
|
|
|
|
|
|
for (const [entrypoint, entryFiles] of entryFilesMap) {
|
2021-10-07 01:46:46 +02:00
|
|
|
const traceOutputName = `../${entrypoint.name}.js.nft.json`
|
2021-09-14 18:13:11 +02:00
|
|
|
const traceOutputPath = nodePath.dirname(
|
|
|
|
nodePath.join(outputPath, traceOutputName)
|
2021-08-16 21:29:11 +02:00
|
|
|
)
|
2021-10-30 00:39:51 +02:00
|
|
|
const allEntryFiles = new Set<string>()
|
|
|
|
|
|
|
|
entryFiles.forEach((file) => {
|
|
|
|
parentFilesMap
|
2021-11-04 10:23:28 +01:00
|
|
|
.get(nodePath.relative(this.tracingRoot, file))
|
2021-10-30 00:39:51 +02:00
|
|
|
?.forEach((child) => {
|
2021-11-04 10:23:28 +01:00
|
|
|
allEntryFiles.add(nodePath.join(this.tracingRoot, child))
|
2021-10-30 00:39:51 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
// don't include the entry itself in the trace
|
|
|
|
entryFiles.delete(nodePath.join(outputPath, `../${entrypoint.name}.js`))
|
2021-08-16 21:29:11 +02:00
|
|
|
|
2021-09-14 18:13:11 +02:00
|
|
|
assets[traceOutputName] = new sources.RawSource(
|
|
|
|
JSON.stringify({
|
|
|
|
version: TRACE_OUTPUT_VERSION,
|
|
|
|
files: [
|
2021-12-14 17:41:10 +01:00
|
|
|
...new Set([
|
|
|
|
...entryFiles,
|
|
|
|
...allEntryFiles,
|
|
|
|
...(this.entryTraces.get(entrypoint.name) || []),
|
|
|
|
]),
|
2021-09-14 18:13:11 +02:00
|
|
|
].map((file) => {
|
|
|
|
return nodePath
|
|
|
|
.relative(traceOutputPath, file)
|
|
|
|
.replace(/\\/g, '/')
|
|
|
|
}),
|
|
|
|
})
|
2021-08-16 21:29:11 +02:00
|
|
|
)
|
2021-09-14 18:13:11 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
tapfinishModules(
|
2021-10-24 23:04:26 +02:00
|
|
|
compilation: webpack5.Compilation,
|
2021-09-24 15:39:48 +02:00
|
|
|
traceEntrypointsPluginSpan: Span,
|
2021-10-30 00:39:51 +02:00
|
|
|
doResolve: (
|
2021-09-24 15:39:48 +02:00
|
|
|
request: string,
|
2021-10-01 12:45:10 +02:00
|
|
|
parent: string,
|
2021-10-18 19:01:02 +02:00
|
|
|
job: import('@vercel/nft/out/node-file-trace').Job,
|
|
|
|
isEsmRequested: boolean
|
2021-10-30 00:39:51 +02:00
|
|
|
) => Promise<string>,
|
|
|
|
readlink: any,
|
|
|
|
stat: any
|
2021-09-14 18:13:11 +02:00
|
|
|
) {
|
|
|
|
compilation.hooks.finishModules.tapAsync(
|
|
|
|
PLUGIN_NAME,
|
|
|
|
async (_stats: any, callback: any) => {
|
|
|
|
const finishModulesSpan =
|
|
|
|
traceEntrypointsPluginSpan.traceChild('finish-modules')
|
|
|
|
await finishModulesSpan
|
|
|
|
.traceAsyncFn(async () => {
|
|
|
|
// we create entry -> module maps so that we can
|
|
|
|
// look them up faster instead of having to iterate
|
|
|
|
// over the compilation modules list
|
|
|
|
const entryNameMap = new Map<string, string>()
|
|
|
|
const entryModMap = new Map<string, any>()
|
|
|
|
const additionalEntries = new Map<string, Map<string, any>>()
|
2021-08-16 21:29:11 +02:00
|
|
|
|
|
|
|
const depModMap = new Map<string, any>()
|
|
|
|
|
2021-09-14 18:13:11 +02:00
|
|
|
finishModulesSpan.traceChild('get-entries').traceFn(() => {
|
2021-10-24 23:04:26 +02:00
|
|
|
compilation.entries.forEach((entry, name) => {
|
2021-09-14 18:13:11 +02:00
|
|
|
if (name?.replace(/\\/g, '/').startsWith('pages/')) {
|
|
|
|
for (const dep of entry.dependencies) {
|
|
|
|
if (!dep) continue
|
|
|
|
const entryMod = getModuleFromDependency(compilation, dep)
|
|
|
|
|
|
|
|
if (entryMod && entryMod.resource) {
|
|
|
|
if (
|
|
|
|
entryMod.resource.replace(/\\/g, '/').includes('pages/')
|
|
|
|
) {
|
|
|
|
entryNameMap.set(entryMod.resource, name)
|
|
|
|
entryModMap.set(entryMod.resource, entryMod)
|
|
|
|
} else {
|
|
|
|
let curMap = additionalEntries.get(name)
|
|
|
|
|
|
|
|
if (!curMap) {
|
|
|
|
curMap = new Map()
|
|
|
|
additionalEntries.set(name, curMap)
|
|
|
|
}
|
2021-10-18 19:01:02 +02:00
|
|
|
depModMap.set(entryMod.resource, entryMod)
|
2021-09-14 18:13:11 +02:00
|
|
|
curMap.set(entryMod.resource, entryMod)
|
2021-09-01 17:56:04 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-08-16 21:29:11 +02:00
|
|
|
}
|
2021-09-14 18:13:11 +02:00
|
|
|
})
|
2021-08-16 21:29:11 +02:00
|
|
|
})
|
|
|
|
|
2021-09-23 23:22:14 +02:00
|
|
|
const readFile = async (
|
|
|
|
path: string
|
|
|
|
): Promise<Buffer | string | null> => {
|
2021-08-16 21:29:11 +02:00
|
|
|
const mod = depModMap.get(path) || entryModMap.get(path)
|
|
|
|
|
|
|
|
// map the transpiled source when available to avoid
|
|
|
|
// parse errors in node-file-trace
|
|
|
|
const source = mod?.originalSource?.()
|
|
|
|
|
|
|
|
if (source) {
|
|
|
|
return source.buffer()
|
|
|
|
}
|
2021-10-30 00:39:51 +02:00
|
|
|
// we don't want to analyze non-transpiled
|
|
|
|
// files here, that is done against webpack output
|
|
|
|
return ''
|
2021-08-16 21:29:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const entryPaths = Array.from(entryModMap.keys())
|
|
|
|
|
2021-10-18 19:01:02 +02:00
|
|
|
const collectDependencies = (mod: any) => {
|
|
|
|
if (!mod || !mod.dependencies) return
|
|
|
|
|
|
|
|
for (const dep of mod.dependencies) {
|
|
|
|
const depMod = getModuleFromDependency(compilation, dep)
|
|
|
|
|
|
|
|
if (depMod?.resource && !depModMap.get(depMod.resource)) {
|
|
|
|
depModMap.set(depMod.resource, depMod)
|
|
|
|
collectDependencies(depMod)
|
2021-08-16 21:29:11 +02:00
|
|
|
}
|
2021-10-18 19:01:02 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
const entriesToTrace = [...entryPaths]
|
2021-08-16 21:29:11 +02:00
|
|
|
|
2021-10-18 19:01:02 +02:00
|
|
|
entryPaths.forEach((entry) => {
|
|
|
|
collectDependencies(entryModMap.get(entry))
|
|
|
|
const entryName = entryNameMap.get(entry)!
|
|
|
|
const curExtraEntries = additionalEntries.get(entryName)
|
2021-08-16 21:29:11 +02:00
|
|
|
|
2021-10-18 19:01:02 +02:00
|
|
|
if (curExtraEntries) {
|
|
|
|
entriesToTrace.push(...curExtraEntries.keys())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
let fileList: Set<string>
|
|
|
|
let reasons: NodeFileTraceReasons
|
|
|
|
await finishModulesSpan
|
|
|
|
.traceChild('node-file-trace', {
|
|
|
|
traceEntryCount: entriesToTrace.length + '',
|
|
|
|
})
|
|
|
|
.traceAsyncFn(async () => {
|
|
|
|
const result = await nodeFileTrace(entriesToTrace, {
|
2021-11-04 10:23:28 +01:00
|
|
|
base: this.tracingRoot,
|
2021-10-18 19:01:02 +02:00
|
|
|
processCwd: this.appDir,
|
|
|
|
readFile,
|
|
|
|
readlink,
|
|
|
|
stat,
|
|
|
|
resolve: doResolve
|
2021-10-30 00:39:51 +02:00
|
|
|
? async (id, parent, job, isCjs) => {
|
|
|
|
return doResolve(id, parent, job, !isCjs)
|
|
|
|
}
|
2021-10-18 19:01:02 +02:00
|
|
|
: undefined,
|
2021-10-30 00:39:51 +02:00
|
|
|
ignore: [
|
|
|
|
...TRACE_IGNORES,
|
|
|
|
...this.excludeFiles,
|
|
|
|
'**/node_modules/**',
|
|
|
|
],
|
2021-10-18 19:01:02 +02:00
|
|
|
mixedModules: true,
|
|
|
|
})
|
|
|
|
// @ts-ignore
|
|
|
|
fileList = result.fileList
|
|
|
|
result.esmFileList.forEach((file) => fileList.add(file))
|
|
|
|
reasons = result.reasons
|
|
|
|
})
|
2021-08-16 21:29:11 +02:00
|
|
|
|
2021-10-18 19:01:02 +02:00
|
|
|
await finishModulesSpan
|
|
|
|
.traceChild('collect-traced-files')
|
|
|
|
.traceAsyncFn(() => {
|
2021-12-14 17:41:10 +01:00
|
|
|
const parentFilesMap = getFilesMapFromReasons(
|
|
|
|
fileList,
|
|
|
|
reasons,
|
|
|
|
(file) => {
|
2022-03-18 23:51:26 +01:00
|
|
|
// if a file was imported and a loader handled it
|
|
|
|
// we don't include it in the trace e.g.
|
|
|
|
// static image imports, CSS imports
|
2021-12-14 17:41:10 +01:00
|
|
|
file = nodePath.join(this.tracingRoot, file)
|
|
|
|
const depMod = depModMap.get(file)
|
2022-03-22 06:03:42 +01:00
|
|
|
const isAsset = reasons
|
|
|
|
.get(nodePath.relative(this.tracingRoot, file))
|
|
|
|
?.type.includes('asset')
|
2021-12-14 17:41:10 +01:00
|
|
|
|
|
|
|
return (
|
2022-03-18 22:18:24 +01:00
|
|
|
!isAsset &&
|
2021-12-14 17:41:10 +01:00
|
|
|
Array.isArray(depMod?.loaders) &&
|
|
|
|
depMod.loaders.length > 0
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
2021-10-18 19:01:02 +02:00
|
|
|
entryPaths.forEach((entry) => {
|
|
|
|
const entryName = entryNameMap.get(entry)!
|
2021-11-04 10:23:28 +01:00
|
|
|
const normalizedEntry = nodePath.relative(
|
|
|
|
this.tracingRoot,
|
|
|
|
entry
|
|
|
|
)
|
2021-10-18 19:01:02 +02:00
|
|
|
const curExtraEntries = additionalEntries.get(entryName)
|
|
|
|
const finalDeps = new Set<string>()
|
|
|
|
|
|
|
|
parentFilesMap.get(normalizedEntry)?.forEach((dep) => {
|
2021-11-04 10:23:28 +01:00
|
|
|
finalDeps.add(nodePath.join(this.tracingRoot, dep))
|
2021-10-18 19:01:02 +02:00
|
|
|
})
|
|
|
|
|
|
|
|
if (curExtraEntries) {
|
|
|
|
for (const extraEntry of curExtraEntries.keys()) {
|
|
|
|
const normalizedExtraEntry = nodePath.relative(
|
2021-11-04 10:23:28 +01:00
|
|
|
this.tracingRoot,
|
2021-10-18 19:01:02 +02:00
|
|
|
extraEntry
|
|
|
|
)
|
|
|
|
finalDeps.add(extraEntry)
|
|
|
|
parentFilesMap
|
|
|
|
.get(normalizedExtraEntry)
|
|
|
|
?.forEach((dep) => {
|
2021-11-04 10:23:28 +01:00
|
|
|
finalDeps.add(nodePath.join(this.tracingRoot, dep))
|
2021-10-18 19:01:02 +02:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.entryTraces.set(entryName, finalDeps)
|
|
|
|
})
|
2021-09-14 18:13:11 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.then(
|
|
|
|
() => callback(),
|
|
|
|
(err) => callback(err)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
2021-08-16 21:29:11 +02:00
|
|
|
|
2021-10-24 23:04:26 +02:00
|
|
|
apply(compiler: webpack5.Compiler) {
|
2021-10-07 01:46:46 +02:00
|
|
|
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
|
2021-10-30 00:39:51 +02:00
|
|
|
const readlink = async (path: string): Promise<string | null> => {
|
|
|
|
try {
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
|
|
;(
|
|
|
|
compilation.inputFileSystem
|
|
|
|
.readlink as typeof import('fs').readlink
|
|
|
|
)(path, (err, link) => {
|
|
|
|
if (err) return reject(err)
|
|
|
|
resolve(link)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
if (
|
|
|
|
isError(e) &&
|
|
|
|
(e.code === 'EINVAL' || e.code === 'ENOENT' || e.code === 'UNKNOWN')
|
|
|
|
) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
throw e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const stat = async (path: string): Promise<import('fs').Stats | null> => {
|
|
|
|
try {
|
|
|
|
return await new Promise((resolve, reject) => {
|
|
|
|
;(compilation.inputFileSystem.stat as typeof import('fs').stat)(
|
|
|
|
path,
|
|
|
|
(err, stats) => {
|
|
|
|
if (err) return reject(err)
|
|
|
|
resolve(stats)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
} catch (e) {
|
|
|
|
if (isError(e) && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
throw e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-07 01:46:46 +02:00
|
|
|
const compilationSpan = spans.get(compilation) || spans.get(compiler)!
|
|
|
|
const traceEntrypointsPluginSpan = compilationSpan.traceChild(
|
|
|
|
'next-trace-entrypoint-plugin'
|
|
|
|
)
|
|
|
|
traceEntrypointsPluginSpan.traceFn(() => {
|
|
|
|
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
|
2021-10-30 00:39:51 +02:00
|
|
|
compilation.hooks.processAssets.tapAsync(
|
2021-10-07 01:46:46 +02:00
|
|
|
{
|
|
|
|
name: PLUGIN_NAME,
|
|
|
|
// @ts-ignore TODO: Remove ignore when webpack 5 is stable
|
|
|
|
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE,
|
|
|
|
},
|
2021-10-30 00:39:51 +02:00
|
|
|
(assets: any, callback: any) => {
|
2021-10-07 01:46:46 +02:00
|
|
|
this.createTraceAssets(
|
|
|
|
compilation,
|
|
|
|
assets,
|
2021-10-30 00:39:51 +02:00
|
|
|
traceEntrypointsPluginSpan,
|
|
|
|
readlink,
|
2021-11-05 02:09:37 +01:00
|
|
|
stat
|
2021-10-07 01:46:46 +02:00
|
|
|
)
|
2021-10-30 00:39:51 +02:00
|
|
|
.then(() => callback())
|
|
|
|
.catch((err) => callback(err))
|
2021-10-07 01:46:46 +02:00
|
|
|
}
|
2021-09-14 18:13:11 +02:00
|
|
|
)
|
2021-12-14 17:41:10 +01:00
|
|
|
|
2021-10-07 01:46:46 +02:00
|
|
|
let resolver = compilation.resolverFactory.get('normal')
|
2021-09-24 15:39:48 +02:00
|
|
|
|
2021-10-07 01:46:46 +02:00
|
|
|
function getPkgName(name: string) {
|
|
|
|
const segments = name.split('/')
|
|
|
|
if (name[0] === '@' && segments.length > 1)
|
|
|
|
return segments.length > 1 ? segments.slice(0, 2).join('/') : null
|
|
|
|
return segments.length ? segments[0] : null
|
|
|
|
}
|
2021-10-01 12:45:10 +02:00
|
|
|
|
2021-10-18 19:01:02 +02:00
|
|
|
const getResolve = (options: any) => {
|
|
|
|
const curResolver = resolver.withOptions(options)
|
|
|
|
|
|
|
|
return (
|
|
|
|
parent: string,
|
|
|
|
request: string,
|
|
|
|
job: import('@vercel/nft/out/node-file-trace').Job
|
|
|
|
) =>
|
2021-10-28 10:14:09 +02:00
|
|
|
new Promise<[string, boolean]>((resolve, reject) => {
|
2021-10-18 19:01:02 +02:00
|
|
|
const context = nodePath.dirname(parent)
|
|
|
|
|
|
|
|
curResolver.resolve(
|
|
|
|
{},
|
|
|
|
context,
|
|
|
|
request,
|
|
|
|
{
|
|
|
|
fileDependencies: compilation.fileDependencies,
|
|
|
|
missingDependencies: compilation.missingDependencies,
|
|
|
|
contextDependencies: compilation.contextDependencies,
|
|
|
|
},
|
2021-10-28 10:14:09 +02:00
|
|
|
async (err: any, result?, resContext?) => {
|
2021-10-18 19:01:02 +02:00
|
|
|
if (err) return reject(err)
|
|
|
|
|
|
|
|
if (!result) {
|
|
|
|
return reject(new Error('module not found'))
|
|
|
|
}
|
2021-10-01 12:45:10 +02:00
|
|
|
|
2021-12-14 17:41:10 +01:00
|
|
|
// webpack resolver doesn't strip loader query info
|
|
|
|
// from the result so use path instead
|
|
|
|
if (result.includes('?') || result.includes('!')) {
|
|
|
|
result = resContext?.path || result
|
|
|
|
}
|
|
|
|
|
2021-10-18 19:01:02 +02:00
|
|
|
try {
|
|
|
|
// we need to collect all parent package.json's used
|
|
|
|
// as webpack's resolve doesn't expose this and parent
|
|
|
|
// package.json could be needed for resolving e.g. stylis
|
|
|
|
// stylis/package.json -> stylis/dist/umd/package.json
|
|
|
|
if (result.includes('node_modules')) {
|
2021-10-26 15:16:48 +02:00
|
|
|
let requestPath = result
|
|
|
|
.replace(/\\/g, '/')
|
|
|
|
.replace(/\0/g, '')
|
2021-10-01 12:45:10 +02:00
|
|
|
|
2021-10-18 19:01:02 +02:00
|
|
|
if (
|
|
|
|
!nodePath.isAbsolute(request) &&
|
|
|
|
request.includes('/') &&
|
|
|
|
resContext?.descriptionFileRoot
|
|
|
|
) {
|
|
|
|
requestPath = (
|
|
|
|
resContext.descriptionFileRoot +
|
2022-03-24 22:49:38 +01:00
|
|
|
request.slice(getPkgName(request)?.length || 0) +
|
2021-10-18 19:01:02 +02:00
|
|
|
nodePath.sep +
|
|
|
|
'package.json'
|
2021-10-26 15:16:48 +02:00
|
|
|
)
|
|
|
|
.replace(/\\/g, '/')
|
|
|
|
.replace(/\0/g, '')
|
2021-10-18 19:01:02 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const rootSeparatorIndex = requestPath.indexOf('/')
|
|
|
|
let separatorIndex: number
|
|
|
|
while (
|
|
|
|
(separatorIndex = requestPath.lastIndexOf('/')) >
|
|
|
|
rootSeparatorIndex
|
|
|
|
) {
|
2022-03-24 22:49:38 +01:00
|
|
|
requestPath = requestPath.slice(0, separatorIndex)
|
2021-10-18 19:01:02 +02:00
|
|
|
const curPackageJsonPath = `${requestPath}/package.json`
|
|
|
|
if (await job.isFile(curPackageJsonPath)) {
|
|
|
|
await job.emitFile(
|
|
|
|
curPackageJsonPath,
|
|
|
|
'resolve',
|
|
|
|
parent
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2021-10-01 12:45:10 +02:00
|
|
|
}
|
2021-10-18 19:01:02 +02:00
|
|
|
} catch (_err) {
|
|
|
|
// we failed to resolve the package.json boundary,
|
|
|
|
// we don't block emitting the initial asset from this
|
2021-10-01 12:45:10 +02:00
|
|
|
}
|
2021-10-28 10:14:09 +02:00
|
|
|
resolve([result, options.dependencyType === 'esm'])
|
2021-09-29 19:38:21 +02:00
|
|
|
}
|
2021-10-18 19:01:02 +02:00
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const CJS_RESOLVE_OPTIONS = {
|
|
|
|
...NODE_RESOLVE_OPTIONS,
|
2021-10-28 10:14:09 +02:00
|
|
|
fullySpecified: undefined,
|
2021-10-26 01:38:30 +02:00
|
|
|
modules: undefined,
|
2021-10-18 19:01:02 +02:00
|
|
|
extensions: undefined,
|
|
|
|
}
|
2021-10-28 10:14:09 +02:00
|
|
|
const BASE_CJS_RESOLVE_OPTIONS = {
|
|
|
|
...CJS_RESOLVE_OPTIONS,
|
|
|
|
alias: false,
|
|
|
|
}
|
2021-10-18 19:01:02 +02:00
|
|
|
const ESM_RESOLVE_OPTIONS = {
|
|
|
|
...NODE_ESM_RESOLVE_OPTIONS,
|
2021-10-28 10:14:09 +02:00
|
|
|
fullySpecified: undefined,
|
2021-10-26 01:38:30 +02:00
|
|
|
modules: undefined,
|
2021-10-18 19:01:02 +02:00
|
|
|
extensions: undefined,
|
|
|
|
}
|
2021-10-28 10:14:09 +02:00
|
|
|
const BASE_ESM_RESOLVE_OPTIONS = {
|
|
|
|
...ESM_RESOLVE_OPTIONS,
|
|
|
|
alias: false,
|
|
|
|
}
|
2021-10-18 19:01:02 +02:00
|
|
|
|
|
|
|
const doResolve = async (
|
|
|
|
request: string,
|
|
|
|
parent: string,
|
|
|
|
job: import('@vercel/nft/out/node-file-trace').Job,
|
|
|
|
isEsmRequested: boolean
|
|
|
|
): Promise<string> => {
|
2021-10-28 10:14:09 +02:00
|
|
|
const context = nodePath.dirname(parent)
|
2021-10-18 19:01:02 +02:00
|
|
|
// When in esm externals mode, and using import, we resolve with
|
|
|
|
// ESM resolving options.
|
2021-10-28 10:14:09 +02:00
|
|
|
const { res } = await resolveExternal(
|
|
|
|
this.appDir,
|
|
|
|
this.esmExternals,
|
|
|
|
context,
|
|
|
|
request,
|
|
|
|
isEsmRequested,
|
|
|
|
(options) => (_: string, resRequest: string) => {
|
|
|
|
return getResolve(options)(parent, resRequest, job)
|
|
|
|
},
|
|
|
|
undefined,
|
|
|
|
undefined,
|
|
|
|
ESM_RESOLVE_OPTIONS,
|
|
|
|
CJS_RESOLVE_OPTIONS,
|
|
|
|
BASE_ESM_RESOLVE_OPTIONS,
|
|
|
|
BASE_CJS_RESOLVE_OPTIONS
|
2021-10-18 19:01:02 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if (!res) {
|
|
|
|
throw new Error(`failed to resolve ${request} from ${parent}`)
|
|
|
|
}
|
2021-10-26 15:16:48 +02:00
|
|
|
return res.replace(/\0/g, '')
|
2021-10-07 01:46:46 +02:00
|
|
|
}
|
2021-09-14 18:13:11 +02:00
|
|
|
|
2021-10-07 01:46:46 +02:00
|
|
|
this.tapfinishModules(
|
|
|
|
compilation,
|
|
|
|
traceEntrypointsPluginSpan,
|
2021-10-30 00:39:51 +02:00
|
|
|
doResolve,
|
|
|
|
readlink,
|
|
|
|
stat
|
2021-09-14 18:13:11 +02:00
|
|
|
)
|
|
|
|
})
|
2021-10-07 01:46:46 +02:00
|
|
|
})
|
2021-08-16 21:29:11 +02:00
|
|
|
}
|
|
|
|
}
|