Skip to content

Commit a32b05f

Browse files
authored
Feature: SSR + Code Splitting 🚀 (#43)
* multi: Prepare for code splitting chore: Add react-loadable to dependencies chore: Move babel options to package.json so they can be retrieved more easily in webpack config chore: Remove webpack patch (https://github.com/gaearon/react-hot-loader/tree/next\#no-patch-required) chore: Update dependencies, move appropriate to devDependencies * feat: split up todos into separate components * chore: move templates to server folder instead * feat: SSR + code splitting 🚀 - Add support for import() syntax in babel and use only on the server (it is already supported on the client with webpack 2) - Update webpack config for import() code splitting - Fix an issue with static fetching for server data * chore: update readme/comments
1 parent cb183a4 commit a32b05f

36 files changed

+2776
-1104
lines changed

.babelrc

Lines changed: 0 additions & 20 deletions
This file was deleted.

.env.example

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ APPLICATION_PORT=3000
66
APPLICATION_BASE_URL=http://localhost:3000
77

88
# The output path of server and client files built by webpack and babel.
9-
OUTPUT_PATH=/dist
10-
PUBLIC_OUTPUT_PATH=/dist/public
9+
OUTPUT_PATH=dist
10+
PUBLIC_OUTPUT_PATH=dist/public
1111

1212
# Settings for webpack-dev-server.
1313
DEV_SERVER_PORT=3001
1414
DEV_SERVER_HOSTNAME=localhost
1515
DEV_SERVER_HOST_URL=http://localhost:3001
1616

1717
# The primary asset path. Can be changed to be a CDN URL.
18-
PUBLIC_ASSET_PATH=/assets
18+
PUBLIC_ASSET_PATH=http://localhost:3001/assets/

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ npm-debug.log*
77
# ignore built static files
88
/dist
99
/webpack-assets.json
10+
/webpack-stats.json
11+
/react-loadable.json

README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ A universal React/Redux boilerplate with sensible defaults. Out of the box, this
44
boilerplate comes with:
55

66
- Server-side rendering with Express
7+
- Code splitting with Webpack's dynamic `import()`s and [react-loadable](https://github.com/thejameskyle/react-loadable)
78
- Sane [webpack configurations](webpack/)
89
- JS hot reloading with `react-hot-loader` and `webpack-dev-server`
910
- CSS, SASS and `css-modules` support with hot reloading and no [flash of unstyled content](https://en.wikipedia.org/wiki/Flash_of_unstyled_content) (`css-hot-loader`)
1011
- Routing with `react-router-v4`
11-
- Full production builds that do not rely on `babel`.
12+
- Full production builds that do not rely on `babel-node`.
1213

1314
## Get started
1415

@@ -161,13 +162,19 @@ Check the `.eslintignore` file for directories excluded from linting.
161162

162163
## Changing the public asset path
163164

164-
By default, assets are built into `/dist/public`. This path is then served by
165-
express under the path `/assets`. This is the public asset path. In a production
165+
By default, assets are built into `dist/public`. This path is then served by
166+
express under the path `assets`. This is the public asset path. In a production
166167
scenario, you may want your assets to be hosted on a CDN. To do so, just change
167168
the `PUBLIC_ASSET_PATH` environment variant.
168169

169-
If you're using Heroku:
170+
Example using Heroku, if serving via CDN:
170171

171172
```
172173
heroku config:set PUBLIC_ASSET_PATH=https://my.cdn.com
173174
```
175+
176+
Example using Heroku, if serving locally:
177+
178+
```
179+
heroku config:set PUBLIC_ASSET_PATH=/assets
180+
```

client/index.js

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
import 'babel-polyfill';
2-
import 'css/base/index.scss';
32
import React from 'react';
43
import ReactDOM from 'react-dom';
54
import { Provider } from 'react-redux';
65
import { ConnectedRouter } from 'react-router-redux';
76
import createHistory from 'history/createBrowserHistory';
87
import configureStore from 'store';
98
import App from 'containers/App';
9+
import Loadable from 'react-loadable';
10+
11+
import './styles';
1012

1113
// Hydrate the redux store from server state.
1214
const initialState = window.__INITIAL_STATE__;
1315
const history = createHistory();
1416
const store = configureStore(initialState, history);
1517

1618
// Render the application
17-
ReactDOM.hydrate(
18-
<Provider store={store}>
19-
<ConnectedRouter history={history}>
20-
<App />
21-
</ConnectedRouter>
22-
</Provider>,
23-
document.getElementById('app')
24-
);
19+
window.main = () => {
20+
Loadable.preloadReady().then(() => {
21+
ReactDOM.hydrate(
22+
<Provider store={store}>
23+
<ConnectedRouter history={history}>
24+
<App />
25+
</ConnectedRouter>
26+
</Provider>,
27+
document.getElementById('app')
28+
);
29+
});
30+
};

client/styles.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Include all Vendor CSS here
2+
import 'semantic-ui-css/semantic.min.css';
3+
4+
// Base styles
5+
import 'css/base/index.scss';

client/vendor.js

Lines changed: 0 additions & 2 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
const Loading = (props) => {
5+
const { error, timedOut, pastDelay } = props;
6+
7+
console.log(props);
8+
9+
if (error) {
10+
// When the loader has errored
11+
return (
12+
<div>Error!</div>
13+
);
14+
} else if (timedOut) {
15+
// When the loader has taken longer than the timeout
16+
return (
17+
<div>Taking a long time...</div>
18+
);
19+
} else if (pastDelay) {
20+
// When the loader has taken longer than the delay
21+
return (
22+
<div>Loading...</div>
23+
);
24+
}
25+
26+
return null;
27+
};
28+
29+
Loading.propTypes = {
30+
error: PropTypes.bool,
31+
timedOut: PropTypes.bool,
32+
pastDelay: PropTypes.bool
33+
};
34+
35+
export default Loading;

common/js/components/common/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export { default as ErrorPage } from './ErrorPage';
22
export { default as Footer } from './Footer';
33
export { default as Header } from './Header';
4+
export { default as Loading } from './Loading';
45
export { default as RouteWithSubRoutes } from './RouteWithSubRoutes';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import React, { Component } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { Form } from 'semantic-ui-react';
4+
import classnames from 'classnames/bind';
5+
import css from './index.scss';
6+
7+
class TodoForm extends Component {
8+
static propTypes = {
9+
onSubmit: PropTypes.func,
10+
className: PropTypes.string
11+
};
12+
13+
state = { todoText: '' };
14+
15+
submitTodo = ev => {
16+
ev.preventDefault();
17+
18+
const { onSubmit } = this.props;
19+
const { todoText } = this.state;
20+
21+
if (todoText && todoText !== '' && typeof onSubmit === 'function') {
22+
onSubmit(todoText);
23+
this.setState({ todoText: '' });
24+
}
25+
};
26+
27+
onTodoChange = ev => {
28+
this.setState({ todoText: ev.target.value });
29+
};
30+
31+
render() {
32+
const { className } = this.props;
33+
const { todoText } = this.state;
34+
35+
return (
36+
<Form
37+
className={classnames(css.todoForm, className)}
38+
onSubmit={this.submitTodo}
39+
>
40+
<Form.Group>
41+
<Form.Input
42+
onChange={this.onTodoChange}
43+
value={todoText}
44+
type="text"
45+
placeholder="Add a todo..."
46+
/>
47+
<Form.Button content="Add" icon="plus" />
48+
</Form.Group>
49+
</Form>
50+
);
51+
}
52+
}
53+
54+
export default TodoForm;

0 commit comments

Comments
 (0)