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

Use webpack dll plugin for faster rebuilds #1651

Closed
wants to merge 64 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
4830854
use webpack dll plugin for faster rebuilds
viankakrisna Feb 26, 2017
a0ad0a0
fix env detection for vendor dependency key
viankakrisna Feb 26, 2017
6931ade
bring utils on eject
viankakrisna Feb 26, 2017
2df7e67
log dependencies
viankakrisna Feb 26, 2017
5e55538
debug current dependencies
viankakrisna Feb 26, 2017
44d0f7f
simplify implementation and let user manage included dependencies
viankakrisna Feb 28, 2017
9e4d1cb
remove lodash from vendor/index
viankakrisna Mar 1, 2017
4a194a2
add vendor/index to kitchensink
viankakrisna Mar 1, 2017
0ca4315
add vendor bundle to kitchensink index.html
viankakrisna Mar 1, 2017
c578226
remove useless args
viankakrisna Mar 1, 2017
9595bfe
move bundleVendorIfStale blocks
viankakrisna Mar 1, 2017
1bfc2a8
clean up unused code
viankakrisna Mar 1, 2017
5fe502c
rename functions and better log message
viankakrisna Mar 1, 2017
f2d2b4a
break the functionality to different functions
viankakrisna Mar 1, 2017
2be8b16
add package json as source of vendor hash
viankakrisna Apr 1, 2017
df67a72
Merge branch 'master' into dll
viankakrisna Apr 13, 2017
96386db
Merge branch 'master' of github.com:viankakrisna/create-react-app int…
viankakrisna May 8, 2017
2af8ba3
Merge branch 'master' of github.com:facebookincubator/create-react-ap…
viankakrisna May 8, 2017
2fd59aa
fix for changes in devServer config
viankakrisna May 8, 2017
369cddd
promisify and fix build script
viankakrisna May 8, 2017
76d2386
move utils
viankakrisna May 8, 2017
05e62eb
add vendorSrc
viankakrisna May 13, 2017
4d1742d
remove unused file
viankakrisna May 13, 2017
0111849
remove unneeded linebreak
viankakrisna May 13, 2017
07cf28e
remove unneeded linebreak
viankakrisna May 13, 2017
51b9807
merge master
viankakrisna May 13, 2017
3898d69
move kitchensink vendor file
viankakrisna May 13, 2017
a4039ab
use add-asset-html-webpack-plugin and move things around
viankakrisna May 14, 2017
c783885
ignore if failed to delete stale files
viankakrisna May 14, 2017
1b04dff
remove unused gitignore
viankakrisna May 14, 2017
c13de8f
add yarn.lock as source of vendor hash if exists
viankakrisna May 14, 2017
582dcae
make it work with PUBLIC_URL
viankakrisna May 14, 2017
cb5d2dc
update comments
viankakrisna May 14, 2017
a94eb01
merge master
viankakrisna May 18, 2017
4e6644a
simplify config and rename vendor util
viankakrisna May 18, 2017
006a284
merge master
viankakrisna May 18, 2017
b41b573
check if ./src/vendor.js exists before modifying config
viankakrisna May 18, 2017
e1ef6c9
re-add vendor entry point and consolidate code
viankakrisna May 18, 2017
ff12a19
tweak vendor entry point
viankakrisna May 18, 2017
7ae5863
move webpackAutoDllCompiler to react-dev-utils
viankakrisna May 19, 2017
69bdf58
merge master
viankakrisna May 19, 2017
17ce19f
Merge branch 'master' into dll
viankakrisna May 22, 2017
b6f4a6b
add additional hash source plugin
viankakrisna May 22, 2017
b2a78f8
Merge branch 'dll' of github.com:viankakrisna/create-react-app into dll
viankakrisna May 22, 2017
c3e7265
fix merge
viankakrisna May 22, 2017
1f28a3e
merge master
viankakrisna May 22, 2017
7d5489b
fix merge
viankakrisna May 22, 2017
1e292bb
add vendor.js to asset-manifest.json
viankakrisna May 22, 2017
e6f6b21
rename vendor -> dll
viankakrisna May 22, 2017
f82a4a6
rename vendor -> dll
viankakrisna May 22, 2017
5d18157
update react-dev-utils/README.md
viankakrisna May 22, 2017
b76e16d
Merge branch 'dll' of github.com:viankakrisna/create-react-app into dll
viankakrisna May 22, 2017
8510f71
more renaming
viankakrisna May 22, 2017
c35b921
rename vendor.js -> dll.js
viankakrisna May 22, 2017
1f9b8fe
filter entry config for duplicate paths
viankakrisna May 22, 2017
e4cfdcf
simplify code path
viankakrisna May 22, 2017
e77e9a5
clean up
viankakrisna May 22, 2017
9ca9d5c
update dll.js
viankakrisna May 22, 2017
3f8fc82
rename dll.js to index.dll.js
viankakrisna May 22, 2017
cb34a1b
clean up code and bail early if no dllHash
viankakrisna May 23, 2017
83794c4
Merge branch 'master' of github.com:facebookincubator/create-react-ap…
viankakrisna May 29, 2017
f943481
make config composable
viankakrisna May 29, 2017
f148634
add support for npm5's package-lock.json
viankakrisna May 31, 2017
6270d63
only include polyfills before dll entry point
viankakrisna Jun 5, 2017
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions packages/react-dev-utils/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,3 +312,24 @@ module.exports = {
// ...
}
```

#### `webpackAutoDllCompiler`
Use this to automatically generate dll bundle based on the hash of yarn.lock (if exists), package.json, and a dll entry point defined in your paths.

usage:
```
webpackAutoDllCompiler({
mainConfig: config,
dllConfig, // a function that returns dll webpack config
paths: {
// dll entry point, import all your dependencies here
dllSrc: './src/dll.js',
// dll cache path
dllPath: './node_modules/.cache/dll',
appPackageJson // path to your package json
yarnLockFile: // path to your yarn lock file
},
}).then(config => {
webpack(config) // or do anything else
})
```
6 changes: 5 additions & 1 deletion packages/react-dev-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@
"openChrome.applescript",
"printHostingInstructions.js",
"WatchMissingNodeModulesPlugin.js",
"webpackAutoDllCompiler.js",
"WebpackDevServerUtils.js",
"webpackHotDevClient.js"
],
"dependencies": {
"@timer/detect-port": "1.1.3",
"add-asset-html-webpack-plugin": "^2.0.1",
"address": "1.0.1",
"anser": "1.3.0",
"babel-code-frame": "6.22.0",
"chalk": "1.1.3",
"cross-spawn": "4.0.2",
"escape-string-regexp": "1.0.5",
"filesize": "3.3.0",
"fs-extra": "3.0.1",
"gzip-size": "3.0.0",
"html-entities": "1.2.1",
"inquirer": "3.0.6",
Expand All @@ -49,6 +52,7 @@
"shell-quote": "1.6.1",
"sockjs-client": "1.1.4",
"strip-ansi": "3.0.1",
"text-table": "0.2.0"
"text-table": "0.2.0",
"webpack": "2.5.1"
}
}
165 changes: 165 additions & 0 deletions packages/react-dev-utils/webpackAutoDllCompiler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
'use strict';
const fs = require('fs-extra');
const path = require('path');
const webpack = require('webpack');
const crypto = require('crypto');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
const clearConsole = require('./clearConsole');
const chalk = require('chalk');
const environment = process.env.NODE_ENV;

// inspired by https://github.com/erm0l0v/webpack-md5-hash/blob/da8efa2fc7fe5c373c95f9ba859dbe208a8b844b/plugin/webpack_md5_hash.js
class WebpackAdditionalSourceHashPlugin {
constructor({ additionalSourceHash }) {
this.additionalSourceHash = additionalSourceHash;
}
apply(compiler) {
compiler.plugin('compilation', compilation => {
compilation.plugin('chunk-hash', (chunk, chunkHash) => {
const oldHash = chunkHash.digest();
chunkHash.digest = () => {
const hash = crypto.createHash('md5');
hash.update(this.additionalSourceHash);
hash.update(oldHash);
return hash.digest('hex');
};
});
});
}
}

module.exports = ({ dllConfig, paths }) =>
mainConfig => new Promise(resolve => {
const dllHash = getDllHash(paths.dllSrc);
if (dllHash === false) {
// we cannot find dllSrc.
// continue without enabling dll feature.
return resolve(mainConfig);
}

//start the procedure for building dll bundle
clearConsole();
const dllPath = paths.dllPath;
const dllBundleFilePath = path.join(dllPath, dllHash + '.js');
const dllManifestFilePath = path.join(dllPath, dllHash + '.json');
const config = dllConfig(dllHash);
console.log('Checking if ' + dllHash + ' dll bundle exists');
if (dllExists()) {
console.log(chalk.green('Dll bundle is up to date and safe to use!'));
// Just run the main compiler if dll bundler is up to date
return resolve(resolveConfig(mainConfig));
}
console.log('Dll bundle needs to be compiled...');
// Read dll path for stale files
fs.readdir(dllPath, (err, files) => {
cleanUpStaleFiles(files);

console.log('Compiling dll bundle for faster rebuilds...');
webpack(config).run((err, stats) => {
checkForErrors(err, stats);

// When the process still run until here, there are no errors :)
console.log(chalk.green('Dll bundle compiled successfully!'));
resolve(resolveConfig(mainConfig)); // Let the main compiler do its job
});
});

function dllExists() {
return fs.existsSync(dllManifestFilePath) &&
fs.existsSync(dllBundleFilePath);
}

function cleanUpStaleFiles(files) {
try {
// delete all stale dll bundle for this environment
files.filter(file => !file.indexOf(environment)).forEach(file => {
fs.unlinkSync(path.join(dllPath, file));
});
} catch (ignored) {
//ignored
}
}

function resolveConfig(mainConfig) {
return Object.assign({}, mainConfig, {
entry: mainConfig.entry.filter(path => !config.entry.includes(path)),
plugins: mainConfig.plugins
.concat([
new WebpackAdditionalSourceHashPlugin({
additionalSourceHash: dllHash,
}),
new webpack.DllReferencePlugin({
context: '.',
manifest: require(dllManifestFilePath),
}),
new AddAssetHtmlPlugin({
outputPath: path.join('static', 'js'),
publicPath: mainConfig.output.publicPath +
path.join('static', 'js'),
filepath: require.resolve(dllBundleFilePath),
}),
])
.map(plugin => {
if (plugin.constructor.name === 'ManifestPlugin') {
plugin.opts.cache = {
'dll.js': path.join('static', 'js', dllHash + '.js'),
'dll.js.map': path.join('static', 'js', dllHash + '.js.map'),
};
}
return plugin;
}),
});
}

function getDllHash(dllSrc) {
if (fs.existsSync(dllSrc)) {
const hash = crypto.createHash('md5');
const input = fs.readFileSync(dllSrc);
const appPackageJson = fs.readFileSync(paths.appPackageJson);

hash.update(input);
hash.update(appPackageJson);

if (fs.existsSync(paths.yarnLockFile)) {
hash.update(fs.readFileSync(paths.yarnLockFile));
}

if (fs.existsSync(paths.packageLockFile)) {
hash.update(fs.readFileSync(paths.packageLockFile));
}

return [environment, hash.digest('hex').substring(0, 8)].join('.');
} else {
return false;
}
}
});

function printErrors(summary, errors) {
console.log(chalk.red(summary));
console.log();
errors.forEach(err => {
console.log(err.message || err);
console.log();
});
}

function checkForErrors(err, stats) {
if (err) {
printErrors('Failed to compile.', [err]);
process.exit(1);
}

if (stats.compilation.errors.length) {
printErrors('Failed to compile.', stats.compilation.errors);
process.exit(1);
}

if (process.env.CI && stats.compilation.warnings.length) {
printErrors(
'Failed to compile. When process.env.CI = true, warnings are treated as failures. Most CI servers set this automatically.',
stats.compilation.warnings
);
process.exit(1);
}
}
11 changes: 11 additions & 0 deletions packages/react-scripts/config/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const url = require('url');
const appDirectory = fs.realpathSync(process.cwd());
const resolveApp = relativePath => path.resolve(appDirectory, relativePath);

const dllPath = 'node_modules/.cache/dll';

const envPublicUrl = process.env.PUBLIC_URL;

function ensureSlash(path, needsSlash) {
Expand Down Expand Up @@ -58,8 +60,11 @@ module.exports = {
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
yarnLockFile: resolveApp('yarn.lock'),
packageLockFile: resolveApp('package-lock.json'),
testsSetup: resolveApp('src/setupTests.js'),
appNodeModules: resolveApp('node_modules'),
dllPath: resolveApp(dllPath),
dllSrc: resolveApp('src/index.dll.js'),
publicUrl: getPublicUrl(resolveApp('package.json')),
servedPath: getServedPath(resolveApp('package.json')),
};
Expand All @@ -78,8 +83,11 @@ module.exports = {
appPackageJson: resolveApp('package.json'),
appSrc: resolveApp('src'),
yarnLockFile: resolveApp('yarn.lock'),
packageLockFile: resolveApp('package-lock.json'),
testsSetup: resolveApp('src/setupTests.js'),
appNodeModules: resolveApp('node_modules'),
dllPath: resolveApp(dllPath),
dllSrc: resolveApp('src/index.dll.js'),
publicUrl: getPublicUrl(resolveApp('package.json')),
servedPath: getServedPath(resolveApp('package.json')),
// These properties only exist before ejecting:
Expand Down Expand Up @@ -107,8 +115,11 @@ if (
appPackageJson: resolveOwn('package.json'),
appSrc: resolveOwn('template/src'),
yarnLockFile: resolveOwn('template/yarn.lock'),
packageLockFile: resolveOwn('template/package-lock.json'),
testsSetup: resolveOwn('template/src/setupTests.js'),
appNodeModules: resolveOwn('node_modules'),
dllPath: resolveOwn(dllPath),
dllSrc: resolveOwn('template/src/index.dll.js'),
publicUrl: getPublicUrl(resolveOwn('package.json')),
servedPath: getServedPath(resolveOwn('package.json')),
// These properties only exist before ejecting:
Expand Down
86 changes: 86 additions & 0 deletions packages/react-scripts/config/webpack.config.dll.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict';
const webpack = require('webpack');
const paths = require('./paths');
const path = require('path');
const getClientEnvironment = require('./env');
const publicPath = paths.servedPath;
const publicUrl = publicPath.slice(0, -1);
const env = getClientEnvironment(publicUrl);
const isProduction = process.env.NODE_ENV === 'production';

module.exports = dllHash => {
const dllGlobalName = '[name]' + dllHash.replace(/\./g, '');
return {
cache: true,
entry: [require.resolve('./polyfills'), paths.dllSrc],
devtool: 'source-map',
output: {
filename: dllHash + '.js',
path: paths.dllPath,
library: dllGlobalName,
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
// We read `NODE_PATH` environment variable in `paths.js` and pass paths here.
// We placed these paths second because we want `node_modules` to "win"
// if there are any conflicts. This matches Node resolution mechanism.
// https://github.com/facebookincubator/create-react-app/issues/253
modules: ['node_modules'].concat(paths.appNodeModules),
// These are the reasonable defaults supported by the Node ecosystem.
// We also include JSX as a common component filename extension to support
// some tools, although we do not recommend using it, see:
// https://github.com/facebookincubator/create-react-app/issues/290
extensions: ['.js', '.json', '.jsx'],
alias: {
// Support React Native Web
// https://www.smashingmagazine.com/2016/08/a-glimpse-into-the-future-with-react-native-for-web/
'react-native': 'react-native-web',
},
},
// @remove-on-eject-begin
// Resolve loaders (webpack plugins for CSS, images, transpilation) from the
// directory of `react-scripts` itself rather than the project directory.
resolveLoader: {
modules: [
paths.ownNodeModules,
// Lerna hoists everything, so we need to look in our app directory
paths.appNodeModules,
],
},
// @remove-on-eject-end
plugins: [
new webpack.DllPlugin({
// The path to the manifest file which maps between
// modules included in a bundle and the internal IDs
// within that bundle
path: path.join(paths.dllPath, dllHash + '.json'),
// The name of the global variable which the library's
// require function has been assigned to. This must match the
// output.library option above
name: dllGlobalName,
}),
isProduction ? new webpack.DefinePlugin(env.stringified) : null,
isProduction
? new webpack.optimize.UglifyJsPlugin({
compress: {
screw_ie8: true, // React doesn't support IE8
warnings: false,
},
mangle: {
screw_ie8: true,
},
output: {
comments: false,
screw_ie8: true,
},
sourceMap: true,
})
: null,
].filter(Boolean),
node: {
fs: 'empty',
net: 'empty',
tls: 'empty',
},
};
};
1 change: 1 addition & 0 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"postcss-flexbugs-fixes": "3.0.0",
"postcss-loader": "2.0.5",
"promise": "7.1.1",
"promise-compose": "^1.1.2",
"react-dev-utils": "^3.0.0",
"react-error-overlay": "^1.0.7",
"style-loader": "0.17.0",
Expand Down
Loading