Skip to content

Commit 459c1c1

Browse files
authoredSep 16, 2018
Improvements to on-demand-entries (#5176)
First wave of changes. Just landing this first so that there is a base to build on.
1 parent 864ea5d commit 459c1c1

File tree

4 files changed

+93
-75
lines changed

4 files changed

+93
-75
lines changed
 

‎build/webpack/plugins/pages-manifest-plugin.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {PAGES_MANIFEST, ROUTE_NAME_REGEX} from '../../../lib/constants'
77
// It's also used by next export to provide defaultPathMap
88
export default class PagesManifestPlugin {
99
apply (compiler: any) {
10-
compiler.hooks.emit.tapAsync('NextJsPagesManifest', (compilation, callback) => {
10+
compiler.hooks.emit.tap('NextJsPagesManifest', (compilation) => {
1111
const {entries} = compilation
1212
const pages = {}
1313

@@ -32,7 +32,6 @@ export default class PagesManifestPlugin {
3232
}
3333

3434
compilation.assets[PAGES_MANIFEST] = new RawSource(JSON.stringify(pages))
35-
callback()
3635
})
3736
}
3837
}

‎package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@
108108
"terser-webpack-plugin": "1.0.2",
109109
"unfetch": "3.0.0",
110110
"url": "0.11.0",
111-
"webpack": "4.17.1",
111+
"webpack": "4.19.0",
112112
"webpack-dev-middleware": "3.2.0",
113113
"webpack-hot-middleware": "2.22.3",
114114
"webpack-sources": "1.2.0",

‎server/hot-reloader.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export default class HotReloader {
319319
heartbeat: 2500
320320
})
321321

322-
const onDemandEntries = onDemandEntryHandler(webpackDevMiddleware, multiCompiler.compilers, {
322+
const onDemandEntries = onDemandEntryHandler(webpackDevMiddleware, multiCompiler, {
323323
dir: this.dir,
324324
buildId: this.buildId,
325325
dev: true,

‎server/on-demand-entry-handler.js

+90-71
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,18 @@ const BUILT = Symbol('built')
1616
const glob = promisify(globModule)
1717
const access = promisify(fs.access)
1818

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, {
2031
buildId,
2132
dir,
2233
dev,
@@ -25,20 +36,18 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
2536
maxInactiveAge = 1000 * 60,
2637
pagesBufferLength = 2
2738
}) {
39+
const {compilers} = multiCompiler
40+
const invalidator = new Invalidator(devMiddleware, multiCompiler)
2841
let entries = {}
2942
let lastAccessPages = ['']
3043
let doneCallbacks = new EventEmitter()
31-
const invalidator = new Invalidator(devMiddleware)
3244
let reloading = false
3345
let stopped = false
3446
let reloadCallbacks = new EventEmitter()
35-
// Keep the names of compilers which are building pages at a given moment.
36-
const currentBuilders = new Set()
3747

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) => {
4050
invalidator.startBuilding()
41-
currentBuilders.add(compiler.name)
4251

4352
const allEntries = Object.keys(entries).map(async (page) => {
4453
const { name, entry } = entries[page]
@@ -57,65 +66,79 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
5766
return addEntry(compilation, compiler.context, name, entry)
5867
})
5968

60-
Promise.all(allEntries)
61-
.then(() => done())
62-
.catch(done)
69+
return Promise.all(allEntries)
6370
})
71+
}
6472

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 = /ENOENT/.test(e.message) || /Module not found/.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+
})
6995

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 = /ENOENT/.test(e.message) || /Module not found/.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+
}
76102

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]
79104

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+
}
90108

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)
95110

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+
}
100115

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
117118
}
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+
}
119142
})
120143

121144
const disposeHandler = setInterval(function () {
@@ -248,17 +271,6 @@ export default function onDemandEntryHandler (devMiddleware, compilers, {
248271
}
249272
}
250273

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-
262274
function disposeInactiveEntries (devMiddleware, entries, lastAccessPages, maxInactiveAge) {
263275
const disposingPages = []
264276

@@ -291,10 +303,11 @@ function disposeInactiveEntries (devMiddleware, entries, lastAccessPages, maxIna
291303
// /index and / is the same. So, we need to identify both pages as the same.
292304
// This also applies to sub pages as well.
293305
export function normalizePage (page) {
294-
if (page === '/index' || page === '/') {
306+
const unixPagePath = page.replace(/\\/g, '/')
307+
if (unixPagePath === '/index' || unixPagePath === '/') {
295308
return '/'
296309
}
297-
return page.replace(/\/index$/, '')
310+
return unixPagePath.replace(/\/index$/, '')
298311
}
299312

300313
function sendJson (res, payload) {
@@ -306,7 +319,8 @@ function sendJson (res, payload) {
306319
// Make sure only one invalidation happens at a time
307320
// Otherwise, webpack hash gets changed and it'll force the client to reload.
308321
class Invalidator {
309-
constructor (devMiddleware) {
322+
constructor (devMiddleware, multiCompiler) {
323+
this.multiCompiler = multiCompiler
310324
this.devMiddleware = devMiddleware
311325
// contains an array of types of compilers currently building
312326
this.building = false
@@ -324,6 +338,11 @@ class Invalidator {
324338
}
325339

326340
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+
}
327346
this.devMiddleware.invalidate()
328347
}
329348

0 commit comments

Comments
 (0)
Please sign in to comment.