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:
parent
23d5ea9164
commit
b7e57f9347
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in a new issue