1
0
Fork 0
mirror of https://github.com/terribleplan/next.js.git synced 2024-01-19 02:48:18 +00:00

Implement "Immutable build artifacts" feature (#745)

* 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.
This commit is contained in:
Arunoda Susiripala 2017-01-11 12:16:18 -08:00 committed by Guillermo Rauch
parent 23d5ea9164
commit b7e57f9347
8 changed files with 61 additions and 12 deletions

View file

@ -1,3 +1,5 @@
/* global __NEXT_DATA__ */
import React from 'react'
import Link, { isLocal } from './link'
import { parse as urlParse } from 'url'
@ -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
}

View file

@ -1,3 +1,5 @@
/* global __NEXT_DATA__ */
import { parse, format } from 'url'
import evalScript from '../eval-script'
import shallowEquals from '../shallow-equals'
@ -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({

View file

@ -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",

View file

@ -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'
@ -10,6 +13,7 @@ export default async function build (dir) {
await runCompiler(compiler)
await gzipAssets(dir)
await writeBuildId(dir)
}
function runCompiler (compiler) {
@ -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')
}

View file

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

View file

@ -1,5 +1,6 @@
import { resolve, join } from 'path'
import { parse } from 'url'
import fs from 'mz/fs'
import http from 'http'
import {
renderToHTML,
@ -46,6 +47,8 @@ export default class Server {
if (this.hotReloader) {
await this.hotReloader.start()
}
this.renderOpts.buildId = await this.readBuildId()
}
async close () {
@ -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)
@ -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

View file

@ -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
@ -88,6 +89,7 @@ async function doRender (req, res, pathname, query, {
props,
pathname,
query,
buildId,
err: (err && dev) ? errorToJSON(err) : null
},
dev,

View file

@ -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"
@ -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"