Skip to content

Commit

Permalink
Merge 44dda13 into bc245b2
Browse files Browse the repository at this point in the history
  • Loading branch information
datapimp committed Jun 8, 2016
2 parents bc245b2 + 44dda13 commit c69ec51
Show file tree
Hide file tree
Showing 9 changed files with 320 additions and 32 deletions.
86 changes: 86 additions & 0 deletions docs/general/webpack.md
@@ -0,0 +1,86 @@
# Webpack Options

## DLL Plugin

[Webpack Wiki](https://github.com/webpack/docs/wiki/list-of-plugins#dllplugin)

The DLL Plugin is a way of caching the module dependency graph that Webpack builds every time it loads our project. It doesn't execute any of the code, but simply makes it available to any modules which depend on it. It is perfect for boilerplates due to the fact that many of the dependency decisions are made up front and won't need to change that frequently.

Using the DLL Plugin allows Webpack to prioritize building your application's code, while relying on caching to handle the dependency code. The end result is a significant boost in both server startup time and in hot-module reload times.

The DLL plugin is enabled by default for development, and depends on a configuration property `dllPlugin` being found in the project's `package.json`. There is also a one-time build step that will take place before the webpack development configuration can be used.

### DLL Plugin Configuration Options

#### Automatic Configuration

By default, a DLL bundle is generated for all of the modules listed as dependencies in the project's `package.json` file.

```
{
"dependencies": {
"babel-polyfill": "^6.7.4",
"chalk": "^1.1.3",
"compression": "^1.6.1",
"express": "^4.13.4",
"file-loader": "^0.8.5",
"fontfaceobserver": "^1.7.1",
"history": "^2.1.0",
"immutable": "^3.8.1",
"react": "^15.0.1",
...
}
}
```

Certain production dependencies which don't belong in the browser bundle can be excluded by naming them in the `exclude` list.

Other dependencies which may not be listed in the project's package.json, can be explicitly included by naming them in the `include` list. For example using `babel-polyfill` will include `core-js` and hot module reloading dependencies may also include `lodash`.

**Example:**

```
{
"dllPlugin": {
"exclude": ["express","chalk","compression"],
"include": ["core-js", "eventsource-polyfill", "lodash"]
}
}
```

Using the `npm run analyse` script will give you a good idea of what exactly is part of your bundle.

### Manual Configuration

There may be situations where you want fine grained control over the DLL Plugin. In that case you can specify which dependencies belong to which DLL using the following:

```
{
"dllPlugin": {
"dlls": {
"reactCore": [
"react",
"react-dom",
"react-router",
"redux",
"react-redux",
"redux-think",
"redux-saga"
],
"reactBootstrap": [
"jquery",
"bootstrap",
"react-bootstrap"
]
}
}
}
```

### Generating DLL Files

The cost of the performance boost given to us by the DLL Plugin is that we will need to occasionally update the module manifest and dependency bundle.

This can be accomplished by running the `npm run build:dll` script. This will create a dependency bundle javascript file, a JSON manifest, in the `node_modules/react-boilerplate-dlls` folder in your project.

For convenience, this script will automatically be run after an `npm install`
14 changes: 14 additions & 0 deletions internals/scripts/dependencies.js
@@ -0,0 +1,14 @@
/*eslint-disable*/

// No need to build the DLL in production
if (process.env.NODE_ENV === 'production') {
process.exit(0)
}

require('shelljs/global');

rm('-rf', 'node_modules/react-boilerplate-dlls')
mkdir('node_modules/react-boilerplate-dlls')

echo('Building the Webpack DLL...')
exec('cross-env BUILDING_DLL=true webpack --display-chunks --color --config internals/webpack/webpack.dll.babel.js')
1 change: 0 additions & 1 deletion internals/webpack/webpack.base.babel.js
Expand Up @@ -46,7 +46,6 @@ module.exports = (options) => ({
}],
},
plugins: options.plugins.concat([
new webpack.optimize.CommonsChunkPlugin('common.js'),
new webpack.ProvidePlugin({
// make fetch available
fetch: 'exports?self.fetch!whatwg-fetch',
Expand Down
117 changes: 107 additions & 10 deletions internals/webpack/webpack.dev.babel.js
Expand Up @@ -3,14 +3,28 @@
*/

const path = require('path');
const fs = require('fs');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const logger = require('../../server/logger');
const cheerio = require('cheerio');
const pkg = require(path.resolve(process.cwd(), 'package.json'));
const dllPlugin = pkg.dllPlugin;

// PostCSS plugins
const cssnext = require('postcss-cssnext');
const postcssFocus = require('postcss-focus');
const postcssReporter = require('postcss-reporter');

const plugins = [
new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({
inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
templateContent: templateContent(), // eslint-disable-line no-use-before-define
}),
];

module.exports = require('./webpack.base.babel')({
// Add hot reloading in development
entry: [
Expand All @@ -25,6 +39,9 @@ module.exports = require('./webpack.base.babel')({
chunkFilename: '[name].chunk.js',
},

// Add development plugins
plugins: dependencyHandlers().concat(plugins), // eslint-disable-line no-use-before-define

// Load the CSS in a style tag in development
cssLoaders: 'style-loader!css-loader?localIdentName=[local]__[path][name]__[hash:base64:5]&modules&importLoaders=1&sourceMap!postcss-loader',

Expand All @@ -39,16 +56,6 @@ module.exports = require('./webpack.base.babel')({
}),
],

// Add hot reloading
plugins: [
new webpack.HotModuleReplacementPlugin(), // Tell webpack we want hot reloading
new webpack.NoErrorsPlugin(),
new HtmlWebpackPlugin({
template: 'app/index.html',
inject: true, // Inject all files that are generated by webpack, e.g. bundle.js
}),
],

// Tell babel that we want to hot-reload
babelQuery: {
presets: ['react-hmre'],
Expand All @@ -57,3 +64,93 @@ module.exports = require('./webpack.base.babel')({
// Emit a source map for easier debugging
devtool: 'cheap-module-eval-source-map',
});

/**
* Select which plugins to use to optimize the bundle's handling of
* third party dependencies.
*
* If there is a dllPlugin key on the project's package.json, the
* Webpack DLL Plugin will be used. Otherwise the CommonsChunkPlugin
* will be used.
*
*/
function dependencyHandlers() {
// Don't do anything during the DLL Build step
if (process.env.BUILDING_DLL) { return []; }

// If the package.json does not have a dllPlugin property, use the CommonsChunkPlugin
if (!dllPlugin) {
return [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
children: true,
minChunks: 2,
async: true,
}),
];
}

const dllPath = path.resolve(process.cwd(), dllPlugin.path || 'node_modules/react-boilerplate-dlls');

/**
* If DLLs aren't explicitly defined, we assume all production dependencies listed in package.json
* Reminder: You need to exclude any server side dependencies by listing them in dllConfig.exclude
*
* @see https://github.com/mxstbr/react-boilerplate/tree/master/docs/general/webpack.md
*/
if (!dllPlugin.dlls) {
const manifestPath = path.resolve(dllPath, 'reactBoilerplateDeps.json');

if (!fs.existsSync(manifestPath)) {
logger.error('The DLL manifest is missing. Please run `npm run build:dll`');
process.exit(0);
}

return [
new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(manifestPath), // eslint-disable-line global-require
}),
];
}

// If DLLs are explicitly defined, we automatically create a DLLReferencePlugin for each of them.
const dllManifests = Object.keys(dllPlugin.dlls).map((name) => path.join(dllPath, `/${name}.json`));

return dllManifests.map((manifestPath) => {
if (!fs.existsSync(path)) {
if (!fs.existsSync(manifestPath)) {
logger.error(`The following Webpack DLL manifest is missing: ${path.basename(manifestPath)}`);
logger.error(`Expected to find it in ${dllPath}`);
logger.error('Please run: npm run build:dll');

process.exit(0);
}
}

return new webpack.DllReferencePlugin({
context: process.cwd(),
manifest: require(manifestPath), // eslint-disable-line global-require
});
});
}

/**
* We dynamically generate the HTML content in development so that the different
* DLL Javascript files are loaded in script tags and available to our application.
*/
function templateContent() {
const html = fs.readFileSync(
path.resolve(process.cwd(), 'app/index.html')
).toString();

if (!dllPlugin) { return html; }

const doc = cheerio(html);
const body = doc.find('body');
const dllNames = !dllPlugin.dlls ? ['reactBoilerplateDeps'] : Object.keys(dllPlugin.dlls);

dllNames.forEach(dllName => body.append(`<script data-dll='true' src='/${dllName}.js'></script>`));

return doc.toString();
}
75 changes: 75 additions & 0 deletions internals/webpack/webpack.dll.babel.js
@@ -0,0 +1,75 @@
/**
* WEBPACK DLL GENERATOR
*
* This profile is used to cache webpack's module
* contexts for external library and framework type
* dependencies which will usually not change often enough
* to warrant building them from scratch every time we use
* the webpack process.
*
* It makes sense to run this after npm install
* or dependency changes.
*/

const { keys } = Object;
const { resolve } = require('path');
const pkg = require(resolve(process.cwd(), 'package.json'));

if (!pkg.dllPlugin) {
throw new Error('Usage of the Webpack DLL plugin depends on a dllPlugin key being present in your package.json');
}

const pullAll = require('lodash/pullAll');
const uniq = require('lodash/uniq');
const defaults = require('lodash/defaultsDeep');
const webpack = require('webpack');

const dllPlugin = defaults(pkg.dllPlugin, {
/**
* Not all dependencies can be bundled
*/
exclude: [
'express',
'chalk',
'compression',
'sanitize.css',
'cross-env',
'ip',
],

/**
* Some additional dependencies which aren't
* in the production dependencies need to be bundled.
*/
include: [
'babel-polyfill',
'eventsource-polyfill',
'core -js',
],

/**
* folder where the generated dlls get stored.
*/
path: 'node_modules/react-boilerplate-dlls',
});

if (dllPlugin.dlls && typeof dllPlugin.dlls !== 'object') {
throw new Error('The Webpack DLL Plugin configuration in your package.json must contain a dlls property.');
}

const outputPath = resolve(process.cwd(), dllPlugin.path);
const reactBoilerplateDeps = pullAll(uniq(keys(pkg.dependencies).concat(dllPlugin.include)), dllPlugin.exclude);

module.exports = {
context: process.cwd(),
entry: (typeof dllPlugin.dlls === 'undefined' ? { reactBoilerplateDeps } : dllPlugin.dlls),
devtool: 'eval',
output: {
filename: '[name].js',
library: '[name]',
path: outputPath,
},
plugins: [
new webpack.DllPlugin({ name: '[name]', path: resolve(outputPath, '[name].json') }), // eslint-disable-line no-new
],
};
6 changes: 6 additions & 0 deletions internals/webpack/webpack.prod.babel.js
Expand Up @@ -40,6 +40,12 @@ module.exports = require('./webpack.base.babel')({
}),
],
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
children: true,
minChunks: 2,
async: true,
}),

// OccurrenceOrderPlugin is needed for long-term caching to work properly.
// See http://mxs.is/googmv
Expand Down

0 comments on commit c69ec51

Please sign in to comment.