Skip to content
11 changes: 11 additions & 0 deletions docs/guides/production.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ It helps to setup your project with per-environment settings that are appropriat
"jsUrl": "/assets/"
},
"production": {
"compileOnStartup": false,
"jsUrl": "/assets/",
"logLevel": "error",
"minify": true
Expand Down Expand Up @@ -186,6 +187,16 @@ and 3001. This is probably not what you want in production.
A referenced asset would be something that your application links to, but `react-server` doesn't know about. This might
be a logo image that you link to with an `<img>` tag.

## Compiling Static Assets
See the [`react-server-cli`](https://react-server.io/docs/guides/react-server-cli) documentation to understand how to
compile static assets. After you have compiled the assets, you can decrease the startup time on a server by setting the
`compileOnStartup` option to `false`. This tells `react-server` that you previously compiled the assets and it doesn't
need to compile anything at run time. Integrating this into your deployment pipeline can greatly improve server startup
time, especially with immutable infrastructure. As a simple example of this pipeline, you can compile the assets when
building a server image then, when starting many servers using the same image, each server has the already-compiled version
of the software and can reliably startup. If you use this option without previously compiling your application, `react-server`
will fail to start.

## Served through a Fronting Server
The example fronting server configurations show how to configure a fronting HTTP/HTTPS server to serve the static assets
generated by `react-server` after compilation. Simply point the fronting server to the appropriate `__clientTemp/build`
Expand Down
12 changes: 10 additions & 2 deletions docs/guides/react-server-cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ export default (server, reactServerMiddleware) => {
server.use(compression());
server.use(bodyParser.urlencoded({ extended: false }));
server.use(bodyParser.json());
server.use(session(options));
server.use(helmet());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this adding helmet here now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I read through the documentation to update it with compileOnStartup stuff and noticed that the custom express middleware example wasn't updated previously. I can remove it if it's an issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, somehow I thought we were adding it automatically now. Makes sense. 👍

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well...we are because of #812, but we missed updating this example to depict the new default express middleware.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I see, and users are responsible for adding it themselves if they override the default with custom middleware.

Nice catch.

server.use(session(options)); // Custom express middleware being added
reactServerMiddleware(); // Must be called once or server will not start
}
```
Expand Down Expand Up @@ -302,6 +303,14 @@ is incompatible with --hot.

Defaults to **false** in development mode and **true** in production.

#### --compile-on-startup
Tells `react-server` to compile the client code during the startup procedure.
In many cases, such as in production, the code has already been compiled and
the server shouldn't take the time to compile it again.
This option is incompatible with --hot and will be ignored if hot is `true`.

Defaults to **true**.

#### --js-url
A URL base for the pre-compiled client JavaScript; usually this is a base URL
on a CDN or separate server. Setting a value for js-url means that
Expand Down Expand Up @@ -383,7 +392,6 @@ run({
jsPort : 3001,
hot : true,
minify : false,
compileOnly : false,
jsUrl : null,
longTermCaching : true,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"hot": false,
"minify": false,
"longTermCaching": false,
"compileOnly": false,
"https": false,
"logLevel": "debug",
"env": {
Expand Down
5 changes: 4 additions & 1 deletion packages/react-server-cli/src/cli.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
require("babel-core/register");

const cli = require(".");
cli.parseCliArgs().then(cli.run).catch(console.error);
cli.parseCliArgs().then((options) => {
const commandResult = cli.run(options);
return (options.command === "start") ? commandResult.started : commandResult;
}).catch(console.error);
41 changes: 38 additions & 3 deletions packages/react-server-cli/src/commands/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import setupLogging from "../setupLogging";
import logProductionWarnings from "../logProductionWarnings";
import expressState from 'express-state';
import cookieParser from 'cookie-parser';
import fs from 'fs';

const logger = reactServer.logging.getLogger(__LOGGER__);

Expand Down Expand Up @@ -140,7 +141,10 @@ const startStaticJsServer = (compiler, port, bindIp, longTermCaching, httpsOptio
return;
}

logger.debug("Successfully compiled static JavaScript.");
if (stats) {
logger.debug("Successfully compiled static JavaScript.");
}

// TODO: make this parameterized based on what is returned from compileClient
server.use('/', compression(), express.static(`__clientTemp/build`, {
maxage: longTermCaching ? '365d' : '0s',
Expand Down Expand Up @@ -238,15 +242,44 @@ export default function start(options){
httpsOptions,
longTermCaching,
customMiddlewarePath,
compileOnStartup,
} = options;

const {serverRoutes, compiler} = compileClient(options);
const routesFileLocation = path.join(process.cwd(), '__clientTemp/routes_server.js');
let serverRoutes,
compiler;

const startServers = () => {
// if jsUrl is set, we need to run the compiler, but we don't want to start a JS
// server.
let startJsServer = startDummyJsServer;

if ((hot === false || hot === "false") && (compileOnStartup === false || compileOnStartup === "false")) {
serverRoutes = new Promise((resolve, reject) => {
fs.access(routesFileLocation, fs.constants.R_OK, (err) => {
if (err) {
reject("You must manually compile your application when compileOnStartup is set to false.");
} else {
// We need to replace the promise returned by the compiler with an already-resolved promise with the path
// of the compiled routes file.
resolve(routesFileLocation);
}
});
});

// mock the compiler object used by the various JS servers so that the compiler.run function always succeeds.
// This will allow the JS servers to work properly, thinking that the compiler actually ran.
compiler = {};
compiler.run = (cb) => {
cb(null, null);
};
} else {
// ES6 destructuring without a preceding `let` or `const` results in a syntax error. Therefore, the below
// statement must be wrapped in parentheses to work properly.
// http://exploringjs.com/es6/ch_destructuring.html#sec_leading-curly-brace-destructuring
({serverRoutes, compiler} = compileClient(options));
}

if (!jsUrl) {
// if jsUrl is not set, we need to start up a JS server, either hot load
// or static.
Expand All @@ -256,7 +289,9 @@ export default function start(options){
logger.notice("Starting servers...");

const jsServer = startJsServer(compiler, jsPort, bindIp, longTermCaching, httpsOptions);
const htmlServerPromise = serverRoutes.then(serverRoutesFile => startHtmlServer(serverRoutesFile, port, bindIp, httpsOptions, customMiddlewarePath));
const htmlServerPromise = serverRoutes
.then(serverRoutesFile => startHtmlServer(serverRoutesFile, port, bindIp, httpsOptions, customMiddlewarePath))
.catch((e) => { throw e; });

return {
stop: () => Promise.all([jsServer.stop(), htmlServerPromise.then(server => server.stop())]),
Expand Down
5 changes: 3 additions & 2 deletions packages/react-server-cli/src/defaultOptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
// jsPort: the port number for JavaScript and CSS on the server.
// hot: enable hot reloading. do not use in production.
// minify: minify the client-side JavaScript.
// compileOnly: just compile the client-side JavaScript; don't start up a server.
// compileOnStartup: start up a server without compiling the client-side JavaScript; this expects that you have already
// run the compile command prior to starting (i.e. in a production environment where build and run times are separated).
// jsUrl: serve up the HTML, but don't serve up the JavaScript and CSS; instead point the
// JS & CSS URLs at this URL.
// logLevel: the level of logging to use.
Expand All @@ -18,7 +19,7 @@ export default {
bindIp: "0.0.0.0",
hot: true,
minify: false,
compileOnly: false,
compileOnStartup: true,
logLevel: "debug",
timingLogLevel: "fast",
gaugeLogLevel: "ok",
Expand Down
4 changes: 2 additions & 2 deletions packages/react-server-cli/src/handleCompilationErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export default function handleCompilationErrors (err, stats){
logger.error(err);
return new Error(err);
// TODO: inspect stats to see if there are errors -sra.
} else if (stats.hasErrors()) {
} else if (stats && stats.hasErrors()) {
console.error("There were errors in the JavaScript compilation.");
stats.toJson().errors.forEach((error) => {
console.error(error);
});
return new Error("There were errors in the JavaScript compilation.");
} else if (stats.hasWarnings()) {
} else if (stats && stats.hasWarnings()) {
logger.warning("There were warnings in the JavaScript compilation. Note that this is normal if you are minifying your code.");
// for now, don't enumerate warnings; they are absolutely useless in minification mode.
// TODO: handle this more intelligently, perhaps with a --reportwarnings flag or with different
Expand Down
2 changes: 1 addition & 1 deletion packages/react-server-cli/src/mergeOptions.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// takes in an arrray of options objects for `startServer.js`, and returns a merged
// takes in an array of options objects for `startServer.js`, and returns a merged
// version of them. it essentially acts like Object.assign(), so options objects
// later in the argument list override the properties of ones earlier in the list.
// like Object.assign, the merging is done on a property-by-property basis;
Expand Down
10 changes: 5 additions & 5 deletions packages/react-server-cli/src/parseCliArgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,6 @@ export default (args = process.argv) => {
describe: "Set the severity level for the gauge logs. Values are, in ascending order of severity: 'no', 'hi', 'lo', 'ok'. Default is 'no' in production mode, 'ok' otherwise.",
type: "string",
})
.option("compile-only", {
describe: "Compile the client JavaScript only, and don't start any servers. This is what you want to do if you are building the client JavaScript to be hosted on a CDN. Unless you have a very specific reason, it's almost alway a good idea to only do this in production mode. Defaults to false.",
default: undefined,
type: "boolean",
})
.option("js-url", {
describe: "A URL base for the pre-compiled client JavaScript. Setting a value for jsurl means that react-server-cli will not compile the client JavaScript at all, and it will not serve up any JavaScript. Obviously, this means that --jsurl overrides all of the options related to JavaScript compilation: --hot, --minify, and --bundleperroute.",
type: "string",
Expand All @@ -107,6 +102,11 @@ export default (args = process.argv) => {
default: false,
type: "boolean",
})
.option("compile-on-startup", {
describe: "Enable/disable compiling on startup. Recommended for use in production when files have been pre-compiled.",
default: undefined,
type: "boolean",
})
.version(function() {
return require('../package').version;
})
Expand Down
37 changes: 37 additions & 0 deletions packages/react-server-cli/test/parseCliArgs.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,3 +308,40 @@ test('react-server-cli:parseCliArgs::longTermCaching option can be turned on usi
t.is(parsedArgs.longTermCaching, true, 'longTermCaching is true');
t.true(Object.keys(defaultOptions).indexOf('longTermCaching') > -1, 'longTermCaching key exists in defaultOptions');
});

// **** compileOnStartup ****
// compileOnStartup will be undefined if no argument is provided
test('react-server-cli:parseCliArgs::compileOnStartup will be undefined if no argument is provided', async t => {
const args = [
...defaultArgs,
'compile'
];
const parsedArgs = await parseCliArgs(args);
t.is(parsedArgs.compileOnStartup, undefined, 'compileOnStartup is undefined if no argument is provided');
});

// compileOnStartup option can be turned on using --compileOnStartup argument
test('react-server-cli:parseCliArgs::compileOnStartup option can be turned on using --compileOnStartup argument', async t => {
const args = [
...defaultArgs,
'compile',
'--compileOnStartup'
];
const parsedArgs = await parseCliArgs(args);
t.is(typeof parsedArgs.compileOnStartup, 'boolean', 'compileOnStartup is true');
t.is(parsedArgs.compileOnStartup, true, 'compileOnStartup is true');
t.true(Object.keys(defaultOptions).indexOf('compileOnStartup') > -1, 'compileOnStartup key exists in defaultOptions');
});

// compileOnStartup option can be turned on using --compile-on-startup argument
test('react-server-cli:parseCliArgs::compileOnStartup option can be turned on using --compile-on-startup argument', async t => {
const args = [
...defaultArgs,
'compile',
'--compile-on-startup'
];
const parsedArgs = await parseCliArgs(args);
t.is(typeof parsedArgs.compileOnStartup, 'boolean', 'compileOnStartup is true');
t.is(parsedArgs.compileOnStartup, true, 'compileOnStartup is true');
t.true(Object.keys(defaultOptions).indexOf('compileOnStartup') > -1, 'compileOnStartup key exists in defaultOptions');
});
1 change: 0 additions & 1 deletion packages/react-server-examples/redux-basic/.reactserverrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"hot": true,
"minify": false,
"long-term-caching": false,
"compile-only": false,
"https": false,
"log-level": "debug",
"env": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"hot": false,
"minify": false,
"longTermCaching": false,
"compileOnly": false,
"https": false,
"logLevel": "debug",
"env": {
Expand Down