├── .babelrc
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── README.md
├── circle.yml
├── dist
└── favicon.ico
├── nodemon.json
├── package.json
├── platforms
├── browser
│ ├── DevTools.js
│ └── index.js
├── server
│ ├── Html.jsx
│ ├── api
│ │ ├── index.js
│ │ └── todos
│ │ │ ├── index.js
│ │ │ ├── redux.js
│ │ │ └── routes
│ │ │ ├── createTodo.js
│ │ │ ├── deleteTodo.js
│ │ │ ├── readTodo.js
│ │ │ ├── readTodos.js
│ │ │ └── updateTodo.js
│ ├── index.js
│ └── main.js
└── shared
│ ├── config.js
│ ├── createClient.js
│ ├── createStore.js
│ └── reasync.js
├── src
├── App.css
├── App.jsx
├── Homepage
│ └── Homepage.jsx
├── NotFound404
│ └── NotFound404.jsx
├── Repository
│ ├── Author
│ │ └── Author.jsx
│ ├── BaseInfo
│ │ └── BaseInfo.jsx
│ ├── Contributors
│ │ └── Contributors.jsx
│ ├── Repository.jsx
│ ├── redux.js
│ └── routes.jsx
├── reducers.js
└── routes.jsx
└── webpack
├── build.js
├── dev.config.js
├── hot-server
├── index.js
└── main.js
├── prod.config.js
└── webpack-isomorphic-assets.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets":["es2015","react"],
3 | "plugins":[
4 | "transform-object-rest-spread",
5 | "transform-class-properties",
6 | "transform-async-to-generator",
7 | "transform-decorators-legacy"
8 | ],
9 | "env": {
10 | "production": {
11 | "plugins": [
12 | "transform-react-constant-elements",
13 | "transform-react-inline-elements"
14 | ]
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs.
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 |
9 | # We recommend you to keep these unchanged.
10 | charset = utf-8
11 | end_of_line = lf
12 | indent_size = 2
13 | indent_style = space
14 | insert_final_newline = true
15 | trim_trailing_whitespace = true
16 |
17 | [package.json]
18 | indent_style = space
19 | indent_size = 2
20 |
21 | [*.md]
22 | trim_trailing_whitespace = false
23 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "ecmaFeatures": {
9 | "experimentalObjectRestSpread": true
10 | },
11 | "rules": {
12 | "react/no-multi-comp": 0,
13 | "import/default": 0,
14 | "import/no-duplicates": 0,
15 | "import/named": 0,
16 | "import/namespace": 0,
17 | "import/no-unresolved": 0,
18 | "import/no-named-as-default": 2,
19 | "comma-dangle": 0,
20 | // not sure why airbnb turned this on. gross!
21 | "indent": [2, 2, {"SwitchCase": 1}],
22 | "no-console": 0,
23 | "no-case-declarations": 0,
24 | "no-alert": 0,
25 | "max-len":[2,140]
26 | },
27 | "plugins": [
28 | "react",
29 | "import"
30 | ],
31 | "settings": {
32 | "import/parser": "babel-eslint",
33 | "import/resolve": {
34 | moduleDirectory: [
35 | "node_modules",
36 | "platforms",
37 | "src"
38 | ]
39 | }
40 | },
41 | "globals": {
42 | "__DEVELOPMENT__": true,
43 | "__CLIENT__": true,
44 | "__SERVER__": true,
45 | "__DISABLE_SSR__": true,
46 | "__DEVTOOLS__": true,
47 | "socket": true,
48 | "webpackIsomorphicTools": true
49 | },
50 | "parser": "babel-eslint"
51 | }
52 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | webpack-assets.json
3 | webpack-stats.json
4 | .idea/
5 | npm-debug.log
6 | tmp/
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Project is not maintained anymore. There are much better boilerplates for starting your app
2 |
3 | ## Installation
4 |
5 | ```bash
6 | npm install
7 | ```
8 |
9 | ## About
10 |
11 | This is boilerplate I have created to learn, to try, to evaluate how thing works, but I hope you can use it for yourself too.
12 | In this repo you can find a lot of parts of code from another similar projects.
13 | It's far from being complete, there is a lot of dependencies I want to use in future, but until now didn't have time to.
14 |
15 | ## Running Dev Server
16 |
17 | ```bash
18 | //first command line
19 | npm run start:dev
20 |
21 | //second command line
22 | npm run start:hot
23 |
24 | ```
25 |
26 | ## To Be Done?
27 |
28 | * immutable?
29 | * styles (css-modules ???)
30 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 4.0
4 | environment:
5 | CONTINUOUS_INTEGRATION: true
6 |
7 | dependencies:
8 | cache_directories:
9 | - node_modules
10 | override:
11 | - npm prune && npm install
12 |
13 | test:
14 | override:
15 | - npm run lint
16 |
--------------------------------------------------------------------------------
/dist/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/svrcekmichal/universal-react/b91d0eedd0173dc8160f8c66fdd6f1ae447f94c6/dist/favicon.ico
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "restartable": "rs",
3 | "ignore": [
4 | ".git",
5 | "node_modules",
6 | "webpack-assets.json",
7 | ".idea",
8 | "*/tmp/*"
9 | ],
10 | "verbose": true,
11 | "ext": "js jsx json"
12 | }
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "universal-react",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "webpack --verbose --colors --display-error-details --config webpack/prod.config.js",
8 | "start:prod": "NODE_ENV=production NODE_PATH=./platforms:./src PORT=8080 node ./platforms/server",
9 | "start:dev": "NODE_ENV=development NODE_PATH=./platforms:./src PORT=8080 nodemon ./platforms/server",
10 | "start:hot": "UV_THREADPOOL_SIZE=100 NODE_PATH=./platforms:./src PORT=8080 node webpack/hot-server",
11 | "lint": "eslint -c .eslintrc ./"
12 | },
13 | "dependencies": {
14 | "autoprefixer": "^6.3.3",
15 | "axios": "^0.8.1",
16 | "babel-core": "^6.5.2",
17 | "babel-loader": "^6.2.3",
18 | "babel-plugin-transform-async-to-generator": "^6.4.0",
19 | "babel-plugin-transform-class-properties": "^6.4.0",
20 | "babel-plugin-transform-decorators-legacy": "^1.3.4",
21 | "babel-plugin-transform-object-rest-spread": "^6.3.13",
22 | "babel-plugin-transform-react-constant-elements": "^6.3.13",
23 | "babel-plugin-transform-react-inline-elements": "^6.3.13",
24 | "babel-polyfill": "^6.3.14",
25 | "babel-preset-es2015": "^6.3.13",
26 | "babel-preset-react": "^6.3.13",
27 | "babel-runtime": "^6.5.0",
28 | "bluebird": "^3.1.1",
29 | "body-parser": "^1.15.0",
30 | "clean-webpack-plugin": "^0.1.6",
31 | "compression": "^1.6.0",
32 | "cookie-parser": "^1.4.1",
33 | "css-loader": "^0.23.1",
34 | "express": "^4.13.3",
35 | "file-loader": "^0.8.5",
36 | "history": "2.0.0-rc2",
37 | "jsonfile": "^2.2.3",
38 | "postcss-loader": "^0.8.2",
39 | "pretty-error": "^2.0.0",
40 | "react": "^0.14.6",
41 | "react-dom": "^0.14.6",
42 | "react-helmet": "^2.3.1",
43 | "react-redux": "^4.0.6",
44 | "react-router": "2.0.0-rc5",
45 | "react-router-redux": "^4.0.0-beta.1",
46 | "reasync": "^1.0.0-rc.1",
47 | "redux": "^3.3.1",
48 | "redux-axios-middleware": "^0.2.0",
49 | "redux-form": "^4.1.1",
50 | "serialize-javascript": "^1.1.2",
51 | "serve-favicon": "^2.3.0",
52 | "style-loader": "^0.13.0",
53 | "url-loader": "^0.5.7",
54 | "uuid": "^2.0.1",
55 | "webpack": "^1.12.10",
56 | "webpack-isomorphic-tools": "^2.2.24"
57 | },
58 | "devDependencies": {
59 | "babel-eslint": "^5.0.0",
60 | "babel-plugin-react-transform": "^2.0.0",
61 | "clean-webpack-plugin": "^0.1.6",
62 | "eslint": "~2.2.0",
63 | "eslint-config-airbnb": "^6.0.1",
64 | "eslint-plugin-import": "^1.0.0-beta.0",
65 | "eslint-plugin-react": "^4.0.0",
66 | "nodemon": "^1.8.1",
67 | "react-transform-hmr": "^1.0.1",
68 | "redux-devtools": "^3.0.1",
69 | "redux-devtools-dock-monitor": "^1.0.1",
70 | "redux-devtools-log-monitor": "^1.0.1",
71 | "webpack-dev-middleware": "^1.4.0",
72 | "webpack-hot-middleware": "^2.6.0"
73 | },
74 | "keywords": [],
75 | "author": "",
76 | "license": "ISC"
77 | }
78 |
--------------------------------------------------------------------------------
/platforms/browser/DevTools.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createDevTools } from 'redux-devtools';
3 | import LogMonitor from 'redux-devtools-log-monitor';
4 | import DockMonitor from 'redux-devtools-dock-monitor';
5 |
6 | export default createDevTools(
7 |
8 |
9 |
10 | );
11 |
--------------------------------------------------------------------------------
/platforms/browser/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { Provider } from 'react-redux';
4 | import { Router, browserHistory, match } from 'react-router';
5 | import { syncHistoryWithStore } from 'react-router-redux';
6 |
7 | import { getRoutes } from 'routes';
8 | import createStore from 'shared/createStore';
9 | import createClient from 'shared/createClient';
10 | import { createClientResolver } from 'shared/reasync';
11 |
12 | const store = createStore(browserHistory, window.__data__, createClient());
13 | const history = syncHistoryWithStore(browserHistory, store);
14 | const routes = getRoutes(store);
15 | const mountPoint = document.getElementById('content');
16 | const router = ();
17 | createClientResolver(history, routes, store);
18 |
19 | const hasDevToolsExtension = () => typeof window === 'object'
20 | && typeof window.devToolsExtension !== 'undefined';
21 |
22 | const showDevTools = __DEVELOPMENT__ && __DEVTOOLS__ && !hasDevToolsExtension();
23 |
24 | const { pathname, search, hash } = window.location;
25 | const location = `${pathname}${search}${hash}`;
26 | match({ routes, history, location }, () => { // to preload all components needed and not break server markup
27 | if (showDevTools && !hasDevToolsExtension()) {
28 | const DevTools = require('./DevTools').default;
29 | ReactDOM.render(
30 |
31 |
32 | {router}
33 |
34 |
35 | ,
36 | mountPoint
37 | );
38 | } else {
39 | ReactDOM.render(
40 |
41 | {router}
42 | ,
43 | mountPoint
44 | );
45 | }
46 | });
47 |
--------------------------------------------------------------------------------
/platforms/server/Html.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import ReactDOM from '../../node_modules/react-dom/server';
3 | import serialize from 'serialize-javascript';
4 | import Helmet from 'react-helmet';
5 |
6 | /**
7 | * Wrapper component containing HTML metadata and boilerplate tags.
8 | * Used in server-side code only to wrap the string output of the
9 | * rendered route component.
10 | *
11 | * The only thing this component doesn't (and can't) include is the
12 | * HTML doctype declaration, which is added to the rendered output
13 | * by the server.js file.
14 | */
15 | export default class Html extends Component {
16 |
17 | static propTypes = {
18 | assets: PropTypes.object,
19 | component: PropTypes.node,
20 | store: PropTypes.object
21 | };
22 |
23 | render() {
24 | const { assets, component, store } = this.props;
25 | const content = component ? ReactDOM.renderToString(component) : '';
26 | const head = Helmet.rewind();
27 |
28 | return (
29 |
30 |
31 | {head.base.toComponent()}
32 | {head.title.toComponent()}
33 | {head.meta.toComponent()}
34 | {head.link.toComponent()}
35 | {head.script.toComponent()}
36 |
37 |
38 |
39 | {/* styles (will be present only in production with webpack extract text plugin) */}
40 | {Object.keys(assets.styles).map((style, key) =>
41 |
43 | )}
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | );
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/platforms/server/api/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 | import reducer from './todos/redux';
3 | import express from 'express';
4 | import todos from './todos';
5 | import bodyParser from 'body-parser';
6 | import { readFileSync, writeFile } from 'jsonfile';
7 |
8 | const app = express();
9 | const file = `${__dirname}/tmp/store.json`;
10 | let oldState;
11 | try {
12 | oldState = readFileSync(file);
13 | } catch (e) {} // eslint-disable-line no-empty
14 |
15 | const store = createStore(reducer, oldState);
16 |
17 | store.subscribe(() => writeFile(file, store.getState(), (err) => err && console.error('Error writeFile:', err)));
18 |
19 | app.use(bodyParser.json());
20 | app.use('/todos', todos(store));
21 | app.on('mount', () => {
22 | console.log('Api is available at %s', app.mountpath);
23 | });
24 |
25 | export default app;
26 |
--------------------------------------------------------------------------------
/platforms/server/api/todos/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 |
3 | import createTodo from './routes/createTodo';
4 | import readTodo from './routes/readTodo';
5 | import readTodos from './routes/readTodos';
6 | import updateTodo from './routes/updateTodo';
7 | import deleteTodo from './routes/deleteTodo';
8 |
9 | const routes = (store) => {
10 | const app = express();
11 | app.get('/', readTodos(store));
12 | app.get('/:id', readTodo(store));
13 | app.post('/', createTodo(store));
14 | app.put('/:id', updateTodo(store));
15 | app.delete('/:id', deleteTodo(store));
16 | return app;
17 | };
18 |
19 | export default routes;
20 |
--------------------------------------------------------------------------------
/platforms/server/api/todos/redux.js:
--------------------------------------------------------------------------------
1 | import { v4 } from 'uuid';
2 |
3 | const CREATE_TODO = '@api/CREATE_TODO';
4 | const UPDATE_TODO = '@api/UPDATE_TODO';
5 | const DELETE_TODO = '@api/DELETE_TODO';
6 |
7 | export const createTodo = (text) => ({
8 | type: CREATE_TODO,
9 | payload: {
10 | id: v4(),
11 | text
12 | }
13 | });
14 |
15 | export const updateTodo = (id, text) => ({
16 | type: UPDATE_TODO,
17 | payload: {
18 | id,
19 | text
20 | }
21 | });
22 |
23 | export const deleteTodo = (id) => ({
24 | type: DELETE_TODO,
25 | payload: id
26 | });
27 |
28 | export const readTodo = (getState, id) => {
29 | const state = getState();
30 | for (const i in state) {
31 | if (!state.hasOwnProperty(i)) continue;
32 | if (state[i].id === id) {
33 | return state[i];
34 | }
35 | }
36 | return null;
37 | };
38 |
39 | export const readTodos = (getState) => getState();
40 |
41 | const initialState = [{
42 | id: v4(),
43 | text: 'Learn how to live more reactive :D'
44 | }, {
45 | id: v4(),
46 | text: 'Try to learn something about Elm finally'
47 | }, {
48 | id: v4(),
49 | text: 'Make awesome dinner to my lovely fiance <3'
50 | }];
51 |
52 | export default function (state = initialState, action) {
53 | switch (action.type) {
54 | case CREATE_TODO:
55 | const newState = state.concat();
56 | newState.push(action.payload);
57 | return newState;
58 | case UPDATE_TODO:
59 | return state.map((todo) => {
60 | if (todo.id === action.payload.id) {
61 | return (action.payload);
62 | }
63 | return todo;
64 | });
65 | case DELETE_TODO:
66 | return state.filter((todo) => todo.id !== action.payload);
67 | default:
68 | return state;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/platforms/server/api/todos/routes/createTodo.js:
--------------------------------------------------------------------------------
1 | import { createTodo, readTodo } from '../redux';
2 |
3 | const createTodoRoute = ({ getState, dispatch }) => (req, res) => {
4 | // validate
5 | const { text } = req.body;
6 | if (!text || !text.trim || !text.trim()) {
7 | res.status(400).send(400);
8 | return;
9 | }
10 |
11 | // create
12 | const action = createTodo(text);
13 | dispatch(action);
14 |
15 | // return created
16 | const todo = readTodo(getState, action.payload.id);
17 | if (todo) {
18 | res.send(JSON.stringify(todo));
19 | } else {
20 | res.status(500).send('Something went wrong');
21 | }
22 | };
23 |
24 | export default createTodoRoute;
25 |
--------------------------------------------------------------------------------
/platforms/server/api/todos/routes/deleteTodo.js:
--------------------------------------------------------------------------------
1 | import { deleteTodo, readTodo } from '../redux';
2 |
3 | const deleteTodoRoute = ({ getState, dispatch }) => (req, res) => {
4 | // validate
5 | const { id } = req.params;
6 |
7 | // exists?
8 | if (!readTodo(getState, id)) {
9 | res.status(404).send();
10 | return;
11 | }
12 |
13 | // update
14 | dispatch(deleteTodo(id));
15 |
16 | // return updated
17 | const todo = readTodo(getState, id);
18 | if (todo) {
19 | res.status(500).send('Something went wrong');
20 | } else {
21 | res.status(204).send();
22 | }
23 | };
24 |
25 | export default deleteTodoRoute;
26 |
--------------------------------------------------------------------------------
/platforms/server/api/todos/routes/readTodo.js:
--------------------------------------------------------------------------------
1 | import { readTodo } from '../redux';
2 |
3 | const readTodoRoute = ({ getState }) => (req, res) => {
4 | const todo = readTodo(getState, req.params.id);
5 | if (todo) {
6 | res.send(JSON.stringify(todo));
7 | } else {
8 | res.status(404).send();
9 | }
10 | };
11 |
12 | export default readTodoRoute;
13 |
--------------------------------------------------------------------------------
/platforms/server/api/todos/routes/readTodos.js:
--------------------------------------------------------------------------------
1 | import { readTodos } from '../redux';
2 |
3 | const readTodosRoute = ({ getState }) => (req, res) => {
4 | res.send(JSON.stringify(readTodos(getState)));
5 | };
6 |
7 | export default readTodosRoute;
8 |
--------------------------------------------------------------------------------
/platforms/server/api/todos/routes/updateTodo.js:
--------------------------------------------------------------------------------
1 | import { updateTodo, readTodo } from '../redux';
2 |
3 | const updateTodoRoute = ({ getState, dispatch }) => (req, res) => {
4 | // validate
5 | const { text } = req.body;
6 | const { id } = req.params;
7 | if (!text || !text.trim || !text.trim()) {
8 | res.status(400).send('Empty text not allowed');
9 | return;
10 | }
11 |
12 | // exists?
13 | if (!readTodo(getState, id)) {
14 | res.status(404).send();
15 | return;
16 | }
17 |
18 | // update
19 | dispatch(updateTodo(id, text));
20 |
21 | // return updated
22 | const todo = readTodo(getState, id);
23 | if (todo) {
24 | res.send(JSON.stringify(todo));
25 | } else {
26 | res.status(500).send('Something went wrong');
27 | }
28 | };
29 |
30 | export default updateTodoRoute;
31 |
--------------------------------------------------------------------------------
/platforms/server/index.js:
--------------------------------------------------------------------------------
1 | require('babel-register');
2 | require('babel-polyfill');
3 |
4 | const config = require('shared/config').default;
5 |
6 | global.__CLIENT__ = false;
7 | global.__SERVER__ = true;
8 | global.__DISABLE_SSR__ = false; // <----- DISABLES SERVER SIDE RENDERING FOR ERROR DEBUGGING
9 | global.__DEVELOPMENT__ = config.isProduction;
10 |
11 | const WebpackIsomorphicTools = require('webpack-isomorphic-tools');
12 | const webpackIsomorphicAssets = require('./../../webpack/webpack-isomorphic-assets').default;
13 | const rootDir = require('path').resolve(__dirname, '..', '..');
14 |
15 | global.webpackIsomorphicTools = new WebpackIsomorphicTools(webpackIsomorphicAssets)
16 | .development(!config.isProduction)
17 | .server(rootDir, () => {
18 | require('./main');
19 | });
20 |
--------------------------------------------------------------------------------
/platforms/server/main.js:
--------------------------------------------------------------------------------
1 | import Express from 'express';
2 | import React from 'react';
3 | import ReactDOM from 'react-dom/server';
4 | import favicon from 'serve-favicon';
5 | import compression from 'compression';
6 | import cookieParser from 'cookie-parser';
7 | import path from 'path';
8 | import http from 'http';
9 | import { match, RouterContext, createMemoryHistory } from 'react-router';
10 | import { Provider } from 'react-redux';
11 | import { getRoutes } from 'routes';
12 | import { syncHistoryWithStore } from 'react-router-redux';
13 |
14 | import Html from './Html';
15 |
16 | import config from 'shared/config';
17 | import createStore from 'shared/createStore';
18 | import createClient from 'shared/createClient';
19 | import { createServerResolver } from 'shared/reasync';
20 |
21 | import api from './api';
22 |
23 | // const pretty = new PrettyError();
24 | const app = new Express();
25 | const server = new http.Server(app);
26 |
27 | app.use(compression());
28 | app.use(cookieParser());
29 |
30 | app.use(favicon(path.join('dist', 'favicon.ico')));
31 | app.use('/assets', Express.static(path.join('dist', 'assets'), { maxAge: '200d' }));
32 |
33 | app.use('/api', api); // you can safely remove this, and whole api folder if you don't need api
34 |
35 | app.use((req, res) => { // eslint-disable-line consistent-return
36 | if (__DEVELOPMENT__) {
37 | // Do not cache webpack stats: the script file would change since
38 | // hot module replacement is enabled in the development env
39 | webpackIsomorphicTools.refresh();
40 | }
41 |
42 | const client = createClient(req.cookies);
43 | const memoryHistory = createMemoryHistory(req.originalUrl);
44 | const store = createStore(memoryHistory, undefined, client);
45 | const history = syncHistoryWithStore(memoryHistory, store);
46 | const routes = getRoutes(store);
47 |
48 | const hydrateOnClient = (status = 200) => {
49 | const html = ReactDOM.renderToString(
50 |