Skip to content

Commit fd1aa67

Browse files
committed
v0.2.3 - Server functionality added
2 parents d1c0b86 + 80d40d2 commit fd1aa67

27 files changed

+1335
-35
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,16 @@ only need to call `adopt-dev-deps` again if you update
6565
### <a name="utilities">Utilities</a>
6666
- [**Global Styles**](docs/global-styles.md) &mdash; Global styles necessary for
6767
a generic application;
68+
- [**Isomorphy**](docs/isomorphy-utils.md) &mdash; Collection of helpers to deal
69+
with isomorphic aspects of the code;
70+
- [**Jest utils**](docs/jest-utils.md) &mdash; Collection of helpers to be used
71+
in Jest tests code;
6872
- [**SCSS Mixins**](docs/scss-mixins.md) &mdash; Collection of useful style
6973
mixins;
70-
- [**Jest utils**](docs/jest-utils.md) &mdash; Collection of helpers to be used
71-
in Jest tests code.
74+
- [**Server**](docs/server.md) &mdash; Easy creation and launch of web-server
75+
with standard configuration, that serves a ReactJS application with or without
76+
server-side rendering, supports development tools (Hop Module Reloading), and
77+
can be further configured for the needs of specific projects.
7278

7379
### <a name="development">Development</a>
7480
For convenient development you can link this package into your host package:

__tests__/__snapshots__/index.js.snap

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Exports expected stuff 1`] = `
4+
Object {
5+
"Avatar": [Function],
6+
"Button": [Function],
7+
"Link": [Function],
8+
"NavLink": [Function],
9+
"ScalableRect": [Function],
10+
"utils": Object {
11+
"isomorphy": Object {
12+
"buildTimestamp": [Function],
13+
"isClientSide": [Function],
14+
"isDevBuild": [Function],
15+
"isProdBuild": [Function],
16+
"isServerSide": [Function],
17+
},
18+
},
19+
}
20+
`;

__tests__/index.js

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import * as exports from '../src';
22

33
test('Exports expected stuff', () => {
4-
expect(exports).toHaveProperty('Avatar');
5-
expect(exports).toHaveProperty('Button');
6-
expect(exports).toHaveProperty('Link');
7-
expect(exports).toHaveProperty('NavLink');
8-
expect(exports).toHaveProperty('ScalableRect');
4+
expect(exports).toMatchSnapshot();
95
});

__tests__/shared/Avatar.jsx renamed to __tests__/shared/components/Avatar.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Avatar from 'components/Avatar';
22
import React from 'react';
3-
import { snapshot } from '../../jest-utils';
3+
import { snapshot } from 'utils/jest';
44

55
const testTheme = {
66
avatar: 'avatarClassName',
File renamed without changes.
File renamed without changes.

__tests__/shared/utils/isomorphy.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/* Helper for loading of tested module. */
2+
const m = () => require('utils/isomorphy');
3+
4+
afterEach(() => {
5+
delete global.BUILD_INFO;
6+
delete global.BUILD_TIMESTAMP;
7+
delete global.FRONT_END;
8+
delete process.env.BABEL_ENV;
9+
process.env.NODE_ENV = 'test';
10+
});
11+
12+
beforeEach(() => jest.resetModules());
13+
14+
test('Client-side detection', () => {
15+
global.FRONT_END = 'true';
16+
expect(m().isClientSide()).toBe(true);
17+
expect(m().isServerSide()).toBe(false);
18+
});
19+
20+
test('Server-side detection', () => {
21+
expect(global.FRONT_END).toBeUndefined();
22+
expect(m().isClientSide()).toBe(false);
23+
expect(m().isServerSide()).toBe(true);
24+
});
25+
26+
test('Dev code build detection', () => {
27+
process.env.BABEL_ENV = 'development';
28+
expect(m().isDevBuild()).toBe(true);
29+
expect(m().isProdBuild()).toBe(false);
30+
});
31+
32+
test('Prod code build detection', () => {
33+
process.env.BABEL_ENV = 'production';
34+
expect(m().isDevBuild()).toBe(false);
35+
expect(m().isProdBuild()).toBe(true);
36+
});
37+
38+
test('Build timestamp on client-side', () => {
39+
global.BUILD_TIMESTAMP = 'DUMMY_TIMESTAMP';
40+
global.FRONT_END = true;
41+
expect(m().buildTimestamp()).toBe('DUMMY_TIMESTAMP');
42+
});
43+
44+
test('Build timestamp on server-side', () => {
45+
global.BUILD_INFO = { timestamp: 'DUMMY_TIMESTAMP' };
46+
expect(global.FRONT_END).toBeUndefined();
47+
expect(m().buildTimestamp()).toBe('DUMMY_TIMESTAMP');
48+
});

config/webpack/app-base.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ module.exports = function configFactory(ops) {
8888
filename: '[name].css',
8989
}),
9090
new webpack.DefinePlugin({
91-
BUILD_RNDKEY: JSON.stringify(buildInfo.rndkey),
92-
'process.env': {
91+
global: {
92+
BUILD_RNDKEY: JSON.stringify(buildInfo.rndkey),
9393
BUILD_TIMESTAMP: JSON.stringify(buildInfo.timestamp),
9494
FRONT_END: true,
9595
},

docs/isomorphy-utils.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Isomorphy
2+
Collection of helpers to deal with isomorphic aspects of the code.
3+
4+
**Why?** &mdash; Most of our ReactJS code should be isomorphic, i.e. it should
5+
be functional both when executed at client-side (in browser), and when executed
6+
at server-side (in NodeJS), without extra care of the caller. In some cases, it
7+
demands to explicitely check, where the code is executed, and proceed depending
8+
on that. This module provides functions that allow to do such checks, and to get
9+
some additional information about the currently running code.
10+
11+
All functions provided by this module should be imported and used like:
12+
```js
13+
import { utils } from 'topcoder-react-utils';
14+
15+
utils.isomorphy.isClientSide();
16+
```
17+
18+
Note that `isClientSide()` and `isServerSide()` functions rely on the global
19+
`FRONT_END` variable being defined only inside JS bundle build for the frontend
20+
execution by our standard Wepback config for apps. It is assumed that this
21+
variable won't be present in NodeJS environment.
22+
23+
`isDevBuild()` and `isProdBuild()` functions rely on `BABEL_ENV` environment
24+
variable, and they tell about the build-time configuration of the code, which
25+
should be the same at the client- and server-side to ensure proper server-side
26+
rendering. This is independent of `NODE_ENV` environment variable, which sets
27+
the runtime configuration of the app, e.g. the API keys and endpoints to call
28+
for development and production, etc.
29+
30+
`buildTimestamp()` function relies on the global `BUILD_TIMESTAMP` variable
31+
defined in the frontend JS bundle build by our standard Webpack congif for app;
32+
at the server-side it relies on our standard server to expose, via the global
33+
`BUILD_INFO` object, the build info about the bundle served by the server.
34+
35+
### Provided Functions
36+
- **`buildTimestamp()`** &mdash; Returns build timestamp of the frontend JS
37+
bundle, in form of ISO date/time string. At the server-side it will be the
38+
timestamp of bundle being served by the server.
39+
- **`isClientSide()`** &mdash; Returns `true` if executed at client-side
40+
(in browser); `false` otherwise.
41+
- **`isDevBuild()`** &mdash; Returns `true` if development version of the code
42+
is running; `false` otherwise.
43+
- **`isProdBuild()`** &mdash; Returns `true` if the production version of the
44+
code is running; `false` otherwise.
45+
variable (`NODE_ENV`, in turns, determines runtime configuration of the app).
46+
- **`isServerSide()`** &mdash; Returns `true` if executed at the server-side
47+
(in NodeJS); `false` otherwise.

docs/server.md

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Server
2+
Easy creation and launch of web-server with standard configuration, that serves
3+
a ReactJS application with or without server-side rendering, supports
4+
development tools (Hot Module Reloading), and can be further configured for
5+
the needs of specific projects.
6+
7+
**Why?** &mdash; Each ReactJS application demands a web-server, and there is
8+
a bunch of generic boilerplate code involved. Here is our solution to this,
9+
which allows to create a simple, but functional, web-server in a moment, and
10+
permits to further configurate it for specific use.
11+
12+
*NOTE:* It is supposed that this server, even with zero configration, supports
13+
most standard ReactJS setups: i.e. with or without server-side rendering and/or
14+
Redux. However, the current code was spawned out from a specific codebase that
15+
used Redux and server-side rendering. Should you experience any problems in any
16+
other use case, don't hesitate to attract attention to your issues and propose
17+
fixes/enhancements!
18+
19+
### Details
20+
Technically, our server solution consists of three parts: `dist/server/renderer`
21+
takes care about the actual rendering of HTML template, injection of config and
22+
server-side rendered ReactJS markup; `dist/server/server` creates and configures
23+
ExpressJS server; and `dist/server` assemble them together, sets up and launches
24+
the native NodeJS server that exposes ExpressJS to the outside world.
25+
26+
For the practical use, staring the server is as easy as:
27+
```js
28+
import serverFactory from 'topcoder-react-utils/dist/server`;
29+
import webpackConfig from 'config/webpack/production.js`;
30+
31+
const options = {}; // A number of extra options can be provided here.
32+
33+
serverFactory(webpackConfig, options);
34+
```
35+
36+
The `serverFactory(webpackConfig, options)` function initializes, and launches
37+
the server, and it returns a Promise that resolves to an object with two fields,
38+
`expressServer` and `httpServer` that contain the created and launched servers.
39+
40+
The first argument of the factory, `webpackConfig` is the Webpack config used to
41+
build the frontend bundle: in production mode server takes out of it `context`,
42+
`publicPath`, and some other params; in development mode the entire config is
43+
necessary to run ExpressJS in development mode.
44+
45+
The second argument, `options`, is optional; it allows to pass in the following
46+
props:
47+
- **`Application`** &mdash; *Component* &mdash; Optional. The root ReactJS
48+
component of the app. If provided, server will perform server-side rendering,
49+
and it will inject the rendered markup into the HTML template it serves.
50+
- **`devMode`** &mdash; *Boolean* &mdash; Optional. Specifies, whether the
51+
server should be launched in the development mode.
52+
- **`favicon`** &mdash; *String* &mdash; Optional. Path to the favicon to be
53+
served by the server.
54+
- **`logger`** &mdash; *Object* &mdash; Optional. The logger to be used for
55+
logging. Defaults to `console`, otherwise it is expected to provide the same
56+
interface. Note that `console` is not efficient for production use, because
57+
it is not async in NodeJS.
58+
- **`beforeRender`** &mdash; *Function* &mdash; Optional. The hook to be
59+
executed right before the generation of HTML template of the page. If given,
60+
it will receive the HTTP request as its only argument, and
61+
it should return a promise that resolves to an object with the following
62+
fields (all are optional):
63+
- **`config`** &mdash; *Object* &mdash; Config object to inject into the
64+
template.
65+
- **`extraScripts`** &mdash; *String[]* &mdash; Additional script tags to be
66+
injected into the page.
67+
- **`store`** &mdash; *Object* &mdash; Redux store which state will be
68+
injected into HTML template as the initial state of the app.
69+
- **`onExpressJsSetup`** &mdash; *Function* &mdash; Custom setup of ExpressJS
70+
server. Express server instance will be passed in as the only argument to this
71+
function.
72+
- **`port`** &mdash; *Number|String* &mdash; The port to be used by the server.

docs/webpack-config.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ mutation of the config object.
8181
Also `resolve.symlinks` Webpack option is set to *false* to avoid problems
8282
with resolution of assets from packages linked with `npm link`.
8383

84-
- The following environment variables will be emulated inside the output
84+
- The following global variables will be emulated inside the output
8585
JS bundle:
8686
- **`BUILD_RNDKEY`** &mdash; A random 32 bit key that can be used
8787
for encryption, it is set just as a global variable accessible in

0 commit comments

Comments
 (0)