@@ -24,6 +24,7 @@ const TRACE_IGNORES = [
24
24
'**/*/next/dist/server/next.js' ,
25
25
'**/*/next/dist/bin/next' ,
26
26
]
27
+ const root = nodePath . parse ( process . cwd ( ) ) . root
27
28
28
29
function getModuleFromDependency (
29
30
compilation : any ,
@@ -32,6 +33,54 @@ function getModuleFromDependency(
32
33
return compilation . moduleGraph . getModule ( dep )
33
34
}
34
35
36
+ function getFilesMapFromReasons (
37
+ fileList : Set < string > ,
38
+ reasons : NodeFileTraceReasons
39
+ ) {
40
+ // this uses the reasons tree to collect files specific to a
41
+ // certain parent allowing us to not have to trace each parent
42
+ // separately
43
+ const parentFilesMap = new Map < string , Set < string > > ( )
44
+
45
+ function propagateToParents (
46
+ parents : Set < string > ,
47
+ file : string ,
48
+ seen = new Set < string > ( )
49
+ ) {
50
+ for ( const parent of parents || [ ] ) {
51
+ if ( ! seen . has ( parent ) ) {
52
+ seen . add ( parent )
53
+ let parentFiles = parentFilesMap . get ( parent )
54
+
55
+ if ( ! parentFiles ) {
56
+ parentFiles = new Set ( )
57
+ parentFilesMap . set ( parent , parentFiles )
58
+ }
59
+ parentFiles . add ( file )
60
+ const parentReason = reasons . get ( parent )
61
+
62
+ if ( parentReason ?. parents ) {
63
+ propagateToParents ( parentReason . parents , file , seen )
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ for ( const file of fileList ! ) {
70
+ const reason = reasons ! . get ( file )
71
+
72
+ if (
73
+ ! reason ||
74
+ ! reason . parents ||
75
+ ( reason . type === 'initial' && reason . parents . size === 0 )
76
+ ) {
77
+ continue
78
+ }
79
+ propagateToParents ( reason . parents , file )
80
+ }
81
+ return parentFilesMap
82
+ }
83
+
35
84
export class TraceEntryPointsPlugin implements webpack5 . WebpackPluginInstance {
36
85
private appDir : string
37
86
private entryTraces : Map < string , Set < string > >
@@ -59,36 +108,107 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
59
108
60
109
// Here we output all traced assets and webpack chunks to a
61
110
// ${page}.js.nft.json file
62
- createTraceAssets ( compilation : any , assets : any , span : Span ) {
111
+ async createTraceAssets (
112
+ compilation : any ,
113
+ assets : any ,
114
+ span : Span ,
115
+ readlink : any ,
116
+ stat : any ,
117
+ doResolve : any
118
+ ) {
63
119
const outputPath = compilation . outputOptions . path
64
120
65
- const nodeFileTraceSpan = span . traceChild ( 'create-trace-assets' )
66
- nodeFileTraceSpan . traceFn ( ( ) => {
121
+ await span . traceChild ( 'create-trace-assets' ) . traceAsyncFn ( async ( ) => {
122
+ const entryFilesMap = new Map < any , Set < string > > ( )
123
+ const chunksToTrace = new Set < string > ( )
124
+
67
125
for ( const entrypoint of compilation . entrypoints . values ( ) ) {
68
126
const entryFiles = new Set < string > ( )
69
127
70
128
for ( const chunk of entrypoint
71
129
. getEntrypointChunk ( )
72
130
. getAllReferencedChunks ( ) ) {
73
131
for ( const file of chunk . files ) {
74
- entryFiles . add ( nodePath . join ( outputPath , file ) )
132
+ const filePath = nodePath . join ( outputPath , file )
133
+ chunksToTrace . add ( filePath )
134
+ entryFiles . add ( filePath )
75
135
}
76
136
for ( const file of chunk . auxiliaryFiles ) {
77
- entryFiles . add ( nodePath . join ( outputPath , file ) )
137
+ const filePath = nodePath . join ( outputPath , file )
138
+ chunksToTrace . add ( filePath )
139
+ entryFiles . add ( filePath )
78
140
}
79
141
}
80
- // don't include the entry itself in the trace
81
- entryFiles . delete ( nodePath . join ( outputPath , `../${ entrypoint . name } .js` ) )
142
+ entryFilesMap . set ( entrypoint , entryFiles )
143
+ }
144
+
145
+ const result = await nodeFileTrace ( [ ...chunksToTrace ] , {
146
+ base : root ,
147
+ processCwd : this . appDir ,
148
+ readFile : async ( path ) => {
149
+ if ( chunksToTrace . has ( path ) ) {
150
+ const source =
151
+ assets [
152
+ nodePath . relative ( outputPath , path ) . replace ( / \\ / g, '/' )
153
+ ] ?. source ?.( )
154
+ if ( source ) return source
155
+ }
156
+ try {
157
+ return await new Promise ( ( resolve , reject ) => {
158
+ ; (
159
+ compilation . inputFileSystem
160
+ . readFile as typeof import ( 'fs' ) . readFile
161
+ ) ( path , ( err , data ) => {
162
+ if ( err ) return reject ( err )
163
+ resolve ( data )
164
+ } )
165
+ } )
166
+ } catch ( e ) {
167
+ if ( isError ( e ) && ( e . code === 'ENOENT' || e . code === 'EISDIR' ) ) {
168
+ return null
169
+ }
170
+ throw e
171
+ }
172
+ } ,
173
+ readlink,
174
+ stat,
175
+ resolve : doResolve
176
+ ? ( id , parent , job , isCjs ) => {
177
+ return doResolve ( id , parent , job , ! isCjs )
178
+ }
179
+ : undefined ,
180
+ ignore : [ ...TRACE_IGNORES , ...this . excludeFiles ] ,
181
+ mixedModules : true ,
182
+ } )
183
+ const reasons = result . reasons
184
+ const fileList = result . fileList
185
+ result . esmFileList . forEach ( ( file ) => fileList . add ( file ) )
186
+
187
+ const parentFilesMap = getFilesMapFromReasons ( fileList , reasons )
188
+
189
+ for ( const [ entrypoint , entryFiles ] of entryFilesMap ) {
82
190
const traceOutputName = `../${ entrypoint . name } .js.nft.json`
83
191
const traceOutputPath = nodePath . dirname (
84
192
nodePath . join ( outputPath , traceOutputName )
85
193
)
194
+ const allEntryFiles = new Set < string > ( )
195
+
196
+ entryFiles . forEach ( ( file ) => {
197
+ parentFilesMap
198
+ . get ( nodePath . relative ( root , file ) )
199
+ ?. forEach ( ( child ) => {
200
+ allEntryFiles . add ( nodePath . join ( root , child ) )
201
+ } )
202
+ } )
203
+ // don't include the entry itself in the trace
204
+ entryFiles . delete ( nodePath . join ( outputPath , `../${ entrypoint . name } .js` ) )
86
205
87
206
assets [ traceOutputName ] = new sources . RawSource (
88
207
JSON . stringify ( {
89
208
version : TRACE_OUTPUT_VERSION ,
90
209
files : [
91
210
...entryFiles ,
211
+ ...allEntryFiles ,
92
212
...( this . entryTraces . get ( entrypoint . name ) || [ ] ) ,
93
213
] . map ( ( file ) => {
94
214
return nodePath
@@ -104,12 +224,14 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
104
224
tapfinishModules (
105
225
compilation : webpack5 . Compilation ,
106
226
traceEntrypointsPluginSpan : Span ,
107
- doResolve ? : (
227
+ doResolve : (
108
228
request : string ,
109
229
parent : string ,
110
230
job : import ( '@vercel/nft/out/node-file-trace' ) . Job ,
111
231
isEsmRequested : boolean
112
- ) => Promise < string >
232
+ ) => Promise < string > ,
233
+ readlink : any ,
234
+ stat : any
113
235
) {
114
236
compilation . hooks . finishModules . tapAsync (
115
237
PLUGIN_NAME ,
@@ -168,71 +290,9 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
168
290
if ( source ) {
169
291
return source . buffer ( )
170
292
}
171
-
172
- try {
173
- return await new Promise ( ( resolve , reject ) => {
174
- ; (
175
- compilation . inputFileSystem
176
- . readFile as typeof import ( 'fs' ) . readFile
177
- ) ( path , ( err , data ) => {
178
- if ( err ) return reject ( err )
179
- resolve ( data )
180
- } )
181
- } )
182
- } catch ( e ) {
183
- if (
184
- isError ( e ) &&
185
- ( e . code === 'ENOENT' || e . code === 'EISDIR' )
186
- ) {
187
- return null
188
- }
189
- throw e
190
- }
191
- }
192
- const readlink = async ( path : string ) : Promise < string | null > => {
193
- try {
194
- return await new Promise ( ( resolve , reject ) => {
195
- ; (
196
- compilation . inputFileSystem
197
- . readlink as typeof import ( 'fs' ) . readlink
198
- ) ( path , ( err , link ) => {
199
- if ( err ) return reject ( err )
200
- resolve ( link )
201
- } )
202
- } )
203
- } catch ( e ) {
204
- if (
205
- isError ( e ) &&
206
- ( e . code === 'EINVAL' ||
207
- e . code === 'ENOENT' ||
208
- e . code === 'UNKNOWN' )
209
- ) {
210
- return null
211
- }
212
- throw e
213
- }
214
- }
215
- const stat = async (
216
- path : string
217
- ) : Promise < import ( 'fs' ) . Stats | null > => {
218
- try {
219
- return await new Promise ( ( resolve , reject ) => {
220
- ; (
221
- compilation . inputFileSystem . stat as typeof import ( 'fs' ) . stat
222
- ) ( path , ( err , stats ) => {
223
- if ( err ) return reject ( err )
224
- resolve ( stats )
225
- } )
226
- } )
227
- } catch ( e ) {
228
- if (
229
- isError ( e ) &&
230
- ( e . code === 'ENOENT' || e . code === 'ENOTDIR' )
231
- ) {
232
- return null
233
- }
234
- throw e
235
- }
293
+ // we don't want to analyze non-transpiled
294
+ // files here, that is done against webpack output
295
+ return ''
236
296
}
237
297
238
298
const entryPaths = Array . from ( entryModMap . keys ( ) )
@@ -262,8 +322,6 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
262
322
} )
263
323
let fileList : Set < string >
264
324
let reasons : NodeFileTraceReasons
265
- const root = nodePath . parse ( process . cwd ( ) ) . root
266
-
267
325
await finishModulesSpan
268
326
. traceChild ( 'node-file-trace' , {
269
327
traceEntryCount : entriesToTrace . length + '' ,
@@ -276,11 +334,15 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
276
334
readlink,
277
335
stat,
278
336
resolve : doResolve
279
- ? ( id , parent , job , isCjs ) =>
280
- // @ts -ignore
281
- doResolve ( id , parent , job , ! isCjs )
337
+ ? async ( id , parent , job , isCjs ) => {
338
+ return doResolve ( id , parent , job , ! isCjs )
339
+ }
282
340
: undefined ,
283
- ignore : [ ...TRACE_IGNORES , ...this . excludeFiles ] ,
341
+ ignore : [
342
+ ...TRACE_IGNORES ,
343
+ ...this . excludeFiles ,
344
+ '**/node_modules/**' ,
345
+ ] ,
284
346
mixedModules : true ,
285
347
} )
286
348
// @ts -ignore
@@ -289,50 +351,10 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
289
351
reasons = result . reasons
290
352
} )
291
353
292
- // this uses the reasons tree to collect files specific to a certain
293
- // parent allowing us to not have to trace each parent separately
294
- const parentFilesMap = new Map < string , Set < string > > ( )
295
-
296
- function propagateToParents (
297
- parents : Set < string > ,
298
- file : string ,
299
- seen = new Set < string > ( )
300
- ) {
301
- for ( const parent of parents || [ ] ) {
302
- if ( ! seen . has ( parent ) ) {
303
- seen . add ( parent )
304
- let parentFiles = parentFilesMap . get ( parent )
305
-
306
- if ( ! parentFiles ) {
307
- parentFiles = new Set ( )
308
- parentFilesMap . set ( parent , parentFiles )
309
- }
310
- parentFiles . add ( file )
311
- const parentReason = reasons . get ( parent )
312
-
313
- if ( parentReason ?. parents ) {
314
- propagateToParents ( parentReason . parents , file , seen )
315
- }
316
- }
317
- }
318
- }
319
-
320
354
await finishModulesSpan
321
355
. traceChild ( 'collect-traced-files' )
322
356
. traceAsyncFn ( ( ) => {
323
- for ( const file of fileList ! ) {
324
- const reason = reasons ! . get ( file )
325
-
326
- if (
327
- ! reason ||
328
- ! reason . parents ||
329
- ( reason . type === 'initial' && reason . parents . size === 0 )
330
- ) {
331
- continue
332
- }
333
- propagateToParents ( reason . parents , file )
334
- }
335
-
357
+ const parentFilesMap = getFilesMapFromReasons ( fileList , reasons )
336
358
entryPaths . forEach ( ( entry ) => {
337
359
const entryName = entryNameMap . get ( entry ) !
338
360
const normalizedEntry = nodePath . relative ( root , entry )
@@ -371,24 +393,69 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
371
393
372
394
apply ( compiler : webpack5 . Compiler ) {
373
395
compiler . hooks . compilation . tap ( PLUGIN_NAME , ( compilation ) => {
396
+ const readlink = async ( path : string ) : Promise < string | null > => {
397
+ try {
398
+ return await new Promise ( ( resolve , reject ) => {
399
+ ; (
400
+ compilation . inputFileSystem
401
+ . readlink as typeof import ( 'fs' ) . readlink
402
+ ) ( path , ( err , link ) => {
403
+ if ( err ) return reject ( err )
404
+ resolve ( link )
405
+ } )
406
+ } )
407
+ } catch ( e ) {
408
+ if (
409
+ isError ( e ) &&
410
+ ( e . code === 'EINVAL' || e . code === 'ENOENT' || e . code === 'UNKNOWN' )
411
+ ) {
412
+ return null
413
+ }
414
+ throw e
415
+ }
416
+ }
417
+ const stat = async ( path : string ) : Promise < import ( 'fs' ) . Stats | null > => {
418
+ try {
419
+ return await new Promise ( ( resolve , reject ) => {
420
+ ; ( compilation . inputFileSystem . stat as typeof import ( 'fs' ) . stat ) (
421
+ path ,
422
+ ( err , stats ) => {
423
+ if ( err ) return reject ( err )
424
+ resolve ( stats )
425
+ }
426
+ )
427
+ } )
428
+ } catch ( e ) {
429
+ if ( isError ( e ) && ( e . code === 'ENOENT' || e . code === 'ENOTDIR' ) ) {
430
+ return null
431
+ }
432
+ throw e
433
+ }
434
+ }
435
+
374
436
const compilationSpan = spans . get ( compilation ) || spans . get ( compiler ) !
375
437
const traceEntrypointsPluginSpan = compilationSpan . traceChild (
376
438
'next-trace-entrypoint-plugin'
377
439
)
378
440
traceEntrypointsPluginSpan . traceFn ( ( ) => {
379
441
// @ts -ignore TODO: Remove ignore when webpack 5 is stable
380
- compilation . hooks . processAssets . tap (
442
+ compilation . hooks . processAssets . tapAsync (
381
443
{
382
444
name : PLUGIN_NAME ,
383
445
// @ts -ignore TODO: Remove ignore when webpack 5 is stable
384
446
stage : webpack . Compilation . PROCESS_ASSETS_STAGE_SUMMARIZE ,
385
447
} ,
386
- ( assets : any ) => {
448
+ ( assets : any , callback : any ) => {
387
449
this . createTraceAssets (
388
450
compilation ,
389
451
assets ,
390
- traceEntrypointsPluginSpan
452
+ traceEntrypointsPluginSpan ,
453
+ readlink ,
454
+ stat ,
455
+ doResolve
391
456
)
457
+ . then ( ( ) => callback ( ) )
458
+ . catch ( ( err ) => callback ( err ) )
392
459
}
393
460
)
394
461
let resolver = compilation . resolverFactory . get ( 'normal' )
@@ -540,7 +607,9 @@ export class TraceEntryPointsPlugin implements webpack5.WebpackPluginInstance {
540
607
this . tapfinishModules (
541
608
compilation ,
542
609
traceEntrypointsPluginSpan ,
543
- doResolve
610
+ doResolve ,
611
+ readlink ,
612
+ stat
544
613
)
545
614
} )
546
615
} )
0 commit comments