Skip to content

Export render function #1292

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@ script:
- 'if [ $TEST_SUITE = "simple" ]; then tasks/e2e-simple.sh; fi'
- 'if [ $TEST_SUITE = "installs" ]; then tasks/e2e-installs.sh; fi'
- 'if [ $TEST_SUITE = "kitchensink" ]; then tasks/e2e-kitchensink.sh; fi'
- 'if [ $TEST_SUITE = "server-render" ]; then tasks/e2e-server-render.sh; fi'
env:
global:
- USE_YARN=no
matrix:
- TEST_SUITE=simple
- TEST_SUITE=installs
- TEST_SUITE=kitchensink
- TEST_SUITE=server-render
matrix:
include:
- node_js: 0.10
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ The [User Guide](https://github.com/facebookincubator/create-react-app/blob/mast
- [Making a Progressive Web App](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#making-a-progressive-web-app)
- [Deployment](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment)
- [Advanced Configuration](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#advanced-configuration)
- [Server-side Rendering](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#server-side-rendering)
- [Troubleshooting](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#troubleshooting)

A copy of the user guide will be created as `README.md` in your project folder.
Expand Down Expand Up @@ -165,7 +166,7 @@ Please refer to the [User Guide](https://github.com/facebookincubator/create-rea
* Autoprefixed CSS, so you don’t need `-webkit` or other prefixes.
* A `build` script to bundle JS, CSS, and images for production, with sourcemaps.

**The feature set is intentionally limited**. It doesn’t support advanced features such as server rendering or CSS modules. The tool is also **non-configurable** because it is hard to provide a cohesive experience and easy updates across a set of tools when the user can tweak anything.
**The feature set is intentionally limited**. It doesn’t support advanced features such as CSS modules. The tool is also **non-configurable** because it is hard to provide a cohesive experience and easy updates across a set of tools when the user can tweak anything.

**You don’t have to use this.** Historically it has been easy to [gradually adopt](https://www.youtube.com/watch?v=BF58ZJ1ZQxY) React. However many people create new single-page React apps from scratch every day. We’ve heard [loud](https://medium.com/@ericclemmons/javascript-fatigue-48d4011b6fc4) and [clear](https://twitter.com/thomasfuchs/status/708675139253174273) that this process can be error-prone and tedious, especially if this is your first JavaScript build stack. This project is an attempt to figure out a good way to start developing React apps.

Expand All @@ -183,7 +184,6 @@ You don’t have to ever use `eject`. The curated feature set is suitable for sm

Some features are currently **not supported**:

* Server rendering.
* Some experimental syntax extensions (e.g. decorators).
* CSS Modules.
* Importing LESS or Sass directly ([but you still can use them](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-css-preprocessor-sass-less-etc)).
Expand Down
4 changes: 3 additions & 1 deletion packages/react-scripts/config/polyfills.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ if (typeof Promise === 'undefined') {
// inconsistent state due to an error, but it gets swallowed by a Promise,
// and the user has no idea what causes React's erratic future behavior.
require('promise/lib/rejection-tracking').enable();
window.Promise = require('promise/lib/es6-extensions.js');
if (typeof window !== 'undefined') {
window.Promise = require('promise/lib/es6-extensions.js');
}
}

// fetch() polyfill for making API calls.
Expand Down
16 changes: 16 additions & 0 deletions packages/react-scripts/config/webpack.config.prod.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin');
const camelCase = require('lodash.camelcase');
const paths = require('./paths');
const getClientEnvironment = require('./env');

const packageJson = require(paths.appPackageJson);

// Webpack uses `publicPath` to determine where the app is being served from.
// It requires a trailing slash, or the file assets will get an incorrect path.
const publicPath = paths.servedPath;
Expand Down Expand Up @@ -50,6 +53,15 @@ const extractTextPluginOptions = shouldUseRelativeAssetPaths
{ publicPath: Array(cssFilename.split('/').length).join('../') }
: {};

let libraryName;
let libraryTarget;

if (packageJson.name) {
// Will also strip non-alphanumerics
libraryName = camelCase(packageJson.name);
libraryTarget = 'umd';
}

// This is the production configuration.
// It compiles slowly and is focused on producing a fast and minimal bundle.
// The development configuration is different and lives in a separate file.
Expand All @@ -71,6 +83,10 @@ module.exports = {
chunkFilename: 'static/js/[name].[chunkhash:8].chunk.js',
// We inferred the "public path" (such as / or /my-project) from homepage.
publicPath: publicPath,
// Options for generating a final bundle that is a module or library
library: libraryName,
libraryTarget: libraryTarget,
umdNamedDefine: true,
},
resolve: {
// This allows you to set a fallback for where Webpack should look for modules.
Expand Down
1 change: 1 addition & 0 deletions packages/react-scripts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"html-webpack-plugin": "2.28.0",
"http-proxy-middleware": "0.17.3",
"jest": "18.1.0",
"lodash.camelcase": "4.3.0",
"object-assign": "4.1.1",
"postcss-loader": "1.3.3",
"promise": "7.1.1",
Expand Down
38 changes: 38 additions & 0 deletions packages/react-scripts/scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ process.on('unhandledRejection', err => {
require('dotenv').config({ silent: true });

const chalk = require('chalk');
const espree = require('espree');
const fs = require('fs-extra');
const path = require('path');
const url = require('url');
Expand Down Expand Up @@ -69,12 +70,49 @@ function printErrors(summary, errors) {
});
}

function bundleExportsFunction() {
const appIndexJs = fs.readFileSync(paths.appIndexJs, 'utf8');

const parserConfig = {
loc: true,
ecmaVersion: 2017,
sourceType: 'module',
ecmaFeatures: {
jsx: true,
impliedStrict: true,
experimentalObjectRestSpread: true,
},
};

let ast;
try {
ast = espree.parse(appIndexJs, parserConfig);
} catch (err) {
// TODO: It would be super-awesome to use the same formatting as
// Webpack. Need to track down where that comes from.
throw `Failed to parse ${paths.appIndexJs}: ${err.message} at ${err.lineNumber}:${err.column}`;
}

const exportNodes = ast.body.filter(
node =>
node.type === 'ExportDefaultDeclaration' ||
(node.type === 'ExportNamedDeclaration' &&
node.declaration.id.name === 'render')
);

return exportNodes.length > 0;
}

// Create the production build and print the deployment instructions.
function build(previousFileSizes) {
console.log('Creating an optimized production build...');

let compiler;
try {
if (bundleExportsFunction()) {
console.log('Building server bundle...');
}

compiler = webpack(config);
} catch (err) {
printErrors('Failed to compile.', [err]);
Expand Down
23 changes: 23 additions & 0 deletions packages/react-scripts/template/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ You can find the most recent version of this guide [here](https://github.com/fac
- [S3 and CloudFront](#s3-and-cloudfront)
- [Surge](#surge)
- [Advanced Configuration](#advanced-configuration)
- [Server-side Rendering](#server-side-rendering)
- [Troubleshooting](#troubleshooting)
- [`npm start` doesn’t detect changes](#npm-start-doesnt-detect-changes)
- [`npm test` hangs on macOS Sierra](#npm-test-hangs-on-macos-sierra)
Expand Down Expand Up @@ -1562,6 +1563,28 @@ HTTPS | :white_check_mark: | :x: | When set to `true`, Create React App will run
PUBLIC_URL | :x: | :white_check_mark: | Create React App assumes your application is hosted at the serving web server's root or a subpath as specified in [`package.json` (`homepage`)](#building-for-relative-paths). Normally, Create React App ignores the hostname. You may use this variable to force assets to be referenced verbatim to the url you provide (hostname included). This may be particularly useful when using a CDN to host your application.
CI | :large_orange_diamond: | :white_check_mark: | When set to `true`, Create React App treats warnings as failures in the build. It also makes the test runner non-watching. Most CIs set this flag by default.

## Server-side Rendering

Create React App has limited support for server-side rendering. The production build is generated as a [UMD module](http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd/), which means it can be loaded as a CommonsJS module, an AMD module, or as a global variable. The AMD module name and global variable name is derived from the project name in `package.json`, by turning it into a camel-cased version. For example, 'my-app' becomes 'myApp', and '@org/our-app' becomes 'orgOurApp'. This module can then be used to render static markup on the server.

Since the production build generates files with a hash in the name, before you can load the bundle you first need to look up the name in `asset-manifest.json`. For example, in a NodeJS app:

```js
const manifest = require('./build/asset-manifest.json');
const bundleName = manifest['main.js'];
```

When you generate a project with `create-react-app`, `src/index.js` includes an example rendering function as the default export. In a NodeJS app, you might load the bundle and call the render function as following. You then would need to build the final, complete HTML page and send it to the client.

```js
const renderApp = require(`./build/${bundleName}`).default;
const bodyHtml = renderApp();
```

You can change the render function however you like, e.g. to add Redux or react-router, and pass any parameters to the render function that you need. Please keep in mind that server-side rendering is not a primary goal of Create React App, and only the out-of-the-box configuration is supported. In particular, if you are using code-splitting then you will have to eject since the Webpack target is "web", which won't work on the server chunks are loaded with JSONP. You'll need to generate a [second Webpack config](https://webpack.js.org/concepts/targets/#multiple-targets) with e.g. a "node" target.

If you're not interested in server-side rendering, feel free to delete the render function from `src/index.js`.

## Troubleshooting

### `npm start` doesn’t detect changes
Expand Down
9 changes: 8 additions & 1 deletion packages/react-scripts/template/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { renderToString } from 'react-dom/server';
import App from './App';
import './index.css';

ReactDOM.render(<App />, document.getElementById('root'));
if (typeof window !== 'undefined') {
ReactDOM.render(<App />, document.getElementById('root'));
}

export default function render() {
return renderToString(<App />);
}
Loading