mirror of
https://github.com/terribleplan/next.js.git
synced 2024-01-19 02:48:18 +00:00
![Arunoda Susiripala](/assets/img/avatar_default.png)
So, we don't need to add them to individual pages. This also fix the issue where, error pages doesn't ping the server.
222 lines
6.1 KiB
JavaScript
222 lines
6.1 KiB
JavaScript
import DynamicEntryPlugin from 'webpack/lib/DynamicEntryPlugin'
|
|
import { EventEmitter } from 'events'
|
|
import { join } from 'path'
|
|
import { parse } from 'url'
|
|
import resolvePath from './resolve'
|
|
import touch from 'touch'
|
|
|
|
const ADDED = Symbol('added')
|
|
const BUILDING = Symbol('building')
|
|
const BUILT = Symbol('built')
|
|
|
|
export default function onDemandEntryHandler (devMiddleware, compiler, {
|
|
dir,
|
|
dev,
|
|
maxInactiveAge = 1000 * 25
|
|
}) {
|
|
const entries = {}
|
|
const lastAccessPages = ['']
|
|
const doneCallbacks = new EventEmitter()
|
|
const invalidator = new Invalidator(devMiddleware)
|
|
let touchedAPage = false
|
|
|
|
compiler.plugin('make', function (compilation, done) {
|
|
invalidator.startBuilding()
|
|
|
|
const allEntries = Object.keys(entries).map((page) => {
|
|
const { name, entry } = entries[page]
|
|
entries[page].status = BUILDING
|
|
return addEntry(compilation, this.context, name, entry)
|
|
})
|
|
|
|
Promise.all(allEntries)
|
|
.then(() => done())
|
|
.catch(done)
|
|
})
|
|
|
|
compiler.plugin('done', function (stats) {
|
|
// Call all the doneCallbacks
|
|
Object.keys(entries).forEach((page) => {
|
|
const entryInfo = entries[page]
|
|
if (entryInfo.status !== BUILDING) return
|
|
|
|
// With this, we are triggering a filesystem based watch trigger
|
|
// It'll memorize some timestamp related info related to common files used
|
|
// in the page
|
|
// That'll reduce the page building time significantly.
|
|
if (!touchedAPage) {
|
|
setTimeout(() => {
|
|
touch.sync(entryInfo.pathname)
|
|
}, 1000)
|
|
touchedAPage = true
|
|
}
|
|
|
|
entryInfo.status = BUILT
|
|
entries[page].lastActiveTime = Date.now()
|
|
doneCallbacks.emit(page)
|
|
})
|
|
|
|
invalidator.doneBuilding()
|
|
})
|
|
|
|
setInterval(function () {
|
|
disposeInactiveEntries(devMiddleware, entries, lastAccessPages, maxInactiveAge)
|
|
}, 5000)
|
|
|
|
return {
|
|
async ensurePage (page) {
|
|
page = normalizePage(page)
|
|
|
|
const pagePath = join(dir, 'pages', page)
|
|
const pathname = await resolvePath(pagePath)
|
|
const name = join('bundles', pathname.substring(dir.length))
|
|
|
|
const entry = `${pathname}?entry`
|
|
|
|
await new Promise((resolve, reject) => {
|
|
const entryInfo = entries[page]
|
|
|
|
if (entryInfo) {
|
|
if (entryInfo.status === BUILT) {
|
|
resolve()
|
|
return
|
|
}
|
|
|
|
if (entryInfo.status === BUILDING) {
|
|
doneCallbacks.on(page, processCallback)
|
|
return
|
|
}
|
|
}
|
|
|
|
console.log(`> Building page: ${page}`)
|
|
|
|
entries[page] = { name, entry, pathname, status: ADDED }
|
|
doneCallbacks.on(page, processCallback)
|
|
|
|
invalidator.invalidate()
|
|
|
|
function processCallback (err) {
|
|
if (err) return reject(err)
|
|
resolve()
|
|
}
|
|
})
|
|
},
|
|
|
|
middleware () {
|
|
return function (req, res, next) {
|
|
if (!/^\/_next\/on-demand-entries-ping/.test(req.url)) return next()
|
|
|
|
const { query } = parse(req.url, true)
|
|
const page = normalizePage(query.page)
|
|
const entryInfo = entries[page]
|
|
|
|
// If there's no entry.
|
|
// Then it seems like an weird issue.
|
|
if (!entryInfo) {
|
|
const message = `Client pings, but there's no entry for page: ${page}`
|
|
console.error(message)
|
|
sendJson(res, { invalid: true })
|
|
return
|
|
}
|
|
|
|
sendJson(res, { success: true })
|
|
|
|
// We don't need to maintain active state of anything other than BUILT entries
|
|
if (entryInfo.status !== BUILT) return
|
|
|
|
// If there's an entryInfo
|
|
lastAccessPages.pop()
|
|
lastAccessPages.unshift(page)
|
|
entryInfo.lastActiveTime = Date.now()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function addEntry (compilation, context, name, entry) {
|
|
return new Promise((resolve, reject) => {
|
|
const dep = DynamicEntryPlugin.createDependency(entry, name)
|
|
compilation.addEntry(context, dep, name, (err) => {
|
|
if (err) return reject(err)
|
|
resolve()
|
|
})
|
|
})
|
|
}
|
|
|
|
function disposeInactiveEntries (devMiddleware, entries, lastAccessPages, maxInactiveAge) {
|
|
const disposingPages = []
|
|
|
|
Object.keys(entries).forEach((page) => {
|
|
const { lastActiveTime, status } = entries[page]
|
|
|
|
// This means this entry is currently building or just added
|
|
// We don't need to dispose those entries.
|
|
if (status !== BUILT) return
|
|
|
|
// We should not build the last accessed page even we didn't get any pings
|
|
// Sometimes, it's possible our XHR ping to wait before completing other requests.
|
|
// In that case, we should not dispose the current viewing page
|
|
if (lastAccessPages[0] === page) return
|
|
|
|
if (Date.now() - lastActiveTime > maxInactiveAge) {
|
|
disposingPages.push(page)
|
|
}
|
|
})
|
|
|
|
if (disposingPages.length > 0) {
|
|
disposingPages.forEach((page) => {
|
|
delete entries[page]
|
|
})
|
|
console.log(`> Disposing inactive page(s): ${disposingPages.join(', ')}`)
|
|
devMiddleware.invalidate()
|
|
}
|
|
}
|
|
|
|
// /index and / is the same. So, we need to identify both pages as the same.
|
|
// This also applies to sub pages as well.
|
|
function normalizePage (page) {
|
|
return page.replace(/\/index$/, '/')
|
|
}
|
|
|
|
function sendJson (res, payload) {
|
|
res.setHeader('Content-Type', 'application/json')
|
|
res.status = 200
|
|
res.end(JSON.stringify(payload))
|
|
}
|
|
|
|
// Make sure only one invalidation happens at a time
|
|
// Otherwise, webpack hash gets changed and it'll force the client to reload.
|
|
class Invalidator {
|
|
constructor (devMiddleware) {
|
|
this.devMiddleware = devMiddleware
|
|
this.building = false
|
|
this.rebuildAgain = false
|
|
}
|
|
|
|
invalidate () {
|
|
// If there's a current build is processing, we won't abort it by invalidating.
|
|
// (If aborted, it'll cause a client side hard reload)
|
|
// But let it to invalidate just after the completion.
|
|
// So, it can re-build the queued pages at once.
|
|
if (this.building) {
|
|
this.rebuildAgain = true
|
|
return
|
|
}
|
|
|
|
this.building = true
|
|
this.devMiddleware.invalidate()
|
|
}
|
|
|
|
startBuilding () {
|
|
this.building = true
|
|
}
|
|
|
|
doneBuilding () {
|
|
this.building = false
|
|
if (this.rebuildAgain) {
|
|
this.rebuildAgain = false
|
|
this.invalidate()
|
|
}
|
|
}
|
|
}
|