Skip to content
This repository was archived by the owner on Jun 3, 2019. It is now read-only.

Commit c6f0eb2

Browse files
Move webpack manifest out from index chunk
Enables index chunk hash to become independent of children chunk hashes. The manifest is inlined with every server rendered route in production mode.
1 parent 83d533a commit c6f0eb2

File tree

5 files changed

+64
-18
lines changed

5 files changed

+64
-18
lines changed

config/values.js

+4
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ const values = {
117117
// containing details of all output files for a bundle?
118118
bundleAssetsFileName: 'assets.json',
119119

120+
// What should we name the manifest generated by chunk-manifest-webpack-plugin
121+
// containing mappings for chunk ids and names?
122+
bundleManifestFileName: 'manifest.json',
123+
120124
// node_modules are not included in any bundles that target "node" as a
121125
// runtime (e.g.. the server bundle) as including them often breaks builds
122126
// due to thinks like require statements containing expressions..

internal/webpack/configFactory.js

+12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import nodeExternals from 'webpack-node-externals';
55
import path from 'path';
66
import webpack from 'webpack';
77
import WebpackMd5Hash from 'webpack-md5-hash';
8+
import ChunkManifestWebpackPlugin from 'chunk-manifest-webpack-plugin';
89

910
import { happyPackPlugin, log } from '../utils';
1011
import { ifElse } from '../../shared/utils/logic';
@@ -233,6 +234,17 @@ export default function webpackConfigFactory(buildOptions) {
233234
// even though 1 or 2 may have only changed.
234235
ifClient(() => new WebpackMd5Hash()),
235236

237+
// Since chunk-manifest-webpack-plugin doesn't work with webpack-dev-server
238+
// https://github.com/soundcloud/chunk-manifest-webpack-plugin/issues/26
239+
// we generate manifest only in production mode.
240+
// Also this optimisation is to prevent better long term caching of
241+
// index chunk which can be compromised in development mode.
242+
ifProdClient(() => new ChunkManifestWebpackPlugin({
243+
filename: config('bundleManifestFileName'),
244+
manifestVariable: 'webpackManifest',
245+
inlineManifest: false,
246+
})),
247+
236248
// These are process.env flags that you can use in your code in order to
237249
// have advanced control over what is included/excluded in your bundles.
238250
// For example you may only want certain parts of your code to be

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@
6969
"modernizr": "3.5.0",
7070
"normalize.css": "7.0.0",
7171
"offline-plugin": "4.8.3",
72-
"pretty-error": "2.1.1",
72+
"pretty-error": "2.1.1",
7373
"prop-types": "15.5.10",
7474
"react": "15.6.1",
7575
"react-async-bootstrapper": "1.1.1",
@@ -98,6 +98,7 @@
9898
"babel-template": "6.26.0",
9999
"chokidar": "1.7.0",
100100
"css-loader": "0.28.7",
101+
"chunk-manifest-webpack-plugin": "1.1.0",
101102
"enzyme": "2.9.1",
102103
"enzyme-to-json": "2.0.0",
103104
"eslint": "4.7.2",

server/middleware/reactApplication/ServerHTML.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ import serialize from 'serialize-javascript';
1313
import config from '../../../config';
1414
import ifElse from '../../../shared/utils/logic/ifElse';
1515
import removeNil from '../../../shared/utils/arrays/removeNil';
16-
import getClientBundleEntryAssets from './getClientBundleEntryAssets';
16+
import {
17+
getClientBundleEntryAssets,
18+
getClientWebpackManifest,
19+
} from './getClientBundleEntryAssets';
1720

1821
import ClientConfig from '../../../config/components/ClientConfig';
1922
import HTML from '../../../shared/components/HTML';
@@ -27,6 +30,11 @@ function KeyedComponent({ children }) {
2730
// Resolve the assets (js/css) for the client bundle's entry chunk.
2831
const clientEntryAssets = getClientBundleEntryAssets();
2932

33+
// Resolve the webpack manifest. Useful only in production mode.
34+
const clientWebpackManifest = process.env.BUILD_FLAG_IS_DEV === 'false' ?
35+
getClientWebpackManifest() : {};
36+
37+
3038
function stylesheetTag(stylesheetFilePath) {
3139
return (
3240
<link href={stylesheetFilePath} media="screen, projection" rel="stylesheet" type="text/css" />
@@ -46,13 +54,18 @@ function ServerHTML(props) {
4654
const inlineScript = body =>
4755
<script nonce={nonce} type="text/javascript" dangerouslySetInnerHTML={{ __html: body }} />;
4856

57+
const webpackManifestScript = `
58+
window.webpackManifest = ${JSON.stringify(clientWebpackManifest)};
59+
`;
60+
4961
const headerElements = removeNil([
5062
...ifElse(helmet)(() => helmet.meta.toComponent(), []),
5163
...ifElse(helmet)(() => helmet.title.toComponent(), []),
5264
...ifElse(helmet)(() => helmet.base.toComponent(), []),
5365
...ifElse(helmet)(() => helmet.link.toComponent(), []),
5466
ifElse(clientEntryAssets && clientEntryAssets.css)(() => stylesheetTag(clientEntryAssets.css)),
5567
...ifElse(helmet)(() => helmet.style.toComponent(), []),
68+
ifElse(process.env.BUILD_FLAG_IS_DEV === 'false')(() => inlineScript(webpackManifestScript)),
5669
]);
5770

5871
const bodyElements = removeNil([

server/middleware/reactApplication/getClientBundleEntryAssets.js

+32-16
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,21 @@ import { resolve as pathResolve } from 'path';
77
import appRootDir from 'app-root-dir';
88
import config from '../../../config';
99

10-
let resultCache;
10+
function getJSONFromFile(fileName) {
11+
const filePath = pathResolve(
12+
appRootDir.get(),
13+
config('bundles.client.outputPath'),
14+
`./${fileName}`,
15+
);
16+
17+
if (!fs.existsSync(filePath)) {
18+
throw new Error(
19+
`We could not find the "${filePath}" file. Please ensure that the client bundle has been built.`,
20+
);
21+
}
22+
23+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
24+
}
1125

1226
/**
1327
* Retrieves the js/css for the named chunks that belong to our client bundle.
@@ -22,31 +36,33 @@ let resultCache;
2236
* to the render logic. Having this method allows us to easily fetch
2337
* the respective assets simply by using a chunk name. :)
2438
*/
25-
export default function getClientBundleEntryAssets() {
39+
export function getClientBundleEntryAssets() {
40+
let resultCache;
41+
2642
// Return the assets json cache if it exists.
2743
// In development mode we always read the assets json file from disk to avoid
2844
// any cases where an older version gets cached.
2945
if (process.env.BUILD_FLAG_IS_DEV === 'false' && resultCache) {
3046
return resultCache;
3147
}
3248

33-
const assetsFilePath = pathResolve(
34-
appRootDir.get(),
35-
config('bundles.client.outputPath'),
36-
`./${config('bundleAssetsFileName')}`,
37-
);
49+
const clientBundleAssetsJSON = getJSONFromFile(config('bundleAssetsFileName'));
3850

39-
if (!fs.existsSync(assetsFilePath)) {
40-
throw new Error(
41-
`We could not find the "${assetsFilePath}" file, which contains a list of the assets of the client bundle. Please ensure that the client bundle has been built.`,
42-
);
51+
if (typeof clientBundleAssetsJSON.index === 'undefined') {
52+
throw new Error('No asset data found for expected "index" entry chunk of client bundle.');
4353
}
4454

45-
const readAssetsJSONFile = () => JSON.parse(fs.readFileSync(assetsFilePath, 'utf8'));
46-
const assetsJSONCache = readAssetsJSONFile();
47-
if (typeof assetsJSONCache.index === 'undefined') {
48-
throw new Error('No asset data found for expected "index" entry chunk of client bundle.');
55+
resultCache = clientBundleAssetsJSON.index;
56+
return resultCache;
57+
}
58+
59+
export function getClientWebpackManifest() {
60+
let resultCache;
61+
62+
if (resultCache) {
63+
return resultCache;
4964
}
50-
resultCache = assetsJSONCache.index;
65+
66+
resultCache = getJSONFromFile(config('bundleManifestFileName'));
5167
return resultCache;
5268
}

0 commit comments

Comments
 (0)