diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index d80351ec63f..0661c8de161 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -22,6 +22,7 @@ const eslintFormatter = require('react-dev-utils/eslintFormatter'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const paths = require('./paths'); const getClientEnvironment = require('./env'); +const UglifyJSPlugin = require('uglifyjs-webpack-plugin'); // 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. @@ -56,308 +57,358 @@ const extractTextPluginOptions = shouldUseRelativeAssetPaths { publicPath: Array(cssFilename.split('/').length).join('../') } : {}; +const getBabelPresetTarget = target => { + let babelPresetTarget; + if (target === 'module') { + // build an es6 compatible bundle + // https://philipwalton.com/articles/deploying-es2015-code-in-production-today/ + // target all browsers which support the "module" attribute on script + babelPresetTarget = { + browsers: [ + 'Chrome >= 60', + 'Safari >= 10.1', + 'iOS >= 10.3', + 'Firefox >= 54', + 'Edge >= 15', + ], + }; + } else { + babelPresetTarget = { + // React parses on ie 9, so we should too + ie: 9, + // We currently minify with uglify + // Remove after https://github.com/mishoo/UglifyJS2/issues/448 + uglify: true, + }; + } + return babelPresetTarget; +}; + +const getBabelPreset = (babelPresetObj, presetTarget) => { + const clonedBabelPresetObj = Object.create(babelPresetObj); + clonedBabelPresetObj.presets[0][1].targets = presetTarget; + return clonedBabelPresetObj; +}; + +const getEntryPoints = target => { + if (target === 'module') { + return [paths.appIndexJs]; + } + return [require.resolve('./polyfills'), paths.appIndexJs]; +}; + // 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. -module.exports = { - // Don't attempt to continue if there are any errors. - bail: true, - // We generate sourcemaps in production. This is slow but gives good results. - // You can exclude the *.map files from the build during deployment. - devtool: shouldUseSourceMap ? 'source-map' : false, - // In production, we only want to load the polyfills and the app code. - entry: [require.resolve('./polyfills'), paths.appIndexJs], - output: { - // The build folder. - path: paths.appBuild, - // Generated JS file names (with nested folders). - // There will be one main bundle, and one file per asynchronous chunk. - // We don't currently advertise code splitting but Webpack supports it. - filename: 'static/js/[name].[chunkhash:8].js', - chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js', - // We inferred the "public path" (such as / or /my-project) from homepage. - publicPath: publicPath, - // Point sourcemap entries to original disk location (format as URL on Windows) - devtoolModuleFilenameTemplate: info => - path - .relative(paths.appSrc, info.absoluteResourcePath) - .replace(/\\/g, '/'), - }, - resolve: { - // This allows you to set a fallback for where Webpack should look for modules. - // 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', paths.appNodeModules].concat( - // It is guaranteed to exist because we tweak it in `env.js` - process.env.NODE_PATH.split(path.delimiter).filter(Boolean) - ), - // 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 - // `web` extension prefixes have been added for better support - // for React Native Web. - extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx'], - alias: { - // @remove-on-eject-begin - // Resolve Babel runtime relative to react-scripts. - // It usually still works on npm 3 without this but it would be - // unfortunate to rely on, as react-scripts could be symlinked, - // and thus babel-runtime might not be resolvable from the source. - 'babel-runtime': path.dirname( - require.resolve('babel-runtime/package.json') +module.exports = target => { + const babelPresetTarget = getBabelPresetTarget(target); + const babelPreset = getBabelPreset( + require('babel-preset-react-app'), + babelPresetTarget + ); + const es6Suffix = target === 'module' ? '.es6' : ''; + const es6Regex = target === 'module' ? '.es6' : ''; + + return { + // Don't attempt to continue if there are any errors. + bail: true, + // We generate sourcemaps in production. This is slow but gives good results. + // You can exclude the *.map files from the build during deployment. + devtool: shouldUseSourceMap ? 'source-map' : false, + // In production, we only want to load the polyfills and the app code. + entry: getEntryPoints(target), + output: { + // The build folder. + path: paths.appBuild, + // Generated JS file names (with nested folders). + // There will be one main bundle, and one file per asynchronous chunk. + // We don't currently advertise code splitting but Webpack supports it. + filename: 'static/js/[name].[chunkhash:8]' + es6Suffix + '.js', + chunkFilename: 'static/js/[name].[chunkhash:8]' + es6Suffix + '.js', + // We inferred the "public path" (such as / or /my-project) from homepage. + publicPath: publicPath, + // Point sourcemap entries to original disk location (format as URL on Windows) + devtoolModuleFilenameTemplate: info => + path + .relative(paths.appSrc, info.absoluteResourcePath) + .replace(/\\/g, '/'), + }, + resolve: { + // This allows you to set a fallback for where Webpack should look for modules. + // 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', paths.appNodeModules].concat( + // It is guaranteed to exist because we tweak it in `env.js` + process.env.NODE_PATH.split(path.delimiter).filter(Boolean) ), - // @remove-on-eject-end - // 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', + // 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 + // `web` extension prefixes have been added for better support + // for React Native Web. + extensions: ['.web.js', '.js', '.json', '.web.jsx', '.jsx'], + alias: { + // @remove-on-eject-begin + // Resolve Babel runtime relative to react-scripts. + // It usually still works on npm 3 without this but it would be + // unfortunate to rely on, as react-scripts could be symlinked, + // and thus babel-runtime might not be resolvable from the source. + 'babel-runtime': path.dirname( + require.resolve('babel-runtime/package.json') + ), + // @remove-on-eject-end + // 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', + }, + plugins: [ + // Prevents users from importing files from outside of src/ (or node_modules/). + // This often causes confusion because we only process files within src/ with babel. + // To fix this, we prevent you from importing files out of src/ -- if you'd like to, + // please link the files into your node_modules/ and let module-resolution kick in. + // Make sure your source files are compiled, as they will not be processed in any way. + new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), + ], }, - plugins: [ - // Prevents users from importing files from outside of src/ (or node_modules/). - // This often causes confusion because we only process files within src/ with babel. - // To fix this, we prevent you from importing files out of src/ -- if you'd like to, - // please link the files into your node_modules/ and let module-resolution kick in. - // Make sure your source files are compiled, as they will not be processed in any way. - new ModuleScopePlugin(paths.appSrc, [paths.appPackageJson]), - ], - }, - module: { - strictExportPresence: true, - rules: [ - // TODO: Disable require.ensure as it's not a standard language feature. - // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. - // { parser: { requireEnsure: false } }, + module: { + strictExportPresence: true, + rules: [ + // TODO: Disable require.ensure as it's not a standard language feature. + // We are waiting for https://github.com/facebookincubator/create-react-app/issues/2176. + // { parser: { requireEnsure: false } }, - // First, run the linter. - // It's important to do this before Babel processes the JS. - { - test: /\.(js|jsx)$/, - enforce: 'pre', - use: [ - { - options: { - formatter: eslintFormatter, - eslintPath: require.resolve('eslint'), - // @remove-on-eject-begin - // TODO: consider separate config for production, - // e.g. to enable no-console and no-debugger only in production. - baseConfig: { - extends: [require.resolve('eslint-config-react-app')], + // First, run the linter. + // It's important to do this before Babel processes the JS. + { + test: /\.(js|jsx)$/, + enforce: 'pre', + use: [ + { + options: { + formatter: eslintFormatter, + eslintPath: require.resolve('eslint'), + // @remove-on-eject-begin + // TODO: consider separate config for production, + // e.g. to enable no-console and no-debugger only in production. + baseConfig: { + extends: [require.resolve('eslint-config-react-app')], + }, + ignore: false, + useEslintrc: false, + // @remove-on-eject-end }, - ignore: false, - useEslintrc: false, - // @remove-on-eject-end + loader: require.resolve('eslint-loader'), }, - loader: require.resolve('eslint-loader'), - }, - ], - include: paths.appSrc, - }, - { - // "oneOf" will traverse all following loaders until one will - // match the requirements. When no loader matches it will fall - // back to the "file" loader at the end of the loader list. - oneOf: [ - // "url" loader works just like "file" loader but it also embeds - // assets smaller than specified size as data URLs to avoid requests. - { - test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], - loader: require.resolve('url-loader'), - options: { - limit: 10000, - name: 'static/media/[name].[hash:8].[ext]', + ], + include: paths.appSrc, + }, + { + // "oneOf" will traverse all following loaders until one will + // match the requirements. When no loader matches it will fall + // back to the "file" loader at the end of the loader list. + oneOf: [ + // "url" loader works just like "file" loader but it also embeds + // assets smaller than specified size as data URLs to avoid requests. + { + test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/], + loader: require.resolve('url-loader'), + options: { + limit: 10000, + name: 'static/media/[name].[hash:8].[ext]', + }, }, - }, - // Process JS with Babel. - { - 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, + // Process JS with Babel. + { + test: /\.(js|jsx)$/, + include: paths.appSrc, + loader: require.resolve('babel-loader'), + options: { + // @remove-on-eject-begin + babelrc: false, + presets: [babelPreset], + // @remove-on-eject-end + compact: true, + }, }, - }, - // The notation here is somewhat confusing. - // "postcss" loader applies autoprefixer to our CSS. - // "css" loader resolves paths in CSS and adds assets as dependencies. - // "style" loader normally turns CSS into JS modules injecting