Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add content based HASH to main.js and common.js #1336

Merged
merged 11 commits into from
Mar 7, 2017
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