diff --git a/.eslintignore b/.eslintignore index 27c694cea55..531e70634f8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,5 +2,6 @@ node_modules/ build my-app* packages/react-scripts/template +packages/react-scripts/template-universal packages/react-scripts/fixtures fixtures/ diff --git a/README.md b/README.md index 347d4359760..cfc7443636f 100644 --- a/README.md +++ b/README.md @@ -122,6 +122,11 @@ The build is minified and the filenames include the hashes.
Your app is ready to be deployed. +### `npm run serve` or `yarn serve` + +Launches a server for the content in the `build` folder.
+When an `index.node.js` file exists, it loads the Node bundle and server the app with server rendering. + ## User Guide You can find detailed instructions on using Create React App and many tips in [its documentation](https://facebook.github.io/create-react-app/). @@ -149,6 +154,7 @@ Your environment will have everything you need to build a modern single-page Rea - A live development server that warns about common mistakes. - A build script to bundle JS, CSS, and images for production, with hashes and sourcemaps. - An offline-first [service worker](https://developers.google.com/web/fundamentals/getting-started/primers/service-workers) and a [web app manifest](https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/), meeting all the [Progressive Web App](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) criteria. (_Note: Using the service worker is opt-in as of `react-scripts@2.0.0` and higher_) +- Ability to build a Node.js version of the app, which can be used for implementing Server Rendering. - Hassle-free updates for the above tools with a single dependency. Check out [this guide](https://github.com/nitishdayal/cra_closer_look) for an overview of how these tools fit together. @@ -171,8 +177,6 @@ Here are a few common cases where you might want to try something else: - If you need to **publish a React component**, [nwb](https://github.com/insin/nwb) can [also do this](https://github.com/insin/nwb#react-components-and-libraries), as well as [Neutrino's react-components preset](https://neutrino.js.org/packages/react-components/). -- If you want to do **server rendering** with React and Node.js, check out [Next.js](https://github.com/zeit/next.js/) or [Razzle](https://github.com/jaredpalmer/razzle). Create React App is agnostic of the backend, and just produces static HTML/JS/CSS bundles. - - If your website is **mostly static** (for example, a portfolio or a blog), consider using [Gatsby](https://www.gatsbyjs.org/) instead. Unlike Create React App, it pre-renders the website into HTML at the build time. - Finally, if you need **more customization**, check out [Neutrino](https://neutrino.js.org/) and its [React preset](https://neutrino.js.org/packages/react/). diff --git a/packages/babel-preset-react-app/create.js b/packages/babel-preset-react-app/create.js index 7b7f1da6ed1..ee4c99917d0 100644 --- a/packages/babel-preset-react-app/create.js +++ b/packages/babel-preset-react-app/create.js @@ -29,10 +29,12 @@ module.exports = function(api, opts, env) { var isEnvProduction = env === 'production'; var isEnvTest = env === 'test'; + var isTargettingNode = validateBoolOption('node', opts.node, false); + var useESModules = validateBoolOption( 'useESModules', opts.useESModules, - isEnvDevelopment || isEnvProduction + !isTargettingNode && (isEnvDevelopment || isEnvProduction) ); var isFlowEnabled = validateBoolOption('flow', opts.flow, true); var isTypeScriptEnabled = validateBoolOption( @@ -75,21 +77,35 @@ module.exports = function(api, opts, env) { }, }, ], - (isEnvProduction || isEnvDevelopment) && [ + isTargettingNode && [ // Latest stable ECMAScript features require('@babel/preset-env').default, { - // Allow importing core-js in entrypoint and use browserlist to select polyfills - useBuiltIns: 'entry', - // Set the corejs version we are using to avoid warnings in console - // This will need to change once we upgrade to corejs@3 - corejs: 3, + targets: { + node: 6, + }, // Do not transform modules to CJS - modules: false, + modules: 'cjs', // Exclude transforms that make all code slower exclude: ['transform-typeof-symbol'], }, ], + !isTargettingNode && + (isEnvProduction || isEnvDevelopment) && [ + // Latest stable ECMAScript features + require('@babel/preset-env').default, + { + // Allow importing core-js in entrypoint and use browserlist to select polyfills + useBuiltIns: 'entry', + // Set the corejs version we are using to avoid warnings in console + // This will need to change once we upgrade to corejs@3 + corejs: 3, + // Do not transform modules to CJS + modules: false, + // Exclude transforms that make all code slower + exclude: ['transform-typeof-symbol'], + }, + ], [ require('@babel/preset-react').default, { @@ -170,7 +186,7 @@ module.exports = function(api, opts, env) { { corejs: false, helpers: areHelpersEnabled, - regenerator: true, + regenerator: !isTargettingNode, // https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules // We should turn this on once the lowest version of Node LTS // supports ES Modules. @@ -190,7 +206,7 @@ module.exports = function(api, opts, env) { ], // Adds syntax support for import() require('@babel/plugin-syntax-dynamic-import').default, - isEnvTest && + (isTargettingNode || isEnvTest) && // Transform dynamic import to require require('babel-plugin-dynamic-import-node'), ].filter(Boolean), diff --git a/packages/babel-preset-react-app/dependencies.js b/packages/babel-preset-react-app/dependencies.js index 86902d36bee..9273296c671 100644 --- a/packages/babel-preset-react-app/dependencies.js +++ b/packages/babel-preset-react-app/dependencies.js @@ -36,6 +36,8 @@ module.exports = function(api, opts) { var isEnvProduction = env === 'production'; var isEnvTest = env === 'test'; + var isTargettingNode = validateBoolOption('node', opts.node, false); + var areHelpersEnabled = validateBoolOption('helpers', opts.helpers, false); var useAbsoluteRuntime = validateBoolOption( 'absoluteRuntime', @@ -80,21 +82,35 @@ module.exports = function(api, opts) { exclude: ['transform-typeof-symbol'], }, ], - (isEnvProduction || isEnvDevelopment) && [ + isTargettingNode && [ // Latest stable ECMAScript features require('@babel/preset-env').default, { - // Allow importing core-js in entrypoint and use browserlist to select polyfills - useBuiltIns: 'entry', - // Set the corejs version we are using to avoid warnings in console - // This will need to change once we upgrade to corejs@3 - corejs: 3, + targets: { + node: 6, + }, // Do not transform modules to CJS - modules: false, + modules: 'cjs', // Exclude transforms that make all code slower exclude: ['transform-typeof-symbol'], }, ], + !isTargettingNode && + (isEnvProduction || isEnvDevelopment) && [ + // Latest stable ECMAScript features + require('@babel/preset-env').default, + { + // Allow importing core-js in entrypoint and use browserlist to select polyfills + useBuiltIns: 'entry', + // Set the corejs version we are using to avoid warnings in console + // This will need to change once we upgrade to corejs@3 + corejs: 3, + // Do not transform modules to CJS + modules: false, + // Exclude transforms that make all code slower + exclude: ['transform-typeof-symbol'], + }, + ], ].filter(Boolean), plugins: [ // Necessary to include regardless of the environment because @@ -127,11 +143,12 @@ module.exports = function(api, opts) { { corejs: false, helpers: areHelpersEnabled, - regenerator: true, + regenerator: !isTargettingNode, // https://babeljs.io/docs/en/babel-plugin-transform-runtime#useesmodules // We should turn this on once the lowest version of Node LTS // supports ES Modules. - useESModules: isEnvDevelopment || isEnvProduction, + useESModules: + !isTargettingNode && (isEnvDevelopment || isEnvProduction), // Undocumented option that lets us encapsulate our runtime, ensuring // the correct version is used // https://github.com/babel/babel/blob/090c364a90fe73d36a30707fc612ce037bdbbb24/packages/babel-plugin-transform-runtime/src/index.js#L35-L42 @@ -140,9 +157,9 @@ module.exports = function(api, opts) { ], // Adds syntax support for import() require('@babel/plugin-syntax-dynamic-import').default, - isEnvTest && + (isTargettingNode || isEnvTest) && // Transform dynamic import to require - require('babel-plugin-transform-dynamic-import').default, + require('babel-plugin-dynamic-import-node'), ].filter(Boolean), }; }; diff --git a/packages/create-react-app/createReactApp.js b/packages/create-react-app/createReactApp.js index 2e69901b455..138a4471bd7 100755 --- a/packages/create-react-app/createReactApp.js +++ b/packages/create-react-app/createReactApp.js @@ -79,6 +79,7 @@ const program = new commander.Command(packageJson.name) .option('--use-npm') .option('--use-pnp') .option('--typescript') + .option('--universal') .allowUnknownOption() .on('--help', () => { console.log(` Only ${chalk.green('')} is required.`); @@ -181,6 +182,7 @@ createApp( program.useNpm, program.usePnp, program.typescript, + program.universal, hiddenProgram.internalTestingTemplate ); @@ -191,6 +193,7 @@ function createApp( useNpm, usePnp, useTypescript, + useUniversal, template ) { const root = path.resolve(name); @@ -296,7 +299,8 @@ function createApp( template, useYarn, usePnp, - useTypescript + useTypescript, + useUniversal ); } @@ -380,7 +384,8 @@ function run( template, useYarn, usePnp, - useTypescript + useTypescript, + universal ) { getInstallPackage(version, originalDirectory).then(packageToInstall => { const allDependencies = ['react', 'react-dom', packageToInstall]; @@ -436,7 +441,7 @@ function run( cwd: process.cwd(), args: nodeArgs, }, - [root, appName, verbose, originalDirectory, template], + [root, appName, verbose, originalDirectory, template, universal], ` var init = require('${packageName}/scripts/init.js'); init.apply(null, JSON.parse(process.argv[1])); diff --git a/packages/react-dev-utils/WebpackDevServerUtils.js b/packages/react-dev-utils/WebpackDevServerUtils.js index 1d7021e037c..ca533e0625c 100644 --- a/packages/react-dev-utils/WebpackDevServerUtils.js +++ b/packages/react-dev-utils/WebpackDevServerUtils.js @@ -139,14 +139,14 @@ function createCompiler({ let tsMessagesResolver; if (useTypeScript) { - compiler.hooks.beforeCompile.tap('beforeCompile', () => { + compiler.compilers[0].hooks.beforeCompile.tap('beforeCompile', () => { tsMessagesPromise = new Promise(resolve => { tsMessagesResolver = msgs => resolve(msgs); }); }); forkTsCheckerWebpackPlugin - .getCompilerHooks(compiler) + .getCompilerHooks(compiler.compilers[0]) .receive.tap('afterTypeScriptCheck', (diagnostics, lints) => { const allMsgs = [...diagnostics, ...lints]; const format = message => @@ -173,7 +173,7 @@ function createCompiler({ // them in a readable focused way. // We only construct the warnings and errors for speed: // https://github.com/facebook/create-react-app/issues/4492#issuecomment-421959548 - const statsData = stats.toJson({ + const statsData = stats.stats[0].toJson({ all: false, warnings: true, errors: true, @@ -195,8 +195,8 @@ function createCompiler({ // Push errors and warnings into compilation result // to show them after page refresh triggered by user. - stats.compilation.errors.push(...messages.errors); - stats.compilation.warnings.push(...messages.warnings); + stats.stats[0].compilation.errors.push(...messages.errors); + stats.stats[0].compilation.warnings.push(...messages.warnings); if (messages.errors.length > 0) { devSocket.errors(messages.errors); @@ -256,7 +256,7 @@ function createCompiler({ arg => arg.indexOf('--smoke-test') > -1 ); if (isSmokeTest) { - compiler.hooks.failed.tap('smokeTest', async () => { + compiler.compilers[0].hooks.failed.tap('smokeTest', async () => { await tsMessagesPromise; process.exit(1); }); diff --git a/packages/react-dev-utils/devRendererMiddleware.js b/packages/react-dev-utils/devRendererMiddleware.js new file mode 100644 index 00000000000..1276eb2ac48 --- /dev/null +++ b/packages/react-dev-utils/devRendererMiddleware.js @@ -0,0 +1,173 @@ +'use strict'; + +const chalk = require('chalk'); +const Module = require('module'); +const path = require('path'); +const tmp = require('tmp'); +const fs = require('fs'); +const clearConsole = require('./clearConsole'); + +module.exports = function devRendererMiddleware( + nodeBuildPath, + registerSourceMap +) { + return (req, res, next) => { + let cache = {}; + let { webpackStats, fs: memoryFs } = res.locals; + + // The index.html file contains `