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

+504
-381
lines changed

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy