From aeacd8e811f447432b53359adacc3a33eedf28a0 Mon Sep 17 00:00:00 2001 From: Russian Date: Thu, 4 Dec 2025 12:37:01 -0300 Subject: [PATCH 1/2] chore(react-scripts): get code from npmjs and update github repository --- packages/react-scripts/bin/react-scripts.js | 2 +- packages/react-scripts/config/env.js | 7 +- .../config/jest/babelTransform.js | 25 +- packages/react-scripts/config/modules.js | 11 +- packages/react-scripts/config/paths.js | 11 +- .../react-scripts/config/webpack.config.js | 367 +++++++------ .../config/webpackDevServer.config.js | 2 +- packages/react-scripts/lib/react-app.d.ts | 5 + packages/react-scripts/package.json | 107 ++-- packages/react-scripts/scripts/build.js | 29 +- packages/react-scripts/scripts/eject.js | 482 +++++++++--------- packages/react-scripts/scripts/init.js | 60 ++- packages/react-scripts/scripts/start.js | 24 +- .../scripts/utils/createJestConfig.js | 15 +- .../scripts/utils/verifyTypeScriptSetup.js | 50 +- packages/react-scripts/template/README.md | 2 +- 16 files changed, 672 insertions(+), 527 deletions(-) diff --git a/packages/react-scripts/bin/react-scripts.js b/packages/react-scripts/bin/react-scripts.js index 7e6e290251a..09604f6a03f 100755 --- a/packages/react-scripts/bin/react-scripts.js +++ b/packages/react-scripts/bin/react-scripts.js @@ -26,7 +26,7 @@ const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : []; if (['build', 'eject', 'start', 'test'].includes(script)) { const result = spawn.sync( - 'node', + process.execPath, nodeArgs .concat(require.resolve('../scripts/' + script)) .concat(args.slice(scriptIndex + 1)), diff --git a/packages/react-scripts/config/env.js b/packages/react-scripts/config/env.js index d5ad88fefb2..17b9cdb09db 100644 --- a/packages/react-scripts/config/env.js +++ b/packages/react-scripts/config/env.js @@ -25,11 +25,11 @@ if (!NODE_ENV) { // https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use const dotenvFiles = [ `${paths.dotenv}.${NODE_ENV}.local`, - `${paths.dotenv}.${NODE_ENV}`, // Don't include `.env.local` for `test` environment // since normally you expect tests to produce the same // results for everyone NODE_ENV !== 'test' && `${paths.dotenv}.local`, + `${paths.dotenv}.${NODE_ENV}`, paths.dotenv, ].filter(Boolean); @@ -93,6 +93,11 @@ function getClientEnvironment(publicUrl) { WDS_SOCKET_HOST: process.env.WDS_SOCKET_HOST, WDS_SOCKET_PATH: process.env.WDS_SOCKET_PATH, WDS_SOCKET_PORT: process.env.WDS_SOCKET_PORT, + // Whether or not react-refresh is enabled. + // react-refresh is not 100% stable at this time, + // which is why it's disabled by default. + // It is defined here so it is available in the webpackHotDevClient. + FAST_REFRESH: process.env.FAST_REFRESH !== 'false', } ); // Stringify all values so we can feed into webpack DefinePlugin diff --git a/packages/react-scripts/config/jest/babelTransform.js b/packages/react-scripts/config/jest/babelTransform.js index 7feed94c59a..c5830153e80 100644 --- a/packages/react-scripts/config/jest/babelTransform.js +++ b/packages/react-scripts/config/jest/babelTransform.js @@ -1,16 +1,37 @@ -// @remove-file-on-eject +// @remove-on-eject-begin /** * Copyright (c) 2014-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +// @remove-on-eject-end 'use strict'; const babelJest = require('babel-jest'); +const hasJsxRuntime = (() => { + if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { + return false; + } + + try { + require.resolve('react/jsx-runtime'); + return true; + } catch (e) { + return false; + } +})(); + module.exports = babelJest.createTransformer({ - presets: [require.resolve('babel-preset-react-app')], + presets: [ + [ + require.resolve('babel-preset-react-app'), + { + runtime: hasJsxRuntime ? 'automatic' : 'classic', + }, + ], + ], babelrc: false, configFile: false, }); diff --git a/packages/react-scripts/config/modules.js b/packages/react-scripts/config/modules.js index 38c95b91e44..22820993a25 100644 --- a/packages/react-scripts/config/modules.js +++ b/packages/react-scripts/config/modules.js @@ -22,15 +22,8 @@ const resolve = require('resolve'); function getAdditionalModulePaths(options = {}) { const baseUrl = options.baseUrl; - // We need to explicitly check for null and undefined (and not a falsy value) because - // TypeScript treats an empty string as `.`. - if (baseUrl == null) { - // If there's no baseUrl set we respect NODE_PATH - // Note that NODE_PATH is deprecated and will be removed - // in the next major release of create-react-app. - - const nodePath = process.env.NODE_PATH || ''; - return nodePath.split(path.delimiter).filter(Boolean); + if (!baseUrl) { + return ''; } const baseUrlResolved = path.resolve(paths.appPath, baseUrl); diff --git a/packages/react-scripts/config/paths.js b/packages/react-scripts/config/paths.js index 230b35070f9..6a03de5b4f9 100644 --- a/packages/react-scripts/config/paths.js +++ b/packages/react-scripts/config/paths.js @@ -29,6 +29,8 @@ const publicUrlOrPath = getPublicUrlOrPath( process.env.PUBLIC_URL ); +const buildPath = process.env.BUILD_PATH || 'build'; + const moduleFileExtensions = [ 'web.mjs', 'mjs', @@ -60,7 +62,7 @@ const resolveModule = (resolveFn, filePath) => { module.exports = { dotenv: resolveApp('.env'), appPath: resolveApp('.'), - appBuild: resolveApp('build'), + appBuild: resolveApp(buildPath), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), entryHtml: resolveApp(`public/${process.env.SECOND_ENTRY}.html`), @@ -74,6 +76,7 @@ module.exports = { testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), + swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, }; @@ -84,7 +87,7 @@ const resolveOwn = relativePath => path.resolve(__dirname, '..', relativePath); module.exports = { dotenv: resolveApp('.env'), appPath: resolveApp('.'), - appBuild: resolveApp('build'), + appBuild: resolveApp(buildPath), appPublic: resolveApp('public'), appHtml: resolveApp('public/index.html'), entryHtml: resolveApp(`public/${process.env.SECOND_ENTRY}.html`), @@ -98,6 +101,7 @@ module.exports = { testsSetup: resolveModule(resolveApp, 'src/setupTests'), proxySetup: resolveApp('src/setupProxy.js'), appNodeModules: resolveApp('node_modules'), + swSrc: resolveModule(resolveApp, 'src/service-worker'), publicUrlOrPath, // These properties only exist before ejecting: ownPath: resolveOwn('.'), @@ -121,7 +125,7 @@ if ( module.exports = { dotenv: resolveOwn(`${templatePath}/.env`), appPath: resolveApp('.'), - appBuild: resolveOwn('../../build'), + appBuild: resolveOwn(path.join('../..', buildPath)), appPublic: resolveOwn(`${templatePath}/public`), appHtml: resolveOwn(`${templatePath}/public/index.html`), appIndexJs: resolveModule(resolveOwn, `${templatePath}/src/index`), @@ -133,6 +137,7 @@ if ( testsSetup: resolveModule(resolveOwn, `${templatePath}/src/setupTests`), proxySetup: resolveOwn(`${templatePath}/src/setupProxy.js`), appNodeModules: resolveOwn('node_modules'), + swSrc: resolveModule(resolveOwn, `${templatePath}/src/service-worker`), publicUrlOrPath, // These properties only exist before ejecting: ownPath: resolveOwn('.'), diff --git a/packages/react-scripts/config/webpack.config.js b/packages/react-scripts/config/webpack.config.js index 4240c7ef869..50216b84c2c 100644 --- a/packages/react-scripts/config/webpack.config.js +++ b/packages/react-scripts/config/webpack.config.js @@ -27,12 +27,14 @@ const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin'); const getCSSModuleLocalIdent = require('react-dev-utils/getCSSModuleLocalIdent'); +const ESLintPlugin = require('eslint-webpack-plugin'); const paths = require('./paths'); const modules = require('./modules'); const getClientEnvironment = require('./env'); const ModuleNotFoundPlugin = require('react-dev-utils/ModuleNotFoundPlugin'); const ForkTsCheckerWebpackPlugin = require('react-dev-utils/ForkTsCheckerWebpackPlugin'); const typescriptFormatter = require('react-dev-utils/typescriptFormatter'); +const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); // @remove-on-eject-begin const getCacheIdentifier = require('react-dev-utils/getCacheIdentifier'); // @remove-on-eject-end @@ -42,11 +44,20 @@ const appPackageJson = require(paths.appPackageJson); // Source maps are resource heavy and can cause out of memory issue for large source files. const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; + +const webpackDevClientEntry = require.resolve( + 'react-dev-utils/webpackHotDevClient' +); +const reactRefreshOverlayEntry = require.resolve( + 'react-dev-utils/refreshOverlayInterop' +); + // Some apps do not need the benefits of saving a web request, so not inlining the chunk // makes for a smoother build process. const shouldInlineRuntimeChunk = process.env.INLINE_RUNTIME_CHUNK !== 'false'; -const isExtendingEslintConfig = process.env.EXTEND_ESLINT === 'true'; +const emitErrorsAsWarnings = process.env.ESLINT_NO_DEV_ERRORS === 'true'; +const disableESLintPlugin = process.env.DISABLE_ESLINT_PLUGIN === 'true'; const imageInlineSizeLimit = parseInt( process.env.IMAGE_INLINE_SIZE_LIMIT || '10000' @@ -55,12 +66,27 @@ const imageInlineSizeLimit = parseInt( // Check if TypeScript is setup const useTypeScript = fs.existsSync(paths.appTsConfig); +// Get the path to the uncompiled service worker (if it exists). +const swSrc = paths.swSrc; + // style files regexes const cssRegex = /\.css$/; const cssModuleRegex = /\.module\.css$/; const sassRegex = /\.(scss|sass)$/; const sassModuleRegex = /\.module\.(scss|sass)$/; +const hasJsxRuntime = (() => { + if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { + return false; + } + + try { + require.resolve('react/jsx-runtime'); + return true; + } catch (e) { + return false; + } +})(); const toCamel = str => str.replace(/-([a-z])/g, g => g[1].toUpperCase()); // Just to add a second entry module @@ -73,7 +99,7 @@ const isCI = // This is the production and development configuration. // It is focused on developer experience, fast rebuilds, and a minimal bundle. -module.exports = function(webpackEnv) { +module.exports = function (webpackEnv) { const isEnvDevelopment = webpackEnv === 'development'; const isEnvProduction = webpackEnv === 'production'; @@ -88,6 +114,8 @@ module.exports = function(webpackEnv) { // Get environment variables to inject into our app. const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); + const shouldUseReactRefresh = env.raw.FAST_REFRESH; + // common function to get style loaders const getStyleLoaders = (cssOptions, preProcessor) => { const loaders = [ @@ -126,7 +154,7 @@ module.exports = function(webpackEnv) { // which in turn let's users customize the target behavior as per their needs. postcssNormalize(), ], - sourceMap: isEnvProduction && shouldUseSourceMap, + sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, }, }, ].filter(Boolean); @@ -135,7 +163,8 @@ module.exports = function(webpackEnv) { { loader: require.resolve('resolve-url-loader'), options: { - sourceMap: isEnvProduction && shouldUseSourceMap, + sourceMap: isEnvProduction ? shouldUseSourceMap : isEnvDevelopment, + root: paths.appSrc, }, }, { @@ -162,36 +191,19 @@ module.exports = function(webpackEnv) { // This means they will be the "root" imports that are included in JS bundle. entry: secondEntryName ? { - app: [ - isEnvDevelopment && - require.resolve('react-dev-utils/webpackHotDevClient'), - paths.appIndexJs, - ].filter(Boolean), - [secondEntryName]: [ - isEnvDevelopment && - require.resolve('react-dev-utils/webpackHotDevClient'), - paths.entryIndexJS, - ].filter(Boolean), - } - : [ - // 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 - // of CSS changes), or refresh the page (in case of JS changes). When you - // make a syntax error, this client will display a syntax error overlay. - // Note: instead of the default WebpackDevServer client, we use a custom one - // to bring better experience for Create React App users. You can replace - // the line below with these two lines if you prefer the stock client: - // require.resolve('webpack-dev-server/client') + '?/', - // require.resolve('webpack/hot/dev-server'), - isEnvDevelopment && - require.resolve('react-dev-utils/webpackHotDevClient'), - // Finally, this is your app's code: + app: isEnvDevelopment && !shouldUseReactRefresh ? [ + webpackDevClientEntry, + paths.appIndexJs, + ] : paths.appIndexJs, + [secondEntryName]: isEnvDevelopment && !shouldUseReactRefresh ? [ + webpackDevClientEntry, paths.appIndexJs, - // We include the app code last so that if there is a runtime error during - // initialization, it doesn't blow up the WebpackDevServer client, and - // changing JS code would still trigger a refresh. - ].filter(Boolean), + ] : paths.appIndexJs, + } + : isEnvDevelopment && !shouldUseReactRefresh ? [ + webpackDevClientEntry, + paths.appIndexJs, + ] : paths.appIndexJs, output: { // The build folder. path: isEnvProduction ? paths.appBuild : undefined, @@ -216,11 +228,11 @@ module.exports = function(webpackEnv) { // Point sourcemap entries to original disk location (format as URL on Windows) devtoolModuleFilenameTemplate: isEnvProduction ? info => - path - .relative(paths.appSrc, info.absoluteResourcePath) - .replace(/\\/g, '/') + path + .relative(paths.appSrc, info.absoluteResourcePath) + .replace(/\\/g, '/') : isEnvDevelopment && - (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')), + (info => path.resolve(info.absoluteResourcePath).replace(/\\/g, '/')), // Prevents conflicts when multiple webpack runtimes (from different apps) // are used on the same page. jsonpFunction: `webpackJsonp${appPackageJson.name}`, @@ -280,13 +292,13 @@ module.exports = function(webpackEnv) { parser: safePostCssParser, map: shouldUseSourceMap ? { - // `inline: false` forces the sourcemap to be output into a - // separate file - inline: false, - // `annotation: true` appends the sourceMappingURL to the end of - // the css file, helping the browser find the sourcemap - annotation: true, - } + // `inline: false` forces the sourcemap to be output into a + // separate file + inline: false, + // `annotation: true` appends the sourceMappingURL to the end of + // the css file, helping the browser find the sourcemap + annotation: true, + } : false, }, cssProcessorPluginOptions: { @@ -299,7 +311,7 @@ module.exports = function(webpackEnv) { // https://medium.com/webpack/webpack-4-code-splitting-chunk-graph-and-the-splitchunks-optimization-be739a861366 splitChunks: { chunks: 'all', - name: false, + name: isEnvDevelopment, }, // Keep the runtime chunk separated to enable long term caching // https://twitter.com/wSokra/status/969679223278505985 @@ -345,7 +357,10 @@ module.exports = function(webpackEnv) { // 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]), + new ModuleScopePlugin(paths.appSrc, [ + paths.appPackageJson, + reactRefreshOverlayEntry, + ]), ], }, resolveLoader: { @@ -360,39 +375,22 @@ module.exports = function(webpackEnv) { rules: [ // 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|mjs|jsx|ts|tsx)$/, - enforce: 'pre', - use: [ - { - options: { - cache: true, - formatter: require.resolve('react-dev-utils/eslintFormatter'), - eslintPath: require.resolve('eslint'), - resolvePluginsRelativeTo: __dirname, - // @remove-on-eject-begin - ignore: isExtendingEslintConfig, - baseConfig: isExtendingEslintConfig - ? undefined - : { - extends: [require.resolve('eslint-config-react-app')], - }, - useEslintrc: isExtendingEslintConfig, - // @remove-on-eject-end - }, - 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: [ + // TODO: Merge this config once `image/avif` is in the mime-db + // https://github.com/jshttp/mime-db + { + test: [/\.avif$/], + loader: require.resolve('url-loader'), + options: { + limit: imageInlineSizeLimit, + mimetype: 'image/avif', + name: 'static/media/[name].[hash:8].[ext]', + }, + }, // "url" loader works like "file" loader except that it embeds assets // smaller than specified limit in bytes as data URLs to avoid requests. // A missing `test` is equivalent to a match. @@ -471,10 +469,17 @@ module.exports = function(webpackEnv) { customize: require.resolve( 'babel-preset-react-app/webpack-overrides' ), + presets: [ + [ + require.resolve('babel-preset-react-app'), + { + runtime: hasJsxRuntime ? 'automatic' : 'classic', + }, + ], + ], // @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 @@ -504,7 +509,10 @@ module.exports = function(webpackEnv) { }, }, ], - ], + isEnvDevelopment && + shouldUseReactRefresh && + require.resolve('react-refresh/babel'), + ].filter(Boolean), // 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. @@ -521,6 +529,8 @@ module.exports = function(webpackEnv) { exclude: /@babel(?:\/|\\{1,2})runtime/, loader: require.resolve('babel-loader'), options: { + // https://docs.mapbox.com/mapbox-gl-js/api/#transpiling-v2 + ignore: ['./node_modules/mapbox-gl/dist/mapbox-gl.js'], babelrc: false, configFile: false, compact: false, @@ -565,7 +575,9 @@ module.exports = function(webpackEnv) { exclude: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, - sourceMap: isEnvProduction && shouldUseSourceMap, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, }), // Don't consider CSS imports dead code even if the // containing package claims to have no side effects. @@ -579,7 +591,9 @@ module.exports = function(webpackEnv) { test: cssModuleRegex, use: getStyleLoaders({ importLoaders: 1, - sourceMap: isEnvProduction && shouldUseSourceMap, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, modules: { getLocalIdent: getCSSModuleLocalIdent, }, @@ -594,7 +608,9 @@ module.exports = function(webpackEnv) { use: getStyleLoaders( { importLoaders: 3, - sourceMap: isEnvProduction && shouldUseSourceMap, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, }, 'sass-loader' ), @@ -611,7 +627,9 @@ module.exports = function(webpackEnv) { use: getStyleLoaders( { importLoaders: 3, - sourceMap: isEnvProduction && shouldUseSourceMap, + sourceMap: isEnvProduction + ? shouldUseSourceMap + : isEnvDevelopment, modules: { getLocalIdent: getCSSModuleLocalIdent, }, @@ -652,16 +670,16 @@ module.exports = function(webpackEnv) { {}, length > 1 ? { - chunks: [_i === 0 ? 'app' : `${secondEntryName}`], - excludeChunks: [_i === 0 ? `${secondEntryName}` : 'app'], - } + chunks: [_i === 0 ? 'app' : `${secondEntryName}`], + excludeChunks: [_i === 0 ? `${secondEntryName}` : 'app'], + } : {}, _i == 1 ? { - filename: htmlTemplate.split('/')[ - htmlTemplate.split('/').length - 1 - ], - } + filename: htmlTemplate.split('/')[ + htmlTemplate.split('/').length - 1 + ], + } : {}, { inject: true, @@ -669,19 +687,19 @@ module.exports = function(webpackEnv) { }, isEnvProduction ? { - minify: { - removeComments: true, - collapseWhitespace: true, - removeRedundantAttributes: true, - useShortDoctype: true, - removeEmptyAttributes: true, - removeStyleLinkTypeAttributes: true, - keepClosingSlash: true, - minifyJS: true, - minifyCSS: true, - minifyURLs: true, - }, - } + minify: { + removeComments: true, + collapseWhitespace: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + removeStyleLinkTypeAttributes: true, + keepClosingSlash: true, + minifyJS: true, + minifyCSS: true, + minifyURLs: true, + }, + } : undefined ) ) @@ -690,8 +708,8 @@ module.exports = function(webpackEnv) { // a network request. // https://github.com/facebook/create-react-app/issues/5358 isEnvProduction && - shouldInlineRuntimeChunk && - new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]), + shouldInlineRuntimeChunk && + new InlineChunkHtmlPlugin(HtmlWebpackPlugin, [/runtime-.+[.]js/]), // Makes some environment variables available in index.html. // The public URL is available as %PUBLIC_URL% in index.html, e.g.: // @@ -707,8 +725,23 @@ module.exports = function(webpackEnv) { // during a production build. // Otherwise React will be compiled in the very slow development mode. new webpack.DefinePlugin(env.stringified), - // This is necessary to emit hot updates (currently CSS only): + // This is necessary to emit hot updates (CSS and Fast Refresh): isEnvDevelopment && new webpack.HotModuleReplacementPlugin(), + // Experimental hot reloading for React . + // https://github.com/facebook/react/tree/master/packages/react-refresh + isEnvDevelopment && + shouldUseReactRefresh && + new ReactRefreshWebpackPlugin({ + overlay: { + entry: webpackDevClientEntry, + // The expected exports are slightly different from what the overlay exports, + // so an interop is included here to enable feedback on module-level errors. + module: reactRefreshOverlayEntry, + // Since we ship a custom dev client and overlay integration, + // the bundled socket handling logic can be eliminated. + sockIntegration: false, + }, + }), // Watcher doesn't work well if you mistype casing in a path so we use // a plugin that prints an error when you attempt to do this. // See https://github.com/facebook/create-react-app/issues/240 @@ -718,14 +751,14 @@ module.exports = function(webpackEnv) { // makes the discovery automatic so you don't have to restart. // See https://github.com/facebook/create-react-app/issues/186 isEnvDevelopment && - new WatchMissingNodeModulesPlugin(paths.appNodeModules), + new WatchMissingNodeModulesPlugin(paths.appNodeModules), isEnvProduction && - new MiniCssExtractPlugin({ - // Options similar to the same options in webpackOptions.output - // both options are optional - filename: 'static/css/[name].[contenthash:8].css', - chunkFilename: 'static/css/[name].[contenthash:8].chunk.css', - }), + new MiniCssExtractPlugin({ + // Options similar to the same options in webpackOptions.output + // both options are optional + filename: 'static/css/[name].[contenthash:8].css', + chunkFilename: 'static/css/[name].[contenthash:8].chunk.css', + }), // Generate an asset manifest file with the following content: // - "files" key: Mapping of all asset filenames to their corresponding // output file so that tools can pick it up without having to parse @@ -754,13 +787,13 @@ module.exports = function(webpackEnv) { }), // Generate gzip files isEnvProduction && - new CompressionPlugin({ - filename: '[path].gz[query]', - algorithm: 'gzip', - test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/, - threshold: 10240, - minRatio: 0.8, - }), + new CompressionPlugin({ + filename: '[path].gz[query]', + algorithm: 'gzip', + test: /\.js$|\.css$|\.html$|\.eot?.+$|\.ttf?.+$|\.woff?.+$|\.svg?.+$/, + threshold: 10240, + minRatio: 0.8, + }), // Moment.js is an extremely popular library that bundles large locale files // by default due to how webpack interprets its code. This is a practical // solution that requires the user to opt into importing specific locales. @@ -770,48 +803,72 @@ module.exports = function(webpackEnv) { // Generate a service worker script that will precache, and keep up to date, // the HTML & assets that are part of the webpack build. isEnvProduction && - new WorkboxWebpackPlugin.GenerateSW({ - clientsClaim: true, - exclude: [/\.map$/, /asset-manifest\.json$/], - importWorkboxFrom: 'cdn', - navigateFallback: paths.publicUrlOrPath + 'index.html', - navigateFallbackBlacklist: [ - // Exclude URLs starting with /_, as they're likely an API call - new RegExp('^/_'), - // Exclude any URLs whose last part seems to be a file extension - // as they're likely a resource and not a SPA route. - // URLs containing a "?" character won't be blacklisted as they're likely - // a route with query params (e.g. auth callbacks). - new RegExp('/[^/?]+\\.[^/]+$'), - ], + fs.existsSync(swSrc) && + new WorkboxWebpackPlugin.InjectManifest({ + swSrc, + dontCacheBustURLsMatching: /\.[0-9a-f]{8}\./, + exclude: [/\.map$/, /asset-manifest\.json$/, /LICENSE/], + // Bump up the default maximum size (2mb) that's precached, + // to make lazy-loading failure scenarios less likely. + // See https://github.com/cra-template/pwa/issues/13#issuecomment-722667270 + maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, }), // TypeScript type checking useTypeScript && - new ForkTsCheckerWebpackPlugin({ - typescript: resolve.sync('typescript', { - basedir: paths.appNodeModules, - }), - async: isEnvDevelopment, - useTypescriptIncrementalApi: true, - checkSyntacticErrors: true, - resolveModuleNameModule: process.versions.pnp - ? `${__dirname}/pnpTs.js` - : undefined, - resolveTypeReferenceDirectiveModule: process.versions.pnp - ? `${__dirname}/pnpTs.js` - : undefined, - tsconfig: paths.appTsConfig, - reportFiles: [ - '**', - '!**/__tests__/**', - '!**/?(*.)(spec|test).*', - '!**/src/setupProxy.*', - '!**/src/setupTests.*', - ], - silent: true, - // The formatter is invoked directly in WebpackDevServerUtils during development - formatter: isEnvProduction ? typescriptFormatter : undefined, + new ForkTsCheckerWebpackPlugin({ + typescript: resolve.sync('typescript', { + basedir: paths.appNodeModules, }), + async: isEnvDevelopment, + checkSyntacticErrors: true, + resolveModuleNameModule: process.versions.pnp + ? `${__dirname}/pnpTs.js` + : undefined, + resolveTypeReferenceDirectiveModule: process.versions.pnp + ? `${__dirname}/pnpTs.js` + : undefined, + tsconfig: paths.appTsConfig, + reportFiles: [ + // This one is specifically to match during CI tests, + // as micromatch doesn't match + // '../cra-template-typescript/template/src/App.tsx' + // otherwise. + '../**/src/**/*.{ts,tsx}', + '**/src/**/*.{ts,tsx}', + '!**/src/**/__tests__/**', + '!**/src/**/?(*.)(spec|test).*', + '!**/src/setupProxy.*', + '!**/src/setupTests.*', + ], + silent: true, + // The formatter is invoked directly in WebpackDevServerUtils during development + formatter: isEnvProduction ? typescriptFormatter : undefined, + }), + !disableESLintPlugin && + new ESLintPlugin({ + // Plugin options + extensions: ['js', 'mjs', 'jsx', 'ts', 'tsx'], + formatter: require.resolve('react-dev-utils/eslintFormatter'), + eslintPath: require.resolve('eslint'), + failOnError: !(isEnvDevelopment && emitErrorsAsWarnings), + context: paths.appSrc, + cache: true, + cacheLocation: path.resolve( + paths.appNodeModules, + '.cache/.eslintcache' + ), + // ESLint class options + cwd: paths.appPath, + resolvePluginsRelativeTo: __dirname, + baseConfig: { + extends: [require.resolve('eslint-config-react-app/base')], + rules: { + ...(!hasJsxRuntime && { + 'react/react-in-jsx-scope': 'error', + }), + }, + }, + }), ].filter(Boolean), // Some libraries import Node modules but don't use them in the browser. // Tell webpack to provide empty mocks for them so importing them works. diff --git a/packages/react-scripts/config/webpackDevServer.config.js b/packages/react-scripts/config/webpackDevServer.config.js index a6b9d66b96b..252a55fa641 100644 --- a/packages/react-scripts/config/webpackDevServer.config.js +++ b/packages/react-scripts/config/webpackDevServer.config.js @@ -23,7 +23,7 @@ const sockPath = process.env.WDS_SOCKET_PATH; // default: '/sockjs-node' const sockPort = process.env.WDS_SOCKET_PORT; const secondEntry = process.env.SECOND_ENTRY || ''; -module.exports = function(proxy, allowedHost) { +module.exports = function (proxy, allowedHost) { return { // WebpackDevServer 2.4.3 introduced a security fix that prevents remote // websites from potentially accessing local content through DNS rebinding: diff --git a/packages/react-scripts/lib/react-app.d.ts b/packages/react-scripts/lib/react-app.d.ts index 981cd73472c..624c875ec80 100644 --- a/packages/react-scripts/lib/react-app.d.ts +++ b/packages/react-scripts/lib/react-app.d.ts @@ -9,6 +9,11 @@ declare namespace NodeJS { } } +declare module '*.avif' { + const src: string; + export default src; +} + declare module '*.bmp' { const src: string; export default src; diff --git a/packages/react-scripts/package.json b/packages/react-scripts/package.json index 803e7bef92a..559544be072 100644 --- a/packages/react-scripts/package.json +++ b/packages/react-scripts/package.json @@ -1,11 +1,11 @@ { - "name": "@onsmart/react-scripts", - "version": "0.4.3", - "description": "(3.4.1) Beakyn Configuration and scripts for Create React App.", + "name": "@outfront/react-scripts", + "version": "0.4.1", + "description": "(4.0.3) Beakyn Configuration and scripts for Create React App.", "repository": "https://github.com/beakyn/create-react-app", "license": "MIT", "engines": { - "node": ">=8.10" + "node": "^10.12.0 || >=12.0.0" }, "bugs": { "url": "https://github.com/beakyn/create-react-app/issues" @@ -24,70 +24,77 @@ }, "types": "./lib/react-app.d.ts", "dependencies": { - "@babel/core": "7.9.0", - "@svgr/webpack": "4.3.3", - "@typescript-eslint/eslint-plugin": "^2.10.0", - "@typescript-eslint/parser": "^2.10.0", - "babel-eslint": "10.1.0", - "babel-jest": "^24.9.0", + "@babel/core": "7.12.3", + "@pmmmwh/react-refresh-webpack-plugin": "0.4.3", + "@svgr/webpack": "5.5.0", + "@typescript-eslint/eslint-plugin": "^4.5.0", + "@typescript-eslint/parser": "^4.5.0", + "babel-eslint": "^10.1.0", + "babel-jest": "^26.6.0", "babel-loader": "8.1.0", - "babel-plugin-named-asset-import": "^0.3.6", - "babel-preset-react-app": "^9.1.2", - "camelcase": "^5.3.1", + "babel-plugin-named-asset-import": "^0.3.7", + "babel-preset-react-app": "^10.0.0", + "bfj": "^7.0.2", + "camelcase": "^6.1.0", "case-sensitive-paths-webpack-plugin": "2.3.0", - "css-loader": "3.4.2", + "css-loader": "4.3.0", "dotenv": "8.2.0", "dotenv-expand": "5.1.0", - "eslint": "^6.6.0", - "eslint-config-react-app": "^5.2.1", - "eslint-loader": "3.0.3", - "eslint-plugin-flowtype": "4.6.0", - "eslint-plugin-import": "2.20.1", - "eslint-plugin-jsx-a11y": "6.2.3", - "eslint-plugin-react": "7.19.0", - "eslint-plugin-react-hooks": "^1.6.1", - "file-loader": "4.3.0", - "fs-extra": "^8.1.0", - "html-webpack-plugin": "4.0.0-beta.11", + "eslint": "^7.11.0", + "eslint-config-react-app": "^6.0.0", + "eslint-plugin-flowtype": "^5.2.0", + "eslint-plugin-import": "^2.22.1", + "eslint-plugin-jest": "^24.1.0", + "eslint-plugin-jsx-a11y": "^6.3.1", + "eslint-plugin-react": "^7.21.5", + "eslint-plugin-react-hooks": "^4.2.0", + "eslint-plugin-testing-library": "^3.9.2", + "eslint-webpack-plugin": "^2.5.2", + "file-loader": "6.1.1", + "fs-extra": "^9.0.1", + "html-webpack-plugin": "4.5.0", "identity-obj-proxy": "3.0.0", - "jest": "24.9.0", - "jest-environment-jsdom-fourteen": "1.0.1", - "jest-resolve": "24.9.0", - "jest-watch-typeahead": "0.4.2", - "mini-css-extract-plugin": "0.9.0", - "optimize-css-assets-webpack-plugin": "5.0.3", + "jest": "26.6.0", + "jest-circus": "26.6.0", + "jest-resolve": "26.6.0", + "jest-watch-typeahead": "0.6.1", + "mini-css-extract-plugin": "0.11.3", + "optimize-css-assets-webpack-plugin": "5.0.4", "pnp-webpack-plugin": "1.6.4", - "postcss-flexbugs-fixes": "4.1.0", + "postcss-flexbugs-fixes": "4.2.1", "postcss-loader": "3.0.0", "postcss-normalize": "8.0.1", "postcss-preset-env": "6.7.0", - "postcss-safe-parser": "4.0.1", - "react-app-polyfill": "^1.0.6", - "react-dev-utils": "^10.2.1", - "resolve": "1.15.0", - "resolve-url-loader": "3.1.1", - "sass-loader": "8.0.2", - "semver": "6.3.0", - "style-loader": "0.23.1", - "terser-webpack-plugin": "2.3.5", - "ts-pnp": "1.1.6", - "url-loader": "2.3.0", - "webpack": "4.42.0", - "webpack-dev-server": "3.10.3", + "postcss-safe-parser": "5.0.2", + "prompts": "2.4.0", + "react-app-polyfill": "^2.0.0", + "react-dev-utils": "^11.0.3", + "react-refresh": "^0.8.3", + "resolve": "1.18.1", + "resolve-url-loader": "^3.1.2", + "sass-loader": "^10.0.5", + "semver": "7.3.2", + "style-loader": "1.3.0", + "terser-webpack-plugin": "4.2.3", + "ts-pnp": "1.2.0", + "url-loader": "4.1.1", + "webpack": "4.44.2", + "webpack-dev-server": "3.11.1", "webpack-manifest-plugin": "2.2.0", - "workbox-webpack-plugin": "4.3.1", + "workbox-webpack-plugin": "5.1.4", "compression-webpack-plugin": "3.0.0", "worker-loader": "2.0.0" }, "devDependencies": { - "react": "^16.12.0", - "react-dom": "^16.12.0" + "react": "^17.0.1", + "react-dom": "^17.0.1" }, "optionalDependencies": { - "fsevents": "2.1.2" + "fsevents": "^2.1.3" }, "peerDependencies": { - "typescript": "^3.2.1" + "react": ">= 16", + "typescript": "^3.2.1 || ^4" }, "peerDependenciesMeta": { "typescript": { diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js index fa30fb09a3f..de2ff505eb3 100644 --- a/packages/react-scripts/scripts/build.js +++ b/packages/react-scripts/scripts/build.js @@ -34,6 +34,7 @@ verifyTypeScriptSetup(); const path = require('path'); const chalk = require('react-dev-utils/chalk'); const fs = require('fs-extra'); +const bfj = require('bfj'); const webpack = require('webpack'); const configFactory = require('../config/webpack.config'); const paths = require('../config/paths'); @@ -59,6 +60,9 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) { process.exit(1); } +const argv = process.argv.slice(2); +const writeStatsJson = argv.indexOf('--stats') !== -1; + // Generate configuration const config = configFactory('production'); @@ -146,18 +150,6 @@ checkBrowsers(paths.appPath, isInteractive) // Create the production build and print the deployment instructions. function build(previousFileSizes) { - // We used to support resolving modules according to `NODE_PATH`. - // This now has been deprecated in favor of jsconfig/tsconfig.json - // This lets you use absolute paths in imports inside large monorepos: - if (process.env.NODE_PATH) { - console.log( - chalk.yellow( - 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' - ) - ); - console.log(); - } - console.log('Creating an optimized production build...'); const compiler = webpack(config); @@ -210,11 +202,20 @@ function build(previousFileSizes) { return reject(new Error(messages.warnings.join('\n\n'))); } - return resolve({ + const resolveArgs = { stats, previousFileSizes, warnings: messages.warnings, - }); + }; + + if (writeStatsJson) { + return bfj + .write(paths.appBuild + '/bundle-stats.json', stats.toJson()) + .then(() => resolve(resolveArgs)) + .catch(error => reject(new Error(error))); + } + + return resolve(resolveArgs); }); }); } diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index 2a494ffe24f..0972d33384a 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -16,11 +16,11 @@ process.on('unhandledRejection', err => { const fs = require('fs-extra'); const path = require('path'); +const prompts = require('prompts'); const execSync = require('child_process').execSync; const chalk = require('react-dev-utils/chalk'); const paths = require('../config/paths'); const createJestConfig = require('./utils/createJestConfig'); -const inquirer = require('react-dev-utils/inquirer'); const spawnSync = require('react-dev-utils/crossSpawn').sync; const os = require('os'); @@ -62,283 +62,279 @@ console.log( ); console.log(); -inquirer - .prompt({ - type: 'confirm', - name: 'shouldEject', - message: 'Are you sure you want to eject? This action is permanent.', - default: false, - }) - .then(answer => { - if (!answer.shouldEject) { - console.log(cyan('Close one! Eject aborted.')); - return; - } +prompts({ + type: 'confirm', + name: 'shouldEject', + message: 'Are you sure you want to eject? This action is permanent.', + initial: false, +}).then(answer => { + if (!answer.shouldEject) { + console.log(cyan('Close one! Eject aborted.')); + return; + } - const gitStatus = getGitStatus(); - if (gitStatus) { - console.error( + const gitStatus = getGitStatus(); + if (gitStatus) { + console.error( + chalk.red( + 'This git repository has untracked files or uncommitted changes:' + ) + + '\n\n' + + gitStatus + .split('\n') + .map(line => line.match(/ .*/g)[0].trim()) + .join('\n') + + '\n\n' + chalk.red( - 'This git repository has untracked files or uncommitted changes:' - ) + - '\n\n' + - gitStatus - .split('\n') - .map(line => line.match(/ .*/g)[0].trim()) - .join('\n') + - '\n\n' + - chalk.red( - 'Remove untracked files, stash or commit any changes, and try again.' - ) - ); - process.exit(1); - } + 'Remove untracked files, stash or commit any changes, and try again.' + ) + ); + process.exit(1); + } - console.log('Ejecting...'); + console.log('Ejecting...'); - const ownPath = paths.ownPath; - const appPath = paths.appPath; + const ownPath = paths.ownPath; + const appPath = paths.appPath; - function verifyAbsent(file) { - if (fs.existsSync(path.join(appPath, file))) { - console.error( - `\`${file}\` already exists in your app folder. We cannot ` + - 'continue as you would lose all the changes in that file or directory. ' + - 'Please move or delete it (maybe make a copy for backup) and run this ' + - 'command again.' - ); - process.exit(1); - } + function verifyAbsent(file) { + if (fs.existsSync(path.join(appPath, file))) { + console.error( + `\`${file}\` already exists in your app folder. We cannot ` + + 'continue as you would lose all the changes in that file or directory. ' + + 'Please move or delete it (maybe make a copy for backup) and run this ' + + 'command again.' + ); + process.exit(1); } + } - const folders = ['config', 'config/jest', 'scripts']; - - // Make shallow array of files paths - const files = folders.reduce((files, folder) => { - return files.concat( - fs - .readdirSync(path.join(ownPath, folder)) - // set full path - .map(file => path.join(ownPath, folder, file)) - // omit dirs from file list - .filter(file => fs.lstatSync(file).isFile()) - ); - }, []); + const folders = ['config', 'config/jest', 'scripts']; + + // Make shallow array of files paths + const files = folders.reduce((files, folder) => { + return files.concat( + fs + .readdirSync(path.join(ownPath, folder)) + // set full path + .map(file => path.join(ownPath, folder, file)) + // omit dirs from file list + .filter(file => fs.lstatSync(file).isFile()) + ); + }, []); - // Ensure that the app folder is clean and we won't override any files - folders.forEach(verifyAbsent); - files.forEach(verifyAbsent); + // Ensure that the app folder is clean and we won't override any files + folders.forEach(verifyAbsent); + files.forEach(verifyAbsent); - // Prepare Jest config early in case it throws - const jestConfig = createJestConfig( - filePath => path.posix.join('', filePath), - null, - true - ); + // Prepare Jest config early in case it throws + const jestConfig = createJestConfig( + filePath => path.posix.join('', filePath), + null, + true + ); - console.log(); - console.log(cyan(`Copying files into ${appPath}`)); + console.log(); + console.log(cyan(`Copying files into ${appPath}`)); - folders.forEach(folder => { - fs.mkdirSync(path.join(appPath, folder)); - }); + folders.forEach(folder => { + fs.mkdirSync(path.join(appPath, folder)); + }); - files.forEach(file => { - let content = fs.readFileSync(file, 'utf8'); + files.forEach(file => { + let content = fs.readFileSync(file, 'utf8'); - // Skip flagged files - if (content.match(/\/\/ @remove-file-on-eject/)) { + // Skip flagged files + if (content.match(/\/\/ @remove-file-on-eject/)) { + return; + } + content = + content + // Remove dead code from .js files on eject + .replace( + /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/gm, + '' + ) + // Remove dead code from .applescript files on eject + .replace( + /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/gm, + '' + ) + .trim() + '\n'; + console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`); + fs.writeFileSync(file.replace(ownPath, appPath), content); + }); + console.log(); + + const ownPackage = require(path.join(ownPath, 'package.json')); + const appPackage = require(path.join(appPath, 'package.json')); + + console.log(cyan('Updating the dependencies')); + const ownPackageName = ownPackage.name; + if (appPackage.devDependencies) { + // We used to put react-scripts in devDependencies + if (appPackage.devDependencies[ownPackageName]) { + console.log(` Removing ${cyan(ownPackageName)} from devDependencies`); + delete appPackage.devDependencies[ownPackageName]; + } + } + appPackage.dependencies = appPackage.dependencies || {}; + if (appPackage.dependencies[ownPackageName]) { + console.log(` Removing ${cyan(ownPackageName)} from dependencies`); + delete appPackage.dependencies[ownPackageName]; + } + Object.keys(ownPackage.dependencies).forEach(key => { + // For some reason optionalDependencies end up in dependencies after install + if ( + ownPackage.optionalDependencies && + ownPackage.optionalDependencies[key] + ) { + return; + } + console.log(` Adding ${cyan(key)} to dependencies`); + appPackage.dependencies[key] = ownPackage.dependencies[key]; + }); + // Sort the deps + const unsortedDependencies = appPackage.dependencies; + appPackage.dependencies = {}; + Object.keys(unsortedDependencies) + .sort() + .forEach(key => { + appPackage.dependencies[key] = unsortedDependencies[key]; + }); + console.log(); + + console.log(cyan('Updating the scripts')); + delete appPackage.scripts['eject']; + Object.keys(appPackage.scripts).forEach(key => { + Object.keys(ownPackage.bin).forEach(binKey => { + const regex = new RegExp(binKey + ' (\\w+)', 'g'); + if (!regex.test(appPackage.scripts[key])) { return; } + appPackage.scripts[key] = appPackage.scripts[key].replace( + regex, + 'node scripts/$1.js' + ); + console.log( + ` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan( + `"node scripts/${key}.js"` + )}` + ); + }); + }); + + console.log(); + console.log(cyan('Configuring package.json')); + // Add Jest config + console.log(` Adding ${cyan('Jest')} configuration`); + appPackage.jest = jestConfig; + + // Add Babel config + console.log(` Adding ${cyan('Babel')} preset`); + appPackage.babel = { + presets: ['react-app'], + }; + + // Add ESlint config + if (!appPackage.eslintConfig) { + console.log(` Adding ${cyan('ESLint')} configuration`); + appPackage.eslintConfig = { + extends: 'react-app', + }; + } + + fs.writeFileSync( + path.join(appPath, 'package.json'), + JSON.stringify(appPackage, null, 2) + os.EOL + ); + console.log(); + + if (fs.existsSync(paths.appTypeDeclarations)) { + try { + // Read app declarations file + let content = fs.readFileSync(paths.appTypeDeclarations, 'utf8'); + const ownContent = + fs.readFileSync(paths.ownTypeDeclarations, 'utf8').trim() + os.EOL; + + // Remove react-scripts reference since they're getting a copy of the types in their project content = content - // Remove dead code from .js files on eject + // Remove react-scripts types .replace( - /\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/gm, + /^\s*\/\/\/\s*.*(?:\n|$)/gm, '' ) - // Remove dead code from .applescript files on eject - .replace( - /-- @remove-on-eject-begin([\s\S]*?)-- @remove-on-eject-end/gm, - '' - ) - .trim() + '\n'; - console.log(` Adding ${cyan(file.replace(ownPath, ''))} to the project`); - fs.writeFileSync(file.replace(ownPath, appPath), content); - }); - console.log(); - - const ownPackage = require(path.join(ownPath, 'package.json')); - const appPackage = require(path.join(appPath, 'package.json')); + .trim() + os.EOL; - console.log(cyan('Updating the dependencies')); - const ownPackageName = ownPackage.name; - if (appPackage.devDependencies) { - // We used to put react-scripts in devDependencies - if (appPackage.devDependencies[ownPackageName]) { - console.log(` Removing ${cyan(ownPackageName)} from devDependencies`); - delete appPackage.devDependencies[ownPackageName]; - } - } - appPackage.dependencies = appPackage.dependencies || {}; - if (appPackage.dependencies[ownPackageName]) { - console.log(` Removing ${cyan(ownPackageName)} from dependencies`); - delete appPackage.dependencies[ownPackageName]; + fs.writeFileSync( + paths.appTypeDeclarations, + (ownContent + os.EOL + content).trim() + os.EOL + ); + } catch (e) { + // It's not essential that this succeeds, the TypeScript user should + // be able to re-create these types with ease. } - Object.keys(ownPackage.dependencies).forEach(key => { - // For some reason optionalDependencies end up in dependencies after install - if ( - ownPackage.optionalDependencies && - ownPackage.optionalDependencies[key] - ) { - return; - } - console.log(` Adding ${cyan(key)} to dependencies`); - appPackage.dependencies[key] = ownPackage.dependencies[key]; - }); - // Sort the deps - const unsortedDependencies = appPackage.dependencies; - appPackage.dependencies = {}; - Object.keys(unsortedDependencies) - .sort() - .forEach(key => { - appPackage.dependencies[key] = unsortedDependencies[key]; - }); - console.log(); + } - console.log(cyan('Updating the scripts')); - delete appPackage.scripts['eject']; - Object.keys(appPackage.scripts).forEach(key => { + // "Don't destroy what isn't ours" + if (ownPath.indexOf(appPath) === 0) { + try { + // remove react-scripts and react-scripts binaries from app node_modules Object.keys(ownPackage.bin).forEach(binKey => { - const regex = new RegExp(binKey + ' (\\w+)', 'g'); - if (!regex.test(appPackage.scripts[key])) { - return; - } - appPackage.scripts[key] = appPackage.scripts[key].replace( - regex, - 'node scripts/$1.js' - ); - console.log( - ` Replacing ${cyan(`"${binKey} ${key}"`)} with ${cyan( - `"node scripts/${key}.js"` - )}` - ); + fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey)); }); - }); - - console.log(); - console.log(cyan('Configuring package.json')); - // Add Jest config - console.log(` Adding ${cyan('Jest')} configuration`); - appPackage.jest = jestConfig; - - // Add Babel config - console.log(` Adding ${cyan('Babel')} preset`); - appPackage.babel = { - presets: ['react-app'], - }; - - // Add ESlint config - if (!appPackage.eslintConfig) { - console.log(` Adding ${cyan('ESLint')} configuration`); - appPackage.eslintConfig = { - extends: 'react-app', - }; + fs.removeSync(ownPath); + } catch (e) { + // It's not essential that this succeeds } + } - fs.writeFileSync( - path.join(appPath, 'package.json'), - JSON.stringify(appPackage, null, 2) + os.EOL + if (fs.existsSync(paths.yarnLockFile)) { + const windowsCmdFilePath = path.join( + appPath, + 'node_modules', + '.bin', + 'react-scripts.cmd' ); - console.log(); - - if (fs.existsSync(paths.appTypeDeclarations)) { + let windowsCmdFileContent; + if (process.platform === 'win32') { + // https://github.com/facebook/create-react-app/pull/3806#issuecomment-357781035 + // Yarn is diligent about cleaning up after itself, but this causes the react-scripts.cmd file + // to be deleted while it is running. This trips Windows up after the eject completes. + // We'll read the batch file and later "write it back" to match npm behavior. try { - // Read app declarations file - let content = fs.readFileSync(paths.appTypeDeclarations, 'utf8'); - const ownContent = - fs.readFileSync(paths.ownTypeDeclarations, 'utf8').trim() + os.EOL; - - // Remove react-scripts reference since they're getting a copy of the types in their project - content = - content - // Remove react-scripts types - .replace( - /^\s*\/\/\/\s*.*(?:\n|$)/gm, - '' - ) - .trim() + os.EOL; - - fs.writeFileSync( - paths.appTypeDeclarations, - (ownContent + os.EOL + content).trim() + os.EOL - ); - } catch (e) { - // It's not essential that this succeeds, the TypeScript user should - // be able to re-create these types with ease. + windowsCmdFileContent = fs.readFileSync(windowsCmdFilePath); + } catch (err) { + // If this fails we're not worse off than if we didn't try to fix it. } } - // "Don't destroy what isn't ours" - if (ownPath.indexOf(appPath) === 0) { + console.log(cyan('Running yarn...')); + spawnSync('yarnpkg', ['--cwd', process.cwd()], { stdio: 'inherit' }); + + if (windowsCmdFileContent && !fs.existsSync(windowsCmdFilePath)) { try { - // remove react-scripts and react-scripts binaries from app node_modules - Object.keys(ownPackage.bin).forEach(binKey => { - fs.removeSync(path.join(appPath, 'node_modules', '.bin', binKey)); - }); - fs.removeSync(ownPath); - } catch (e) { - // It's not essential that this succeeds + fs.writeFileSync(windowsCmdFilePath, windowsCmdFileContent); + } catch (err) { + // If this fails we're not worse off than if we didn't try to fix it. } } + } else { + console.log(cyan('Running npm install...')); + spawnSync('npm', ['install', '--loglevel', 'error'], { + stdio: 'inherit', + }); + } + console.log(green('Ejected successfully!')); + console.log(); - if (fs.existsSync(paths.yarnLockFile)) { - const windowsCmdFilePath = path.join( - appPath, - 'node_modules', - '.bin', - 'react-scripts.cmd' - ); - let windowsCmdFileContent; - if (process.platform === 'win32') { - // https://github.com/facebook/create-react-app/pull/3806#issuecomment-357781035 - // Yarn is diligent about cleaning up after itself, but this causes the react-scripts.cmd file - // to be deleted while it is running. This trips Windows up after the eject completes. - // We'll read the batch file and later "write it back" to match npm behavior. - try { - windowsCmdFileContent = fs.readFileSync(windowsCmdFilePath); - } catch (err) { - // If this fails we're not worse off than if we didn't try to fix it. - } - } - - console.log(cyan('Running yarn...')); - spawnSync('yarnpkg', ['--cwd', process.cwd()], { stdio: 'inherit' }); - - if (windowsCmdFileContent && !fs.existsSync(windowsCmdFilePath)) { - try { - fs.writeFileSync(windowsCmdFilePath, windowsCmdFileContent); - } catch (err) { - // If this fails we're not worse off than if we didn't try to fix it. - } - } - } else { - console.log(cyan('Running npm install...')); - spawnSync('npm', ['install', '--loglevel', 'error'], { - stdio: 'inherit', - }); - } - console.log(green('Ejected successfully!')); + if (tryGitAdd(appPath)) { + console.log(cyan('Staged ejected files for commit.')); console.log(); + } - if (tryGitAdd(appPath)) { - console.log(cyan('Staged ejected files for commit.')); - console.log(); - } - - console.log( - green('Please consider sharing why you ejected in this survey:') - ); - console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1')); - console.log(); - }); + console.log(green('Please consider sharing why you ejected in this survey:')); + console.log(green(' http://goo.gl/forms/Bi6CZjk1EqsdelXk1')); + console.log(); +}); diff --git a/packages/react-scripts/scripts/init.js b/packages/react-scripts/scripts/init.js index fdc4e0ae624..7b9958e8148 100644 --- a/packages/react-scripts/scripts/init.js +++ b/packages/react-scripts/scripts/init.js @@ -81,7 +81,7 @@ function tryGitCommit(appPath) { } } -module.exports = function( +module.exports = function ( appPath, appName, verbose, @@ -103,21 +103,21 @@ module.exports = function( 'create-react-app' )} are no longer supported.` ); + console.error( + `You can fix this by running ${chalk.cyan( + 'npm uninstall -g create-react-app' + )} or ${chalk.cyan( + 'yarn global remove create-react-app' + )} before using ${chalk.cyan('create-react-app')} again.` + ); return; } - const templatePath = path.join( - require.resolve(templateName, { paths: [appPath] }), - '..' + const templatePath = path.dirname( + require.resolve(`${templateName}/package.json`, { paths: [appPath] }) ); - let templateJsonPath; - if (templateName) { - templateJsonPath = path.join(templatePath, 'template.json'); - } else { - // TODO: Remove support for this in v4. - templateJsonPath = path.join(appPath, '.template.dependencies.json'); - } + const templateJsonPath = path.join(templatePath, 'template.json'); let templateJson = {}; if (fs.existsSync(templateJsonPath)) { @@ -126,6 +126,25 @@ module.exports = function( const templatePackage = templateJson.package || {}; + // TODO: Deprecate support for root-level `dependencies` and `scripts` in v5. + // These should now be set under the `package` key. + if (templateJson.dependencies || templateJson.scripts) { + console.log(); + console.log( + chalk.yellow( + 'Root-level `dependencies` and `scripts` keys in `template.json` are deprecated.\n' + + 'This template should be updated to use the new `package` key.' + ) + ); + console.log('For more information, visit https://cra.link/templates'); + } + if (templateJson.dependencies) { + templatePackage.dependencies = templateJson.dependencies; + } + if (templateJson.scripts) { + templatePackage.scripts = templateJson.scripts; + } + // Keys to ignore in templatePackage const templatePackageBlacklist = [ 'name', @@ -142,7 +161,6 @@ module.exports = function( 'man', 'directories', 'repository', - 'devDependencies', 'peerDependencies', 'bundledDependencies', 'optionalDependencies', @@ -170,8 +188,7 @@ module.exports = function( appPackage.dependencies = appPackage.dependencies || {}; // Setup the script rules - // TODO: deprecate 'scripts' key directly on templateJson - const templateScripts = templatePackage.scripts || templateJson.scripts || {}; + const templateScripts = templatePackage.scripts || {}; appPackage.scripts = Object.assign( { start: 'react-scripts start', @@ -283,14 +300,15 @@ module.exports = function( args = ['install', '--save', verbose && '--verbose'].filter(e => e); } - // Install additional template dependencies, if present - // TODO: deprecate 'dependencies' key directly on templateJson - const templateDependencies = - templatePackage.dependencies || templateJson.dependencies; - if (templateDependencies) { + // Install additional template dependencies, if present. + const dependenciesToInstall = Object.entries({ + ...templatePackage.dependencies, + ...templatePackage.devDependencies, + }); + if (dependenciesToInstall.length) { args = args.concat( - Object.keys(templateDependencies).map(key => { - return `${key}@${templateDependencies[key]}`; + dependenciesToInstall.map(([dependency, version]) => { + return `${dependency}@${version}`; }) ); } diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 2568ab36db1..ffbb15d1204 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -44,10 +44,14 @@ const { prepareUrls, } = require('react-dev-utils/WebpackDevServerUtils'); const openBrowser = require('react-dev-utils/openBrowser'); +const semver = require('semver'); const paths = require('../config/paths'); const configFactory = require('../config/webpack.config'); const createDevServerConfig = require('../config/webpackDevServer.config'); +const getClientEnvironment = require('../config/env'); +const react = require(require.resolve('react', { paths: [paths.appPath] })); +const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1)); const useYarn = fs.existsSync(paths.yarnLockFile); const isInteractive = process.stdout.isTTY; @@ -72,7 +76,7 @@ if (process.env.HOST) { `If this was unintentional, check that you haven't mistakenly set it in your shell.` ); console.log( - `Learn more here: ${chalk.yellow('https://bit.ly/CRA-advanced-config')}` + `Learn more here: ${chalk.yellow('https://cra.link/advanced-config')}` ); console.log(); } @@ -95,6 +99,7 @@ checkBrowsers(paths.appPath, isInteractive) const config = configFactory('development'); const protocol = process.env.HTTPS === 'true' ? 'https' : 'http'; const appName = require(paths.appPackageJson).name; + const useTypeScript = fs.existsSync(paths.appTsConfig); const tscCompileOnError = process.env.TSC_COMPILE_ON_ERROR === 'true'; const urls = prepareUrls( @@ -142,36 +147,31 @@ checkBrowsers(paths.appPath, isInteractive) clearConsole(); } - // We used to support resolving modules according to `NODE_PATH`. - // This now has been deprecated in favor of jsconfig/tsconfig.json - // This lets you use absolute paths in imports inside large monorepos: - if (process.env.NODE_PATH) { + if (env.raw.FAST_REFRESH && semver.lt(react.version, '16.10.0')) { console.log( chalk.yellow( - 'Setting NODE_PATH to resolve modules absolutely has been deprecated in favor of setting baseUrl in jsconfig.json (or tsconfig.json if you are using TypeScript) and will be removed in a future major release of create-react-app.' + `Fast Refresh requires React 16.10 or higher. You are using React ${react.version}.` ) ); - console.log(); } console.log(chalk.cyan('Starting the development server...\n')); openBrowser(urls.localUrlForBrowser); }); - ['SIGINT', 'SIGTERM'].forEach(function(sig) { - process.on(sig, function() { + ['SIGINT', 'SIGTERM'].forEach(function (sig) { + process.on(sig, function () { devServer.close(); process.exit(); }); }); - if (isInteractive || process.env.CI !== 'true') { + if (process.env.CI !== 'true') { // Gracefully exit when stdin ends - process.stdin.on('end', function() { + process.stdin.on('end', function () { devServer.close(); process.exit(); }); - process.stdin.resume(); } }) .catch(err => { diff --git a/packages/react-scripts/scripts/utils/createJestConfig.js b/packages/react-scripts/scripts/utils/createJestConfig.js index 5c17ad69a0e..ca316aab5c5 100644 --- a/packages/react-scripts/scripts/utils/createJestConfig.js +++ b/packages/react-scripts/scripts/utils/createJestConfig.js @@ -38,18 +38,19 @@ module.exports = (resolve, rootDir, isEjecting) => { '/src/**/__tests__/**/*.{js,jsx,ts,tsx}', '/src/**/*.{spec,test}.{js,jsx,ts,tsx}', ], - testEnvironment: 'jest-environment-jsdom-fourteen', + testEnvironment: 'jsdom', + testRunner: require.resolve('jest-circus/runner'), transform: { - '^.+\\.(js|jsx|ts|tsx)$': isEjecting - ? '/node_modules/babel-jest' - : resolve('config/jest/babelTransform.js'), + '^.+\\.(js|jsx|mjs|cjs|ts|tsx)$': resolve( + 'config/jest/babelTransform.js' + ), '^.+\\.css$': resolve('config/jest/cssTransform.js'), - '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': resolve( + '^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)': resolve( 'config/jest/fileTransform.js' ), }, transformIgnorePatterns: [ - '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|ts|tsx)$', + '[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$', '^.+\\.module\\.(css|sass|scss)$', ], modulePaths: modules.additionalModulePaths || [], @@ -65,6 +66,7 @@ module.exports = (resolve, rootDir, isEjecting) => { 'jest-watch-typeahead/filename', 'jest-watch-typeahead/testname', ], + resetMocks: true, }; if (rootDir) { config.rootDir = rootDir; @@ -85,6 +87,7 @@ module.exports = (resolve, rootDir, isEjecting) => { 'resetModules', 'restoreMocks', 'snapshotSerializers', + 'testMatch', 'transform', 'transformIgnorePatterns', 'watchPathIgnorePatterns', diff --git a/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js b/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js index ebd93d7b667..949f34ab7d2 100644 --- a/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js +++ b/packages/react-scripts/scripts/utils/verifyTypeScriptSetup.js @@ -14,9 +14,23 @@ const resolve = require('resolve'); const path = require('path'); const paths = require('../../config/paths'); const os = require('os'); +const semver = require('semver'); const immer = require('react-dev-utils/immer').produce; const globby = require('react-dev-utils/globby').sync; +const hasJsxRuntime = (() => { + if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { + return false; + } + + try { + require.resolve('react/jsx-runtime', { paths: [paths.appPath] }); + return true; + } catch (e) { + return false; + } +})(); + function writeJson(fileName, object) { fs.writeFileSync( fileName, @@ -59,9 +73,17 @@ function verifyTypeScriptSetup() { // Ensure typescript is installed let ts; try { + // TODO: Remove this hack once `globalThis` issue is resolved + // https://github.com/jsdom/jsdom/issues/2961 + const globalThisWasDefined = !!global.globalThis; + ts = require(resolve.sync('typescript', { basedir: paths.appNodeModules, })); + + if (!globalThisWasDefined && !!global.globalThis) { + delete global.globalThis; + } } catch (_) { console.error( chalk.bold.red( @@ -106,8 +128,7 @@ function verifyTypeScriptSetup() { allowSyntheticDefaultImports: { suggested: true }, strict: { suggested: true }, forceConsistentCasingInFileNames: { suggested: true }, - // TODO: Enable for v4.0 (#6936) - // noFallthroughCasesInSwitch: { suggested: true }, + noFallthroughCasesInSwitch: { suggested: true }, // These values are required and cannot be changed by the user // Keep this in sync with the webpack config @@ -125,8 +146,15 @@ function verifyTypeScriptSetup() { isolatedModules: { value: true, reason: 'implementation limitation' }, noEmit: { value: true }, jsx: { - parsedValue: ts.JsxEmit.React, - suggested: 'react', + parsedValue: + hasJsxRuntime && semver.gte(ts.version, '4.1.0-beta') + ? ts.JsxEmit.ReactJSX + : ts.JsxEmit.React, + value: + hasJsxRuntime && semver.gte(ts.version, '4.1.0-beta') + ? 'react-jsx' + : 'react', + reason: 'to support the new JSX transform in React 17', }, paths: { value: undefined, reason: 'aliased imports are not supported' }, }; @@ -190,7 +218,7 @@ function verifyTypeScriptSetup() { if (appTsConfig.compilerOptions == null) { appTsConfig.compilerOptions = {}; firstTimeSetup = true; - } + } for (const option of Object.keys(compilerOptions)) { const { parsedValue, value, suggested, reason } = compilerOptions[option]; @@ -200,7 +228,9 @@ function verifyTypeScriptSetup() { if (suggested != null) { if (parsedCompilerOptions[option] === undefined) { - appTsConfig.compilerOptions[option] = suggested; + appTsConfig = immer(appTsConfig, config => { + config.compilerOptions[option] = suggested; + }); messages.push( `${coloredOption} to be ${chalk.bold( 'suggested' @@ -208,7 +238,9 @@ function verifyTypeScriptSetup() { ); } } else if (parsedCompilerOptions[option] !== valueToCheck) { - appTsConfig.compilerOptions[option] = value; + appTsConfig = immer(appTsConfig, config => { + config.compilerOptions[option] = value; + }); messages.push( `${coloredOption} ${chalk.bold( valueToCheck == null ? 'must not' : 'must' @@ -220,7 +252,9 @@ function verifyTypeScriptSetup() { // tsconfig will have the merged "include" and "exclude" by this point if (parsedTsConfig.include == null) { - appTsConfig.include = ['src']; + appTsConfig = immer(appTsConfig, config => { + config.include = ['src']; + }); messages.push( `${chalk.cyan('include')} should be ${chalk.cyan.bold('src')}` ); diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index 03ced3cd297..1269cf81993 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -1 +1 @@ -Learn about making a Progressive Web App [here](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) +This file has moved [here](https://github.com/facebook/create-react-app/blob/master/packages/cra-template/template/README.md) From 3b3b8a375d4c17073ba5d7b0adfa7a04a3cd9365 Mon Sep 17 00:00:00 2001 From: Russian Date: Thu, 4 Dec 2025 13:02:27 -0300 Subject: [PATCH 2/2] fix build npmjs package script --- package.json | 6 +++--- packages/react-error-overlay/package.json | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 92a9291ecb3..affd65b0461 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "packages/*" ], "scripts": { - "build": "cd packages/react-scripts && node bin/react-scripts.js build", + "build": "cd packages/react-error-overlay/ && yarn build:prod", "changelog": "lerna-changelog", "create-react-app": "node tasks/cra.js", "e2e": "tasks/e2e-simple.sh", @@ -24,13 +24,13 @@ "@testing-library/react": "^9.3.0", "@testing-library/user-event": "^7.1.2", "alex": "^8.0.0", - "eslint": "^6.1.0", + "eslint": "^7.11.0", "execa": "1.0.0", "fs-extra": "^7.0.1", "get-port": "^4.2.0", "globby": "^9.1.0", "husky": "^1.3.1", - "jest": "24.9.0", + "jest": "26.6.0", "lerna": "3.19.0", "lerna-changelog": "~0.8.2", "lint-staged": "^8.0.4", diff --git a/packages/react-error-overlay/package.json b/packages/react-error-overlay/package.json index d3455b42347..83ca9ae5328 100644 --- a/packages/react-error-overlay/package.json +++ b/packages/react-error-overlay/package.json @@ -38,13 +38,13 @@ "@babel/core": "7.9.0", "anser": "1.4.9", "babel-eslint": "10.1.0", - "babel-jest": "^24.9.0", + "babel-jest": "^26.6.0", "babel-loader": "8.1.0", "babel-preset-react-app": "^9.1.2", "chalk": "2.4.2", "chokidar": "^3.3.0", "cross-env": "6.0.3", - "eslint": "^6.1.0", + "eslint": "^7.11.0", "eslint-config-react-app": "^5.2.1", "eslint-plugin-flowtype": "4.5.2", "eslint-plugin-import": "2.20.1", @@ -52,7 +52,7 @@ "eslint-plugin-react": "7.19.0", "flow-bin": "^0.116.0", "html-entities": "1.2.1", - "jest": "24.9.0", + "jest": "26.6.0", "jest-fetch-mock": "2.1.2", "object-assign": "4.1.1", "promise": "8.0.3",