├── Procfile
├── .eslintignore
├── .gitignore
├── static
├── favicon.ico
└── logo.svg
├── src
├── containers
│ ├── RadiumContainer.js
│ └── StargazersContainer.js
├── actions
│ ├── actionTypes.js
│ └── StargazersActions.js
├── reducers
│ ├── index.js
│ └── stargazers.js
├── components
│ ├── User.js
│ ├── About.js
│ ├── Home.js
│ └── Header.js
├── routes.js
├── store.js
├── client.js
└── server.js
├── .editorconfig
├── .eslintrc
├── configs
├── webpack.server-watch.js
├── webpack.client.js
├── webpack.server.js
└── webpack.client-watch.js
├── LICENSE.md
├── README.md
└── package.json
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run build && npm start
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | webpack.client-watch.js
2 | webpack.client.js
3 | babel.server.js
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | dist/
4 | etc/
5 | *.log
6 | *.map
7 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/luandro/hapi-universal-redux/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/src/containers/RadiumContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Radium from 'radium';
3 |
4 | const RadiumContainer = ({children}) => children
5 | export default Radium(RadiumContainer)
6 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import stargazers from './stargazers';
3 | import { routerReducer } from 'react-router-redux'
4 |
5 | const rootReducer = combineReducers({
6 | stargazers,
7 | routing: routerReducer
8 | });
9 |
10 | export default rootReducer;
11 |
--------------------------------------------------------------------------------
/src/components/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | const avatarSize = 32;
3 | const avatarUrl = (id) => `https://avatars.githubusercontent.com/u/${id}?v=3&s=${avatarSize}`;
4 |
5 | /**
6 | * Stateless React component
7 | */
8 | export default ({user, styles}) => (
9 |
10 |
11 |
12 | );
13 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/src/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, Route } from 'react-router';
3 | import StargazersContainer from './containers/StargazersContainer';
4 | import Header from './components/Header';
5 | import Home from './components/Home';
6 | import About from './components/About';
7 |
8 | /**
9 | * The React Routes for both the server and the client.
10 | */
11 | module.exports = (
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from './reducers';
4 |
5 | export default function (initialState) {
6 | const finalCreateStore = compose(
7 | applyMiddleware(thunk),
8 | typeof window === 'object' && typeof window.devToolsExtension !== 'undefined' ? window.devToolsExtension() : f => f
9 | )(createStore);
10 |
11 | const store = finalCreateStore(rootReducer, initialState);
12 |
13 | if (module.hot) {
14 | // Enable Webpack hot module replacement for reducers
15 | module.hot.accept('./reducers', () => {
16 | const nextRootReducer = require('./reducers/index').default;
17 | store.replaceReducer(nextRootReducer);
18 | });
19 | }
20 |
21 | return store;
22 | }
23 |
--------------------------------------------------------------------------------
/src/reducers/stargazers.js:
--------------------------------------------------------------------------------
1 | import {
2 | STARGAZERS_FETCH, STARGAZERS_REQUEST,
3 | STARGAZERS_STOP_FETCH
4 | } from '../actions/actionTypes';
5 |
6 | const initialState = {
7 | users: [],
8 | nextPage: 1,
9 | pagesToFetch: 30,
10 | isLoading: false
11 | };
12 |
13 | export default function stargazers(state = initialState, action) {
14 | switch (action.type) {
15 | case STARGAZERS_FETCH:
16 | return Object.assign({}, state, {
17 | users: state.users.concat(action.fetchedStargazers),
18 | nextPage: state.nextPage + 1,
19 | pagesToFetch: state.pagesToFetch - 1
20 | })
21 | case STARGAZERS_REQUEST:
22 | return Object.assign({}, state, {
23 | isLoading: true
24 | })
25 | case STARGAZERS_STOP_FETCH:
26 | return Object.assign({}, state, {
27 | pagesToFetch: 0,
28 | isLoading: false
29 | })
30 | default:
31 | return state;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/components/About.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default ({ styles }) => (
4 |
5 |
Getting started
6 |
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 |
Redux DevTools
13 |
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 |
19 | )
20 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
21 |
22 |
23 |
24 | ,
25 | reactRoot
26 | )
27 |
28 |
29 | /**
30 | * Detect whether the server-side render has been discarded due to an invalid checksum.
31 | */
32 | if(process.env.NODE_ENV === "production"){
33 | if (!reactRoot.firstChild || !reactRoot.firstChild.attributes ||
34 | !reactRoot.firstChild.attributes["data-react-checksum"]) {
35 | console.error("Server-side React render was discarded. Make sure that your initial render does not contain any client-side code.");
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import User from './User';
4 |
5 | const Home = ({ styles, stargazers }) => (
6 |
7 |
Features
8 |
9 | Redux for managing app state
10 | Redux DevTools for state time travelling
11 | React Transform for instant client updates
12 | Fully automated with npm run scripts
13 | Server hot reloads with piping and Hapi.js
14 | Webpack for watch + production builds
15 | React and React Router on the client and server
16 | Babel automatically compiles ES6 + ES7
17 | Radium for advanced inline styling
18 |
19 |
20 | In short – an excellent choice .
21 | Ready to start{'?'}
22 |
23 |
24 | Community
25 |
26 | {stargazers.map((user, key) => {
27 | return
28 | })}
29 |
30 | )
31 |
32 | /**
33 | * Connect to Redux store.
34 | */
35 | export default connect(
36 | state => ({
37 | stargazers: state.stargazers.users,
38 | })
39 | )(Home)
40 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/containers/StargazersContainer.js:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import fetch from 'isomorphic-fetch';
3 | import { connect } from 'react-redux';
4 | import { fetchUsers, requestUsers } from '../actions/StargazersActions';
5 | import Radium, { Style } from 'radium';
6 |
7 |
8 | class StargazersContainer extends Component {
9 |
10 | componentWillMount = () => {
11 | /**
12 | * Start loading.
13 | */
14 | this.props.dispatch(requestUsers());
15 | /**
16 | * Start recursive loading.
17 | */
18 | this.recursiveFetch();
19 | };
20 |
21 | componentDidUpdate = () => {
22 | const {stargazers, dispatch} = this.props;
23 | /**
24 | * Recursive fetch everytime component updates.
25 | */
26 | if(stargazers.nextPage > 1 && stargazers.pagesToFetch > 0 && stargazers.isLoading === true){
27 | this.recursiveFetch();
28 | }
29 | };
30 |
31 | /**
32 | * Function that dispatches the fetch action.
33 | */
34 | recursiveFetch = () => {
35 | const {stargazers, dispatch} = this.props;
36 | dispatch(fetchUsers(stargazers.nextPage, stargazers.pagesToFetch));
37 | };
38 |
39 | /**
40 | * Render child routes and Radium's Style component, for css-like global styles.
41 | */
42 | render() {
43 | return (
44 |
45 |
55 | {this.props.children}
56 |
57 | )
58 | };
59 | }
60 |
61 | /**
62 | * Radium connect.
63 | */
64 | StargazersContainer = Radium(StargazersContainer);
65 |
66 | /**
67 | * Redux connect.
68 | */
69 | export default connect(
70 | state => ({ stargazers: state.stargazers })
71 | )(StargazersContainer)
72 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/components/Header.js:
--------------------------------------------------------------------------------
1 | import React, { cloneElement } from 'react';
2 | import { connect } from 'react-redux';
3 | import { Link } from 'react-router';
4 |
5 | /*
6 | * Main view colors.
7 | */
8 | const colors = {
9 | white: '#BEBEBE',
10 | pink: '#D381C3',
11 | blue: '#6FB3D2',
12 | green: '#A1C659',
13 | darkGrey: '#2A2F3A',
14 | lightGrey: '#4F5A65'
15 |
16 | }
17 | /**
18 | * Main view styles.
19 | */
20 | const styles = {
21 | base: {
22 | fontFamily: 'sans-serif',
23 | color: colors.white,
24 | padding: '10px 30px 30px',
25 | width: '380px',
26 | margin: '0 auto 10px',
27 | background: colors.darkGrey,
28 | boxShadow: '15px 5px ' + colors.lightGrey
29 | },
30 | link: {
31 | color: colors.white,
32 | textDecoration: 'none',
33 | },
34 | navLink: {
35 | fontFamily: 'sans-serif',
36 | color: colors.lightGrey,
37 | textDecoration: 'none',
38 | padding: '0 30px'
39 | },
40 | nav: {
41 | height: 40,
42 | width: '380px',
43 | margin: '10px auto 0',
44 | padding: '10px 30px 30px',
45 | color: 'white',
46 | backgroundColor: colors.blue,
47 | boxShadow: '15px 5px ' + colors.lightGrey,
48 | textTransform: 'uppercase'
49 | },
50 | list: {
51 | display: 'inline-block',
52 | listStyle: 'none',
53 | },
54 | feature: {
55 | color: colors.pink,
56 | },
57 | github: {
58 | position: 'absolute',
59 | top: 0,
60 | right: 0,
61 | border: 0,
62 | },
63 | avatar: {
64 | borderRadius: '50%',
65 | width: 32,
66 | height: 32,
67 | margin: '0 2px 2px 0',
68 | },
69 |
70 | };
71 |
72 | const repositoryUrl = 'https://github.com/luandro/hapi-universal-redux';
73 |
74 | /**
75 | * Main component
76 | */
77 | export default ({children}) => (
78 |
79 |
80 |
81 | Home
82 | About
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | {/*
94 | * Pass props down to child Routes.
95 | */}
96 | {cloneElement(children, Object.assign({}, {styles: styles, colors: colors }))}
97 |
98 |
99 | )
100 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import babelPolyfill from "babel-polyfill";
2 | import { Server } from "hapi";
3 | import h2o2 from "h2o2";
4 | import inert from "inert";
5 | import React from "react";
6 | import ReactDOM from "react-dom/server";
7 | import { RouterContext, match } from "react-router";
8 | import configureStore from "./store.js";
9 | import RadiumContainer from './containers/RadiumContainer';
10 | import { Provider } from 'react-redux';
11 | import routesContainer from "./routes";
12 | import url from "url";
13 | let routes = routesContainer;
14 |
15 | /**
16 | * Create Redux store, and get intitial state.
17 | */
18 | const store = configureStore();
19 | const initialState = store.getState();
20 | /**
21 | * Start Hapi server
22 | */
23 | var envset = {
24 | production: process.env.NODE_ENV === 'production'
25 | };
26 |
27 | const hostname = envset.production ? (process.env.HOSTNAME || process['env'].HOSTNAME) : "localhost";
28 | var port = envset.production ? (process.env.PORT || process['env'].PORT) : 8000
29 | const server = new Server();
30 |
31 | server.connection({host: hostname, port: port});
32 |
33 | server.register(
34 | [
35 | h2o2,
36 | inert,
37 | // WebpackPlugin
38 | ],
39 | (err) => {
40 | if (err) {
41 | throw err;
42 | }
43 |
44 | server.start(() => {
45 | console.info("==> ✅ Server is listening");
46 | console.info("==> 🌎 Go to " + server.info.uri.toLowerCase());
47 | });
48 | });
49 |
50 | /**
51 | * Attempt to serve static requests from the public folder.
52 | */
53 | server.route({
54 | method: "GET",
55 | path: "/{params*}",
56 | handler: {
57 | file: (request) => "static" + request.path
58 | }
59 | });
60 |
61 | /**
62 | * Endpoint that proxies all GitHub API requests to https://api.github.com.
63 | */
64 | server.route({
65 | method: "GET",
66 | path: "/api/github/{path*}",
67 | handler: {
68 | proxy: {
69 | passThrough: true,
70 | mapUri (request, callback) {
71 | callback(null, url.format({
72 | protocol: "https",
73 | host: "api.github.com",
74 | pathname: request.params.path,
75 | query: request.query
76 | }));
77 | },
78 | onResponse (err, res, request, reply, settings, ttl) {
79 | reply(res);
80 | }
81 | }
82 | }
83 | });
84 |
85 | /**
86 | * Catch dynamic requests here to fire-up React Router.
87 | */
88 | server.ext("onPreResponse", (request, reply) => {
89 | if (typeof request.response.statusCode !== "undefined") {
90 | return reply.continue();
91 | }
92 |
93 | match({routes, location: request.path}, (error, redirectLocation, renderProps) => {
94 | if (redirectLocation) {
95 | reply.redirect(redirectLocation.pathname + redirectLocation.search);
96 | return;
97 | }
98 | if (error || !renderProps) {
99 | reply.continue();
100 | return;
101 | }
102 | const reactString = ReactDOM.renderToString(
103 |
104 |
105 |
106 |
107 |
108 | );
109 | const webserver = __PRODUCTION__ ? "" : `//${hostname}:8080`;
110 | let output = (
111 | `
112 |
113 |
114 |
115 | Hapi Universal Redux
116 |
117 |
118 |
119 | ${reactString}
120 |
124 |
125 |
126 | `
127 | );
128 | reply(output);
129 | });
130 | });
131 |
132 | if (__DEV__) {
133 | if (module.hot) {
134 | console.log("[HMR] Waiting for server-side updates");
135 |
136 | module.hot.accept("./routes", () => {
137 | routes = require("./routes");
138 | });
139 |
140 | module.hot.addStatusHandler((status) => {
141 | if (status === "abort") {
142 | setTimeout(() => process.exit(0), 0);
143 | }
144 | });
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/static/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------