Skip to content
Merged
5 changes: 3 additions & 2 deletions lib/router/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* global window, location */
/* global window */
import _Router from './router'

const SingletonRouter = {
Expand Down Expand Up @@ -81,6 +81,7 @@ export function _notifyBuildIdMismatch (nextRoute) {
if (SingletonRouter.onAppUpdated) {
SingletonRouter.onAppUpdated(nextRoute)
} else {
location.href = nextRoute
console.warn(`An app update detected. Loading the SSR version of "${nextRoute}"`)
window.location.href = nextRoute
}
}
3 changes: 2 additions & 1 deletion lib/router/router.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { parse, format } from 'url'
import { EventEmitter } from 'events'
import fetch from 'unfetch'
import evalScript from '../eval-script'
import shallowEquals from '../shallow-equals'
import PQueue from '../p-queue'
import { loadGetInitialProps, getURL } from '../utils'
import { _notifyBuildIdMismatch } from './'
import fetch from 'unfetch'

if (typeof window !== 'undefined' && typeof navigator.serviceWorker !== 'undefined') {
navigator.serviceWorker.getRegistrations()
Expand Down Expand Up @@ -322,6 +322,7 @@ export default class Router extends EventEmitter {
doFetchRoute (route) {
const { buildId } = window.__NEXT_DATA__
const url = `/_next/${encodeURIComponent(buildId)}/pages${route}`

return fetch(url, {
method: 'GET',
headers: { 'Accept': 'application/json' }
Expand Down
19 changes: 17 additions & 2 deletions server/build/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export default async function build (dir) {
const compiler = await webpack(dir, { buildDir })

try {
await runCompiler(compiler)
const webpackStats = await runCompiler(compiler)
await writeBuildStats(buildDir, webpackStats)
await writeBuildId(buildDir)
} catch (err) {
console.error(`> Failed to build on ${buildDir}`)
Expand All @@ -30,18 +31,32 @@ function runCompiler (compiler) {
if (err) return reject(err)

const jsonStats = stats.toJson()

if (jsonStats.errors.length > 0) {
const error = new Error(jsonStats.errors[0])
error.errors = jsonStats.errors
error.warnings = jsonStats.warnings
return reject(error)
}

resolve()
resolve(jsonStats)
})
})
}

async function writeBuildStats (dir, webpackStats) {
const chunkHashMap = {}
webpackStats.chunks
// We are not interested about pages
.filter(({ files }) => !/^bundles/.test(files[0]))
.forEach(({ hash, files }) => {
chunkHashMap[files[0]] = { hash }
})

const buildStatsPath = join(dir, '.next', 'build-stats.json')
await fs.writeFile(buildStatsPath, JSON.stringify(chunkHashMap), 'utf8')
}

async function writeBuildId (dir) {
const buildIdPath = join(dir, '.next', 'BUILD_ID')
const buildId = uuid.v4()
Expand Down
15 changes: 12 additions & 3 deletions server/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,25 @@ export class NextScript extends Component {
_documentProps: PropTypes.any
}

getChunkScript (filename) {
const { __NEXT_DATA__ } = this.context._documentProps
let { buildStats } = __NEXT_DATA__
const hash = buildStats ? buildStats[filename].hash : '-'

return (
<script type='text/javascript' src={`/_next/${hash}/${filename}`} />
)
}

render () {
const { staticMarkup, __NEXT_DATA__ } = this.context._documentProps
let { buildId } = __NEXT_DATA__

return <div>
{staticMarkup ? null : <script dangerouslySetInnerHTML={{
__html: `__NEXT_DATA__ = ${htmlescape(__NEXT_DATA__)}; module={};`
}} />}
{ staticMarkup ? null : <script type='text/javascript' src={`/_next/${buildId}/commons.js`} /> }
{ staticMarkup ? null : <script type='text/javascript' src={`/_next/${buildId}/main.js`} /> }
{ staticMarkup ? null : this.getChunkScript('commons.js') }
{ staticMarkup ? null : this.getChunkScript('main.js') }
</div>
}
}
54 changes: 26 additions & 28 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { resolve, join } from 'path'
import { parse as parseUrl } from 'url'
import { parse as parseQs } from 'querystring'
import fs from 'mz/fs'
import fs from 'fs'
import http, { STATUS_CODES } from 'http'
import {
renderToHTML,
Expand All @@ -25,9 +25,18 @@ export default class Server {
this.quiet = quiet
this.router = new Router()
this.hotReloader = dev ? new HotReloader(this.dir, { quiet }) : null
this.renderOpts = { dir: this.dir, dev, staticMarkup, hotReloader: this.hotReloader }
this.http = null
this.config = getConfig(this.dir)
this.buildStats = !dev ? require(join(this.dir, '.next', 'build-stats.json')) : null
this.buildId = !dev ? this.readBuildId() : '-'
this.renderOpts = {
dev,
staticMarkup,
dir: this.dir,
hotReloader: this.hotReloader,
buildStats: this.buildStats,
buildId: this.buildId
}

this.defineRoutes()
}
Expand Down Expand Up @@ -57,8 +66,6 @@ export default class Server {
if (this.hotReloader) {
await this.hotReloader.start()
}

this.renderOpts.buildId = await this.readBuildId()
}

async close () {
Expand All @@ -83,20 +90,14 @@ export default class Server {
await this.serveStatic(req, res, p)
},

'/_next/:buildId/main.js': async (req, res, params) => {
if (!this.handleBuildId(params.buildId, res)) {
throwBuildIdMismatchError()
}

'/_next/:hash/main.js': async (req, res, params) => {
this.handleBuildHash('main.js', params.hash, res)
const p = join(this.dir, '.next/main.js')
await this.serveStatic(req, res, p)
},

'/_next/:buildId/commons.js': async (req, res, params) => {
if (!this.handleBuildId(params.buildId, res)) {
throwBuildIdMismatchError()
}

'/_next/:hash/commons.js': async (req, res, params) => {
this.handleBuildHash('commons.js', params.hash, res)
const p = join(this.dir, '.next/commons.js')
await this.serveStatic(req, res, p)
},
Expand Down Expand Up @@ -277,18 +278,10 @@ export default class Server {
}
}

async readBuildId () {
readBuildId () {
const buildIdPath = join(this.dir, '.next', 'BUILD_ID')
try {
const buildId = await fs.readFile(buildIdPath, 'utf8')
return buildId.trim()
} catch (err) {
if (err.code === 'ENOENT') {
return '-'
} else {
throw err
}
}
const buildId = fs.readFileSync(buildIdPath, 'utf8')
return buildId.trim()
}

handleBuildId (buildId, res) {
Expand All @@ -311,8 +304,13 @@ export default class Server {
const p = resolveFromList(id, errors.keys())
if (p) return errors.get(p)[0]
}
}

function throwBuildIdMismatchError () {
throw new Error('BUILD_ID Mismatched!')
handleBuildHash (filename, hash, res) {
if (this.dev) return
if (hash !== this.buildStats[filename].hash) {
throw new Error(`Invalid Build File Hash(${hash}) for chunk: ${filename}`)
}

res.setHeader('Cache-Control', 'max-age=365000000, immutable')
}
}
2 changes: 2 additions & 0 deletions server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async function doRender (req, res, pathname, query, {
err,
page,
buildId,
buildStats,
hotReloader,
dir = process.cwd(),
dev = false,
Expand Down Expand Up @@ -94,6 +95,7 @@ async function doRender (req, res, pathname, query, {
pathname,
query,
buildId,
buildStats,
err: (err && dev) ? errorToJSON(err) : null
},
dev,
Expand Down