From 15dde33794622919d20709da97fa412a01831807 Mon Sep 17 00:00:00 2001 From: Tim Neutkens Date: Thu, 12 Apr 2018 09:47:42 +0200 Subject: [PATCH] Add build manifest (#4119) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add build manifest * Split out css since they don’t have exact name * Remove pages map * Fix locations test * Re-run tests * Get consistent open ports * Fix static tests * Add comment about Cache-Control header --- bin/next-export | 8 +-- lib/constants.js | 1 + package.json | 1 - readme.md | 4 +- server/build/plugins/build-manifest-plugin.js | 46 +++++++++++++++ server/build/webpack.js | 8 ++- server/document.js | 40 ++++++------- server/export.js | 11 +--- server/index.js | 56 ++----------------- server/render.js | 5 +- test/integration/dist-dir/test/index.test.js | 5 +- test/integration/static/.gitignore | 1 + test/integration/static/next.config.js | 33 ++++++----- test/integration/static/test/index.test.js | 10 ++-- test/lib/next-test-utils.js | 5 +- test/lib/next-webdriver.js | 4 ++ yarn.lock | 44 ++------------- 17 files changed, 125 insertions(+), 157 deletions(-) create mode 100644 server/build/plugins/build-manifest-plugin.js create mode 100644 test/integration/static/.gitignore diff --git a/bin/next-export b/bin/next-export index a98f6ae7a7d9e58..c9ac30b368a6742 100755 --- a/bin/next-export +++ b/bin/next-export @@ -57,8 +57,6 @@ const options = { outdir: argv.outdir ? resolve(argv.outdir) : resolve(dir, 'out') } -exportApp(dir, options) - .catch((err) => { - console.error(err) - process.exit(1) - }) +exportApp(dir, options).catch((err) => { + printAndExit(err) +}) diff --git a/lib/constants.js b/lib/constants.js index ec595fa6dad85eb..19cb799242e5e64 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -3,3 +3,4 @@ export const PHASE_PRODUCTION_BUILD = 'phase-production-build' export const PHASE_PRODUCTION_SERVER = 'phase-production-server' export const PHASE_DEVELOPMENT_SERVER = 'phase-development-server' export const PAGES_MANIFEST = 'pages-manifest.json' +export const BUILD_MANIFEST = 'build-manifest.json' diff --git a/package.json b/package.json index 6b098e238bee6b6..d372fad24020694 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,6 @@ "node-fetch": "1.7.3", "node-notifier": "5.1.2", "nyc": "11.2.1", - "portfinder": "1.0.13", "react": "16.2.0", "react-dom": "16.2.0", "rimraf": "2.6.2", diff --git a/readme.md b/readme.md index 3994141bf24c7f5..0505d64e50f695f 100644 --- a/readme.md +++ b/readme.md @@ -943,9 +943,9 @@ import flush from 'styled-jsx/server' export default class MyDocument extends Document { static getInitialProps({ renderPage }) { - const { html, head, errorHtml, chunks } = renderPage() + const { html, head, errorHtml, chunks, buildManifest } = renderPage() const styles = flush() - return { html, head, errorHtml, chunks, styles } + return { html, head, errorHtml, chunks, styles, buildManifest } } render() { diff --git a/server/build/plugins/build-manifest-plugin.js b/server/build/plugins/build-manifest-plugin.js new file mode 100644 index 000000000000000..be2e5fee26ff7e9 --- /dev/null +++ b/server/build/plugins/build-manifest-plugin.js @@ -0,0 +1,46 @@ +// @flow +import { RawSource } from 'webpack-sources' +import {BUILD_MANIFEST} from '../../../lib/constants' + +// This plugin creates a build-manifest.json for all assets that are being output +// It has a mapping of "entry" filename to real filename. Because the real filename can be hashed in production +export default class BuildManifestPlugin { + apply (compiler: any) { + compiler.plugin('emit', (compilation, callback) => { + const {chunks} = compilation + const assetMap = {pages: {}, css: []} + + for (const chunk of chunks) { + if (!chunk.name || !chunk.files) { + continue + } + + const files = [] + + for (const file of chunk.files) { + if (/\.map$/.test(file)) { + continue + } + + if (/\.hot-update\.js$/.test(file)) { + continue + } + + if (/\.css$/.exec(file)) { + assetMap.css.push(file) + continue + } + + files.push(file) + } + + if (files.length > 0) { + assetMap[chunk.name] = files + } + } + + compilation.assets[BUILD_MANIFEST] = new RawSource(JSON.stringify(assetMap)) + callback() + }) + } +} diff --git a/server/build/webpack.js b/server/build/webpack.js index ebc466a2d77f49c..344ee32c99bf0ce 100644 --- a/server/build/webpack.js +++ b/server/build/webpack.js @@ -12,6 +12,7 @@ import NextJsSsrImportPlugin from './plugins/nextjs-ssr-import' import DynamicChunksPlugin from './plugins/dynamic-chunks-plugin' import UnlinkFilePlugin from './plugins/unlink-file-plugin' import PagesManifestPlugin from './plugins/pages-manifest-plugin' +import BuildManifestPlugin from './plugins/build-manifest-plugin' const presetItem = createConfigItem(require('./babel/preset'), {type: 'preset'}) const hotLoaderItem = createConfigItem(require('react-hot-loader/babel'), {type: 'plugin'}) @@ -259,6 +260,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer }), !dev && new webpack.optimize.ModuleConcatenationPlugin(), isServer && new PagesManifestPlugin(), + !isServer && new BuildManifestPlugin(), !isServer && new PagesPlugin(), !isServer && new DynamicChunksPlugin(), isServer && new NextJsSsrImportPlugin(), @@ -266,7 +268,7 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer // In production we move common modules into the existing main.js bundle !isServer && new webpack.optimize.CommonsChunkPlugin({ name: 'main.js', - filename: 'main.js', + filename: dev ? 'static/commons/main.js' : 'static/commons/main-[chunkhash].js', minChunks (module, count) { // React and React DOM are used everywhere in Next.js. So they should always be common. Even in development mode, to speed up compilation. if (module.resource && module.resource.includes(`${sep}react-dom${sep}`) && count >= 0) { @@ -297,8 +299,8 @@ export default async function getBaseWebpackConfig (dir, {dev = false, isServer }), // We use a manifest file in development to speed up HMR dev && !isServer && new webpack.optimize.CommonsChunkPlugin({ - name: 'manifest', - filename: 'manifest.js' + name: 'manifest.js', + filename: dev ? 'static/commons/manifest.js' : 'static/commons/manifest-[chunkhash].js' }) ].filter(Boolean) } diff --git a/server/document.js b/server/document.js index 3ab9eda757ae37a..48fcdbc5fbaaabb 100644 --- a/server/document.js +++ b/server/document.js @@ -10,9 +10,9 @@ const Fragment = React.Fragment || function Fragment ({ children }) { export default class Document extends Component { static getInitialProps ({ renderPage }) { - const { html, head, errorHtml, chunks } = renderPage() + const { html, head, errorHtml, chunks, buildManifest } = renderPage() const styles = flush() - return { html, head, errorHtml, chunks, styles } + return { html, head, errorHtml, chunks, styles, buildManifest } } static childContextTypes = { @@ -40,32 +40,33 @@ export class Head extends Component { } getChunkPreloadLink (filename) { - const { __NEXT_DATA__ } = this.context._documentProps + const { __NEXT_DATA__, buildManifest } = this.context._documentProps let { assetPrefix, buildId } = __NEXT_DATA__ - const hash = buildId - return ( - { + return - ) + }) } getPreloadMainLinks () { const { dev } = this.context._documentProps if (dev) { return [ - this.getChunkPreloadLink('manifest.js'), - this.getChunkPreloadLink('main.js') + ...this.getChunkPreloadLink('manifest.js'), + ...this.getChunkPreloadLink('main.js') ] } // In the production mode, we have a single asset with all the JS content. return [ - this.getChunkPreloadLink('main.js') + ...this.getChunkPreloadLink('main.js') ] } @@ -125,31 +126,32 @@ export class NextScript extends Component { } getChunkScript (filename, additionalProps = {}) { - const { __NEXT_DATA__ } = this.context._documentProps + const { __NEXT_DATA__, buildManifest } = this.context._documentProps let { assetPrefix, buildId } = __NEXT_DATA__ - const hash = buildId - return ( + const files = buildManifest[filename] + + return files.map((file) => (