diff --git a/packages/react-scripts/bin/react-scripts.js b/packages/react-scripts/bin/react-scripts.js index 09604f6a03f..59832af1a14 100755 --- a/packages/react-scripts/bin/react-scripts.js +++ b/packages/react-scripts/bin/react-scripts.js @@ -8,6 +8,20 @@ 'use strict'; +// Check Node.js version early for clearer errors (see #5430) +const currentNodeVersion = process.versions.node; +const [major] = currentNodeVersion.split('.'); +if (Number(major) < 14) { + console.error( + 'You are running Node ' + + currentNodeVersion + + '.\n' + + 'react-scripts requires Node 14 or higher. \n' + + 'Please update your version of Node.' + ); + process.exit(1); +} + // 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. diff --git a/packages/react-scripts/config/modules.js b/packages/react-scripts/config/modules.js index 22820993a25..1ab4f7ed414 100644 --- a/packages/react-scripts/config/modules.js +++ b/packages/react-scripts/config/modules.js @@ -123,7 +123,39 @@ function getModules() { // Otherwise we'll check if there is jsconfig.json // for non TS projects. } else if (hasJsConfig) { - config = require(paths.appJsConfig); + // Prefer using TypeScript parser to support JSON with comments in jsconfig.json + let ts; + try { + ts = require( + resolve.sync('typescript', { + basedir: paths.appNodeModules, + }) + ); + } catch (e) { + ts = null; + } + + if (ts) { + // Use TypeScript's tolerant JSON parser which supports comments (JSONC) + config = ts.readConfigFile(paths.appJsConfig, ts.sys.readFile).config; + } else { + // Fallback: parse as strict JSON and provide actionable error for JSONC + try { + const fileContent = fs.readFileSync(paths.appJsConfig, 'utf8'); + config = JSON.parse(fileContent); + } catch (e) { + throw new Error( + chalk.red.bold( + 'Failed to parse jsconfig.json.\n' + + 'If your jsconfig.json contains comments, please install TypeScript in your project:\n' + + ' npm install --save-dev typescript\n' + + 'or\n' + + ' yarn add --dev typescript\n' + + 'Create React App will then parse jsconfig.json with comment support.' + ) + ); + } + } } config = config || {}; diff --git a/packages/react-scripts/scripts/eject.js b/packages/react-scripts/scripts/eject.js index 67e2bb71cde..0addfda6ed4 100644 --- a/packages/react-scripts/scripts/eject.js +++ b/packages/react-scripts/scripts/eject.js @@ -237,8 +237,29 @@ prompts({ // Add Babel config console.log(` Adding ${cyan('Babel')} preset`); + // Detect if the new JSX transform is available and prefer it after eject + const hasJsxRuntime = (() => { + if (process.env.DISABLE_NEW_JSX_TRANSFORM === 'true') { + return false; + } + try { + require.resolve('react/jsx-runtime'); + return true; + } catch (e) { + return false; + } + })(); + appPackage.babel = { - presets: ['react-app'], + // Preserve preset with explicit runtime so ejected apps keep working without importing React + presets: [ + [ + 'react-app', + { + runtime: hasJsxRuntime ? 'automatic' : 'classic', + }, + ], + ], }; // Add ESlint config