Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Webpack DLL Plugin #495

Merged
merged 8 commits into from Jun 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions app/components/LoadingIndicator/styles.css
Expand Up @@ -128,14 +128,14 @@
opacity: 0;
}

40% {
opacity: 1;
}

39% {
opacity: 0;
}

40% {
opacity: 1;
}

100% {
opacity: 0;
}
Expand Down
56 changes: 56 additions & 0 deletions internals/config.js
@@ -0,0 +1,56 @@
const resolve = require('path').resolve;
const pullAll = require('lodash/pullAll');
const uniq = require('lodash/uniq');

const ReactBoilerplate = {
// This refers to the react-boilerplate version this project is based on.
version: '3.0.0',

/**
* The DLL Plugin provides a dramatic speed increase to webpack build and hot module reloading
* by caching the module metadata for all of our npm dependencies. We enable it by default
* in development.
*
*
* To disable the DLL Plugin, set this value to false.
*/
dllPlugin: {
defaults: {
/**
* we need to exclude dependencies which are not intended for the browser
* by listing them here.
*/
exclude: [
'chalk',
'compression',
'cross-env',
'express',
'ip',
'minimist',
'sanitize.css',
],

/**
* Specify any additional dependencies here. We include core-js and lodash
* since a lot of our dependencies depend on them and they get picked up by webpack.
*/
include: ['core-js', 'eventsource-polyfill', 'babel-polyfill', 'lodash'],

// The path where the DLL manifest and bundle will get built
path: resolve('../node_modules/react-boilerplate-dlls'),
},

entry(pkg) {
const dependencyNames = Object.keys(pkg.dependencies);
const exclude = pkg.dllPlugin.exclude || ReactBoilerplate.dllPlugin.defaults.exclude;
const include = pkg.dllPlugin.include || ReactBoilerplate.dllPlugin.defaults.include;
const includeDependencies = uniq(dependencyNames.concat(include));

return {
reactBoilerplateDeps: pullAll(includeDependencies, exclude),
};
},
},
};

module.exports = ReactBoilerplate;
49 changes: 49 additions & 0 deletions internals/scripts/dependencies.js
@@ -0,0 +1,49 @@
/*eslint-disable*/

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

require('shelljs/global')

const path = require('path')
const fs = require('fs')
const exists = fs.existsSync
const writeFile = fs.writeFileSync

const defaults = require('lodash/defaultsDeep')
const pkg = require(path.join(process.cwd(), 'package.json'))
const config = require('../config')
const dllConfig = defaults(pkg.dllPlugin, config.dllPlugin.defaults)
const outputPath = path.join(process.cwd(), dllConfig.path)
const dllManifestPath = path.join(outputPath, 'package.json')

/**
* I use node_modules/react-boilerplate-dlls by default just because
* it isn't going to be version controlled and babel wont try to parse it.
*/
mkdir('-p', outputPath)

echo('Building the Webpack DLL...')

/**
* Create a manifest so npm install doesnt warn us
*/
if (!exists(dllManifestPath)) {
writeFile(
dllManifestPath,
JSON.stringify(defaults({
name: 'react-boilerplate-dlls',
private: true,
author: pkg.author,
repository: pkg.repository,
version: pkg.version
}), null, 2),

'utf8'
)
}

// the BUILDING_DLL env var is set to avoid confusing the development environment
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 @@ -55,7 +55,6 @@ module.exports = (options) => ({
}],
},
plugins: options.plugins.concat([
new webpack.optimize.CommonsChunkPlugin('common'),
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}.dll.js'></script>`));

return doc.toString();
}
34 changes: 34 additions & 0 deletions internals/webpack/webpack.dll.babel.js
@@ -0,0 +1,34 @@
/**
* 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.
*/

const { join } = require('path');
const defaults = require('lodash/defaultsDeep');
const webpack = require('webpack');
const pkg = require(join(process.cwd(), 'package.json'));
const dllPlugin = require('../config').dllPlugin;

if (!pkg.dllPlugin) { process.exit(0); }

const dllConfig = defaults(pkg.dllPlugin, dllPlugin.defaults);
const outputPath = join(process.cwd(), dllConfig.path);

module.exports = {
context: process.cwd(),
entry: dllConfig.dlls ? dllConfig.dlls : dllPlugin.entry(pkg),
devtool: 'eval',
output: {
filename: '[name].dll.js',
path: outputPath,
library: '[name]',
},
plugins: [
new webpack.DllPlugin({ name: '[name]', path: join(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
28 changes: 25 additions & 3 deletions package.json
Expand Up @@ -12,19 +12,22 @@
"author": "Max Stoiber",
"license": "MIT",
"scripts": {
"analyze": "node ./internals/scripts/analyze.js",
"npmcheckversion": "node ./internals/scripts/npmcheckversion.js",
"preinstall": "npm run npmcheckversion",
"postinstall": "npm run build:dll",
"prebuild": "npm run test && npm run build:clean",
"build:clean": "rimraf ./build/*",
"build": "cross-env NODE_ENV=production webpack --config internals/webpack/webpack.prod.babel.js --color -p",
"analyze": "node ./internals/scripts/analyze.js",
"build:clean": "rimraf ./build/*",
"build:dll": "node ./internals/scripts/dependencies.js",
"start": "cross-env NODE_ENV=development node server",
"start:tunnel": "cross-env NODE_ENV=development ENABLE_TUNNEL=true node server",
"start:production": "npm run build && npm run start:prod",
"start:prod": "cross-env NODE_ENV=production node server",
"pagespeed": "node ./internals/scripts/pagespeed.js",
"presetup": "npm i chalk",
"setup": "node ./internals/scripts/setup.js",
"postsetup": "npm run build:dll",
"clean": "shjs ./internals/scripts/clean.js",
"generate": "plop --plopfile internals/generators/index.js",
"lint": "npm run lint:js && npm run lint:css",
Expand Down Expand Up @@ -99,15 +102,33 @@
"indentation": 2
}
},
"dllPlugin": {
"path": "node_modules/react-boilerplate-dlls",
"exclude": [
"chalk",
"compression",
"cross-env",
"express",
"ip",
"minimist",
"sanitize.css"
],
"include": [
"core-js",
"lodash",
"eventsource-polyfill"
]
},
"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",
"ip": "^1.1.2",
"minimist": "^1.2.0",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"react-redux": "^4.4.5",
Expand Down Expand Up @@ -172,6 +193,7 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^1.7.0",
"lint-staged": "^1.0.0",
"lodash": "^4.13.1",
"minimist": "^1.2.0",
"mocha": "^2.4.5",
"ngrok": "2.1.8",
Expand Down