Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit c95d2b2

Browse files
committed
Implement router using the page-loader.
1 parent 8938843 commit c95d2b2

File tree

6 files changed

+138
-110
lines changed

6 files changed

+138
-110
lines changed

client/index.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ export default () => {
6161
}
6262

6363
export async function render (props) {
64-
if (props.err) {
64+
// There are some errors we should ignore.
65+
// Next.js rendering logic knows how to handle them.
66+
// These are specially 404 errors
67+
if (props.err && !props.err.ignore) {
6568
await renderError(props.err)
6669
return
6770
}

lib/error.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import HTTPStatus from 'http-status'
33
import Head from './head'
44

55
export default class Error extends React.Component {
6-
static getInitialProps ({ res, jsonPageRes }) {
7-
const statusCode = res ? res.statusCode : (jsonPageRes ? jsonPageRes.status : null)
8-
return { statusCode }
6+
static getInitialProps ({ res, err }) {
7+
const statusCode = res ? res.statusCode : (err ? err.statusCode : null)
8+
const pageNotFound = statusCode === 404 || (err ? err.pageNotFound : false)
9+
return { statusCode, pageNotFound }
910
}
1011

1112
render () {
12-
const { statusCode } = this.props
13-
const title = statusCode === 404
13+
const { statusCode, pageNotFound } = this.props
14+
const title = pageNotFound
1415
? 'This page could not be found'
1516
: HTTPStatus[statusCode] || 'An unexpected error has occurred'
1617

lib/page-loader.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,23 @@ export default class PageLoader {
1010
this.loadingRoutes = {}
1111
}
1212

13-
loadPage (route) {
13+
normalizeRoute (route) {
1414
if (route[0] !== '/') {
1515
throw new Error('Route name should start with a "/"')
1616
}
1717

18-
route = route.replace(/index$/, '')
18+
return route.replace(/index$/, '')
19+
}
20+
21+
loadPage (route) {
22+
route = this.normalizeRoute(route)
1923

20-
if (this.pageCache[route]) {
21-
return Promise.resolve(this.pageCache[route])
24+
const cachedPage = this.pageCache[route]
25+
if (cachedPage) {
26+
return new Promise((resolve, reject) => {
27+
if (cachedPage.error) return reject(cachedPage.error)
28+
return resolve(cachedPage.page)
29+
})
2230
}
2331

2432
return new Promise((resolve, reject) => {
@@ -43,6 +51,8 @@ export default class PageLoader {
4351
}
4452

4553
loadScript (route) {
54+
route = this.normalizeRoute(route)
55+
4656
const script = document.createElement('script')
4757
const url = `/_next/${encodeURIComponent(this.buildId)}/page${route}`
4858
script.src = url
@@ -57,8 +67,16 @@ export default class PageLoader {
5767

5868
// This method if called by the route code.
5969
registerPage (route, error, page) {
70+
route = this.normalizeRoute(route)
71+
6072
// add the page to the cache
61-
this.pageCache[route] = page
73+
this.pageCache[route] = { error, page }
6274
this.registerEvents.emit(route, { error, page })
6375
}
76+
77+
clearCache (route) {
78+
route = this.normalizeRoute(route)
79+
delete this.pageCache[route]
80+
delete this.loadingRoutes[route]
81+
}
6482
}

lib/router/router.js

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
/* global NEXT_PAGE_LOADER */
2+
13
import { parse, format } from 'url'
24
import mitt from 'mitt'
3-
import fetch from 'unfetch'
4-
import evalScript from '../eval-script'
55
import shallowEquals from '../shallow-equals'
66
import PQueue from '../p-queue'
77
import { loadGetInitialProps, getURL } from '../utils'
@@ -15,10 +15,13 @@ export default class Router {
1515
this.route = toRoute(pathname)
1616

1717
// set up the component cache (by route keys)
18-
this.components = { [this.route]: { Component, err } }
19-
20-
// contain a map of promise of fetch routes
21-
this.fetchingRoutes = {}
18+
this.components = {}
19+
// We should not keep the cache, if there's an error
20+
// Otherwise, this cause issues when when going back and
21+
// come again to the errored page.
22+
if (Component !== ErrorComponent) {
23+
this.components[this.route] = { Component, err }
24+
}
2225

2326
// Handling Router Events
2427
this.events = mitt()
@@ -77,7 +80,7 @@ export default class Router {
7780

7881
async reload (route) {
7982
delete this.components[route]
80-
delete this.fetchingRoutes[route]
83+
NEXT_PAGE_LOADER.clearCache(route)
8184

8285
if (route !== this.route) return
8386

@@ -186,11 +189,11 @@ export default class Router {
186189
try {
187190
routeInfo = this.components[route]
188191
if (!routeInfo) {
189-
routeInfo = await this.fetchComponent(route, as)
192+
routeInfo = { Component: await this.fetchComponent(route, as) }
190193
}
191194

192-
const { Component, err, jsonPageRes } = routeInfo
193-
const ctx = { err, pathname, query, jsonPageRes }
195+
const { Component } = routeInfo
196+
const ctx = { pathname, query }
194197
routeInfo.props = await this.getInitialProps(Component, ctx)
195198

196199
this.components[route] = routeInfo
@@ -199,13 +202,27 @@ export default class Router {
199202
return { error: err }
200203
}
201204

205+
if (err.buildIdMismatched) {
206+
// Now we need to reload the page or do the action asked by the user
207+
_notifyBuildIdMismatch(as)
208+
// We also need to cancel this current route change.
209+
// We do it like this.
210+
err.cancelled = true
211+
return { error: err }
212+
}
213+
214+
if (err.pageNotFound) {
215+
// Indicate main error display logic to
216+
// ignore rendering this error as a runtime error.
217+
err.ignore = true
218+
}
219+
202220
const Component = this.ErrorComponent
203221
routeInfo = { Component, err }
204222
const ctx = { err, pathname, query }
205223
routeInfo.props = await this.getInitialProps(Component, ctx)
206224

207225
routeInfo.error = err
208-
console.error(err)
209226
}
210227

211228
return routeInfo
@@ -268,28 +285,7 @@ export default class Router {
268285
cancelled = true
269286
}
270287

271-
const jsonPageRes = await this.fetchRoute(route)
272-
let jsonData
273-
// We can call .json() only once for a response.
274-
// That's why we need to keep a copy of data if we already parsed it.
275-
if (jsonPageRes.data) {
276-
jsonData = jsonPageRes.data
277-
} else {
278-
jsonData = jsonPageRes.data = await jsonPageRes.json()
279-
}
280-
281-
if (jsonData.buildIdMismatch) {
282-
_notifyBuildIdMismatch(as)
283-
284-
const error = Error('Abort due to BUILD_ID mismatch')
285-
error.cancelled = true
286-
throw error
287-
}
288-
289-
const newData = {
290-
...await loadComponent(jsonData),
291-
jsonPageRes
292-
}
288+
const Component = await this.fetchRoute(route)
293289

294290
if (cancelled) {
295291
const error = new Error(`Abort fetching component for route: "${route}"`)
@@ -301,7 +297,7 @@ export default class Router {
301297
this.componentLoadCancel = null
302298
}
303299

304-
return newData
300+
return Component
305301
}
306302

307303
async getInitialProps (Component, ctx) {
@@ -324,24 +320,22 @@ export default class Router {
324320
return props
325321
}
326322

327-
fetchRoute (route) {
328-
let promise = this.fetchingRoutes[route]
329-
if (!promise) {
330-
promise = this.fetchingRoutes[route] = this.doFetchRoute(route)
323+
async fetchRoute (route) {
324+
// Wait for webpack to became idle if it's not.
325+
// More info: https://github.com/zeit/next.js/pull/1511
326+
if (webpackModule && webpackModule.hot && webpackModule.hot.status() !== 'idle') {
327+
await new Promise((resolve) => {
328+
const check = (status) => {
329+
if (status === 'idle') {
330+
webpackModule.hot.removeStatusHandler(check)
331+
resolve()
332+
}
333+
}
334+
webpackModule.hot.status(check)
335+
})
331336
}
332337

333-
return promise
334-
}
335-
336-
doFetchRoute (route) {
337-
const { buildId } = window.__NEXT_DATA__
338-
const url = `/_next/${encodeURIComponent(buildId)}/pages${route}`
339-
340-
return fetch(url, {
341-
method: 'GET',
342-
credentials: 'same-origin',
343-
headers: { 'Accept': 'application/json' }
344-
})
338+
return await NEXT_PAGE_LOADER.loadPage(route)
345339
}
346340

347341
abortComponentLoad (as) {
@@ -365,22 +359,3 @@ export default class Router {
365359
function toRoute (path) {
366360
return path.replace(/\/$/, '') || '/'
367361
}
368-
369-
async function loadComponent (jsonData) {
370-
if (webpackModule && webpackModule.hot && webpackModule.hot.status() !== 'idle') {
371-
await new Promise((resolve) => {
372-
const check = (status) => {
373-
if (status === 'idle') {
374-
webpackModule.hot.removeStatusHandler(check)
375-
resolve()
376-
}
377-
}
378-
webpackModule.hot.status(check)
379-
})
380-
}
381-
382-
const module = evalScript(jsonData.component)
383-
const Component = module.default || module
384-
385-
return { Component, err: jsonData.err }
386-
}

server/index.js

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import {
99
renderJSON,
1010
renderErrorJSON,
1111
sendHTML,
12-
serveStatic
12+
serveStatic,
13+
renderScript,
14+
renderScriptError
1315
} from './render'
1416
import Router from './router'
1517
import HotReloader from './hot-reloader'
16-
import resolvePath, { resolveFromList } from './resolve'
18+
import { resolveFromList } from './resolve'
1719
import getConfig from './config'
1820
// We need to go up one more level since we are in the `dist` directory
1921
import pkg from '../../package'
@@ -114,41 +116,28 @@ export default class Server {
114116
await this.serveStatic(req, res, p)
115117
},
116118

117-
'/_next/:buildId/pages/:path*': async (req, res, params) => {
118-
if (!this.handleBuildId(params.buildId, res)) {
119-
res.setHeader('Content-Type', 'application/json')
120-
res.end(JSON.stringify({ buildIdMismatch: true }))
121-
return
122-
}
123-
124-
const paths = params.path || ['index']
125-
const pathname = `/${paths.join('/')}`
126-
127-
await this.renderJSON(req, res, pathname)
128-
},
129-
130119
'/_next/:buildId/page/:path*': async (req, res, params) => {
131120
const paths = params.path || ['']
132-
const pathname = `/${paths.join('/')}`
133-
134-
if (this.dev) {
135-
await this.hotReloader.ensurePage(pathname)
136-
}
121+
const page = `/${paths.join('/')}`
137122

138123
if (!this.handleBuildId(params.buildId, res)) {
139-
res.setHeader('Content-Type', 'text/javascript')
140-
// TODO: Handle buildId mismatches properly.
141-
res.end(`
142-
var error = new Error('INVALID_BUILD_ID')
143-
error.buildIdMismatched = true
144-
NEXT_PAGE_LOADER.registerPage('${pathname}', error)
145-
`)
124+
const error = new Error('INVALID_BUILD_ID')
125+
const customFields = { buildIdMismatched: true }
126+
127+
await renderScriptError(req, res, page, error, customFields, this.renderOpts)
146128
return
147129
}
148130

149-
const path = join(this.dir, '.next', 'client-bundles', 'pages', pathname)
150-
const realPath = await resolvePath(path)
151-
await this.serveStatic(req, res, realPath)
131+
if (this.dev) {
132+
const compilationErr = this.getCompilationError(page)
133+
if (compilationErr) {
134+
const customFields = { buildError: true }
135+
await renderScriptError(req, res, page, compilationErr, customFields, this.renderOpts)
136+
return
137+
}
138+
}
139+
140+
await renderScript(req, res, page, this.renderOpts)
152141
},
153142

154143
'/_next/:path+': async (req, res, params) => {
@@ -314,6 +303,10 @@ export default class Server {
314303
}
315304
}
316305

306+
serveScript (req, res, path) {
307+
return serveStatic(req, res, path)
308+
}
309+
317310
readBuildId () {
318311
const buildIdPath = join(this.dir, '.next', 'BUILD_ID')
319312
const buildId = fs.readFileSync(buildIdPath, 'utf8')

server/render.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,44 @@ export async function renderJSON (req, res, page, { dir = process.cwd(), hotRelo
118118
return serveStatic(req, res, pagePath)
119119
}
120120

121+
export async function renderScript (req, res, page, opts) {
122+
try {
123+
if (opts.dev) {
124+
await opts.hotReloader.ensurePage(page)
125+
}
126+
127+
const path = join(opts.dir, '.next', 'client-bundles', 'pages', page)
128+
const realPath = await resolvePath(path)
129+
await serveStatic(req, res, realPath)
130+
} catch (err) {
131+
if (err.code === 'ENOENT') {
132+
res.setHeader('Content-Type', 'text/javascript')
133+
res.end(`
134+
var error = new Error('Page not exists: ${page}')
135+
error.pageNotFound = true
136+
error.statusCode = 404
137+
NEXT_PAGE_LOADER.registerPage('${page}', error)
138+
`)
139+
return
140+
}
141+
142+
throw err
143+
}
144+
}
145+
146+
export async function renderScriptError (req, res, page, error, customFields, opts) {
147+
res.setHeader('Content-Type', 'text/javascript')
148+
const errorJson = {
149+
...errorToJSON(error),
150+
...customFields
151+
}
152+
153+
res.end(`
154+
var error = ${JSON.stringify(errorJson)}
155+
NEXT_PAGE_LOADER.registerPage('${page}', error)
156+
`)
157+
}
158+
121159
export async function renderErrorJSON (err, req, res, { dir = process.cwd(), dev = false } = {}) {
122160
const component = await readPage(join(dir, '.next', 'bundles', 'pages', '_error'))
123161

0 commit comments

Comments
 (0)