Skip to content

Commit ba6c861

Browse files
committed
refactor: improve overall dev experience and defaults
- Clean up webpack configurations: - Development: removed the need to pre-build a static development bundle by switching to css-hot-loader to avoid flashes of unstyled content. Dev environment boot up time is now much faster. - Production: Use the base configuration but override them depending on production.server (for SSR) and production.client builds. - Refactor asset path handling as it relates to environment variables to be less confusing, and document it in the README. - Other miscellaneous improvements. - Clean up webpack/constants and move/rename it to config/index.js since the values in there are mostly application configuration level, which makes more sense.
1 parent fb40a77 commit ba6c861

22 files changed

+327
-332
lines changed

.env.example

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,14 @@ APPLICATION_PORT=3000
55
# The absolute URL to the application.
66
APPLICATION_BASE_URL=http://localhost:3000
77

8-
# The primary asset host for JS, CSS, and images. This can be changed to a CDN
9-
# URL. If serving assets locally, it should be the same as WEBPACK_OUTPUT_PATH
10-
# below. This env variable only takes effect in production mode.
11-
ASSET_HOST=/dist
12-
13-
# The output path for webpack builds.
14-
WEBPACK_OUTPUT_PATH=/dist/public
8+
# The output path of server and client files built by webpack and babel.
9+
OUTPUT_PATH=/dist
10+
PUBLIC_OUTPUT_PATH=/dist/public
1511

1612
# Settings for webpack-dev-server.
1713
DEV_SERVER_PORT=3001
1814
DEV_SERVER_HOSTNAME=localhost
1915
DEV_SERVER_HOST_URL=http://localhost:3001
2016

17+
# The primary asset path. Can be changed to be a CDN URL.
18+
PUBLIC_ASSET_PATH=/assets

.gitignore

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,3 @@ npm-debug.log*
77
# ignore built static files
88
/dist
99
/webpack-assets.json
10-
11-
# webpack-built server files
12-
server/renderer/*.built.js

README.md

Lines changed: 55 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
# universal-react-redux
22

3-
A universal starter kit built with ES2015, react, react-router and redux. Server
3+
A simple, clean, React and Redux boilerplate with sensible defaults. Server
44
rendering with react and express. Bundled with Webpack, with HMR transforms and
55
support for `css-modules`.
66

7-
**NOTE: This repository is meant to be continually updated to use the latest in
8-
the react ecosystem. It is meant to be more of a guiding template for you to
9-
customize and build your own universal apps with React and Redux. I do not
10-
promise that future updates will not break your existing application.**
11-
127
## Get started
138

149
Copy environment variables and edit them if necessary:
10+
1511
```
1612
cp .env.example .env
1713
```
1814

1915
Then:
16+
2017
```
2118
npm install
2219
npm start
@@ -30,66 +27,84 @@ For production builds:
3027
npm run prod:build
3128
npm run serve
3229
```
30+
3331
Or simply
3432

3533
```
3634
npm run prod
3735
```
3836

3937
For Heroku, simply add a `Procfile`:
40-
```
41-
web: npm run serve
42-
```
4338

44-
## Directory Structure
4539
```
46-
├── client # Client-side code
47-
├── common # Shared code between client and server
48-
│   ├── css # CSS/Sass Resources
49-
│   ├── fonts
50-
│   ├── images
51-
│   ├── js
52-
│   │   ├── actions
53-
│   │   ├── components # "Dumb" components
54-
│   │   ├── containers # Smart containers
55-
│   │   ├── lib # Misc. libraries like helpers, etc.
56-
│   │   ├── middleware # Middleware for redux
57-
│   │   ├── reducers # Redux reducers
58-
│   │   ├── routes # Routes each have an index.js which exports a react-router Route.
59-
│   │   └── store # Store configuration for production and dev.
60-
│   └── layouts # Layout files to be rendered by the server.
61-
├── server # Server-side code
62-
├── webpack # Webpack configuration files
40+
web: npm run serve
6341
```
6442

6543
## CSS Modules
44+
6645
This project uses [CSS Modules](https://github.com/css-modules/css-modules).
6746
Class names should be in `camelCase`. Place the css file as a sibling to the
6847
component with the same name, for example:
48+
6949
```
7050
├── components
7151
│   ├── Header.js
7252
│   ├── Header.scss
7353
```
7454

55+
## Environment Variables
56+
57+
In development mode, environment variables are loaded by `dotenv` off the `.env`
58+
file in your root directory. In production, you'll have to manage these
59+
yourself. In Heroku, this is simple as running:
60+
61+
```
62+
heroku config:set FOO=bar
63+
```
64+
7565
## Redux Devtools
66+
7667
This project supports the awesome [Redux Devtools Extension](https://github.com/zalmoxisus/redux-devtools-extension). Install the
7768
Chrome or Firefox extension and it should just work.
7869

79-
## Server Side Rendering (SSR) and Asynchronous Data Fetching
70+
## Pre-fetching Data for Server Side Rendering (SSR)
71+
8072
When rendering components on the server, you'll find that you may need to fetch
8173
some data before it can be rendered. The [server code](server/server.js) looks
8274
for a `fetchData` method on the container component and its child components,
8375
then executes all of them and only renders after the promises have all been
8476
resolved.
8577

86-
See the [TodosContainer](common/js/containers/Todos/index.js) for an example.
78+
```
79+
// As an ES6 class
80+
81+
class TodosContainer extends React.Component {
82+
static fetchData = ({ store }) => {
83+
return store.dispatch(fetchTodos());
84+
};
85+
}
86+
87+
// As a functional stateless component
88+
89+
const TodosContainer = (props) => {
90+
const { todos } = props;
91+
return (
92+
// ...component code
93+
);
94+
}
95+
96+
TodosContainer.fetchData = ({ store }) => {
97+
return store.dispatch(fetchTodos());
98+
}
99+
```
87100

88101
## Async / Await
102+
89103
This project uses `async/await`, available by default in Node.js v8.x.x or
90104
higher. If you experience errors, please upgrade your version of Node.js.
91105

92-
## Writing Tests
106+
## Testing
107+
93108
The default testing framework is Mocha, though you can use whatever you want.
94109
Make sure you have it installed:
95110

@@ -109,7 +124,9 @@ Tests should reside in `test/spec` in their appropriate folders:
109124
Tests can be written with ES2015, since it passes through `babel-register`.
110125

111126
## Running Tests
127+
112128
To run a single test:
129+
113130
```
114131
npm test /path/to/single.test.js
115132
```
@@ -126,7 +143,7 @@ To run all tests:
126143
npm run test:all
127144
```
128145

129-
This will run all files that are suffixed with a `.test.js`.
146+
This will run all tests in the `test/spec` directory.
130147

131148
## Running ESLint
132149

@@ -136,16 +153,15 @@ npm run lint
136153

137154
Check the `.eslintignore` file for directories excluded from linting.
138155

139-
## Changing the Asset Host
156+
## Changing the public asset path
140157

141-
In production scenarios, you may want your assets to be hosted elsewhere besides
142-
on your server. Just set an environment variable to point the asset host to
143-
where ever you want, as defaults to `localhost:3001`. Just set it to the CDN of
144-
your choice.
158+
By default, assets are built into `/dist/public`. This path is then served by
159+
express under the path `/assets`. This is the public asset path. In a production
160+
scenario, you may want your assets to be hosted on a CDN. To do so, just change
161+
the `PUBLIC_ASSET_PATH` environment variant.
145162

146163
If you're using Heroku:
164+
147165
```
148-
heroku config:set ASSET_HOST=/dist/
149-
# OR
150-
heroku config:set ASSET_HOST=https://s3.amazonaws.com/mybucket/myasssets/
166+
heroku config:set PUBLIC_ASSET_PATH=https://my.cdn.com
151167
```

common/js/actions/todos.js

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,34 @@
11
import {
2-
ADD_TODO, REMOVE_TODO, TOGGLE_TODO,
3-
FETCH_TODOS_REQUEST, FETCH_TODOS_SUCCESS, FETCH_TODOS_FAILURE
2+
ADD_TODO,
3+
REMOVE_TODO,
4+
TOGGLE_TODO,
5+
FETCH_TODOS_REQUEST,
6+
FETCH_TODOS_SUCCESS,
7+
FETCH_TODOS_FAILURE
48
} from 'constants/index';
5-
import api from 'helper/api';
6-
import generateActionCreator from 'helper/generateActionCreator';
9+
import api from 'lib/api';
10+
import generateActionCreator from 'lib/generateActionCreator';
711

812
export const addTodo = generateActionCreator(ADD_TODO, 'text');
913
export const removeTodo = generateActionCreator(REMOVE_TODO, 'id');
1014
export const toggleTodo = generateActionCreator(TOGGLE_TODO, 'id');
1115

1216
export const fetchTodosRequest = generateActionCreator(FETCH_TODOS_REQUEST);
13-
export const fetchTodosSuccess = generateActionCreator(FETCH_TODOS_SUCCESS, 'todos');
14-
export const fetchTodosFailure = generateActionCreator(FETCH_TODOS_FAILURE, 'error');
17+
export const fetchTodosSuccess = generateActionCreator(
18+
FETCH_TODOS_SUCCESS,
19+
'todos'
20+
);
21+
export const fetchTodosFailure = generateActionCreator(
22+
FETCH_TODOS_FAILURE,
23+
'error'
24+
);
1525

1626
export const fetchTodos = () => {
17-
return (dispatch) => {
27+
return dispatch => {
1828
dispatch(fetchTodosRequest());
1929

20-
return api.get('/api/todos')
30+
return api
31+
.get('/api/todos')
2132
.then(todos => {
2233
dispatch(fetchTodosSuccess(todos));
2334

File renamed without changes.
File renamed without changes.

common/templates/layouts/application.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<%= helmet.title %>
55
<meta charset="utf-8" />
66
<meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width height=device-height" />
7-
<link rel="shortcut icon" type="image/x-icon" href="/dist/images/favicon.png">
7+
<link rel="shortcut icon" type="image/x-icon" href="/assets/favicon.png">
88
<link type="text/css" rel="stylesheet" href="<%= vendorCss %>" />
99
<link type="text/css" rel="stylesheet" href="<%= appCss %>" />
1010
</head>

config/index.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const { mapValues, keyBy } = require('lodash');
2+
3+
module.exports = {
4+
// The env vars to expose on the client side.
5+
clientEnv: mapValues(
6+
keyBy([
7+
// All CLIENT env vars that you wish to expose go here
8+
// WARNING: Be careful not to expose any secrets here!
9+
'NODE_ENV',
10+
'APPLICATION_BASE_URL'
11+
]),
12+
(env) => JSON.stringify(process.env[env])
13+
),
14+
15+
/* The identifier to use for css-modules.
16+
*/
17+
cssModulesIdentifier: '[name]__[local]__[hash:base64:5]',
18+
19+
/* Paths for webpack to resolve into non-relative directories, so that instead
20+
* of having to use relative paths:
21+
*
22+
* import SomeComponents from '../../../../SomeComponent';
23+
*
24+
* we can write this instead:
25+
*
26+
* import SomeComponent from 'components/SomeComponent';
27+
*/
28+
clientResolvePaths: {
29+
actions: 'common/js/actions',
30+
components: 'common/js/components',
31+
constants: 'common/js/constants',
32+
containers: 'common/js/containers',
33+
css: 'common/css',
34+
fonts: 'common/fonts',
35+
images: 'common/images',
36+
lib: 'common/js/lib',
37+
pages: 'common/js/pages',
38+
reducers: 'common/js/reducers',
39+
routes: 'common/js/routes',
40+
store: 'common/js/store',
41+
templates: 'common/templates'
42+
},
43+
44+
serverResolvePaths: {
45+
api: 'server/api',
46+
constants: 'common/js/constants'
47+
},
48+
49+
// Isomorphic configuration
50+
isomorphicConfig: {
51+
assets: {
52+
images: {
53+
extensions: [
54+
'png',
55+
'jpg',
56+
'jpeg',
57+
'gif',
58+
'ico',
59+
'svg'
60+
]
61+
}
62+
}
63+
}
64+
};

package-lock.json

Lines changed: 12 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)