Skip to content

Commit dec85fe

Browse files
arunodarauchg
authored andcommitted
Add CDN support with assetPrefix (vercel#1700)
* Introduce script tag based page loading system. * Call ensurePage only in the dev mode. * Implement router using the page-loader. * Fix a typo and remove unwanted code. * Fix some issues related to rendering. * Fix production tests. * Fix ondemand test cases. * Fix unit tests. * Get rid of eval completely. * Remove all the inline code. * Remove the json-pages plugin. * Rename NEXT_PAGE_LOADER into __NEXT_PAGE_LOADER__ * Rename NEXT_LOADED_PAGES into __NEXT_LOADED_PAGES__ * Remove some unwanted code. * Load everything async. * Remove lib/eval-script.js We no longer need it. * Move webpack idle wait code to the page-loader. Because that's the place to do it. * Remove pageNotFound key from the error. * Remove unused error field 'buildError' * Add much better logic to normalize routes. * Get rid of mitt. * Introduce a better way to register pages. * Came back to the mitt() based page-loader. * Add link rel=preload support. * Add assetPrefix support to add support for CDNs. * Add assetPrefix support for preload links. * Update readme.md
1 parent bdc30bc commit dec85fe

24 files changed

Lines changed: 504 additions & 381 deletions

client/index.js

Lines changed: 38 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import mitt from 'mitt'
44
import HeadManager from './head-manager'
55
import { createRouter } from '../lib/router'
66
import App from '../lib/app'
7-
import evalScript from '../lib/eval-script'
87
import { loadGetInitialProps, getURL } from '../lib/utils'
98
import ErrorDebugComponent from '../lib/error-debug'
9+
import PageLoader from '../lib/page-loader'
1010

1111
// Polyfill Promise globally
1212
// This is needed because Webpack2's dynamic loading(common chunks) code
@@ -19,31 +19,50 @@ if (!window.Promise) {
1919

2020
const {
2121
__NEXT_DATA__: {
22-
component,
23-
errorComponent,
2422
props,
2523
err,
2624
pathname,
27-
query
25+
query,
26+
buildId,
27+
assetPrefix
2828
},
2929
location
3030
} = window
3131

32-
const Component = evalScript(component).default
33-
const ErrorComponent = evalScript(errorComponent).default
34-
let lastAppProps
35-
36-
export const router = createRouter(pathname, query, getURL(), {
37-
Component,
38-
ErrorComponent,
39-
err
32+
const pageLoader = new PageLoader(buildId, assetPrefix)
33+
window.__NEXT_LOADED_PAGES__.forEach(({ route, fn }) => {
34+
pageLoader.registerPage(route, fn)
4035
})
36+
delete window.__NEXT_LOADED_PAGES__
37+
38+
window.__NEXT_REGISTER_PAGE = pageLoader.registerPage.bind(pageLoader)
4139

4240
const headManager = new HeadManager()
4341
const appContainer = document.getElementById('__next')
4442
const errorContainer = document.getElementById('__next-error')
4543

46-
export default () => {
44+
let lastAppProps
45+
export let router
46+
export let ErrorComponent
47+
let Component
48+
49+
export default async () => {
50+
ErrorComponent = await pageLoader.loadPage('/_error')
51+
52+
try {
53+
Component = await pageLoader.loadPage(pathname)
54+
} catch (err) {
55+
console.error(`${err.message}\n${err.stack}`)
56+
Component = ErrorComponent
57+
}
58+
59+
router = createRouter(pathname, query, getURL(), {
60+
pageLoader,
61+
Component,
62+
ErrorComponent,
63+
err
64+
})
65+
4766
const emitter = mitt()
4867

4968
router.subscribe(({ Component, props, hash, err }) => {
@@ -57,7 +76,10 @@ export default () => {
5776
}
5877

5978
export async function render (props) {
60-
if (props.err) {
79+
// There are some errors we should ignore.
80+
// Next.js rendering logic knows how to handle them.
81+
// These are specially 404 errors
82+
if (props.err && !props.err.ignore) {
6183
await renderError(props.err)
6284
return
6385
}
@@ -103,7 +125,7 @@ async function doRender ({ Component, props, hash, err, emitter }) {
103125
}
104126

105127
if (emitter) {
106-
emitter.emit('before-reactdom-render', { Component })
128+
emitter.emit('before-reactdom-render', { Component, ErrorComponent })
107129
}
108130

109131
Component = Component || lastAppProps.Component
@@ -118,6 +140,6 @@ async function doRender ({ Component, props, hash, err, emitter }) {
118140
ReactDOM.render(createElement(App, appProps), appContainer)
119141

120142
if (emitter) {
121-
emitter.emit('after-reactdom-render', { Component })
143+
emitter.emit('after-reactdom-render', { Component, ErrorComponent })
122144
}
123145
}

client/next-dev.js

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,40 @@
1-
import evalScript from '../lib/eval-script'
1+
import 'react-hot-loader/patch'
22
import ReactReconciler from 'react-dom/lib/ReactReconciler'
3-
4-
const { __NEXT_DATA__: { errorComponent } } = window
5-
const ErrorComponent = evalScript(errorComponent).default
6-
7-
require('react-hot-loader/patch')
3+
import initOnDemandEntries from './on-demand-entries-client'
4+
import initWebpackHMR from './webpack-hot-middleware-client'
85

96
const next = window.next = require('./')
107

11-
const emitter = next.default()
8+
next.default()
9+
.then((emitter) => {
10+
initOnDemandEntries()
11+
initWebpackHMR()
12+
13+
let lastScroll
14+
15+
emitter.on('before-reactdom-render', ({ Component, ErrorComponent }) => {
16+
// Remember scroll when ErrorComponent is being rendered to later restore it
17+
if (!lastScroll && Component === ErrorComponent) {
18+
const { pageXOffset, pageYOffset } = window
19+
lastScroll = {
20+
x: pageXOffset,
21+
y: pageYOffset
22+
}
23+
}
24+
})
25+
26+
emitter.on('after-reactdom-render', ({ Component, ErrorComponent }) => {
27+
if (lastScroll && Component !== ErrorComponent) {
28+
// Restore scroll after ErrorComponent was replaced with a page component by HMR
29+
const { x, y } = lastScroll
30+
window.scroll(x, y)
31+
lastScroll = null
32+
}
33+
})
34+
})
35+
.catch((err) => {
36+
console.error(`${err.message}\n${err.stack}`)
37+
})
1238

1339
// This is a patch to catch most of the errors throw inside React components.
1440
const originalMountComponent = ReactReconciler.mountComponent
@@ -21,25 +47,3 @@ ReactReconciler.mountComponent = function (...args) {
2147
throw err
2248
}
2349
}
24-
25-
let lastScroll
26-
27-
emitter.on('before-reactdom-render', ({ Component }) => {
28-
// Remember scroll when ErrorComponent is being rendered to later restore it
29-
if (!lastScroll && Component === ErrorComponent) {
30-
const { pageXOffset, pageYOffset } = window
31-
lastScroll = {
32-
x: pageXOffset,
33-
y: pageYOffset
34-
}
35-
}
36-
})
37-
38-
emitter.on('after-reactdom-render', ({ Component }) => {
39-
if (lastScroll && Component !== ErrorComponent) {
40-
// Restore scroll after ErrorComponent was replaced with a page component by HMR
41-
const { x, y } = lastScroll
42-
window.scroll(x, y)
43-
lastScroll = null
44-
}
45-
})

client/next.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
import next from './'
22

33
next()
4+
.catch((err) => {
5+
console.error(`${err.message}\n${err.stack}`)
6+
})

client/on-demand-entries-client.js

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,33 @@
33
import Router from '../lib/router'
44
import fetch from 'unfetch'
55

6-
Router.ready(() => {
7-
Router.router.events.on('routeChangeComplete', ping)
8-
})
6+
export default () => {
7+
Router.ready(() => {
8+
Router.router.events.on('routeChangeComplete', ping)
9+
})
910

10-
async function ping () {
11-
try {
12-
const url = `/_next/on-demand-entries-ping?page=${Router.pathname}`
13-
const res = await fetch(url)
14-
const payload = await res.json()
15-
if (payload.invalid) {
16-
location.reload()
11+
async function ping () {
12+
try {
13+
const url = `/_next/on-demand-entries-ping?page=${Router.pathname}`
14+
const res = await fetch(url)
15+
const payload = await res.json()
16+
if (payload.invalid) {
17+
location.reload()
18+
}
19+
} catch (err) {
20+
console.error(`Error with on-demand-entries-ping: ${err.message}`)
1721
}
18-
} catch (err) {
19-
console.error(`Error with on-demand-entries-ping: ${err.message}`)
2022
}
21-
}
2223

23-
async function runPinger () {
24-
while (true) {
25-
await new Promise((resolve) => setTimeout(resolve, 5000))
26-
await ping()
24+
async function runPinger () {
25+
while (true) {
26+
await new Promise((resolve) => setTimeout(resolve, 5000))
27+
await ping()
28+
}
2729
}
28-
}
2930

30-
runPinger()
31-
.catch((err) => {
32-
console.error(err)
33-
})
31+
runPinger()
32+
.catch((err) => {
33+
console.error(err)
34+
})
35+
}
Lines changed: 38 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,50 @@
11
import webpackHotMiddlewareClient from 'webpack-hot-middleware/client?overlay=false&reload=true&path=/_next/webpack-hmr'
22
import Router from '../lib/router'
33

4-
const handlers = {
5-
reload (route) {
6-
if (route === '/_error') {
7-
for (const r of Object.keys(Router.components)) {
8-
const { err } = Router.components[r]
9-
if (err) {
10-
// reload all error routes
11-
// which are expected to be errors of '/_error' routes
12-
Router.reload(r)
4+
export default () => {
5+
const handlers = {
6+
reload (route) {
7+
if (route === '/_error') {
8+
for (const r of Object.keys(Router.components)) {
9+
const { err } = Router.components[r]
10+
if (err) {
11+
// reload all error routes
12+
// which are expected to be errors of '/_error' routes
13+
Router.reload(r)
14+
}
1315
}
16+
return
1417
}
15-
return
16-
}
1718

18-
if (route === '/_document') {
19-
window.location.reload()
20-
return
21-
}
19+
if (route === '/_document') {
20+
window.location.reload()
21+
return
22+
}
2223

23-
Router.reload(route)
24-
},
24+
Router.reload(route)
25+
},
2526

26-
change (route) {
27-
if (route === '/_document') {
28-
window.location.reload()
29-
return
30-
}
27+
change (route) {
28+
if (route === '/_document') {
29+
window.location.reload()
30+
return
31+
}
3132

32-
const { err } = Router.components[route] || {}
33-
if (err) {
34-
// reload to recover from runtime errors
35-
Router.reload(route)
33+
const { err } = Router.components[route] || {}
34+
if (err) {
35+
// reload to recover from runtime errors
36+
Router.reload(route)
37+
}
3638
}
3739
}
38-
}
3940

40-
webpackHotMiddlewareClient.subscribe((obj) => {
41-
const fn = handlers[obj.action]
42-
if (fn) {
43-
const data = obj.data || []
44-
fn(...data)
45-
} else {
46-
throw new Error('Unexpected action ' + obj.action)
47-
}
48-
})
41+
webpackHotMiddlewareClient.subscribe((obj) => {
42+
const fn = handlers[obj.action]
43+
if (fn) {
44+
const data = obj.data || []
45+
fn(...data)
46+
} else {
47+
throw new Error('Unexpected action ' + obj.action)
48+
}
49+
})
50+
}

lib/error.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ 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)
6+
static getInitialProps ({ res, err }) {
7+
const statusCode = res ? res.statusCode : (err ? err.statusCode : null)
88
return { statusCode }
99
}
1010

lib/eval-script.js

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)