diff --git a/packages/react-app-tools/config/paths.js b/packages/react-app-tools/config/paths.js index 5d8c7aaa..8cd5cf43 100644 --- a/packages/react-app-tools/config/paths.js +++ b/packages/react-app-tools/config/paths.js @@ -11,8 +11,7 @@ const path = require('path'); const fs = require('fs'); const url = require('url'); -const findPkg = require('find-pkg'); -const globby = require('globby'); +const findMonorepo = require('react-dev-utils/workspaceUtils').findMonorepo; // Make sure any symlinks in the project folder are resolved: // https://github.com/facebook/create-react-app/issues/637 @@ -58,7 +57,6 @@ module.exports = { appIndexJs: resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), - yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), publicUrl: getPublicUrl(resolveApp('package.json')), @@ -80,7 +78,6 @@ module.exports = { appIndexJs: resolveApp('src/index.js'), appPackageJson: resolveApp('package.json'), appSrc: resolveApp('src'), - yarnLockFile: resolveApp('yarn.lock'), testsSetup: resolveApp('src/setupTests.js'), appNodeModules: resolveApp('node_modules'), publicUrl: getPublicUrl(resolveApp('package.json')), @@ -106,7 +103,6 @@ if (useTemplate) { appIndexJs: resolveOwn('template/src/index.js'), appPackageJson: resolveOwn('package.json'), appSrc: resolveOwn('template/src'), - yarnLockFile: resolveOwn('template/yarn.lock'), testsSetup: resolveOwn('template/src/setupTests.js'), appNodeModules: resolveOwn('node_modules'), publicUrl: getPublicUrl(resolveOwn('package.json')), @@ -120,40 +116,16 @@ if (useTemplate) { module.exports.srcPaths = [module.exports.appSrc]; -const findPkgs = (rootPath, globPatterns) => { - const globOpts = { - cwd: rootPath, - strict: true, - absolute: true, - }; - return globPatterns - .reduce( - (pkgs, pattern) => - pkgs.concat(globby.sync(path.join(pattern, 'package.json'), globOpts)), - [] - ) - .map(f => path.dirname(path.normalize(f))); -}; - -const getMonorepoPkgPaths = () => { - const monoPkgPath = findPkg.sync(path.resolve(appDirectory, '..')); - if (monoPkgPath) { - // get monorepo config from yarn workspace - const pkgPatterns = require(monoPkgPath).workspaces; - if (pkgPatterns == null) { - return []; - } - const pkgPaths = findPkgs(path.dirname(monoPkgPath), pkgPatterns); - // only include monorepo pkgs if app itself is included in monorepo - if (pkgPaths.indexOf(appDirectory) !== -1) { - return pkgPaths.filter(f => fs.realpathSync(f) !== appDirectory); - } - } - return []; -}; +module.exports.useYarn = fs.existsSync( + path.join(module.exports.appPath, 'yarn.lock') +); if (checkForMonorepo) { // if app is in a monorepo (lerna or yarn workspace), treat other packages in // the monorepo as if they are app source - Array.prototype.push.apply(module.exports.srcPaths, getMonorepoPkgPaths()); + const mono = findMonorepo(appDirectory); + if (mono.isAppIncluded) { + Array.prototype.push.apply(module.exports.srcPaths, mono.pkgs); + } + module.exports.useYarn = module.exports.useYarn || mono.isYarnWs; } diff --git a/packages/react-app-tools/config/webpack.config.dev.js b/packages/react-app-tools/config/webpack.config.dev.js index 398b8bf5..7a1b6680 100644 --- a/packages/react-app-tools/config/webpack.config.dev.js +++ b/packages/react-app-tools/config/webpack.config.dev.js @@ -17,6 +17,7 @@ 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 getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const getClientEnvironment = require('./env'); const paths = require('./paths'); @@ -45,6 +46,31 @@ const postCSSLoaderOptions = { ], }; +// 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, + }, + { + loader: require.resolve('postcss-loader'), + options: postCSSLoaderOptions, + }, + ]; + 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. @@ -242,41 +268,44 @@ module.exports = { // in development "style" loader enables hot editing of CSS. // By default we support CSS Modules with the extension .module.css { - test: /\.css$/, - exclude: /\.module\.css$/, - use: [ - require.resolve('style-loader'), - { - loader: require.resolve('css-loader'), - options: { - importLoaders: 1, - }, - }, - { - loader: require.resolve('postcss-loader'), - options: postCSSLoaderOptions, - }, - ], + test: cssRegex, + exclude: cssModuleRegex, + use: getStyleLoaders({ + importLoaders: 1, + }), }, // Adds support for CSS Modules (https://github.com/css-modules/css-modules) // using the extension .module.css { - test: /\.module\.css$/, - use: [ - require.resolve('style-loader'), - { - loader: require.resolve('css-loader'), - options: { - importLoaders: 1, - modules: true, - localIdentName: '[path]__[name]___[local]', - }, - }, + test: cssModuleRegex, + use: getStyleLoaders({ + importLoaders: 1, + modules: true, + getLocalIdent: getCSSModuleLocalIdent, + }), + }, + // Opt-in support for SASS (using .scss or .sass extensions). + // Chains the sass-loader with the css-loader and the style-loader + // to immediately apply all styles to the DOM. + // By default we support SASS Modules with the + // extensions .module.scss or .module.sass + { + test: sassRegex, + exclude: sassModuleRegex, + use: getStyleLoaders({ importLoaders: 2 }, 'sass-loader'), + }, + // Adds support for CSS Modules, but using SASS + // using the extension .module.scss or .module.sass + { + test: sassModuleRegex, + use: getStyleLoaders( { - loader: require.resolve('postcss-loader'), - options: postCSSLoaderOptions, + importLoaders: 2, + modules: true, + getLocalIdent: getCSSModuleLocalIdent, }, - ], + 'sass-loader' + ), }, // The GraphQL loader preprocesses GraphQL queries in .graphql files. { diff --git a/packages/react-app-tools/config/webpack.config.prod.js b/packages/react-app-tools/config/webpack.config.prod.js index 7b73dd78..e05c8ae0 100644 --- a/packages/react-app-tools/config/webpack.config.prod.js +++ b/packages/react-app-tools/config/webpack.config.prod.js @@ -19,6 +19,7 @@ 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 getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); const paths = require('./paths'); const getClientEnvironment = require('./env'); @@ -68,6 +69,49 @@ const postCSSLoaderOptions = { flexbox: 'no-2009', }), ], + sourceMap: shouldUseSourceMap, +}; + +// 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 = [ + { + loader: require.resolve('css-loader'), + options: cssOptions, + }, + { + loader: require.resolve('postcss-loader'), + options: postCSSLoaderOptions, + }, + ]; + if (preProcessor) { + loaders.push({ + loader: require.resolve(preProcessor), + options: { + sourceMap: shouldUseSourceMap, + }, + }); + } + return ExtractTextPlugin.extract( + Object.assign( + { + fallback: { + loader: require.resolve('style-loader'), + options: { + hmr: false, + }, + }, + use: loaders, + }, + extractTextPluginOptions + ) + ); }; // This is the production configuration. @@ -254,69 +298,59 @@ module.exports = { // in the main CSS file. // By default we support CSS Modules with the extension .module.css { - test: /\.css$/, - exclude: /\.module\.css$/, - loader: ExtractTextPlugin.extract( - Object.assign( - { - fallback: { - loader: require.resolve('style-loader'), - options: { - hmr: false, - }, - }, - use: [ - { - loader: require.resolve('css-loader'), - options: { - importLoaders: 1, - minimize: true, - sourceMap: shouldUseSourceMap, - }, - }, - { - loader: require.resolve('postcss-loader'), - options: postCSSLoaderOptions, - }, - ], - }, - extractTextPluginOptions - ) - ), + test: cssRegex, + exclude: cssModuleRegex, + loader: getStyleLoaders({ + importLoaders: 1, + minimize: true, + sourceMap: shouldUseSourceMap, + }), // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. }, // Adds support for CSS Modules (https://github.com/css-modules/css-modules) // using the extension .module.css { - test: /\.module\.css$/, - loader: ExtractTextPlugin.extract( - Object.assign( - { - fallback: { - loader: require.resolve('style-loader'), - options: { - hmr: false, - }, - }, - use: [ - { - loader: require.resolve('css-loader'), - options: { - importLoaders: 1, - minimize: true, - sourceMap: shouldUseSourceMap, - modules: true, - localIdentName: '[path]__[name]___[local]', - }, - }, - { - loader: require.resolve('postcss-loader'), - options: postCSSLoaderOptions, - }, - ], - }, - extractTextPluginOptions - ) + test: cssRegex, + loader: getStyleLoaders({ + importLoaders: 1, + minimize: true, + sourceMap: shouldUseSourceMap, + modules: true, + getLocalIdent: getCSSModuleLocalIdent, + }), + // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. + }, + // Opt-in support for SASS. The logic here is somewhat similar + // as in the CSS routine, except that "sass-loader" runs first + // to compile SASS files into CSS. + // By default we support SASS Modules with the + // extensions .module.scss or .module.sass + { + test: sassRegex, + exclude: sassModuleRegex, + loader: getStyleLoaders( + { + importLoaders: 2, + minimize: true, + sourceMap: shouldUseSourceMap, + }, + 'sass-loader' + ), + // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. + }, + // Adds support for CSS Modules, but using SASS + // using the extension .module.scss or .module.sass + { + test: sassModuleRegex, + loader: getStyleLoaders( + { + importLoaders: 2, + minimize: true, + sourceMap: shouldUseSourceMap, + modules: true, + getLocalIdent: getCSSModuleLocalIdent, + }, + 'sass-loader' ), // Note: this won't work without `new ExtractTextPlugin()` in `plugins`. }, @@ -378,8 +412,16 @@ module.exports = { // Minify the code. new UglifyJsPlugin({ uglifyOptions: { - ecma: 8, + parse: { + // we want uglify-js to parse ecma 8 code. However, we don't want it + // to apply any minfication steps that turns valid ecma 5 code + // into invalid ecma 5 code. This is why the 'compress' and 'output' + // sections only apply transformations that are ecma 5 safe + // https://github.com/facebook/create-react-app/pull/4234 + ecma: 8, + }, compress: { + ecma: 5, warnings: false, // Disabled because of an issue with Uglify breaking seemingly valid code: // https://github.com/facebook/create-react-app/issues/2376 @@ -391,6 +433,7 @@ module.exports = { safari10: true, }, output: { + ecma: 5, comments: false, // Turned on because emoji and regex is not minified properly using default // https://github.com/facebook/create-react-app/issues/2488 @@ -413,6 +456,7 @@ module.exports = { // having to parse `index.html`. new ManifestPlugin({ fileName: 'asset-manifest.json', + publicPath: publicPath, }), // Generate a service worker script that will precache, and keep up to date, // the HTML & assets that are part of the Webpack build. diff --git a/packages/react-app-tools/package.json b/packages/react-app-tools/package.json index 85ac431c..f7d37760 100644 --- a/packages/react-app-tools/package.json +++ b/packages/react-app-tools/package.json @@ -1,6 +1,6 @@ { "name": "react-scripts", - "version": "2.0.0-next.47d2d941", + "version": "2.0.0-next.66cc7a90", "description": "Configuration and scripts for Create React App.", "repository": "facebook/create-react-app", "license": "MIT", @@ -21,43 +21,44 @@ "react-scripts": "./bin/react-scripts.js" }, "dependencies": { - "@babel/core": "7.0.0-beta.38", - "@babel/runtime": "7.0.0-beta.38", + "@babel/core": "7.0.0-beta.44", + "@babel/runtime": "7.0.0-beta.44", "autoprefixer": "7.2.5", "babel-core": "7.0.0-bridge.0", - "babel-eslint": "8.2.1", + "babel-eslint": "8.2.2", "babel-jest": "22.1.0", "babel-loader": "8.0.0-beta.0", - "babel-plugin-named-asset-import": "1.0.0-next.47d2d941", - "babel-preset-react-app": "4.0.0-next.47d2d941", + "babel-plugin-named-asset-import": "1.0.0-next.66cc7a90", + "babel-preset-react-app": "4.0.0-next.66cc7a90", "case-sensitive-paths-webpack-plugin": "2.1.1", "chalk": "2.3.0", "css-loader": "0.28.9", - "dotenv": "4.0.0", - "dotenv-expand": "4.0.1", + "dotenv": "5.0.0", + "dotenv-expand": "4.2.0", "eslint": "4.15.0", - "eslint-config-react-app": "3.0.0-next.47d2d941", + "eslint-config-react-app": "3.0.0-next.66cc7a90", "eslint-loader": "1.9.0", "eslint-plugin-flowtype": "2.41.0", "eslint-plugin-import": "2.8.0", "eslint-plugin-jsx-a11y": "6.0.3", - "eslint-plugin-react": "7.5.1", + "eslint-plugin-react": "7.7.0", "extract-text-webpack-plugin": "3.0.2", "file-loader": "1.1.6", - "find-pkg": "1.0.0", "fs-extra": "5.0.0", - "globby": "7.1.1", "graphql": "0.12.3", "graphql-tag": "2.6.1", "html-webpack-plugin": "2.30.1", "identity-obj-proxy": "3.0.0", "jest": "22.1.2", + "loader-utils": "^1.1.0", "object-assign": "4.1.1", "postcss-flexbugs-fixes": "3.2.0", "postcss-loader": "2.0.10", "promise": "8.0.1", "raf": "3.4.0", - "react-dev-utils": "6.0.0-next.47d2d941", + "react-dev-utils": "6.0.0-next.66cc7a90", + "resolve": "1.6.0", + "sass-loader": "7.0.0", "style-loader": "0.19.1", "svgr": "1.8.1", "sw-precache-webpack-plugin": "0.11.4", @@ -74,7 +75,7 @@ "react-dom": "^16.0.0" }, "optionalDependencies": { - "fsevents": "1.1.3" + "fsevents": "1.2.0" }, "browserslist": { "development": [ diff --git a/packages/react-app-tools/scripts/build.js b/packages/react-app-tools/scripts/build.js index cca5ff28..f69400dc 100644 --- a/packages/react-app-tools/scripts/build.js +++ b/packages/react-app-tools/scripts/build.js @@ -45,7 +45,6 @@ const { printBrowsers } = require('react-dev-utils/browsersHelper'); const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild; const printFileSizesAfterBuild = FileSizeReporter.printFileSizesAfterBuild; -const useYarn = fs.existsSync(paths.yarnLockFile); // These sizes are pretty large. We'll warn for bundles exceeding them. const WARN_AFTER_BUNDLE_GZIP_SIZE = 512 * 1024; @@ -112,7 +111,7 @@ checkBrowsers(paths.appPath) publicUrl, publicPath, buildFolder, - useYarn + paths.useYarn ); printBrowsers(paths.appPath); }, diff --git a/packages/react-app-tools/scripts/eject.js b/packages/react-app-tools/scripts/eject.js index e8c64a77..98863b91 100644 --- a/packages/react-app-tools/scripts/eject.js +++ b/packages/react-app-tools/scripts/eject.js @@ -224,7 +224,7 @@ inquirer } } - if (fs.existsSync(paths.yarnLockFile)) { + if (paths.useYarn) { const windowsCmdFilePath = path.join( appPath, 'node_modules', diff --git a/packages/react-app-tools/scripts/start.js b/packages/react-app-tools/scripts/start.js index 83e4bb63..95502bc2 100644 --- a/packages/react-app-tools/scripts/start.js +++ b/packages/react-app-tools/scripts/start.js @@ -29,7 +29,6 @@ if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') { } // @remove-on-eject-end -const fs = require('fs'); const chalk = require('chalk'); const webpack = require('webpack'); const WebpackDevServer = require('webpack-dev-server'); @@ -46,7 +45,6 @@ const paths = require('../config/paths'); const config = require('../config/webpack.config.dev'); const createDevServerConfig = require('../config/webpackDevServer.config'); -const useYarn = fs.existsSync(paths.yarnLockFile); const isInteractive = process.stdout.isTTY; // Warn and crash if required files are missing @@ -69,7 +67,9 @@ if (process.env.HOST) { console.log( `If this was unintentional, check that you haven't mistakenly set it in your shell.` ); - console.log(`Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}`); + console.log( + `Learn more here: ${chalk.yellow('http://bit.ly/CRA-advanced-config')}` + ); console.log(); } @@ -91,7 +91,13 @@ checkBrowsers(paths.appPath) const appName = require(paths.appPackageJson).name; const urls = prepareUrls(protocol, HOST, port); // Create a webpack compiler that is configured with custom messages. - const compiler = createCompiler(webpack, config, appName, urls, useYarn); + const compiler = createCompiler( + webpack, + config, + appName, + urls, + paths.useYarn + ); // Load proxy config const proxySetting = require(paths.appPackageJson).proxy; const proxyConfig = prepareProxy(proxySetting, paths.appPublic); diff --git a/packages/react-app-tools/scripts/test.js b/packages/react-app-tools/scripts/test.js index 7d2acf77..b77db920 100644 --- a/packages/react-app-tools/scripts/test.js +++ b/packages/react-app-tools/scripts/test.js @@ -31,7 +31,7 @@ if (process.env.SKIP_PREFLIGHT_CHECK !== 'true') { // @remove-on-eject-end const jest = require('jest'); -const argv = process.argv.slice(2); +let argv = process.argv.slice(2); // Watch unless on CI, in coverage mode, or explicitly running all tests if ( @@ -57,5 +57,59 @@ argv.push( ) ) ); + +// This is a very dirty workaround for https://github.com/facebook/jest/issues/5913. +// We're trying to resolve the environment ourselves because Jest does it incorrectly. +// TODO: remove this (and the `resolve` dependency) as soon as it's fixed in Jest. +const resolve = require('resolve'); +function resolveJestDefaultEnvironment(name) { + const jestDir = path.dirname( + resolve.sync('jest', { + basedir: __dirname, + }) + ); + const jestCLIDir = path.dirname( + resolve.sync('jest-cli', { + basedir: jestDir, + }) + ); + const jestConfigDir = path.dirname( + resolve.sync('jest-config', { + basedir: jestCLIDir, + }) + ); + return resolve.sync(name, { + basedir: jestConfigDir, + }); +} +let cleanArgv = []; +let env = 'node'; +let next; +do { + next = argv.shift(); + if (next === '--env') { + env = argv.shift(); + } else if (next.indexOf('--env=') === 0) { + env = next.substring('--env='.length); + } else { + cleanArgv.push(next); + } +} while (argv.length > 0); +argv = cleanArgv; +let resolvedEnv; +try { + resolvedEnv = resolveJestDefaultEnvironment(`jest-environment-${env}`); +} catch (e) { + // ignore +} +if (!resolvedEnv) { + try { + resolvedEnv = resolveJestDefaultEnvironment(env); + } catch (e) { + // ignore + } +} +const testEnvironment = resolvedEnv || env; +argv.push('--env', testEnvironment); // @remove-on-eject-end jest.run(argv); diff --git a/packages/react-app-tools/template/README.md b/packages/react-app-tools/template/README.md index 2c533d49..21cf719e 100644 --- a/packages/react-app-tools/template/README.md +++ b/packages/react-app-tools/template/README.md @@ -87,6 +87,7 @@ You can find the most recent version of this guide [here](https://github.com/fac - [Serving Apps with Client-Side Routing](#serving-apps-with-client-side-routing) - [Service Worker Considerations](#service-worker-considerations) - [Building for Relative Paths](#building-for-relative-paths) + - [Customizing Environment Variables for Arbitrary Build Environments](#customizing-environment-variables-for-arbitrary-build-environments) - [Azure](#azure) - [Firebase](#firebase) - [GitHub Pages](#github-pages) @@ -214,7 +215,7 @@ In addition to [ES6](https://github.com/lukehoban/es6features) syntax features, * [Exponentiation Operator](https://github.com/rwaldron/exponentiation-operator) (ES2016). * [Async/await](https://github.com/tc39/ecmascript-asyncawait) (ES2017). -* [Object Rest/Spread Properties](https://github.com/sebmarkbage/ecmascript-rest-spread) (stage 3 proposal). +* [Object Rest/Spread Properties](https://github.com/tc39/proposal-object-rest-spread) (ES2018). * [Dynamic import()](https://github.com/tc39/proposal-dynamic-import) (stage 3 proposal) * [Class Fields and Static Properties](https://github.com/tc39/proposal-class-public-fields) (part of stage 3 proposal). * [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) and [Flow](https://flowtype.org/) syntax. @@ -509,7 +510,7 @@ class Button extends Component { } ``` -**This is not required for React** but many people find this feature convenient. You can read about the benefits of this approach [here](https://medium.com/seek-ui-engineering/block-element-modifying-your-javascript-components-d7f99fcab52b). However you should be aware that this makes your code less portable to other build tools and environments than Webpack. +**This is not required for React** but many people find this feature convenient. You can read about the benefits of this approach [here](https://medium.com/seek-blog/block-element-modifying-your-javascript-components-d7f99fcab52b). However you should be aware that this makes your code less portable to other build tools and environments than Webpack. In development, expressing dependencies this way allows your styles to be reloaded on the fly as you edit them. In production, all CSS files will be concatenated into a single minified `.css` file in the build output. @@ -518,23 +519,23 @@ If you are concerned about using Webpack-specific semantics, you can put all you +