Skip to content

Commit

Permalink
Implement "Immutable build artifacts" feature (#745)
Browse files Browse the repository at this point in the history
* Write BUILD_ID when building.
It's a random id (uuid.v4())

* Add buildId to the core JS files.

* Add immutable cache-control header.
Only if the buildId is matched.

* Set '-' as the dev buildId always.

* Add buildId handling for JSON pages.
  • Loading branch information
arunoda authored and rauchg committed Jan 11, 2017
1 parent 23d5ea9 commit b7e57f9
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 12 deletions.
4 changes: 3 additions & 1 deletion lib/prefetch.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* global __NEXT_DATA__ */

import React from 'react'
import Link, { isLocal } from './link'
import { parse as urlParse } from 'url'
Expand Down Expand Up @@ -108,7 +110,7 @@ if (hasServiceWorkerSupport()) {

function getPrefetchUrl (href) {
let { pathname } = urlParse(href)
const url = `/_next/pages${pathname}`
const url = `/_next/${__NEXT_DATA__.buildId}/pages${pathname}`

return url
}
Expand Down
4 changes: 3 additions & 1 deletion lib/router/router.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* global __NEXT_DATA__ */

import { parse, format } from 'url'
import evalScript from '../eval-script'
import shallowEquals from '../shallow-equals'
Expand Down Expand Up @@ -210,7 +212,7 @@ export default class Router extends EventEmitter {
}
}

const url = `/_next/pages${route}`
const url = `/_next/${__NEXT_DATA__.buildId}/pages${route}`
const xhr = loadComponent(url, (err, data) => {
if (err) return reject(err)
resolve({
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"strip-ansi": "3.0.1",
"styled-jsx": "0.4.1",
"url": "0.11.0",
"uuid": "3.0.1",
"webpack": "2.2.0-rc.3",
"webpack-dev-middleware": "1.9.0",
"webpack-hot-middleware": "2.15.0",
Expand Down
10 changes: 10 additions & 0 deletions server/build/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import fs from 'mz/fs'
import uuid from 'uuid'
import path from 'path'
import webpack from './webpack'
import clean from './clean'
import gzipAssets from './gzip'
Expand All @@ -10,6 +13,7 @@ export default async function build (dir) {

await runCompiler(compiler)
await gzipAssets(dir)
await writeBuildId(dir)
}

function runCompiler (compiler) {
Expand All @@ -29,3 +33,9 @@ function runCompiler (compiler) {
})
})
}

async function writeBuildId (dir) {
const buildIdPath = path.resolve(dir, '.next', 'BUILD_ID')
const buildId = uuid.v4()
await fs.writeFile(buildIdPath, buildId, 'utf8')
}
7 changes: 4 additions & 3 deletions server/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,12 @@ export class NextScript extends Component {
}

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

return <div>
{ staticMarkup ? null : <script type='text/javascript' src='/_next/commons.js' /> }
{ staticMarkup ? null : <script type='text/javascript' src='/_next/main.js' /> }
{ staticMarkup ? null : <script type='text/javascript' src={`/_next/${buildId}/commons.js`} /> }
{ staticMarkup ? null : <script type='text/javascript' src={`/_next/${buildId}/main.js`} /> }
</div>
}
}
37 changes: 34 additions & 3 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { resolve, join } from 'path'
import { parse } from 'url'
import fs from 'mz/fs'
import http from 'http'
import {
renderToHTML,
Expand Down Expand Up @@ -46,6 +47,8 @@ export default class Server {
if (this.hotReloader) {
await this.hotReloader.start()
}

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

async close () {
Expand All @@ -60,17 +63,20 @@ export default class Server {
await serveStatic(req, res, p)
})

this.router.get('/_next/main.js', async (req, res, params) => {
this.router.get('/_next/:buildId/main.js', async (req, res, params) => {
this.handleBuildId(params.buildId, res)
const p = join(this.dir, '.next/main.js')
await serveStaticWithGzip(req, res, p)
})

this.router.get('/_next/commons.js', async (req, res, params) => {
this.router.get('/_next/:buildId/commons.js', async (req, res, params) => {
this.handleBuildId(params.buildId, res)
const p = join(this.dir, '.next/commons.js')
await serveStaticWithGzip(req, res, p)
})

this.router.get('/_next/pages/:path*', async (req, res, params) => {
this.router.get('/_next/:buildId/pages/:path*', async (req, res, params) => {
this.handleBuildId(params.buildId, res)
const paths = params.path || ['index']
const pathname = `/${paths.join('/')}`
await this.renderJSON(req, res, pathname)
Expand Down Expand Up @@ -237,6 +243,31 @@ export default class Server {
}
}

async 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
}
}
}

handleBuildId (buildId, res) {
if (this.dev) return
if (buildId !== this.renderOpts.buildId) {
const errorMessage = 'Build id mismatch!' +
'Seems like the server and the client version of files are not the same.'
throw new Error(errorMessage)
}

res.setHeader('Cache-Control', 'max-age=365000000, immutable')
}

getCompilationError (page) {
if (!this.hotReloader) return

Expand Down
2 changes: 2 additions & 0 deletions server/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function renderErrorToHTML (err, req, res, pathname, query, opts = {}) {
async function doRender (req, res, pathname, query, {
err,
page,
buildId,
dir = process.cwd(),
dev = false,
staticMarkup = false
Expand Down Expand Up @@ -88,6 +89,7 @@ async function doRender (req, res, pathname, query, {
props,
pathname,
query,
buildId,
err: (err && dev) ? errorToJSON(err) : null
},
dev,
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -733,9 +733,9 @@ babel-plugin-transform-strict-mode@^6.18.0:
babel-runtime "^6.0.0"
babel-types "^6.18.0"

babel-preset-env@1.1.6:
version "1.1.6"
resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.1.6.tgz#83ce1402088e661cb5799e680d20c5a432b2873b"
babel-preset-env@1.1.8:
version "1.1.8"
resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.1.8.tgz#c46734c6233c3f87d177513773db3cf3c1758aaa"
dependencies:
babel-plugin-check-es2015-constants "^6.3.13"
babel-plugin-syntax-trailing-function-commas "^6.13.0"
Expand Down Expand Up @@ -5028,7 +5028,7 @@ util@^0.10.3, util@0.10.3:
dependencies:
inherits "2.0.1"

uuid@^3.0.0:
uuid, uuid@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1"

Expand Down

0 comments on commit b7e57f9

Please sign in to comment.