@@ -16,7 +16,18 @@ const BUILT = Symbol('built')
16
16
const glob = promisify ( globModule )
17
17
const access = promisify ( fs . access )
18
18
19
- export default function onDemandEntryHandler ( devMiddleware , compilers , {
19
+ // Based on https://github.com/webpack/webpack/blob/master/lib/DynamicEntryPlugin.js#L29-L37
20
+ function addEntry ( compilation , context , name , entry ) {
21
+ return new Promise ( ( resolve , reject ) => {
22
+ const dep = DynamicEntryPlugin . createDependency ( entry , name )
23
+ compilation . addEntry ( context , dep , name , ( err ) => {
24
+ if ( err ) return reject ( err )
25
+ resolve ( )
26
+ } )
27
+ } )
28
+ }
29
+
30
+ export default function onDemandEntryHandler ( devMiddleware , multiCompiler , {
20
31
buildId,
21
32
dir,
22
33
dev,
@@ -25,20 +36,18 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
25
36
maxInactiveAge = 1000 * 60 ,
26
37
pagesBufferLength = 2
27
38
} ) {
39
+ const { compilers} = multiCompiler
40
+ const invalidator = new Invalidator ( devMiddleware , multiCompiler )
28
41
let entries = { }
29
42
let lastAccessPages = [ '' ]
30
43
let doneCallbacks = new EventEmitter ( )
31
- const invalidator = new Invalidator ( devMiddleware )
32
44
let reloading = false
33
45
let stopped = false
34
46
let reloadCallbacks = new EventEmitter ( )
35
- // Keep the names of compilers which are building pages at a given moment.
36
- const currentBuilders = new Set ( )
37
47
38
- compilers . forEach ( compiler => {
39
- compiler . hooks . make . tapAsync ( 'NextJsOnDemandEntries' , function ( compilation , done ) {
48
+ for ( const compiler of compilers ) {
49
+ compiler . hooks . make . tapPromise ( 'NextJsOnDemandEntries' , ( compilation ) => {
40
50
invalidator . startBuilding ( )
41
- currentBuilders . add ( compiler . name )
42
51
43
52
const allEntries = Object . keys ( entries ) . map ( async ( page ) => {
44
53
const { name, entry } = entries [ page ]
@@ -57,65 +66,79 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
57
66
return addEntry ( compilation , compiler . context , name , entry )
58
67
} )
59
68
60
- Promise . all ( allEntries )
61
- . then ( ( ) => done ( ) )
62
- . catch ( done )
69
+ return Promise . all ( allEntries )
63
70
} )
71
+ }
64
72
65
- compiler . hooks . done . tap ( 'NextJsOnDemandEntries' , function ( stats ) {
66
- // Wait until all the compilers mark the build as done.
67
- currentBuilders . delete ( compiler . name )
68
- if ( currentBuilders . size !== 0 ) return
73
+ multiCompiler . hooks . done . tap ( 'NextJsOnDemandEntries' , ( multiStats ) => {
74
+ const clientStats = multiStats . stats [ 0 ]
75
+ const { compilation } = clientStats
76
+ const hardFailedPages = compilation . errors
77
+ . filter ( e => {
78
+ // Make sure to only pick errors which marked with missing modules
79
+ const hasNoModuleFoundError = / E N O E N T / . test ( e . message ) || / M o d u l e n o t f o u n d / . test ( e . message )
80
+ if ( ! hasNoModuleFoundError ) return false
81
+
82
+ // The page itself is missing. So this is a failed page.
83
+ if ( IS_BUNDLED_PAGE_REGEX . test ( e . module . name ) ) return true
84
+
85
+ // No dependencies means this is a top level page.
86
+ // So this is a failed page.
87
+ return e . module . dependencies . length === 0
88
+ } )
89
+ . map ( e => e . module . chunks )
90
+ . reduce ( ( a , b ) => [ ...a , ...b ] , [ ] )
91
+ . map ( c => {
92
+ const pageName = ROUTE_NAME_REGEX . exec ( c . name ) [ 1 ]
93
+ return normalizePage ( `/${ pageName } ` )
94
+ } )
69
95
70
- const { compilation } = stats
71
- const hardFailedPages = compilation . errors
72
- . filter ( e => {
73
- // Make sure to only pick errors which marked with missing modules
74
- const hasNoModuleFoundError = / E N O E N T / . test ( e . message ) || / M o d u l e n o t f o u n d / . test ( e . message )
75
- if ( ! hasNoModuleFoundError ) return false
96
+ // compilation.entrypoints is a Map object, so iterating over it 0 is the key and 1 is the value
97
+ for ( const [ , entrypoint ] of compilation . entrypoints . entries ( ) ) {
98
+ const result = ROUTE_NAME_REGEX . exec ( entrypoint . name )
99
+ if ( ! result ) {
100
+ continue
101
+ }
76
102
77
- // The page itself is missing. So this is a failed page.
78
- if ( IS_BUNDLED_PAGE_REGEX . test ( e . module . name ) ) return true
103
+ const pagePath = result [ 1 ]
79
104
80
- // No dependencies means this is a top level page.
81
- // So this is a failed page.
82
- return e . module . dependencies . length === 0
83
- } )
84
- . map ( e => e . module . chunks )
85
- . reduce ( ( a , b ) => [ ...a , ...b ] , [ ] )
86
- . map ( c => {
87
- const pageName = ROUTE_NAME_REGEX . exec ( c . name ) [ 1 ]
88
- return normalizePage ( `/${ pageName } ` )
89
- } )
105
+ if ( ! pagePath ) {
106
+ continue
107
+ }
90
108
91
- // Call all the doneCallbacks
92
- Object . keys ( entries ) . forEach ( ( page ) => {
93
- const entryInfo = entries [ page ]
94
- if ( entryInfo . status !== BUILDING ) return
109
+ const page = normalizePage ( '/' + pagePath )
95
110
96
- entryInfo . status = BUILT
97
- entries [ page ] . lastActiveTime = Date . now ( )
98
- doneCallbacks . emit ( page )
99
- } )
111
+ const entry = entries [ page ]
112
+ if ( ! entry ) {
113
+ continue
114
+ }
100
115
101
- invalidator . doneBuilding ( compiler . name )
102
-
103
- if ( hardFailedPages . length > 0 && ! reloading ) {
104
- console . log ( `> Reloading webpack due to inconsistant state of pages(s): ${ hardFailedPages . join ( ', ' ) } ` )
105
- reloading = true
106
- reload ( )
107
- . then ( ( ) => {
108
- console . log ( '> Webpack reloaded.' )
109
- reloadCallbacks . emit ( 'done' )
110
- stop ( )
111
- } )
112
- . catch ( err => {
113
- console . error ( `> Webpack reloading failed: ${ err . message } ` )
114
- console . error ( err . stack )
115
- process . exit ( 1 )
116
- } )
116
+ if ( entry . status !== BUILDING ) {
117
+ continue
117
118
}
118
- } )
119
+
120
+ entry . status = BUILT
121
+ entry . lastActiveTime = Date . now ( )
122
+ doneCallbacks . emit ( page )
123
+ }
124
+
125
+ invalidator . doneBuilding ( )
126
+
127
+ if ( hardFailedPages . length > 0 && ! reloading ) {
128
+ console . log ( `> Reloading webpack due to inconsistant state of pages(s): ${ hardFailedPages . join ( ', ' ) } ` )
129
+ reloading = true
130
+ reload ( )
131
+ . then ( ( ) => {
132
+ console . log ( '> Webpack reloaded.' )
133
+ reloadCallbacks . emit ( 'done' )
134
+ stop ( )
135
+ } )
136
+ . catch ( err => {
137
+ console . error ( `> Webpack reloading failed: ${ err . message } ` )
138
+ console . error ( err . stack )
139
+ process . exit ( 1 )
140
+ } )
141
+ }
119
142
} )
120
143
121
144
const disposeHandler = setInterval ( function ( ) {
@@ -248,17 +271,6 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
248
271
}
249
272
}
250
273
251
- // Based on https://github.com/webpack/webpack/blob/master/lib/DynamicEntryPlugin.js#L29-L37
252
- function addEntry ( compilation , context , name , entry ) {
253
- return new Promise ( ( resolve , reject ) => {
254
- const dep = DynamicEntryPlugin . createDependency ( entry , name )
255
- compilation . addEntry ( context , dep , name , ( err ) => {
256
- if ( err ) return reject ( err )
257
- resolve ( )
258
- } )
259
- } )
260
- }
261
-
262
274
function disposeInactiveEntries ( devMiddleware , entries , lastAccessPages , maxInactiveAge ) {
263
275
const disposingPages = [ ]
264
276
@@ -291,10 +303,11 @@ function disposeInactiveEntries (devMiddleware, entries, lastAccessPages, maxIna
291
303
// /index and / is the same. So, we need to identify both pages as the same.
292
304
// This also applies to sub pages as well.
293
305
export function normalizePage ( page ) {
294
- if ( page === '/index' || page === '/' ) {
306
+ const unixPagePath = page . replace ( / \\ / g, '/' )
307
+ if ( unixPagePath === '/index' || unixPagePath === '/' ) {
295
308
return '/'
296
309
}
297
- return page . replace ( / \/ i n d e x $ / , '' )
310
+ return unixPagePath . replace ( / \/ i n d e x $ / , '' )
298
311
}
299
312
300
313
function sendJson ( res , payload ) {
@@ -306,7 +319,8 @@ function sendJson (res, payload) {
306
319
// Make sure only one invalidation happens at a time
307
320
// Otherwise, webpack hash gets changed and it'll force the client to reload.
308
321
class Invalidator {
309
- constructor ( devMiddleware ) {
322
+ constructor ( devMiddleware , multiCompiler ) {
323
+ this . multiCompiler = multiCompiler
310
324
this . devMiddleware = devMiddleware
311
325
// contains an array of types of compilers currently building
312
326
this . building = false
@@ -324,6 +338,11 @@ class Invalidator {
324
338
}
325
339
326
340
this . building = true
341
+ // Work around a bug in webpack, calling `invalidate` on Watching.js
342
+ // doesn't trigger the invalid call used to keep track of the `.done` hook on multiCompiler
343
+ for ( const compiler of this . multiCompiler . compilers ) {
344
+ compiler . hooks . invalid . call ( )
345
+ }
327
346
this . devMiddleware . invalidate ( )
328
347
}
329
348
0 commit comments