2017-05-11 15:24:27 +00:00
|
|
|
/* global window, document, __NEXT_DATA__ */
|
2017-07-06 06:49:33 +00:00
|
|
|
import EventEmitter from './EventEmitter'
|
2017-04-11 16:37:59 +00:00
|
|
|
|
2017-04-06 06:49:00 +00:00
|
|
|
const webpackModule = module
|
|
|
|
|
2017-04-03 18:10:24 +00:00
|
|
|
export default class PageLoader {
|
2017-04-18 04:18:43 +00:00
|
|
|
constructor (buildId, assetPrefix) {
|
2017-04-03 18:10:24 +00:00
|
|
|
this.buildId = buildId
|
2017-04-18 04:18:43 +00:00
|
|
|
this.assetPrefix = assetPrefix
|
|
|
|
|
2017-04-03 18:10:24 +00:00
|
|
|
this.pageCache = {}
|
|
|
|
this.pageLoadedHandlers = {}
|
2017-07-06 06:49:33 +00:00
|
|
|
this.pageRegisterEvents = new EventEmitter()
|
2017-04-03 18:10:24 +00:00
|
|
|
this.loadingRoutes = {}
|
2017-04-17 20:15:50 +00:00
|
|
|
|
2017-07-06 06:49:33 +00:00
|
|
|
this.chunkRegisterEvents = new EventEmitter()
|
2017-04-17 20:15:50 +00:00
|
|
|
this.loadedChunks = {}
|
2017-04-03 18:10:24 +00:00
|
|
|
}
|
|
|
|
|
2017-04-04 19:55:56 +00:00
|
|
|
normalizeRoute (route) {
|
2017-04-03 18:10:24 +00:00
|
|
|
if (route[0] !== '/') {
|
2017-06-20 19:43:38 +00:00
|
|
|
throw new Error(`Route name should start with a "/", got "${route}"`)
|
2017-04-03 18:10:24 +00:00
|
|
|
}
|
2017-07-09 04:20:30 +00:00
|
|
|
route = route.replace(/\/index$/, '/')
|
2017-04-03 18:10:24 +00:00
|
|
|
|
2017-05-01 23:26:18 +00:00
|
|
|
if (route === '/') return route
|
2017-05-04 20:05:47 +00:00
|
|
|
return route.replace(/\/$/, '')
|
2017-04-04 19:55:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
loadPage (route) {
|
|
|
|
route = this.normalizeRoute(route)
|
2017-04-03 18:10:24 +00:00
|
|
|
|
2017-04-11 16:37:59 +00:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const fire = ({ error, page }) => {
|
2017-04-17 20:15:50 +00:00
|
|
|
this.pageRegisterEvents.off(route, fire)
|
2017-06-23 05:16:55 +00:00
|
|
|
delete this.loadingRoutes[route]
|
2017-04-03 18:10:24 +00:00
|
|
|
|
2017-04-11 16:37:59 +00:00
|
|
|
if (error) {
|
|
|
|
reject(error)
|
|
|
|
} else {
|
|
|
|
resolve(page)
|
|
|
|
}
|
|
|
|
}
|
2017-04-03 18:10:24 +00:00
|
|
|
|
2017-06-23 05:16:55 +00:00
|
|
|
// If there's a cached version of the page, let's use it.
|
|
|
|
const cachedPage = this.pageCache[route]
|
|
|
|
if (cachedPage) {
|
|
|
|
const { error, page } = cachedPage
|
|
|
|
error ? reject(error) : resolve(page)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register a listener to get the page
|
2017-04-17 20:15:50 +00:00
|
|
|
this.pageRegisterEvents.on(route, fire)
|
2017-04-03 18:10:24 +00:00
|
|
|
|
2017-05-09 01:20:50 +00:00
|
|
|
// If the page is loading via SSR, we need to wait for it
|
|
|
|
// rather downloading it again.
|
|
|
|
if (document.getElementById(`__NEXT_PAGE__${route}`)) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-04-11 16:37:59 +00:00
|
|
|
// Load the script if not asked to load yet.
|
|
|
|
if (!this.loadingRoutes[route]) {
|
|
|
|
this.loadScript(route)
|
|
|
|
this.loadingRoutes[route] = true
|
|
|
|
}
|
2017-04-03 18:10:24 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-04-11 16:37:59 +00:00
|
|
|
loadScript (route) {
|
2017-04-04 19:55:56 +00:00
|
|
|
route = this.normalizeRoute(route)
|
2017-11-23 13:05:17 +00:00
|
|
|
let scriptRoute = route
|
2017-04-04 19:55:56 +00:00
|
|
|
|
2017-05-11 15:24:27 +00:00
|
|
|
if (__NEXT_DATA__.nextExport) {
|
2017-11-23 13:05:17 +00:00
|
|
|
scriptRoute = route === '/' ? '/index.js' : `${route}/index.js`
|
2017-05-11 15:24:27 +00:00
|
|
|
}
|
|
|
|
|
2017-04-03 18:10:24 +00:00
|
|
|
const script = document.createElement('script')
|
2017-11-23 13:05:17 +00:00
|
|
|
const url = `${this.assetPrefix}/_next/${encodeURIComponent(this.buildId)}/page${scriptRoute}`
|
2017-04-03 18:10:24 +00:00
|
|
|
script.src = url
|
|
|
|
script.type = 'text/javascript'
|
2017-04-11 16:37:59 +00:00
|
|
|
script.onerror = () => {
|
|
|
|
const error = new Error(`Error when loading route: ${route}`)
|
2017-04-17 20:15:50 +00:00
|
|
|
this.pageRegisterEvents.emit(route, { error })
|
2017-04-03 18:10:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
document.body.appendChild(script)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This method if called by the route code.
|
2017-04-06 06:49:00 +00:00
|
|
|
registerPage (route, regFn) {
|
2017-04-11 14:33:18 +00:00
|
|
|
const register = () => {
|
2017-06-16 11:13:55 +00:00
|
|
|
try {
|
|
|
|
const { error, page } = regFn()
|
|
|
|
this.pageCache[route] = { error, page }
|
|
|
|
this.pageRegisterEvents.emit(route, { error, page })
|
|
|
|
} catch (error) {
|
|
|
|
this.pageCache[route] = { error }
|
|
|
|
this.pageRegisterEvents.emit(route, { error })
|
|
|
|
}
|
2017-04-06 06:49:00 +00:00
|
|
|
}
|
|
|
|
|
2017-08-23 20:06:11 +00:00
|
|
|
// Wait for webpack to become idle if it's not.
|
2017-04-06 06:49:00 +00:00
|
|
|
// More info: https://github.com/zeit/next.js/pull/1511
|
|
|
|
if (webpackModule && webpackModule.hot && webpackModule.hot.status() !== 'idle') {
|
2017-08-23 20:06:11 +00:00
|
|
|
console.log(`Waiting for webpack to become "idle" to initialize the page: "${route}"`)
|
2017-04-04 19:55:56 +00:00
|
|
|
|
2017-04-06 06:49:00 +00:00
|
|
|
const check = (status) => {
|
|
|
|
if (status === 'idle') {
|
|
|
|
webpackModule.hot.removeStatusHandler(check)
|
|
|
|
register()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
webpackModule.hot.status(check)
|
|
|
|
} else {
|
|
|
|
register()
|
|
|
|
}
|
2017-04-03 18:10:24 +00:00
|
|
|
}
|
2017-04-04 19:55:56 +00:00
|
|
|
|
2017-04-17 20:15:50 +00:00
|
|
|
registerChunk (chunkName, regFn) {
|
|
|
|
const chunk = regFn()
|
|
|
|
this.loadedChunks[chunkName] = true
|
|
|
|
this.chunkRegisterEvents.emit(chunkName, chunk)
|
|
|
|
}
|
|
|
|
|
|
|
|
waitForChunk (chunkName, regFn) {
|
|
|
|
const loadedChunk = this.loadedChunks[chunkName]
|
|
|
|
if (loadedChunk) {
|
|
|
|
return Promise.resolve(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
const register = (chunk) => {
|
|
|
|
this.chunkRegisterEvents.off(chunkName, register)
|
|
|
|
resolve(chunk)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.chunkRegisterEvents.on(chunkName, register)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2017-04-04 19:55:56 +00:00
|
|
|
clearCache (route) {
|
|
|
|
route = this.normalizeRoute(route)
|
|
|
|
delete this.pageCache[route]
|
|
|
|
delete this.loadingRoutes[route]
|
2017-05-09 07:42:48 +00:00
|
|
|
|
|
|
|
const script = document.getElementById(`__NEXT_PAGE__${route}`)
|
|
|
|
if (script) {
|
|
|
|
script.parentNode.removeChild(script)
|
|
|
|
}
|
2017-04-04 19:55:56 +00:00
|
|
|
}
|
2017-04-03 18:10:24 +00:00
|
|
|
}
|