Skip to content

Commit 8cb3e89

Browse files
committed
Merge master into v3-beta.
2 parents 679e8d3 + 70da357 commit 8cb3e89

8 files changed

Lines changed: 227 additions & 60 deletions

File tree

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ environment:
66
# Install scripts. (runs after repo cloning)
77
install:
88
# Install Google Chrome for e2e testing
9-
- choco install googlechrome
9+
- choco install --ignore-checksums googlechrome
1010
# Get the latest stable version of Node.js or io.js
1111
- ps: Install-Product node $env:nodejs_version
1212
# install modules

server/build/plugins/pages-plugin.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1+
import {
2+
IS_BUNDLED_PAGE,
3+
MATCH_ROUTE_NAME
4+
} from '../../utils'
5+
16
export default class PagesPlugin {
27
apply (compiler) {
3-
const isBundledPage = /^bundles[/\\]pages.*\.js$/
4-
const matchRouteName = /^bundles[/\\]pages[/\\](.*)\.js$/
5-
68
compiler.plugin('after-compile', (compilation, callback) => {
79
const pages = Object
810
.keys(compilation.namedChunks)
911
.map(key => compilation.namedChunks[key])
10-
.filter(chunk => isBundledPage.test(chunk.name))
12+
.filter(chunk => IS_BUNDLED_PAGE.test(chunk.name))
1113

1214
pages.forEach((chunk) => {
1315
const page = compilation.assets[chunk.name]
14-
const pageName = matchRouteName.exec(chunk.name)[1]
16+
const pageName = MATCH_ROUTE_NAME.exec(chunk.name)[1]
1517
let routeName = `/${pageName.replace(/[/\\]?index$/, '')}`
1618

1719
// We need to convert \ into / when we are in windows

server/hot-reloader.js

Lines changed: 59 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import { join, relative, sep } from 'path'
2-
import webpackDevMiddleware from 'webpack-dev-middleware'
3-
import webpackHotMiddleware from 'webpack-hot-middleware'
2+
import WebpackDevMiddleware from 'webpack-dev-middleware'
3+
import WebpackHotMiddleware from 'webpack-hot-middleware'
44
import onDemandEntryHandler from './on-demand-entry-handler'
55
import isWindowsBash from 'is-windows-bash'
66
import webpack from './build/webpack'
77
import clean from './build/clean'
88
import getConfig from './config'
9-
10-
const isBundledPage = /^bundles[/\\]pages.*\.js$/
9+
import {
10+
IS_BUNDLED_PAGE
11+
} from './utils'
1112

1213
export default class HotReloader {
1314
constructor (dir, { quiet, conf } = {}) {
@@ -44,22 +45,53 @@ export default class HotReloader {
4445
clean(this.dir)
4546
])
4647

47-
this.prepareMiddlewares(compiler)
48+
const buildTools = await this.prepareBuildTools(compiler)
49+
this.assignBuildTools(buildTools)
50+
4851
this.stats = await this.waitUntilValid()
4952
}
5053

51-
async stop () {
52-
if (this.webpackDevMiddleware) {
54+
async stop (webpackDevMiddleware) {
55+
const middleware = webpackDevMiddleware || this.webpackDevMiddleware
56+
if (middleware) {
5357
return new Promise((resolve, reject) => {
54-
this.webpackDevMiddleware.close((err) => {
58+
middleware.close((err) => {
5559
if (err) return reject(err)
5660
resolve()
5761
})
5862
})
5963
}
6064
}
6165

62-
async prepareMiddlewares (compiler) {
66+
async reload () {
67+
this.stats = null
68+
69+
const [compiler] = await Promise.all([
70+
webpack(this.dir, { dev: true, quiet: this.quiet }),
71+
clean(this.dir)
72+
])
73+
74+
const buildTools = await this.prepareBuildTools(compiler)
75+
this.stats = await this.waitUntilValid(buildTools.webpackDevMiddleware)
76+
77+
const oldWebpackDevMiddleware = this.webpackDevMiddleware
78+
79+
this.assignBuildTools(buildTools)
80+
await this.stop(oldWebpackDevMiddleware)
81+
}
82+
83+
assignBuildTools ({ webpackDevMiddleware, webpackHotMiddleware, onDemandEntries }) {
84+
this.webpackDevMiddleware = webpackDevMiddleware
85+
this.webpackHotMiddleware = webpackHotMiddleware
86+
this.onDemandEntries = onDemandEntries
87+
this.middlewares = [
88+
webpackDevMiddleware,
89+
webpackHotMiddleware,
90+
onDemandEntries.middleware()
91+
]
92+
}
93+
94+
async prepareBuildTools (compiler) {
6395
compiler.plugin('after-emit', (compilation, callback) => {
6496
const { assets } = compilation
6597

@@ -83,7 +115,7 @@ export default class HotReloader {
83115
const chunkNames = new Set(
84116
compilation.chunks
85117
.map((c) => c.name)
86-
.filter(name => isBundledPage.test(name))
118+
.filter(name => IS_BUNDLED_PAGE.test(name))
87119
)
88120

89121
const failedChunkNames = new Set(compilation.errors
@@ -95,7 +127,7 @@ export default class HotReloader {
95127

96128
const chunkHashes = new Map(
97129
compilation.chunks
98-
.filter(c => isBundledPage.test(c.name))
130+
.filter(c => IS_BUNDLED_PAGE.test(c.name))
99131
.map((c) => [c.name, c.hash])
100132
)
101133

@@ -163,33 +195,38 @@ export default class HotReloader {
163195
webpackDevMiddlewareConfig = this.config.webpackDevMiddleware(webpackDevMiddlewareConfig)
164196
}
165197

166-
this.webpackDevMiddleware = webpackDevMiddleware(compiler, webpackDevMiddlewareConfig)
198+
const webpackDevMiddleware = WebpackDevMiddleware(compiler, webpackDevMiddlewareConfig)
167199

168-
this.webpackHotMiddleware = webpackHotMiddleware(compiler, {
200+
const webpackHotMiddleware = WebpackHotMiddleware(compiler, {
169201
path: '/_next/webpack-hmr',
170202
log: false,
171203
heartbeat: 2500
172204
})
173-
this.onDemandEntries = onDemandEntryHandler(this.webpackDevMiddleware, compiler, {
205+
const onDemandEntries = onDemandEntryHandler(webpackDevMiddleware, compiler, {
174206
dir: this.dir,
175207
dev: true,
208+
reload: this.reload.bind(this),
176209
...this.config.onDemandEntries
177210
})
178211

179-
this.middlewares = [
180-
this.webpackDevMiddleware,
181-
this.webpackHotMiddleware,
182-
this.onDemandEntries.middleware()
183-
]
212+
return {
213+
webpackDevMiddleware,
214+
webpackHotMiddleware,
215+
onDemandEntries
216+
}
184217
}
185218

186-
waitUntilValid () {
219+
waitUntilValid (webpackDevMiddleware) {
220+
const middleware = webpackDevMiddleware || this.webpackDevMiddleware
187221
return new Promise((resolve) => {
188-
this.webpackDevMiddleware.waitUntilValid(resolve)
222+
middleware.waitUntilValid(resolve)
189223
})
190224
}
191225

192-
getCompilationErrors () {
226+
async getCompilationErrors () {
227+
// When we are reloading, we need to wait until it's reloaded properly.
228+
await this.onDemandEntries.waitUntilReloaded()
229+
193230
if (!this.compilationErrors) {
194231
this.compilationErrors = new Map()
195232

server/index.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ export default class Server {
174174
return await renderScriptError(req, res, page, error, {}, this.renderOpts)
175175
}
176176

177-
const compilationErr = this.getCompilationError(page)
177+
const compilationErr = await this.getCompilationError(page, req, res)
178178
if (compilationErr) {
179179
const customFields = { statusCode: 500 }
180180
return await renderScriptError(req, res, page, compilationErr, customFields, this.renderOpts)
@@ -253,7 +253,7 @@ export default class Server {
253253

254254
async renderToHTML (req, res, pathname, query) {
255255
if (this.dev) {
256-
const compilationErr = this.getCompilationError(pathname)
256+
const compilationErr = await this.getCompilationError(pathname)
257257
if (compilationErr) {
258258
res.statusCode = 500
259259
return this.renderErrorToHTML(compilationErr, req, res, pathname, query)
@@ -281,7 +281,7 @@ export default class Server {
281281

282282
async renderErrorToHTML (err, req, res, pathname, query) {
283283
if (this.dev) {
284-
const compilationErr = this.getCompilationError('/_error')
284+
const compilationErr = await this.getCompilationError('/_error')
285285
if (compilationErr) {
286286
res.statusCode = 500
287287
return renderErrorToHTML(compilationErr, req, res, pathname, query, this.renderOpts)
@@ -362,10 +362,10 @@ export default class Server {
362362
return true
363363
}
364364

365-
getCompilationError (page) {
365+
async getCompilationError (page, req, res) {
366366
if (!this.hotReloader) return
367367

368-
const errors = this.hotReloader.getCompilationErrors()
368+
const errors = await this.hotReloader.getCompilationErrors()
369369
if (!errors.size) return
370370

371371
const id = join(this.dir, this.dist, 'bundles', 'pages', page)

server/on-demand-entry-handler.js

Lines changed: 104 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { join } from 'path'
44
import { parse } from 'url'
55
import resolvePath from './resolve'
66
import touch from 'touch'
7+
import { MATCH_ROUTE_NAME, IS_BUNDLED_PAGE } from './utils'
78

89
const ADDED = Symbol('added')
910
const BUILDING = Symbol('building')
@@ -12,13 +13,17 @@ const BUILT = Symbol('built')
1213
export default function onDemandEntryHandler (devMiddleware, compiler, {
1314
dir,
1415
dev,
16+
reload,
1517
maxInactiveAge = 1000 * 25
1618
}) {
17-
const entries = {}
18-
const lastAccessPages = ['']
19-
const doneCallbacks = new EventEmitter()
19+
let entries = {}
20+
let lastAccessPages = ['']
21+
let doneCallbacks = new EventEmitter()
2022
const invalidator = new Invalidator(devMiddleware)
2123
let touchedAPage = false
24+
let reloading = false
25+
let stopped = false
26+
let reloadCallbacks = new EventEmitter()
2227

2328
compiler.plugin('make', function (compilation, done) {
2429
invalidator.startBuilding()
@@ -35,6 +40,27 @@ export default function onDemandEntryHandler (devMiddleware, compiler, {
3540
})
3641

3742
compiler.plugin('done', function (stats) {
43+
const { compilation } = stats
44+
const hardFailedPages = compilation.errors
45+
.filter(e => {
46+
// Make sure to only pick errors which marked with missing modules
47+
const hasNoModuleFoundError = /ENOENT/.test(e.message) || /Module not found/.test(e.message)
48+
if (!hasNoModuleFoundError) return false
49+
50+
// The page itself is missing. So this is a failed page.
51+
if (IS_BUNDLED_PAGE.test(e.module.name)) return true
52+
53+
// No dependencies means this is a top level page.
54+
// So this is a failed page.
55+
return e.module.dependencies.length === 0
56+
})
57+
.map(e => e.module.chunks)
58+
.reduce((a, b) => [...a, ...b], [])
59+
.map(c => {
60+
const pageName = MATCH_ROUTE_NAME.exec(c.name)[1]
61+
return normalizePage(`/${pageName}`)
62+
})
63+
3864
// Call all the doneCallbacks
3965
Object.keys(entries).forEach((page) => {
4066
const entryInfo = entries[page]
@@ -57,14 +83,48 @@ export default function onDemandEntryHandler (devMiddleware, compiler, {
5783
})
5884

5985
invalidator.doneBuilding()
86+
87+
if (hardFailedPages.length > 0 && !reloading) {
88+
console.log(`> Reloading webpack due to inconsistant state of pages(s): ${hardFailedPages.join(', ')}`)
89+
reloading = true
90+
reload()
91+
.then(() => {
92+
console.log('> Webpack reloaded.')
93+
reloadCallbacks.emit('done')
94+
stop()
95+
})
96+
.catch(err => {
97+
console.error(`> Webpack reloading failed: ${err.message}`)
98+
console.error(err.stack)
99+
process.exit(1)
100+
})
101+
}
60102
})
61103

62-
setInterval(function () {
104+
const disposeHandler = setInterval(function () {
105+
if (stopped) return
63106
disposeInactiveEntries(devMiddleware, entries, lastAccessPages, maxInactiveAge)
64107
}, 5000)
65108

109+
function stop () {
110+
clearInterval(disposeHandler)
111+
stopped = true
112+
doneCallbacks = null
113+
reloadCallbacks = null
114+
}
115+
66116
return {
117+
waitUntilReloaded () {
118+
if (!reloading) return Promise.resolve(true)
119+
return new Promise((resolve) => {
120+
reloadCallbacks.once('done', function () {
121+
resolve()
122+
})
123+
})
124+
},
125+
67126
async ensurePage (page) {
127+
await this.waitUntilReloaded()
68128
page = normalizePage(page)
69129

70130
const pagePath = join(dir, 'pages', page)
@@ -103,31 +163,49 @@ export default function onDemandEntryHandler (devMiddleware, compiler, {
103163
},
104164

105165
middleware () {
106-
return function (req, res, next) {
107-
if (!/^\/_next\/on-demand-entries-ping/.test(req.url)) return next()
108-
109-
const { query } = parse(req.url, true)
110-
const page = normalizePage(query.page)
111-
const entryInfo = entries[page]
112-
113-
// If there's no entry.
114-
// Then it seems like an weird issue.
115-
if (!entryInfo) {
116-
const message = `Client pings, but there's no entry for page: ${page}`
117-
console.error(message)
118-
sendJson(res, { invalid: true })
119-
return
120-
}
166+
return (req, res, next) => {
167+
if (stopped) {
168+
// If this handler is stopped, we need to reload the user's browser.
169+
// So the user could connect to the actually running handler.
170+
res.statusCode = 302
171+
res.setHeader('Location', req.url)
172+
res.end('302')
173+
} else if (reloading) {
174+
// Webpack config is reloading. So, we need to wait until it's done and
175+
// reload user's browser.
176+
// So the user could connect to the new handler and webpack setup.
177+
this.waitUntilReloaded()
178+
.then(() => {
179+
res.statusCode = 302
180+
res.setHeader('Location', req.url)
181+
res.end('302')
182+
})
183+
} else {
184+
if (!/^\/_next\/on-demand-entries-ping/.test(req.url)) return next()
185+
186+
const { query } = parse(req.url, true)
187+
const page = normalizePage(query.page)
188+
const entryInfo = entries[page]
189+
190+
// If there's no entry.
191+
// Then it seems like an weird issue.
192+
if (!entryInfo) {
193+
const message = `Client pings, but there's no entry for page: ${page}`
194+
console.error(message)
195+
sendJson(res, { invalid: true })
196+
return
197+
}
121198

122-
sendJson(res, { success: true })
199+
sendJson(res, { success: true })
123200

124-
// We don't need to maintain active state of anything other than BUILT entries
125-
if (entryInfo.status !== BUILT) return
201+
// We don't need to maintain active state of anything other than BUILT entries
202+
if (entryInfo.status !== BUILT) return
126203

127-
// If there's an entryInfo
128-
lastAccessPages.pop()
129-
lastAccessPages.unshift(page)
130-
entryInfo.lastActiveTime = Date.now()
204+
// If there's an entryInfo
205+
lastAccessPages.pop()
206+
lastAccessPages.unshift(page)
207+
entryInfo.lastActiveTime = Date.now()
208+
}
131209
}
132210
}
133211
}

0 commit comments

Comments
 (0)