From b9da1741cb59d3756cb2b2a0ae37b1a4fd7eef0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 27 Apr 2017 21:03:32 -0700 Subject: [PATCH] SSR fixture (#9547) Adds a server-rendering fixture based on create-react-app without ejecting. This is using the full-page strategy because I wanted to flush out any issues with it. Turns out there's a lot of little things that are fixable and some non-fixable. This actually surfaced some differences between the current strategy and the one I had in mind. This uses the asset manifest from webpack to pass the final URLs to the client. This ensures that we can render the same exact mark up on the client - including the URL to our own script that we're running in. Doing full document renders work with 15.x as long as the checksum matches. However, with the patch up reviving strategy I had in mind it would end up removing the CSS tags that webpack injects before running our render call. This is a behavior change. In dev mode the server runs a proxy in front of the normal CRA webpack server so that we can replace the HTML request for the root page. I don't know what the best way to link in the react and react-dom packages. Each fixture has a different strategy so here's another one. Just add NODE_PATH=../../build/packages in front of all commands. --- fixtures/ssr/README.md | 24 + fixtures/ssr/package.json | 25 + fixtures/ssr/public/favicon.ico | Bin 0 -> 24838 bytes fixtures/ssr/public/index.html | 13 + fixtures/ssr/server/index.js | 69 + fixtures/ssr/server/render.js | 21 + fixtures/ssr/src/components/App.js | 17 + fixtures/ssr/src/components/Chrome.css | 5 + fixtures/ssr/src/components/Chrome.js | 27 + fixtures/ssr/src/components/Page.css | 3 + fixtures/ssr/src/components/Page.js | 24 + fixtures/ssr/src/index.js | 6 + fixtures/ssr/yarn.lock | 5307 ++++++++++++++++++++++++ 13 files changed, 5541 insertions(+) create mode 100644 fixtures/ssr/README.md create mode 100644 fixtures/ssr/package.json create mode 100644 fixtures/ssr/public/favicon.ico create mode 100644 fixtures/ssr/public/index.html create mode 100644 fixtures/ssr/server/index.js create mode 100644 fixtures/ssr/server/render.js create mode 100644 fixtures/ssr/src/components/App.js create mode 100644 fixtures/ssr/src/components/Chrome.css create mode 100644 fixtures/ssr/src/components/Chrome.js create mode 100644 fixtures/ssr/src/components/Page.css create mode 100644 fixtures/ssr/src/components/Page.js create mode 100644 fixtures/ssr/src/index.js create mode 100644 fixtures/ssr/yarn.lock diff --git a/fixtures/ssr/README.md b/fixtures/ssr/README.md new file mode 100644 index 0000000000..b3195de543 --- /dev/null +++ b/fixtures/ssr/README.md @@ -0,0 +1,24 @@ +# SSR Fixtures + +A set of test cases for quickly identifying issues with server-side rendering. + +## Setup + +To reference a local build of React, first run `npm run build` at the root +of the React project. Then: + +``` +cd fixtures/ssr +npm install +npm start +``` + +The `start` command runs a webpack dev server and a server-side rendering server in development mode with hot reloading. + +If you want to try the production mode instead run: + +``` +npm run start:prod +``` + +This will pre-build all static resources and then start a server-side rendering HTTP server that hosts the React app and service the static resources (without hot reloading). diff --git a/fixtures/ssr/package.json b/fixtures/ssr/package.json new file mode 100644 index 0000000000..126cba70ca --- /dev/null +++ b/fixtures/ssr/package.json @@ -0,0 +1,25 @@ +{ + "name": "react-fixtures-ssr", + "version": "0.1.0", + "private": true, + "devDependencies": { + "concurrently": "3.1.0", + "http-proxy-middleware": "0.17.3", + "react-scripts": "0.9.5" + }, + "dependencies": { + "express": "^4.14.0", + "ignore-styles": "^5.0.1", + "import-export": "^1.0.1", + "node-fetch": "^1.6.3" + }, + "scripts": { + "start": "concurrently \"npm run start:server\" \"npm run start:client\"", + "start:client": "NODE_PATH=../../build/packages PORT=3001 react-scripts start", + "start:server": "NODE_PATH=../../build/packages NODE_ENV=development node server", + "start:prod": "NODE_PATH=../../build/packages react-scripts build && NODE_PATH=../../build/packages NODE_ENV=production node server", + "build": "NODE_PATH=../../build/packages react-scripts build", + "test": "NODE_PATH=../../build/packages react-scripts test --env=jsdom", + "eject": "react-scripts eject" + } +} diff --git a/fixtures/ssr/public/favicon.ico b/fixtures/ssr/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c125de5d897c1ff5692a656485b3216123dcd89 GIT binary patch literal 24838 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 + + + + + diff --git a/fixtures/ssr/server/index.js b/fixtures/ssr/server/index.js new file mode 100644 index 0000000000..80ab7df3ad --- /dev/null +++ b/fixtures/ssr/server/index.js @@ -0,0 +1,69 @@ +require('ignore-styles'); +const babelRegister = require('babel-register'); +const proxy = require('http-proxy-middleware'); + +babelRegister({ + ignore: /\/(build|node_modules)\//, + presets: ['react-app'], +}); + +const express = require('express'); +const path = require('path'); + +const app = express(); + +// Application +if (process.env.NODE_ENV === 'development') { + app.get('/', function(req, res) { + // In development mode we clear the module cache between each request to + // get automatic hot reloading. + for (var key in require.cache) { + delete require.cache[key]; + } + const render = require('./render').default; + res.send(render(req.url)); + }); +} else { + const render = require('./render').default; + app.get('/', function(req, res) { + res.send(render(req.url)); + }); +} + +// Static resources +app.use(express.static(path.resolve(__dirname, '..', 'build'))); + +// Proxy everything else to create-react-app's webpack development server +if (process.env.NODE_ENV === 'development') { + app.use('/', proxy({ + ws: true, + target: 'http://localhost:3001' + })); +} + +app.listen(3000, () => { + console.log('Listening on port 3000...'); +}); + +app.on('error', function(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +}); diff --git a/fixtures/ssr/server/render.js b/fixtures/ssr/server/render.js new file mode 100644 index 0000000000..d002db830a --- /dev/null +++ b/fixtures/ssr/server/render.js @@ -0,0 +1,21 @@ +import React from 'react'; +import {renderToString} from 'react-dom/server'; + +import App from '../src/components/App'; +import assetManifest from '../build/asset-manifest.json'; + +let assets = assetManifest; +if (process.env.NODE_ENV === 'development') { + // Use the bundle from create-react-app's server in development mode. + assets = { + 'main.js': '/static/js/bundle.js', + 'main.css': '', + }; +} + +export default function render() { + var html = renderToString(); + // There's no way to render a doctype in React so prepend manually. + // Also append a bootstrap script tag. + return '' + html; +}; diff --git a/fixtures/ssr/src/components/App.js b/fixtures/ssr/src/components/App.js new file mode 100644 index 0000000000..9b4550d2c8 --- /dev/null +++ b/fixtures/ssr/src/components/App.js @@ -0,0 +1,17 @@ +import React, { Component } from 'react'; + +import Chrome from './Chrome'; +import Page from './Page'; + +export default class App extends Component { + render() { + return ( + +
+

Hello World

+ +
+
+ ); + } +} diff --git a/fixtures/ssr/src/components/Chrome.css b/fixtures/ssr/src/components/Chrome.css new file mode 100644 index 0000000000..b019b57b1d --- /dev/null +++ b/fixtures/ssr/src/components/Chrome.css @@ -0,0 +1,5 @@ +body { + margin: 10px; + padding: 0; + font-family: sans-serif; +} diff --git a/fixtures/ssr/src/components/Chrome.js b/fixtures/ssr/src/components/Chrome.js new file mode 100644 index 0000000000..91b6c782c4 --- /dev/null +++ b/fixtures/ssr/src/components/Chrome.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; + +import './Chrome.css'; + +export default class Chrome extends Component { + render() { + const assets = this.props.assets; + return ( + + + + + + + {this.props.title} + + + {this.props.children} +