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

Commit

Permalink
Move webpack manifest out from index chunk
Browse files Browse the repository at this point in the history
Enables index chunk hash to become independent of children
chunk hashes. The manifest is inlined with every server
rendered route in production mode.
  • Loading branch information
dhruvparmar372 committed Jul 29, 2018
1 parent 83d533a commit c6f0eb2
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 18 deletions.
4 changes: 4 additions & 0 deletions config/values.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ const values = {
// containing details of all output files for a bundle?
bundleAssetsFileName: 'assets.json',

// What should we name the manifest generated by chunk-manifest-webpack-plugin
// containing mappings for chunk ids and names?
bundleManifestFileName: 'manifest.json',

// node_modules are not included in any bundles that target "node" as a
// runtime (e.g.. the server bundle) as including them often breaks builds
// due to thinks like require statements containing expressions..
Expand Down
12 changes: 12 additions & 0 deletions internal/webpack/configFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import nodeExternals from 'webpack-node-externals';
import path from 'path';
import webpack from 'webpack';
import WebpackMd5Hash from 'webpack-md5-hash';
import ChunkManifestWebpackPlugin from 'chunk-manifest-webpack-plugin';

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

// Since chunk-manifest-webpack-plugin doesn't work with webpack-dev-server
// https://github.com/soundcloud/chunk-manifest-webpack-plugin/issues/26
// we generate manifest only in production mode.
// Also this optimisation is to prevent better long term caching of
// index chunk which can be compromised in development mode.
ifProdClient(() => new ChunkManifestWebpackPlugin({
filename: config('bundleManifestFileName'),
manifestVariable: 'webpackManifest',
inlineManifest: false,
})),

// These are process.env flags that you can use in your code in order to
// have advanced control over what is included/excluded in your bundles.
// For example you may only want certain parts of your code to be
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"modernizr": "3.5.0",
"normalize.css": "7.0.0",
"offline-plugin": "4.8.3",
"pretty-error": "2.1.1",
"pretty-error": "2.1.1",
"prop-types": "15.5.10",
"react": "15.6.1",
"react-async-bootstrapper": "1.1.1",
Expand Down Expand Up @@ -98,6 +98,7 @@
"babel-template": "6.26.0",
"chokidar": "1.7.0",
"css-loader": "0.28.7",
"chunk-manifest-webpack-plugin": "1.1.0",
"enzyme": "2.9.1",
"enzyme-to-json": "2.0.0",
"eslint": "4.7.2",
Expand Down
15 changes: 14 additions & 1 deletion server/middleware/reactApplication/ServerHTML.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import serialize from 'serialize-javascript';
import config from '../../../config';
import ifElse from '../../../shared/utils/logic/ifElse';
import removeNil from '../../../shared/utils/arrays/removeNil';
import getClientBundleEntryAssets from './getClientBundleEntryAssets';
import {
getClientBundleEntryAssets,
getClientWebpackManifest,
} from './getClientBundleEntryAssets';

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

// Resolve the webpack manifest. Useful only in production mode.
const clientWebpackManifest = process.env.BUILD_FLAG_IS_DEV === 'false' ?
getClientWebpackManifest() : {};


function stylesheetTag(stylesheetFilePath) {
return (
<link href={stylesheetFilePath} media="screen, projection" rel="stylesheet" type="text/css" />
Expand All @@ -46,13 +54,18 @@ function ServerHTML(props) {
const inlineScript = body =>
<script nonce={nonce} type="text/javascript" dangerouslySetInnerHTML={{ __html: body }} />;

const webpackManifestScript = `
window.webpackManifest = ${JSON.stringify(clientWebpackManifest)};
`;

const headerElements = removeNil([
...ifElse(helmet)(() => helmet.meta.toComponent(), []),
...ifElse(helmet)(() => helmet.title.toComponent(), []),
...ifElse(helmet)(() => helmet.base.toComponent(), []),
...ifElse(helmet)(() => helmet.link.toComponent(), []),
ifElse(clientEntryAssets && clientEntryAssets.css)(() => stylesheetTag(clientEntryAssets.css)),
...ifElse(helmet)(() => helmet.style.toComponent(), []),
ifElse(process.env.BUILD_FLAG_IS_DEV === 'false')(() => inlineScript(webpackManifestScript)),
]);

const bodyElements = removeNil([
Expand Down
48 changes: 32 additions & 16 deletions server/middleware/reactApplication/getClientBundleEntryAssets.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,21 @@ import { resolve as pathResolve } from 'path';
import appRootDir from 'app-root-dir';
import config from '../../../config';

let resultCache;
function getJSONFromFile(fileName) {
const filePath = pathResolve(
appRootDir.get(),
config('bundles.client.outputPath'),
`./${fileName}`,
);

if (!fs.existsSync(filePath)) {
throw new Error(
`We could not find the "${filePath}" file. Please ensure that the client bundle has been built.`,
);
}

return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}

/**
* Retrieves the js/css for the named chunks that belong to our client bundle.
Expand All @@ -22,31 +36,33 @@ let resultCache;
* to the render logic. Having this method allows us to easily fetch
* the respective assets simply by using a chunk name. :)
*/
export default function getClientBundleEntryAssets() {
export function getClientBundleEntryAssets() {
let resultCache;

// Return the assets json cache if it exists.
// In development mode we always read the assets json file from disk to avoid
// any cases where an older version gets cached.
if (process.env.BUILD_FLAG_IS_DEV === 'false' && resultCache) {
return resultCache;
}

const assetsFilePath = pathResolve(
appRootDir.get(),
config('bundles.client.outputPath'),
`./${config('bundleAssetsFileName')}`,
);
const clientBundleAssetsJSON = getJSONFromFile(config('bundleAssetsFileName'));

if (!fs.existsSync(assetsFilePath)) {
throw new Error(
`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.`,
);
if (typeof clientBundleAssetsJSON.index === 'undefined') {
throw new Error('No asset data found for expected "index" entry chunk of client bundle.');
}

const readAssetsJSONFile = () => JSON.parse(fs.readFileSync(assetsFilePath, 'utf8'));
const assetsJSONCache = readAssetsJSONFile();
if (typeof assetsJSONCache.index === 'undefined') {
throw new Error('No asset data found for expected "index" entry chunk of client bundle.');
resultCache = clientBundleAssetsJSON.index;
return resultCache;
}

export function getClientWebpackManifest() {
let resultCache;

if (resultCache) {
return resultCache;
}
resultCache = assetsJSONCache.index;

resultCache = getJSONFromFile(config('bundleManifestFileName'));
return resultCache;
}

0 comments on commit c6f0eb2

Please sign in to comment.