Skip to content

Commit 63efb95

Browse files
committed
Fixes of the standard client-side initialization code
1 parent 5529155 commit 63efb95

File tree

10 files changed

+111
-60
lines changed

10 files changed

+111
-60
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Topcoder React Utils Changelog
22

3+
### v0.3.2
4+
- Adds the client-side initialization code, and makes some corrections of the
5+
standard Wepback configs and server setup.
6+
37
### v0.3.0
48
- Added the standard, configurable server setup;
59
- Jest utils are now exposed in a different way;

config/webpack/app-base.js

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ const webpack = require('webpack');
3939
* passed in, it is assigned to the "main" entry point, and the "polyfills"
4040
* entry point will be added to it.
4141
*
42+
* @param {Boolean} ops.keepBuildInfo Optional. If `true` and a build info file
43+
* from a previous build is found, the factory will use that rather than
44+
* re-generating it. This provide the way to re-create webpack config at the
45+
* server startup, without re-writing the build info generated previously
46+
* during the bundling. Defaults to `false`.
47+
*
4248
* @param {String} ops.publicPath Base URL for the output of the build assets.
4349
*/
4450
module.exports = function configFactory(ops) {
@@ -47,20 +53,26 @@ module.exports = function configFactory(ops) {
4753
publicPath: '',
4854
});
4955

50-
/* Stores misc build info into the local ".build-info" file in the context
51-
* directory. */
52-
const buildInfo = {
53-
/* A random 32-bit key, that can be used for encryption. */
54-
key: forge.random.getBytesSync(32),
56+
let buildInfo;
57+
const buildInfoUrl = path.resolve(o.context, '.build-info');
58+
/* If build-info file is found, we reuse those data. */
59+
if (fs.existsSync(buildInfoUrl) && o.keepBuildInfo) {
60+
buildInfo = JSON.parse(fs.readFileSync(buildInfoUrl));
61+
} else {
62+
/* Stores misc build info into the local ".build-info" file in the context
63+
* directory. */
64+
buildInfo = {
65+
/* A random 32-bit key, that can be used for encryption. */
66+
key: forge.random.getBytesSync(32),
5567

56-
/* This will be equal to "development" or "production" */
57-
mode: ops.babelEnv,
68+
/* This will be equal to "development" or "production" */
69+
mode: ops.babelEnv,
5870

59-
/* Build timestamp. */
60-
timestamp: moment.utc().toISOString(),
61-
};
62-
const buildInfoUrl = path.resolve(o.context, '.build-info');
63-
fs.writeFileSync(buildInfoUrl, JSON.stringify(buildInfo));
71+
/* Build timestamp. */
72+
timestamp: moment.utc().toISOString(),
73+
};
74+
fs.writeFileSync(buildInfoUrl, JSON.stringify(buildInfo));
75+
}
6476

6577
/* Entry points normalization. */
6678
const entry = _.isPlainObject(o.entry)
@@ -72,7 +84,7 @@ module.exports = function configFactory(ops) {
7284
entry.polyfills = _.union(entry.polyfills, [
7385
'babel-polyfill',
7486
'nodelist-foreach-polyfill',
75-
'topcoder-react-utils/src/client',
87+
'topcoder-react-utils/src/client/init',
7688
]);
7789
return {
7890
context: o.context,
@@ -117,7 +129,7 @@ module.exports = function configFactory(ops) {
117129
loader: 'file-loader',
118130
options: {
119131
outputPath: '/fonts/',
120-
publicPath: o.publicPath,
132+
publicPath: `${o.publicPath}/fonts`,
121133
},
122134
}, {
123135
/* Loads JS and JSX moudles, and inlines SVG assets. */
@@ -135,7 +147,7 @@ module.exports = function configFactory(ops) {
135147
loader: 'file-loader',
136148
options: {
137149
outputPath: '/images/',
138-
publicPath: o.publicPath,
150+
publicPath: `${o.publicPath}/images`,
139151
},
140152
}, {
141153
/* Loads SCSS stylesheets. */

config/webpack/lib-base.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
const autoprefixer = require('autoprefixer');
88
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
99
const path = require('path');
10+
const webpack = require('webpack');
1011

1112
/**
1213
* Creates a new Webpack config object.
@@ -52,6 +53,12 @@ module.exports = function configFactory(ops) {
5253
libraryTarget: 'umd',
5354
},
5455
plugins: [
56+
new webpack.DefinePlugin({
57+
'process.env': {
58+
BABEL_ENV: JSON.stringify(ops.babelEnv),
59+
NODE_ENV: JSON.stringify(ops.babelEnv),
60+
},
61+
}),
5562
new ExtractCssChunks({
5663
filename: 'style.css',
5764
}),

docs/client.md

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,27 @@
11
# Client
22
Client-side intialization code.
33

4-
*Documentation will be added here*
4+
- **Why?** — For the same reasons we have the standard
5+
[server setup](./server.md).
6+
7+
This module exports a single **`Launch(options)`** function that starts the app
8+
at the client side. It renders the app, wraps it with
9+
[`react-router`](https://www.npmjs.com/package/react-router)'s `<BrowserRouter>`,
10+
and Redux store `<Provider>`, if necessary; then injects it into the page HTML
11+
template generated by the server. In the case of use with development server,
12+
it also takes care about HMR (Hot Module Reloading) setup at the clients-side.
13+
14+
**`options`** should be a JS object, with the following fields:
15+
- **`Application`** &mdash; *Function* &mdash; The root ReactJS component of
16+
the app to be rendered.
17+
- **`storeFactory`** &mdash; *Function* &mdash; Optional. Redux store factory.
18+
If provided, it will be used to generate Redux store that will be provided to
19+
the app. Its signature should be:
20+
21+
**Arguments**
22+
- **`initialState`** &mdash; *Object* &mdash; Initial Redux state, injected by
23+
the server (will be `undefined`, if it is not generated by the current
24+
server setup).
25+
26+
**Returns**
27+
- *Promise* that resolves to the created Redux store.

docs/webpack-config.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ mutation of the config object.
5050
[nodelist-foreach-polyfill](https://www.npmjs.com/package/nodelist-foreach-polyfill)).
5151
If a string or a string array is passed in, it is assigned to the *main*
5252
entry pont, and the *polyfills* entry point is added then;
53+
- **`keepBuildInfo`** &mdash; *Boolean* &mdash; Optional. If `true` and a
54+
`.build-info` file from a previous build exists in the context directory,
55+
it will be loaded and used, rather than re-generated by the config factory.
56+
It allows to re-create the Webpack config during a server launch without
57+
re-generation of the build info file created during a previous build
58+
(and thus bundled into the frontend bundle). Defaults to `false`.
5359
- **`publicPath`** &mdash; *String* &mdash; Base URL for the output of
5460
the build assets;
5561

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,15 +96,15 @@
9696
"url": "git+https://github.com/topcoder-platform/topcoder-react-utils.git"
9797
},
9898
"scripts": {
99-
"build": "npm run clean && ./node_modules/.bin/webpack --env=production --colors --display-optimization-bailout --profile --progress && BABEL_ENV=production babel src/server --out-dir dist/server && BABEL_ENV=production babel src/shared/utils/jest.jsx --out-file dist/shared/utils/jest.js",
100-
"build:dev": "npm run clean && (./node_modules/.bin/webpack --env=development --bail --colors --display-optimization-bailout --profile --progress --watch | BABEL_ENV=development babel src/server --out-dir dist/server --watch | BABEL_ENV=development babel src/shared/utils/jest.jsx --out-file dist/shared/utils/jest.js --watch)",
101-
"clean": "rm -rf dist && mkdir -p dist/shared/utils",
99+
"build": "npm run clean && ./node_modules/.bin/webpack --env=production --colors --display-optimization-bailout --profile --progress && BABEL_ENV=production babel src/server --out-dir dist/server && BABEL_ENV=production babel src/shared/utils/jest.jsx --out-file dist/shared/utils/jest.js && BABEL_ENV=production babel src/client/init.js --out-file dist/client/init.js",
100+
"build:dev": "npm run clean && (./node_modules/.bin/webpack --env=development --bail --colors --display-optimization-bailout --profile --progress --watch | BABEL_ENV=development babel src/server --out-dir dist/server --watch | BABEL_ENV=development babel src/shared/utils/jest.jsx --out-file dist/shared/utils/jest.js --watch | BABEL_ENV=development babel src/client/init.js --out-file dist/client/init.js --watch)",
101+
"clean": "rm -rf dist && mkdir -p dist/shared/utils && mkdir -p dist/client",
102102
"jest": "jest --no-cache --maxWorkers=4 --config config/jest/default.js",
103103
"lint": "npm run lint:js && npm run lint:scss",
104104
"lint:js": "eslint --ext .js,.jsx .",
105105
"lint:scss": "./node_modules/.bin/stylelint **/*.scss --syntax scss",
106106
"prepublishOnly": "npm run build",
107107
"test": "npm run lint && npm run jest"
108108
},
109-
"version": "0.3.1"
109+
"version": "0.3.2"
110110
}

src/client/index.jsx

Lines changed: 10 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22
* Initialization of client-side code.
33
*/
44

5-
/* global BUILD_INFO document window */
5+
/* global document window */
66

7-
import forge from 'node-forge';
87
import React from 'react';
98
import ReactDom from 'react-dom';
109
import shortId from 'shortid';
@@ -27,45 +26,19 @@ function render(Application, store) {
2726
* injected at the server-side, and also about setting up client side of hot
2827
* module reloading (HMR).
2928
* @param {Object} options Parameters accepted by the function.
30-
* @param {Function} options.Application Root component of the application to be
31-
* rendered at the client side.
32-
* @param {String} options.appModulePath Optional. Path to the root module of
33-
* the application. It should be provided to setup HMR.
34-
* @param {Function} options.customInit Optional. A custom function to be called
35-
* before initial client-side rendering. It should return a promise that
36-
* resolves to an object with the following params:
37-
* - store {Object} - Optional. Redux store to be used.
29+
* @param {Function} options.Application Rendered app component.
30+
* @param {Function} options.storeFactory Optional. Given the initials state
31+
* of redux store should create the store.
3832
*/
39-
export default async function Init(options) {
40-
/* Injection of build-time data information into the client-side bundle. */
41-
window.TRU_BUILD_INFO = BUILD_INFO;
42-
window.TRU_FRONT_END = true;
43-
44-
/* Removes data injection script out of the document. */
45-
const block = document.querySelector('script[id="inj"]');
46-
document.getElementsByTagName('body')[0].removeChild(block);
47-
48-
/* Decodes data injected at the server side. */
49-
const { key } = window.TRU_BUILD_INFO;
50-
let data = forge.util.decode64(window.INJ);
51-
const decipher = forge.cipher.createDecipher('AES-CBC', key);
52-
decipher.start({ iv: data.slice(0, 32) });
53-
decipher.update(forge.util.createBuffer(data.slice(32)));
54-
decipher.finish();
55-
data = JSON.parse(forge.util.decodeUtf8(decipher.output.data));
56-
57-
window.CONFIG = data.CONFIG;
58-
window.ISTATE = data.ISTATE;
59-
33+
export default async function Launch(options) {
6034
let store;
61-
if (options.customInit) ({ store } = await options.customInit());
35+
if (options.storeFactory) {
36+
store = await options.storeFactory(window.ISTATE);
37+
}
6238
render(options.Application, store);
6339

64-
if (module.hot && options.appModulePath) {
65-
module.hot.accept(
66-
options.appModulePath,
67-
() => render(options.Application, store),
68-
);
40+
if (module.hot) {
41+
module.hot.accept('.', () => render(options.Application, store));
6942

7043
/* HMR of CSS code each time webpack hot middleware updates the code. */
7144
/* eslint-disable no-underscore-dangle */

src/client/init.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Initialization of client-side environment.
3+
*/
4+
5+
/* global BUILD_INFO, document, window */
6+
7+
import forge from 'node-forge';
8+
9+
window.TRU_BUILD_INFO = BUILD_INFO;
10+
window.TRU_FRONT_END = true;
11+
12+
/* Removes data injection script out of the document. */
13+
const block = document.querySelector('script[id="inj"]');
14+
document.getElementsByTagName('body')[0].removeChild(block);
15+
16+
/* Decodes data injected at the server side. */
17+
const { key } = window.TRU_BUILD_INFO;
18+
let data = forge.util.decode64(window.INJ);
19+
const decipher = forge.cipher.createDecipher('AES-CBC', key);
20+
decipher.start({ iv: data.slice(0, 32) });
21+
decipher.update(forge.util.createBuffer(data.slice(32)));
22+
decipher.finish();
23+
data = JSON.parse(forge.util.decodeUtf8(decipher.output.data));
24+
25+
window.CONFIG = data.CONFIG;
26+
window.ISTATE = data.ISTATE;

src/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import client from 'client';
21
import Avatar from 'components/Avatar';
32
import Button from 'components/Button';
3+
import client from 'client';
44
import Link from 'components/Link';
55
import NavLink from 'components/NavLink';
66
import ScalableRect from 'components/ScalableRect';
@@ -14,9 +14,9 @@ const server = utils.isomorphy.isServerSide() ?
1414
requireWeak('topcoder-react-utils/dist/server') : null;
1515

1616
module.exports = {
17-
client,
1817
Avatar,
1918
Button,
19+
client,
2020
Link,
2121
NavLink,
2222
ScalableRect,

0 commit comments

Comments
 (0)