diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 42ec8374a15..5934e73fa8d 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -55,6 +55,7 @@ module.exports = { appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), appIndexJs: resolveApp('src/index.js'), + appVendorJs: resolveApp('src/vendor.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), yarnLockFile: resolveApp('yarn.lock'), @@ -75,6 +76,7 @@ module.exports = { appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), appIndexJs: resolveApp('src/index.js'), + appVendorJs: resolveApp('src/vendor.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), yarnLockFile: resolveApp('yarn.lock'), @@ -104,6 +106,7 @@ if ( appPublic: resolveOwn('template/public'), appHtml: resolveOwn('template/public/index.html'), appIndexJs: resolveOwn('template/src/index.js'), + appVendorJs: resolveOwn('template/src/vendor.js'), appPackageJson: resolveOwn('package.json'), appSrc: resolveOwn('template/src'), yarnLockFile: resolveOwn('template/yarn.lock'), diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 3de2775e030..32231f118ec 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -12,8 +12,11 @@ const autoprefixer = require('autoprefixer'); const path = require('path'); +const fs = require('fs'); const webpack = require('webpack'); +const NameAllModulesPlugin = require('name-all-modules-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); +const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ManifestPlugin = require('webpack-manifest-plugin'); const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); @@ -53,6 +56,18 @@ const extractTextPluginOptions = shouldUseRelativeAssetPaths ? // Making sure that the publicPath goes back to to build folder. { publicPath: Array(cssFilename.split('/').length).join('../') } : {}; +// Check if vendor file exists +const checkIfVendorFileExists = fs.existsSync(paths.appVendorJs); +// If the vendor file exists, add an entry point for vendor, +// and a seperate entry for polyfills and app index file, +// otherwise keep only polyfills and app index. +const appEntryFiles = [require.resolve('./polyfills'), paths.appIndexJs]; +const entryFiles = checkIfVendorFileExists + ? { + vendor: paths.appVendorJs, + main: appEntryFiles, + } + : appEntryFiles; // This is the production configuration. // It compiles slowly and is focused on producing a fast and minimal bundle. @@ -63,8 +78,8 @@ module.exports = { // We generate sourcemaps in production. This is slow but gives good results. // You can exclude the *.map files from the build during deployment. devtool: 'source-map', - // In production, we only want to load the polyfills and the app code. - entry: [require.resolve('./polyfills'), paths.appIndexJs], + // Add the entry point based on whether vendor file exists. + entry: entryFiles, output: { // The build folder. path: paths.appBuild, @@ -250,6 +265,41 @@ module.exports = { ], }, plugins: [ + // configuration for vendor splitting and long term caching + // more info: https://medium.com/webpack/predictable-long-term-caching-with-webpack-d3eee1d3fa31 + new webpack.NamedModulesPlugin(), + new webpack.NamedChunksPlugin(chunk => { + if (chunk.name) { + return chunk.name; + } + return chunk.modules + .map(m => path.relative(m.context, m.request)) + .join('_'); + }), + new webpack.optimize.CommonsChunkPlugin( + // Check if vendor file exists, if it does, + // generate a seperate chucks for vendor + // else don't generate any common chunck + checkIfVendorFileExists + ? { + name: 'vendor', + minChunks: Infinity, + } + : { names: [] } + ), + // We need to extract out the runtime into a separate manifest file. + // more info: https://webpack.js.org/guides/code-splitting-libraries/#manifest-file + new webpack.optimize.CommonsChunkPlugin( + // Check if vendor file exists, if it does, + // generate a seperate chucks for manifest file + // else don't generate any common chunck + checkIfVendorFileExists + ? { + name: 'manifest', + } + : { names: [] } + ), + new NameAllModulesPlugin(), // Makes some environment variables available in index.html. // The public URL is available as %PUBLIC_URL% in index.html, e.g.: // @@ -273,6 +323,11 @@ module.exports = { minifyURLs: true, }, }), + // This ensures that the browser will load the scripts in parallel, + // but execute them in the order they appear in the document. + new ScriptExtHtmlWebpackPlugin({ + defaultAttribute: 'defer', + }), // 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. diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 06b0d6b76c4..5fcbbf83b7a 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -44,12 +44,14 @@ "fs-extra": "3.0.1", "html-webpack-plugin": "2.28.0", "jest": "20.0.3", + "name-all-modules-plugin": "^1.0.1", "object-assign": "4.1.1", "postcss-flexbugs-fixes": "3.0.0", "postcss-loader": "2.0.5", "promise": "7.1.1", "react-dev-utils": "^2.0.0", "react-error-overlay": "^1.0.5", + "script-ext-html-webpack-plugin": "^1.8.0", "style-loader": "0.17.0", "sw-precache-webpack-plugin": "0.9.1", "url-loader": "0.5.8",