From b04b4f9cac8caf756102d9e2b968ae38ce890fca Mon Sep 17 00:00:00 2001 From: William Monk Date: Sat, 29 Sep 2018 08:49:15 +0100 Subject: [PATCH 1/3] Merge react-scripts@2.x.x upstream --- bin/react-scripts-ts.js | 7 + config/env.js | 15 +- config/jest/babelTransform.js | 1 + config/jest/fileTransform.js | 12 +- config/paths.js | 23 +- config/webpack.config.dev.js | 327 ++++-- config/webpack.config.prod.js | 539 +++++---- config/webpackDevServer.config.js | 32 +- package.json | 95 +- scripts/build.js | 54 +- scripts/eject.js | 113 +- scripts/init.js | 134 ++- scripts/start.js | 25 +- scripts/test.js | 9 +- scripts/utils/createJestConfig.js | 89 +- scripts/utils/verifyPackageTree.js | 153 +++ template/README.md | 1034 ++++++++--------- template/gitignore | 2 +- template/images.d.ts | 3 + template/public/index.html | 2 +- template/public/manifest.json | 2 +- template/src/App.css | 37 +- template/src/App.tsx | 25 +- template/src/index.css | 11 +- template/src/index.tsx | 8 +- ...isterServiceWorker.ts => serviceWorker.ts} | 64 +- 26 files changed, 1742 insertions(+), 1074 deletions(-) create mode 100644 scripts/utils/verifyPackageTree.js rename template/src/{registerServiceWorker.ts => serviceWorker.ts} (64%) diff --git a/bin/react-scripts-ts.js b/bin/react-scripts-ts.js index a4d1b11b1..dddf2e0ae 100755 --- a/bin/react-scripts-ts.js +++ b/bin/react-scripts-ts.js @@ -8,6 +8,13 @@ 'use strict'; +// Makes the script crash on unhandled rejections instead of silently +// ignoring them. In the future, promise rejections that are not handled will +// terminate the Node.js process with a non-zero exit code. +process.on('unhandledRejection', err => { + throw err; +}); + const spawn = require('react-dev-utils/crossSpawn'); const args = process.argv.slice(2); diff --git a/config/env.js b/config/env.js index 7793fadf0..7565cecd0 100644 --- a/config/env.js +++ b/config/env.js @@ -50,12 +50,12 @@ dotenvFiles.forEach(dotenvFile => { // We support resolving modules according to `NODE_PATH`. // This lets you use absolute paths in imports inside large monorepos: -// https://github.com/facebookincubator/create-react-app/issues/253. +// https://github.com/facebook/create-react-app/issues/253. // It works similar to `NODE_PATH` in Node itself: // https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders // Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. // Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. -// https://github.com/facebookincubator/create-react-app/issues/1023#issuecomment-265344421 +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 // We also resolve them to make sure all tools using them work consistently. const appDirectory = fs.realpathSync(process.cwd()); process.env.NODE_PATH = (process.env.NODE_PATH || '') @@ -89,13 +89,10 @@ function getClientEnvironment(publicUrl) { ); // Stringify all values so we can feed into Webpack DefinePlugin const stringified = { - 'process.env': Object.keys(raw).reduce( - (env, key) => { - env[key] = JSON.stringify(raw[key]); - return env; - }, - {} - ), + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), }; return { raw, stringified }; diff --git a/config/jest/babelTransform.js b/config/jest/babelTransform.js index 02742e90c..7feed94c5 100644 --- a/config/jest/babelTransform.js +++ b/config/jest/babelTransform.js @@ -12,4 +12,5 @@ const babelJest = require('babel-jest'); module.exports = babelJest.createTransformer({ presets: [require.resolve('babel-preset-react-app')], babelrc: false, + configFile: false, }); diff --git a/config/jest/fileTransform.js b/config/jest/fileTransform.js index 38910e18b..b5aa17e0f 100644 --- a/config/jest/fileTransform.js +++ b/config/jest/fileTransform.js @@ -15,6 +15,16 @@ const path = require('path'); module.exports = { process(src, filename) { - return `module.exports = ${JSON.stringify(path.basename(filename))};`; + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + return `module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: () => ${assetFilename}, + };`; + } + + return `module.exports = ${assetFilename};`; }, }; diff --git a/config/paths.js b/config/paths.js index cc6891d6a..fe5fd4120 100644 --- a/config/paths.js +++ b/config/paths.js @@ -13,20 +13,20 @@ const fs = require('fs'); const url = require('url'); // Make sure any symlinks in the project folder are resolved: -// https://github.com/facebookincubator/create-react-app/issues/637 +// https://github.com/facebook/create-react-app/issues/637 const appDirectory = fs.realpathSync(process.cwd()); const resolveApp = relativePath => path.resolve(appDirectory, relativePath); const envPublicUrl = process.env.PUBLIC_URL; -function ensureSlash(path, needsSlash) { - const hasSlash = path.endsWith('/'); +function ensureSlash(inputPath, needsSlash) { + const hasSlash = inputPath.endsWith('/'); if (hasSlash && !needsSlash) { - return path.substr(path, path.length - 1); + return inputPath.substr(0, inputPath.length - 1); } else if (!hasSlash && needsSlash) { - return `${path}/`; + return `${inputPath}/`; } else { - return path; + return inputPath; } } @@ -41,14 +41,15 @@ const getPublicUrl = appPackageJson => // like /todos/42/static/js/bundle.7289d.js. We have to know the root. function getServedPath(appPackageJson) { const publicUrl = getPublicUrl(appPackageJson); - const servedUrl = envPublicUrl || - (publicUrl ? url.parse(publicUrl).pathname : '/'); + const servedUrl = + envPublicUrl || (publicUrl ? url.parse(publicUrl).pathname : '/'); return ensureSlash(servedUrl, true); } // config after eject: we're in ./config/ module.exports = { dotenv: resolveApp('.env'), + appPath: resolveApp('.'), appBuild: resolveApp('build'), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), @@ -57,6 +58,7 @@ module.exports = { appSrc: resolveApp('src'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.ts'), + proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), appTsConfig: resolveApp('tsconfig.json'), appTsProdConfig: resolveApp('tsconfig.prod.json'), @@ -80,6 +82,7 @@ module.exports = { appSrc: resolveApp('src'), yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.ts'), + proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), appTsConfig: resolveApp('tsconfig.json'), appTsProdConfig: resolveApp('tsconfig.prod.json'), @@ -94,7 +97,8 @@ module.exports = { const ownPackageJson = require('../package.json'); const reactScriptsPath = resolveApp(`node_modules/${ownPackageJson.name}`); -const reactScriptsLinked = fs.existsSync(reactScriptsPath) && +const reactScriptsLinked = + fs.existsSync(reactScriptsPath) && fs.lstatSync(reactScriptsPath).isSymbolicLink(); // config before publish: we're in ./packages/react-scripts/config/ @@ -113,6 +117,7 @@ if ( appSrc: resolveOwn('template/src'), yarnLockFile: resolveOwn('template/yarn.lock'), testsSetup: resolveOwn('template/src/setupTests.ts'), + proxySetup: resolveOwn('template/src/setupProxy.js'), appNodeModules: resolveOwn('node_modules'), appTsConfig: resolveOwn('template/tsconfig.json'), appTsProdConfig: resolveOwn('template/tsconfig.prod.json'), diff --git a/config/webpack.config.dev.js b/config/webpack.config.dev.js index 924e3e4f0..57f60d8d9 100644 --- a/config/webpack.config.dev.js +++ b/config/webpack.config.dev.js @@ -8,7 +8,6 @@ // @remove-on-eject-end 'use strict'; -const autoprefixer = require('autoprefixer'); const path = require('path'); const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); @@ -17,9 +16,12 @@ const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const getClientEnvironment = require('./env'); const paths = require('./paths'); const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); +const ManifestPlugin = require('webpack-manifest-plugin'); +const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); // Webpack uses `publicPath` to determine where the app is being served from. // In development, we always serve from the root. This makes config easier. @@ -31,19 +33,58 @@ const publicUrl = ''; // Get environment variables to inject into our app. const env = getClientEnvironment(publicUrl); +// style files regexes +const cssRegex = /\.css$/; +const cssModuleRegex = /\.module\.css$/; +const sassRegex = /\.(scss|sass)$/; +const sassModuleRegex = /\.module\.(scss|sass)$/; + +// common function to get style loaders +const getStyleLoaders = (cssOptions, preProcessor) => { + const loaders = [ + require.resolve('style-loader'), + { + loader: require.resolve('css-loader'), + options: cssOptions, + }, + { + // Options for PostCSS as we reference these options twice + // Adds vendor prefixing based on your specified browser support in + // package.json + loader: require.resolve('postcss-loader'), + options: { + // Necessary for external CSS imports to work + // https://github.com/facebook/create-react-app/issues/2677 + ident: 'postcss', + plugins: () => [ + require('postcss-flexbugs-fixes'), + require('postcss-preset-env')({ + autoprefixer: { + flexbox: 'no-2009', + }, + stage: 3, + }), + ], + }, + }, + ]; + if (preProcessor) { + loaders.push(require.resolve(preProcessor)); + } + return loaders; +}; + // 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. module.exports = { + mode: 'development', // You may want 'eval' instead if you prefer to see the compiled output in DevTools. - // See the discussion in https://github.com/facebookincubator/create-react-app/issues/343. + // See the discussion in https://github.com/facebook/create-react-app/issues/343 devtool: 'cheap-module-source-map', // These are the "entry points" to our application. // This means they will be the "root" imports that are included in JS bundle. - // The first two entry points enable "hot" CSS and auto-refreshes for JS. entry: [ - // We ship a few polyfills by default: - require.resolve('./polyfills'), // Include an alternative client for WebpackDevServer. A client's job is to // connect to WebpackDevServer by a socket and get notified about changes. // When you save a file, the client will either apply hot updates (in case @@ -76,43 +117,35 @@ module.exports = { devtoolModuleFilenameTemplate: info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/'), }, + optimization: { + // Automatically split vendor and commons + // https://twitter.com/wSokra/status/969633336732905474 + // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 + splitChunks: { + chunks: 'all', + name: false, + }, + // Keep the runtime chunk seperated to enable long term caching + // https://twitter.com/wSokra/status/969679223278505985 + runtimeChunk: true, + }, 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( + // https://github.com/facebook/create-react-app/issues/253 + modules: ['node_modules'].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 + // https://github.com/facebook/create-react-app/issues/290 // `web` extension prefixes have been added for better support // for React Native Web. - extensions: [ - '.mjs', - '.web.ts', - '.ts', - '.web.tsx', - '.tsx', - '.web.js', - '.js', - '.json', - '.web.jsx', - '.jsx', - ], + extensions: ['.web.ts', '.ts', '.web.tsx', '.tsx', '.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', @@ -130,16 +163,27 @@ module.exports = { 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 } }, + // Disable require.ensure as it's not a standard language feature. + { parser: { requireEnsure: false } }, + // First, run the linter. + // It's important to do this before Babel processes the JS. { test: /\.(js|jsx|mjs)$/, loader: require.resolve('source-map-loader'), enforce: 'pre', include: paths.appSrc, }, + { + // `mjs` support is still in its infancy in the ecosystem, so we don't + // support it. + // Modules who define their `browser` or `module` key as `mjs` force + // the use of this extension, so we need to tell webpack to fall back + // to auto mode (ES Module interop, allows ESM to import CommonJS). + test: /\.mjs$/, + include: /node_modules/, + type: 'javascript/auto', + }, { // "oneOf" will traverse all following loaders until one will // match the requirements. When no loader matches it will fall @@ -157,69 +201,169 @@ module.exports = { }, }, { - test: /\.(js|jsx|mjs)$/, - 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, - }, - }, - - // Compile .tsx? + test: /\.(ts|tsx)$/, + include: paths.appSrc, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + // disable type checker - we will use it in fork plugin + transpileOnly: true, + }, + }, + ], + }, + // Process application JS with Babel. + // The preset includes JSX, Flow, and some ESnext features. { - test: /\.(ts|tsx)$/, + test: /\.(js|jsx)$/, include: paths.appSrc, use: [ + // This loader parallelizes code compilation, it is optional but + // improves compile time on larger projects { - loader: require.resolve('ts-loader'), + loader: require.resolve('thread-loader'), options: { - // disable type checker - we will use it in fork plugin - transpileOnly: true, + poolTimeout: Infinity, // keep workers alive for more effective watch mode + }, + }, + { + loader: require.resolve('babel-loader'), + options: { + customize: require.resolve( + 'babel-preset-react-app/webpack-overrides' + ), + // @remove-on-eject-begin + babelrc: false, + configFile: false, + presets: [require.resolve('babel-preset-react-app')], + // Make sure we have a unique cache identifier, erring on the + // side of caution. + // We remove this when the user ejects because the default + // is sane and uses Babel options. Instead of options, we use + // the react-scripts and babel-preset-react-app versions. + cacheIdentifier: getCacheIdentifier('development', [ + 'babel-plugin-named-asset-import', + 'babel-preset-react-app', + 'react-dev-utils', + 'react-scripts', + ]), + // @remove-on-eject-end + plugins: [ + [ + require.resolve('babel-plugin-named-asset-import'), + { + loaderMap: { + svg: { + ReactComponent: + '@svgr/webpack?-prettier,-svgo![path]', + }, + }, + }, + ], + ], + // 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, + // Don't waste time on Gzipping the cache + cacheCompression: false, }, }, ], }, - // "postcss" loader applies autoprefixer to our CSS. - // "css" loader resolves paths in CSS and adds assets as dependencies. - // "style" loader turns CSS into JS modules that inject