├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitignore
├── LICENSE.md
├── Procfile
├── README.md
├── configs
├── webpack.client-watch.js
├── webpack.client.js
├── webpack.server-watch.js
└── webpack.server.js
├── package.json
├── src
├── actions
│ ├── StargazersActions.js
│ └── actionTypes.js
├── client.js
├── components
│ ├── About.js
│ ├── Header.js
│ ├── Home.js
│ └── User.js
├── containers
│ ├── RadiumContainer.js
│ └── StargazersContainer.js
├── reducers
│ ├── index.js
│ └── stargazers.js
├── routes.js
├── server.js
└── store.js
├── static
├── favicon.ico
└── logo.svg
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig: http://EditorConfig.org
2 |
3 | root = true
4 |
5 | [*]
6 | end_of_line = crlf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 | indent_style = tab
11 | tab_width = 4
12 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | webpack.client-watch.js
2 | webpack.client.js
3 | babel.server.js
4 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "ecmaFeatures": {
4 | globalReturn: true
5 | },
6 | "rules": {
7 | // Temporarily disabled due to a possible bug in babel-eslint (todomvc example)
8 | "block-scoped-var": 0,
9 | "no-console": 0,
10 | // Temporarily disabled for test/* until babel/babel-eslint#33 is resolved
11 | "padded-blocks": 0,
12 | "space-before-function-paren": [2, { "anonymous": "always", "named": "never" }]
13 | },
14 | "globals": {
15 | "__CLIENT__": true,
16 | "__SERVER__": true
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | dist/
4 | etc/
5 | *.log
6 | *.map
7 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # BSD 3-Clause License
2 |
3 | Copyright © 2015, Rick Wong
4 | All rights reserved.
5 |
6 | Redistribution and use in source and binary forms, with or without
7 | modification, are permitted provided that the following conditions are met:
8 |
9 | 1. Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | 2. Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the distribution.
14 | 3. Neither the name of the copyright holder nor the
15 | names of its contributors may be used to endorse or promote products
16 | derived from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
22 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run build && npm start
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 |
5 | ***DEPRECATED***: check out alternatives to building a server-side rendered React app:
6 | - [nextJS](https://github.com/zeit/next.js)
7 | - [electrode](https://github.com/electrode-io/electrode-boilerplate-universal-react-node)
8 |
9 |
10 | Isomorphic starterkit with server-side React rendering using
11 | [npm](https://www.npmjs.com/),
12 | [webpack](https://webpack.github.io/),
13 | [webpack-dev-server](https://github.com/webpack/webpack-dev-server),
14 | [react-transform-hmr](https://github.com/danmartinez101/babel-preset-react-hmre),
15 | [hapi](http://www.hapijs.com/),
16 | [babel](http://babeljs.io/),
17 | [react](https://facebook.github.io/react),
18 | [react-router](https://github.com/reactjs/react-router)
19 | [redux](https://github.com/reactjs/redux),
20 | [redux-devtools-extension](https://github.com/zalmoxisus/redux-devtools-extension),
21 | [react-router-redux](https://github.com/reactjs/react-router-redux),
22 | [radium](https://github.com/FormidableLabs/radium).
23 |
24 |
25 | 
26 |
27 | ## Features
28 |
29 | - Fully automated with npm run scripts
30 | - Server hot reloads with webpack hmr
31 | - Webpack for watch + production builds
32 | - React + Router on the client and server
33 | - React-Transform for instant client updates
34 | - Babel automatically compiles ES6 + ES7
35 | - Redux and Redux-DevTools-Extension for managing app state
36 | - Radium for advanced inline styling
37 |
38 | It just works out-of-the-box.
39 |
40 | ## Installation
41 |
42 | Make sure you're using Node >= 4.0.0.
43 |
44 | ```bash
45 | git clone https://github.com/luandro/hapi-universal-redux.git
46 | cd hapi-universal-redux
47 |
48 | npm install
49 | npm run dev # start Hapi server and webpack-dev-server hot server
50 |
51 | # production build and run
52 | npm run production
53 | # or
54 | NODE_ENV=production npm run build
55 | NODE_ENV=production npm run start
56 | ```
57 |
58 | ## Usage
59 |
60 | Run `npm run dev` in your terminal and play with `views/Main.js` to get a feel of
61 | the server-side rendering and client-side hot updates.
62 |
63 |
64 | ## License
65 |
66 | MIT license. Copyright © 2016, Luandro. All rights reserved.
67 |
--------------------------------------------------------------------------------
/configs/webpack.client-watch.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 | var config = require("./webpack.client.js");
3 | var wds = {
4 | hostname: process.env.HOSTNAME || "localhost",
5 | port: 8080
6 | };
7 |
8 | config.cache = true;
9 | config.debug = true;
10 | config.devtool = "cheap-module-eval-source-map";
11 |
12 | config.entry.unshift(
13 | "webpack-dev-server/client?http://" + wds.hostname + ":" + wds.port,
14 | "webpack/hot/only-dev-server"
15 | );
16 |
17 | config.devServer = {
18 | publicPath: "http://" + wds.hostname + ":" + wds.port + "/dist",
19 | hot: true,
20 | inline: false,
21 | lazy: false,
22 | quiet: true,
23 | noInfo: true,
24 | headers: {"Access-Control-Allow-Origin": "*"},
25 | stats: {colors: true},
26 | host: wds.hostname
27 | };
28 |
29 | config.output.publicPath = config.devServer.publicPath;
30 | config.output.hotUpdateMainFilename = "update/[hash]/update.json";
31 | config.output.hotUpdateChunkFilename = "update/[hash]/[id].update.js";
32 |
33 | config.plugins = [
34 | new webpack.DefinePlugin({__CLIENT__: true, __SERVER__: false, __PRODUCTION__: false, __DEV__: true}),
35 | new webpack.HotModuleReplacementPlugin(),
36 | new webpack.NoErrorsPlugin()
37 | ];
38 |
39 | config.module.postLoaders = [
40 | {test: /\.js$/, loaders: ["babel?cacheDirectory&presets[]=es2015&presets[]=stage-0&presets[]=react&presets[]=react-hmre"], exclude: /node_modules/}
41 | ];
42 |
43 | module.exports = config;
44 |
--------------------------------------------------------------------------------
/configs/webpack.client.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 | var path = require("path");
3 |
4 | module.exports = {
5 | target: "web",
6 | cache: false,
7 | context: __dirname,
8 | debug: false,
9 | devtool: false,
10 | entry: ["../src/client"],
11 | output: {
12 | path: path.join(__dirname, "../static/dist"),
13 | filename: "client.js",
14 | chunkFilename: "[name].[id].js"
15 | },
16 | plugins: [
17 | new webpack.DefinePlugin({__CLIENT__: true, __SERVER__: false, __PRODUCTION__: true, __DEV__: false}),
18 | new webpack.DefinePlugin({"process.env": {NODE_ENV: '"production"'}}),
19 | new webpack.optimize.DedupePlugin(),
20 | new webpack.optimize.OccurenceOrderPlugin(),
21 | new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}})
22 | ],
23 | module: {
24 | loaders: [
25 | {test: /\.json$/, loaders: ["json"]}
26 | ],
27 | postLoaders: [
28 | {test: /\.js$/, loaders: ["babel?presets[]=es2015&presets[]=stage-0&presets[]=react"], exclude: /node_modules/}
29 | ],
30 | noParse: /\.min\.js/
31 | },
32 | resolve: {
33 | modulesDirectories: [
34 | "src",
35 | "node_modules",
36 | "web_modules"
37 | ],
38 | extensions: ["", ".json", ".js"]
39 | },
40 | node: {
41 | __dirname: true,
42 | fs: 'empty'
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/configs/webpack.server-watch.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 | var config = require("./webpack.server.js");
3 | var wds = {
4 | hostname: process.env.HOSTNAME || "localhost",
5 | port: 8080
6 | };
7 |
8 | config.cache = true;
9 | config.debug = true;
10 |
11 | config.entry.unshift(
12 | "webpack/hot/poll?1000"
13 | );
14 |
15 | config.output.publicPath = "http://" + wds.hostname + ":" + wds.port + "/dist";
16 |
17 | config.plugins = [
18 | new webpack.DefinePlugin({__CLIENT__: false, __SERVER__: true, __PRODUCTION__: false, __DEV__: true}),
19 | new webpack.HotModuleReplacementPlugin(),
20 | new webpack.NoErrorsPlugin()
21 | ];
22 |
23 | module.exports = config;
24 |
--------------------------------------------------------------------------------
/configs/webpack.server.js:
--------------------------------------------------------------------------------
1 | var webpack = require("webpack");
2 | var nodeExternals = require("webpack-node-externals");
3 | var path = require("path");
4 | var fs = require("fs");
5 |
6 | module.exports = {
7 | target: "node",
8 | cache: false,
9 | context: __dirname,
10 | debug: false,
11 | devtool: "source-map",
12 | entry: ["../src/server"],
13 | output: {
14 | path: path.join(__dirname, "../dist"),
15 | filename: "server.js"
16 | },
17 | plugins: [
18 | new webpack.DefinePlugin({__CLIENT__: false, __SERVER__: true, __PRODUCTION__: true, __DEV__: false}),
19 | new webpack.DefinePlugin({"process.env": {NODE_ENV: '"production"'}})
20 | ],
21 | module: {
22 | loaders: [
23 | {test: /\.json$/, loaders: ["json"]},
24 | {test: /\.(ico|gif|png|jpg|jpeg|svg|webp)$/, loaders: ["file?context=static&name=/[path][name].[ext]"], exclude: /node_modules/},
25 | {test: /\.js$/, loaders: ["babel?presets[]=es2015&presets[]=stage-0&presets[]=react"], exclude: /node_modules/}
26 | ],
27 | postLoaders: [
28 | ],
29 | noParse: /\.min\.js/
30 | },
31 | externals: [nodeExternals({
32 | whitelist: ["webpack/hot/poll?1000"]
33 | })],
34 | resolve: {
35 | modulesDirectories: [
36 | "src",
37 | "node_modules",
38 | "static"
39 | ],
40 | extensions: ["", ".json", ".js"]
41 | },
42 | node: {
43 | __dirname: true,
44 | fs: "empty"
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hapi-universal-redux",
3 | "description": "Isomorphic starterkit with server-side React rendering.",
4 | "version": "1.1.1",
5 | "license": "MIT",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/Luandro/hapi-universal-redux.git"
9 | },
10 | "homepage": "https://github.com/Luandro/hapi-universal-redux",
11 | "keywords": [
12 | "react",
13 | "react-router",
14 | "isomorphic",
15 | "starter",
16 | "boilerplate",
17 | "template",
18 | "webpack",
19 | "hapi",
20 | "redux",
21 | "redux-dev-tools"
22 | ],
23 | "main": "dist/server.js",
24 | "scripts": {
25 | "start": "forever --minUptime 1000 --spinSleepTime 1000 -c \"node --harmony\" ./dist/server.js",
26 | "build-server": "webpack --colors --display-error-details --config configs/webpack.server.js",
27 | "build-client": "webpack --colors --display-error-details --config configs/webpack.client.js",
28 | "build": "concurrently \"npm run build-server\" \"npm run build-client\"",
29 | "watch-server": "webpack --watch --verbose --colors --display-error-details --config configs/webpack.server-watch.js",
30 | "watch-server-start": "node node_modules/just-wait --pattern \"dist/*.js\" && npm run start",
31 | "watch-client": "webpack-dev-server --config configs/webpack.client-watch.js",
32 | "production": "cross-env NODE_ENV=production npm run build && cross-env NODE_ENV=production npm run start",
33 | "dev": "concurrently --kill-others \"npm run watch-server-start\" \"npm run watch-server\" \"npm run watch-client\"",
34 | "postinstall": "webpack -p --config configs/webpack.client.js"
35 | },
36 | "dependencies": {
37 | "babel": "6.5.2",
38 | "babel-core": "6.18.2",
39 | "babel-loader": "^6.2.8",
40 | "babel-polyfill": "6.16.0",
41 | "babel-preset-es2015": "6.18.0",
42 | "babel-preset-react": "6.16.0",
43 | "babel-preset-react-hmre": "1.1.1",
44 | "babel-preset-stage-0": "6.16.0",
45 | "concurrently": "^3.1.0",
46 | "cross-env": "^3.1.3",
47 | "file-loader": "^0.9.0",
48 | "forever": "0.15.3",
49 | "h2o2": "^5.4.0",
50 | "hapi": "^15.2.0",
51 | "inert": "^4.0.2",
52 | "isomorphic-fetch": "^2.2.1",
53 | "piping": "1.0.0-rc.4",
54 | "radium": "^0.18.1",
55 | "react": "^15.4.1",
56 | "react-dom": "^15.4.1",
57 | "react-redux": "^4.4.5",
58 | "react-router": "^3.0.0",
59 | "react-router-redux": "^4.0.6",
60 | "react-tap-event-plugin": "^2.0.1",
61 | "redux": "^3.6.0",
62 | "redux-thunk": "^2.1.0",
63 | "webpack": "^1.13.3",
64 | "webpack-node-externals": "^1.5.4"
65 | },
66 | "devDependencies": {
67 | "eslint": "^3.9.1",
68 | "eslint-config-airbnb": "^13.0.0",
69 | "eslint-plugin-react": "^6.5.0",
70 | "json-loader": "^0.5.4",
71 | "just-wait": "1.0.9",
72 | "webpack-dev-server": "^1.16.2"
73 | },
74 | "engines": {
75 | "node": ">=4.0.0"
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/actions/StargazersActions.js:
--------------------------------------------------------------------------------
1 | import fetch from 'isomorphic-fetch';
2 | import {
3 | STARGAZERS_FETCH, STARGAZERS_REQUEST,
4 | STARGAZERS_STOP_FETCH,
5 | } from './actionTypes';
6 |
7 | let githubApi = "https://api.github.com";
8 | if (__CLIENT__) {
9 | const { protocol, hostname, port } = window.location;
10 | githubApi = `${protocol}//${hostname}:${port}/api/github`;
11 | }
12 |
13 | function receiveUsers(fetchedStargazers) {
14 | return {
15 | type: STARGAZERS_FETCH,
16 | fetchedStargazers
17 | };
18 | }
19 |
20 | export function requestUsers() {
21 | return {
22 | type: STARGAZERS_REQUEST
23 | }
24 | }
25 | function stopFetching() {
26 | return {
27 | type: STARGAZERS_STOP_FETCH
28 | }
29 | }
30 |
31 | export function fetchUsers(nextPage, pagesToFetch) {
32 | return function (dispatch) {
33 | return fetch(githubApi + "/repos/Luandro/hapi-universal-redux/stargazers" +`?per_page=10&page=${nextPage}`)
34 | .then((response) => response.json())
35 | .then((body) => {
36 | if (!body || !body.length) {
37 | dispatch(stopFetching());
38 | return
39 | }
40 | const fetchedStargazers = body.map(({ id, login }) => ({ id, login }));
41 | dispatch(receiveUsers(fetchedStargazers));
42 | })
43 | .catch((error) => {
44 | console.error(error);
45 | })
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/actions/actionTypes.js:
--------------------------------------------------------------------------------
1 | export const STARGAZERS_FETCH = 'STARGAZERS_FETCH';
2 | export const STARGAZERS_STOP_FETCH = 'STARGAZERS_STOP_FETCH';
3 | export const STARGAZERS_REQUEST = 'STARGAZERS_REQUEST';
4 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { Router, browserHistory } from 'react-router'
4 | import configureStore from "./store.js";
5 | import { Provider } from 'react-redux';
6 | import routes from "./routes";
7 | import RadiumContainer from "./containers/RadiumContainer";
8 | import { syncHistoryWithStore } from 'react-router-redux'
9 |
10 | const store = configureStore(window.__INITIAL_STATE__);
11 | delete window.__INITIAL_STATE__;
12 | const history = syncHistoryWithStore(browserHistory, store)
13 |
14 | /**
15 | * Fire-up React Router.
16 | */
17 | const reactRoot = window.document.getElementById("react-root");
18 |
19 | ReactDOM.render(
20 |
7 | Welcome to Hapi Universal Redux. 8 | Start editing the components to see the page hot reloading on the client. 9 | The goal of this project is to create a simple, 10 | yet production ready starterkit for React and Redux apps. 11 |
12 |14 | To start managing your state create new constants, actions and reducers. 15 | Install Redux DevTools Extension for Chrome if you haven't already, 16 | and use it to debug you're app's state. 17 |
18 |20 | In short – an excellent choice. 21 | Ready to start{'?'} 22 |
23 |