From 655a47928cead888c313c7257d6c23f6c5368f90 Mon Sep 17 00:00:00 2001 From: Ade Viankakrisna Fadlil Date: Fri, 14 Jul 2017 06:02:01 +0700 Subject: [PATCH 1/4] add cached loaders and plugins tweak settings tweak variable names --- .gitignore | 1 + .../react-dev-utils/createHashFromPaths.js | 69 +++++++++++++++++++ packages/react-dev-utils/package.json | 1 + .../config/webpack.config.dev.js | 42 ++++++++--- .../config/webpack.config.prod.js | 39 ++++++++--- packages/react-scripts/package.json | 3 + 6 files changed, 135 insertions(+), 20 deletions(-) create mode 100644 packages/react-dev-utils/createHashFromPaths.js diff --git a/.gitignore b/.gitignore index 79ce88915d7..d9daac7e7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea/ .vscode/ +.cache-loader/ node_modules/ build .DS_Store diff --git a/packages/react-dev-utils/createHashFromPaths.js b/packages/react-dev-utils/createHashFromPaths.js new file mode 100644 index 00000000000..a974aa069a9 --- /dev/null +++ b/packages/react-dev-utils/createHashFromPaths.js @@ -0,0 +1,69 @@ +'use strict'; + +const crypto = require('crypto'); +const path = require('path'); +const fs = require('fs'); + +const getSources = (paths = [], sourceMethod = function() {}, exclude = []) => { + const getSource = (somePath = '') => { + try { + if (fs.lstatSync(somePath).isDirectory()) { + if ( + exclude.includes(somePath) || + exclude.find(excluded => somePath.startsWith(excluded)) + ) { + return somePath; + } + const fileList = fs.readdirSync(somePath); + return getSources( + fileList.map(file => path.join(somePath, file)), + sourceMethod + ); + } else { + return sourceMethod(somePath); + } + } catch (ignored) { + return somePath; + } + }; + let result = ''; + for (let i = paths.length - 1; i >= 0; i--) { + result += getSource(paths[i]); + } + return result; +}; + +const getSourceMethod = key => { + const cases = { + content: filePath => fs.readFileSync(filePath, 'utf-8'), + mtime: filePath => `${filePath}_${fs.statSync(filePath).mtime}`, + }; + + const found = cases[key]; + + if (found) { + return found; + } + + console.error( + 'Error from createHashFromPaths:\n\n', + 'Source method is not recognized, possible values are:\n\n', + Object.keys(cases).map(e => '`' + e + '`').join(', '), + '\n' + ); + process.exit(1); +}; + +const createHashFromPaths = ({ paths, exclude, method = 'mtime' }) => { + const hash = crypto.createHash('md5'); + hash.update(paths.join('')); + if (Array.isArray(paths)) { + const sourceMethod = getSourceMethod(method); + const sources = getSources(paths, sourceMethod, exclude); + hash.update(sources); + } + + return hash.digest('hex'); +}; + +module.exports = createHashFromPaths; diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index e09e990d0b7..02b7eb84d02 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -15,6 +15,7 @@ "checkRequiredFiles.js", "clearConsole.js", "crashOverlay.js", + "createHashFromPaths.js", "crossSpawn.js", "eslintFormatter.js", "FileSizeReporter.js", diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 253bc34f062..21db4206b8b 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -19,8 +19,10 @@ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const createHashFromPaths = require('react-dev-utils/createHashFromPaths'); const getClientEnvironment = require('./env'); const paths = require('./paths'); +const AutoDllWebpackPlugin = require('autodll-webpack-plugin'); // Webpack uses `publicPath` to determine where the app is being served from. // In development, we always serve from the root. This makes config easier. @@ -170,17 +172,22 @@ module.exports = { { test: /\.(js|jsx)$/, include: paths.appSrc, - loader: require.resolve('babel-loader'), - options: { - // @remove-on-eject-begin - babelrc: false, - presets: [require.resolve('babel-preset-react-app')], - // @remove-on-eject-end - // This is a feature of `babel-loader` for webpack (not Babel itself). - // It enables caching results in ./node_modules/.cache/babel-loader/ - // directory for faster rebuilds. - cacheDirectory: true, - }, + use: [ + require.resolve('cache-loader'), + { + loader: require.resolve('babel-loader'), + options: { + // @remove-on-eject-begin + babelrc: false, + presets: [require.resolve('babel-preset-react-app')], + // @remove-on-eject-end + // This is a feature of `babel-loader` for webpack (not Babel itself). + // It enables caching results in ./node_modules/.cache/babel-loader/ + // directory for faster rebuilds. + cacheDirectory: true, + }, + }, + ], }, // "postcss" loader applies autoprefixer to our CSS. // "css" loader resolves paths in CSS and adds assets as dependencies. @@ -190,6 +197,7 @@ module.exports = { { test: /\.css$/, use: [ + require.resolve('cache-loader'), require.resolve('style-loader'), { loader: require.resolve('css-loader'), @@ -252,6 +260,18 @@ module.exports = { inject: true, template: paths.appHtml, }), + new AutoDllWebpackPlugin({ + env: process.env.NODE_ENV, + additionalHash: createHashFromPaths({ + paths: [paths.appNodeModules], + exclude: [path.join(paths.appNodeModules, '.cache')], + }), + inject: true, + filename: '[name].[hash].js', + entry: { + vendor: ['react', 'react-dom'], + }, + }), // Add module names to factory functions so they appear in browser profiler. new webpack.NamedModulesPlugin(), // Makes some environment variables available to the JS code, for example: diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 0077c34a3f6..6fe65ffd215 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -20,8 +20,12 @@ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const SWPrecacheWebpackPlugin = require('sw-precache-webpack-plugin'); const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); +const createHashFromPaths = require('react-dev-utils/createHashFromPaths'); +const AutoDllWebpackPlugin = require('autodll-webpack-plugin'); +const WebpackUglifyParallel = require('webpack-uglify-parallel'); const paths = require('./paths'); const getClientEnvironment = require('./env'); +const os = require('os'); // Webpack uses `publicPath` to determine where the app is being served from. // It requires a trailing slash, or the file assets will get an incorrect path. @@ -171,14 +175,19 @@ module.exports = { { test: /\.(js|jsx)$/, include: paths.appSrc, - loader: require.resolve('babel-loader'), - options: { - // @remove-on-eject-begin - babelrc: false, - presets: [require.resolve('babel-preset-react-app')], - // @remove-on-eject-end - compact: true, - }, + use: [ + require.resolve('cache-loader'), + { + loader: require.resolve('babel-loader'), + options: { + // @remove-on-eject-begin + babelrc: false, + presets: [require.resolve('babel-preset-react-app')], + // @remove-on-eject-end + compact: true, + }, + }, + ], }, // The notation here is somewhat confusing. // "postcss" loader applies autoprefixer to our CSS. @@ -199,6 +208,8 @@ module.exports = { { fallback: require.resolve('style-loader'), use: [ + require.resolve('cache-loader'), + { loader: require.resolve('css-loader'), options: { @@ -279,13 +290,23 @@ module.exports = { minifyURLs: true, }, }), + new AutoDllWebpackPlugin({ + env: process.env.NODE_ENV, + additionalHash: createHashFromPaths({ + paths: [paths.appNodeModules], + exclude: [path.join(paths.appNodeModules, '.cache')], + }), + inject: true, + filename: '[name].[hash].js', + }), // Makes some environment variables available to the JS code, for example: // if (process.env.NODE_ENV === 'production') { ... }. See `./env.js`. // It is absolutely essential that NODE_ENV was set to production here. // Otherwise React will be compiled in the very slow development mode. new webpack.DefinePlugin(env.stringified), // Minify the code. - new webpack.optimize.UglifyJsPlugin({ + new WebpackUglifyParallel({ + workers: os.cpus().length, compress: { warnings: false, // Disabled because of an issue with Uglify breaking seemingly valid code: diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index d0eddad69e5..d51e739234e 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -21,6 +21,7 @@ "react-scripts": "./bin/react-scripts.js" }, "dependencies": { + "autodll-webpack-plugin": "^0.2.1", "autoprefixer": "7.1.1", "babel-core": "6.25.0", "babel-eslint": "7.2.3", @@ -28,6 +29,7 @@ "babel-loader": "7.0.0", "babel-preset-react-app": "^3.0.1", "babel-runtime": "6.23.0", + "cache-loader": "^1.0.3", "case-sensitive-paths-webpack-plugin": "2.1.1", "chalk": "1.1.3", "css-loader": "0.28.4", @@ -56,6 +58,7 @@ "webpack": "2.6.1", "webpack-dev-server": "2.5.0", "webpack-manifest-plugin": "1.1.0", + "webpack-uglify-parallel": "^0.1.3", "whatwg-fetch": "2.0.3" }, "devDependencies": { From 20f0a19fcaa6e15ab3da0bc733a8462001db9aa6 Mon Sep 17 00:00:00 2001 From: Ade Viankakrisna Fadlil Date: Sat, 15 Jul 2017 05:05:28 +0700 Subject: [PATCH 2/4] use hash for setting up cache-loader cache location --- .gitignore | 1 - .../react-scripts/config/createCacheLoader.js | 18 ++++++++++++++++++ .../react-scripts/config/webpack.config.dev.js | 17 +++++++++++------ .../config/webpack.config.prod.js | 18 +++++++++++------- 4 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 packages/react-scripts/config/createCacheLoader.js diff --git a/.gitignore b/.gitignore index d9daac7e7e1..79ce88915d7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .idea/ .vscode/ -.cache-loader/ node_modules/ build .DS_Store diff --git a/packages/react-scripts/config/createCacheLoader.js b/packages/react-scripts/config/createCacheLoader.js new file mode 100644 index 00000000000..77a5535e1b7 --- /dev/null +++ b/packages/react-scripts/config/createCacheLoader.js @@ -0,0 +1,18 @@ +'use strict'; + +const path = require('path'); +module.exports = function(hash, paths) { + console.log(hash); + process.exit(); + return { + loader: require.resolve('cache-loader'), + options: { + cacheDirectory: path.join( + paths.appNodeModules, + '.cache', + 'cache-loader', + hash + ), + }, + }; +}; diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 21db4206b8b..d6246a3e2d0 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -20,6 +20,7 @@ const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeM const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const createHashFromPaths = require('react-dev-utils/createHashFromPaths'); +const createCacheLoader = require('./createCacheLoader'); const getClientEnvironment = require('./env'); const paths = require('./paths'); const AutoDllWebpackPlugin = require('autodll-webpack-plugin'); @@ -34,6 +35,13 @@ const publicUrl = ''; // Get environment variables to inject into our app. const env = getClientEnvironment(publicUrl); +const hash = createHashFromPaths({ + paths: [paths.appNodeModules], + exclude: [path.join(paths.appNodeModules, '.cache')], +}); + +const cacheLoader = createCacheLoader(hash, paths); + // This is the development configuration. // It is focused on developer experience and fast rebuilds. // The production configuration is different and lives in a separate file. @@ -173,7 +181,7 @@ module.exports = { test: /\.(js|jsx)$/, include: paths.appSrc, use: [ - require.resolve('cache-loader'), + cacheLoader, { loader: require.resolve('babel-loader'), options: { @@ -197,7 +205,7 @@ module.exports = { { test: /\.css$/, use: [ - require.resolve('cache-loader'), + cacheLoader, require.resolve('style-loader'), { loader: require.resolve('css-loader'), @@ -262,10 +270,7 @@ module.exports = { }), new AutoDllWebpackPlugin({ env: process.env.NODE_ENV, - additionalHash: createHashFromPaths({ - paths: [paths.appNodeModules], - exclude: [path.join(paths.appNodeModules, '.cache')], - }), + additionalHash: hash, inject: true, filename: '[name].[hash].js', entry: { diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 6fe65ffd215..a357af778c4 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -23,6 +23,7 @@ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const createHashFromPaths = require('react-dev-utils/createHashFromPaths'); const AutoDllWebpackPlugin = require('autodll-webpack-plugin'); const WebpackUglifyParallel = require('webpack-uglify-parallel'); +const createCacheLoader = require('./createCacheLoader'); const paths = require('./paths'); const getClientEnvironment = require('./env'); const os = require('os'); @@ -58,6 +59,13 @@ const extractTextPluginOptions = shouldUseRelativeAssetPaths { publicPath: Array(cssFilename.split('/').length).join('../') } : {}; +const hash = createHashFromPaths({ + paths: [paths.appNodeModules], + exclude: [path.join(paths.appNodeModules, '.cache')], +}); + +const cacheLoader = createCacheLoader(hash, paths); + // This is the production configuration. // It compiles slowly and is focused on producing a fast and minimal bundle. // The development configuration is different and lives in a separate file. @@ -176,7 +184,7 @@ module.exports = { test: /\.(js|jsx)$/, include: paths.appSrc, use: [ - require.resolve('cache-loader'), + cacheLoader, { loader: require.resolve('babel-loader'), options: { @@ -208,8 +216,7 @@ module.exports = { { fallback: require.resolve('style-loader'), use: [ - require.resolve('cache-loader'), - + cacheLoader, { loader: require.resolve('css-loader'), options: { @@ -292,10 +299,7 @@ module.exports = { }), new AutoDllWebpackPlugin({ env: process.env.NODE_ENV, - additionalHash: createHashFromPaths({ - paths: [paths.appNodeModules], - exclude: [path.join(paths.appNodeModules, '.cache')], - }), + additionalHash: hash, inject: true, filename: '[name].[hash].js', }), From ca0c4320b2cfaf79bc1c9011599d8c439e41560a Mon Sep 17 00:00:00 2001 From: Ade Viankakrisna Fadlil Date: Sat, 15 Jul 2017 05:14:44 +0700 Subject: [PATCH 3/4] use native tar cf if available --- .../react-dev-utils/createHashFromPaths.js | 23 +++++++++++-------- .../react-scripts/config/createCacheLoader.js | 2 -- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/react-dev-utils/createHashFromPaths.js b/packages/react-dev-utils/createHashFromPaths.js index a974aa069a9..6a0c8bce363 100644 --- a/packages/react-dev-utils/createHashFromPaths.js +++ b/packages/react-dev-utils/createHashFromPaths.js @@ -1,6 +1,6 @@ 'use strict'; - -const crypto = require('crypto'); +const { execSync } = require('child_process'); +// const crypto = require('crypto'); const path = require('path'); const fs = require('fs'); @@ -55,15 +55,18 @@ const getSourceMethod = key => { }; const createHashFromPaths = ({ paths, exclude, method = 'mtime' }) => { - const hash = crypto.createHash('md5'); - hash.update(paths.join('')); - if (Array.isArray(paths)) { - const sourceMethod = getSourceMethod(method); - const sources = getSources(paths, sourceMethod, exclude); - hash.update(sources); + try { + return String(execSync(`tar cf - ${paths.join(' ')} | md5`)); + } catch (ignored) { + const hash = crypto.createHash('md5'); + hash.update(paths.join('')); + if (Array.isArray(paths)) { + const sourceMethod = getSourceMethod(method); + const sources = getSources(paths, sourceMethod, exclude); + hash.update(sources); + } + return hash.digest('hex'); } - - return hash.digest('hex'); }; module.exports = createHashFromPaths; diff --git a/packages/react-scripts/config/createCacheLoader.js b/packages/react-scripts/config/createCacheLoader.js index 77a5535e1b7..a30c9d7f739 100644 --- a/packages/react-scripts/config/createCacheLoader.js +++ b/packages/react-scripts/config/createCacheLoader.js @@ -2,8 +2,6 @@ const path = require('path'); module.exports = function(hash, paths) { - console.log(hash); - process.exit(); return { loader: require.resolve('cache-loader'), options: { From b21f775aeeadee8628fde79758eff0bb12041b45 Mon Sep 17 00:00:00 2001 From: Ade Viankakrisna Fadlil Date: Sat, 15 Jul 2017 05:33:10 +0700 Subject: [PATCH 4/4] add exclude option to tar --- packages/react-dev-utils/createHashFromPaths.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/react-dev-utils/createHashFromPaths.js b/packages/react-dev-utils/createHashFromPaths.js index 6a0c8bce363..543ee04a800 100644 --- a/packages/react-dev-utils/createHashFromPaths.js +++ b/packages/react-dev-utils/createHashFromPaths.js @@ -1,6 +1,6 @@ 'use strict'; const { execSync } = require('child_process'); -// const crypto = require('crypto'); +const crypto = require('crypto'); const path = require('path'); const fs = require('fs'); @@ -54,9 +54,14 @@ const getSourceMethod = key => { process.exit(1); }; +const toRelative = filePath => path.relative(process.cwd(), filePath); + const createHashFromPaths = ({ paths, exclude, method = 'mtime' }) => { try { - return String(execSync(`tar cf - ${paths.join(' ')} | md5`)); + const fileList = paths.map(toRelative).join(' '); + const excludedPaths = exclude.map(toRelative).join(' '); + const command = `tar --exclude ${excludedPaths} -cf - ${fileList} | md5`; + return String(execSync(command)); } catch (ignored) { const hash = crypto.createHash('md5'); hash.update(paths.join(''));