├── dist
└── .gitignore
├── static
├── dist
│ └── .gitignore
└── favicon.ico
├── .gitignore
├── src
├── components
│ ├── Avatar.scss
│ └── Avatar.js
├── containers
│ ├── routes.js
│ └── Main.js
├── apis
│ └── github.js
├── client.js
└── server.js
├── .editorconfig
├── configs
├── webpack.server-watch.js
├── webpack.server.js
├── webpack.client.js
└── webpack.client-watch.js
├── LICENSE.md
├── README.md
└── package.json
/dist/.gitignore:
--------------------------------------------------------------------------------
1 | [^.]*
--------------------------------------------------------------------------------
/static/dist/.gitignore:
--------------------------------------------------------------------------------
1 | [^.]*
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | node_modules/
3 | *.log
4 | *.map
5 |
--------------------------------------------------------------------------------
/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RickWong/react-isomorphic-starterkit/HEAD/static/favicon.ico
--------------------------------------------------------------------------------
/src/components/Avatar.scss:
--------------------------------------------------------------------------------
1 | .avatar {
2 | img {
3 | border-radius: 50%;
4 | width: 32px;
5 | height: 32px;
6 | margin: 0 2px 2px 0;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/.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/containers/routes.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import {Router, Route} from "react-router";
3 |
4 | import Main from "./Main";
5 |
6 | /**
7 | * The React Router routes for both the server and the client.
8 | */
9 | module.exports = (
10 |
15 |
16 | );
17 | }, styles);
18 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import * as ReactRouter from "react-router";
4 | import Transmit from "react-transmit";
5 |
6 | import routesContainer from "containers/routes";
7 |
8 | /**
9 | * Fire-up React Router.
10 | */
11 | const reactRoot = window.document.getElementById("react-root");
12 | Transmit.render(ReactRouter.Router, {routes: routesContainer, history: ReactRouter.browserHistory}, reactRoot);
13 |
14 | /**
15 | * Detect whether the server-side render has been discarded due to an invalid checksum.
16 | */
17 | if (process.env.NODE_ENV !== "production") {
18 | if (!reactRoot.firstChild || !reactRoot.firstChild.attributes ||
19 | !reactRoot.firstChild.attributes["data-react-checksum"]) {
20 | console.error("Server-side React render was discarded. Make sure that your initial render does not contain any client-side code.");
21 | }
22 | }
23 |
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 |
--------------------------------------------------------------------------------
/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 | {test: /\.(ico|gif|png|jpg|jpeg|svg|webp)$/, loaders: ["file?context=static&name=/[path][name].[ext]"], exclude: /node_modules/}
27 | ],
28 | postLoaders: [
29 | {test: /\.js$/, loaders: ["babel?presets[]=es2015&presets[]=stage-0&presets[]=react"], exclude: /node_modules/}
30 | ],
31 | noParse: /\.min\.js/
32 | },
33 | resolve: {
34 | modulesDirectories: [
35 | "src",
36 | "node_modules",
37 | "static"
38 | ],
39 | extensions: ["", ".json", ".js"]
40 | },
41 | node: {
42 | __dirname: true,
43 | fs: 'empty'
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # React Isomorphic Starterkit
4 |
5 | Isomorphic starterkit with server-side React rendering using
6 | [npm](https://www.npmjs.com),
7 | [koa](http://koajs.com),
8 | [webpack](https://webpack.github.io/),
9 | [babel](http://babeljs.io),
10 | [react](https://facebook.github.io/react),
11 | [react-router](https://github.com/rackt/react-router),
12 | [react-transform-hmr](https://github.com/gaearon/react-transform-hmr),
13 | [react-transmit](https://github.com/RickWong/react-transmit),
14 | [react-inline-css](https://github.com/RickWong/react-inline-css)
15 |
16 |   [](http://packagequality.com/#?package=react-isomorphic-starterkit)  
17 |
18 | ## Features
19 |
20 | - Fully automated toolchain with npm run scripts
21 | - React 0.14 + React Router 2.0 on the client and server
22 | - Babel 6 automatically compiles ES2015 + ES7 stage-0
23 | - Webpack HMR for instant server updates
24 | - React Transform HMR for instant client updates
25 | - React Transmit to preload on server and hydrate client
26 | - InlineCss-component for styling components
27 |
28 | It just works out-of-the-box.
29 |
30 | ## Installation
31 |
32 | Development
33 |
34 | ```bash
35 | git clone https://github.com/RickWong/react-isomorphic-starterkit.git
36 | cd react-isomorphic-starterkit
37 |
38 | npm install
39 | npm run watch # Yes, ONE command for both server AND client development!
40 | ```
41 |
42 | Production
43 |
44 | ```bash
45 | npm run build
46 | npm run start
47 | ```
48 |
49 | ## Usage
50 |
51 | Run `npm run watch` in your terminal and play with `views/Main.js` to get a feel of
52 | the server-side rendering and client-side hot updates.
53 |
54 | ## Community
55 |
56 | Let's start one together! After you ★Star this project, follow [@Rygu](https://twitter.com/rygu)
57 | on Twitter.
58 |
59 | ## License
60 |
61 | BSD 3-Clause license. Copyright © 2015, Rick Wong. All rights reserved.
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-isomorphic-starterkit",
3 | "description": "Isomorphic starterkit with server-side React rendering.",
4 | "version": "5.4.0",
5 | "license": "BSD-3-Clause",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/RickWong/react-isomorphic-starterkit.git"
9 | },
10 | "homepage": "https://github.com/RickWong/react-isomorphic-starterkit",
11 | "keywords": [
12 | "react",
13 | "isomorphic",
14 | "universal",
15 | "starter",
16 | "boilerplate",
17 | "template",
18 | "webpack",
19 | "koa",
20 | "transmit"
21 | ],
22 | "main": "dist/server.js",
23 | "scripts": {
24 | "start": "forever --minUptime 1000 --spinSleepTime 1000 -c \"node --harmony\" ./dist/server.js",
25 | "build-server": "webpack --colors --display-error-details --config configs/webpack.server.js",
26 | "build-client": "webpack --colors --display-error-details --config configs/webpack.client.js",
27 | "build": "concurrently \"npm run build-server\" \"npm run build-client\"",
28 | "watch-server": "webpack --watch --verbose --colors --display-error-details --config configs/webpack.server-watch.js",
29 | "watch-server-start": "node node_modules/just-wait --pattern \"dist/*.js\" && npm run start",
30 | "watch-client": "webpack-dev-server --config configs/webpack.client-watch.js",
31 | "watch": "concurrently --kill-others \"npm run watch-server-start\" \"npm run watch-server\" \"npm run watch-client\""
32 | },
33 | "dependencies": {
34 | "babel-polyfill": "6.6.1",
35 | "cross-fetch": "1.1.0",
36 | "css-loader": "0.23.1",
37 | "fetch-plus": "3.8.1",
38 | "fetch-plus-bearerauth": "3.5.0",
39 | "fetch-plus-json": "3.6.0",
40 | "file-loader": "0.8.5",
41 | "isomorphic-style-loader": "0.0.10",
42 | "koa": "1.2.0",
43 | "koa-proxy": "0.5.0",
44 | "koa-static": "2.0.0",
45 | "node-sass": "3.4.2",
46 | "react": "0.14.7",
47 | "react-dom": "0.14.7",
48 | "react-inline-css": "2.1.0",
49 | "react-router": "2.0.0",
50 | "react-transmit": "3.1.7",
51 | "sass-loader": "3.1.2",
52 | "style-loader": "0.13.0"
53 | },
54 | "devDependencies": {
55 | "babel": "6.5.2",
56 | "babel-core": "6.6.5",
57 | "babel-loader": "6.2.4",
58 | "babel-preset-es2015": "6.6.0",
59 | "babel-preset-react": "6.5.0",
60 | "babel-preset-react-hmre": "1.1.1",
61 | "babel-preset-stage-0": "6.5.0",
62 | "concurrently": "2.0.0",
63 | "forever": "0.15.1",
64 | "json-loader": "0.5.4",
65 | "just-wait": "1.0.5",
66 | "webpack": "1.12.14",
67 | "webpack-dev-server": "1.14.1",
68 | "webpack-node-externals": "1.0.0"
69 | },
70 | "engines": {
71 | "node": ">=0.10.32"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import babelPolyfill from "babel-polyfill";
2 | import koa from "koa";
3 | import koaProxy from "koa-proxy";
4 | import koaStatic from "koa-static";
5 | import React from "react";
6 | import ReactDOM from "react-dom/server";
7 | import * as ReactRouter from "react-router";
8 | import Transmit from "react-transmit";
9 |
10 | import githubApi from "apis/github";
11 | import routesContainer from "containers/routes";
12 | import favicon from "favicon.ico";
13 |
14 | try {
15 | const app = koa();
16 | const hostname = process.env.HOSTNAME || "localhost";
17 | const port = process.env.PORT || 8000;
18 | let routes = routesContainer;
19 |
20 | app.use(koaStatic("static"));
21 |
22 | app.use(koaProxy({
23 | host: githubApi.url,
24 | match: /^\/api\/github\//i,
25 | map: (path) => path.replace(/^\/api\/github\//i, "/")
26 | }));
27 |
28 | app.use(function *(next) {
29 | yield ((callback) => {
30 | const webserver = __PRODUCTION__ ? "" : `//${this.hostname}:8080`;
31 | const location = this.path;
32 |
33 | ReactRouter.match({routes, location}, (error, redirectLocation, renderProps) => {
34 | if (redirectLocation) {
35 | this.redirect(redirectLocation.pathname + redirectLocation.search, "/");
36 | return;
37 | }
38 |
39 | if (error || !renderProps) {
40 | callback(error);
41 | return;
42 | }
43 |
44 | const styles = {};
45 |
46 | const StyleProvider = React.createClass({
47 | childContextTypes:{
48 | styles: React.PropTypes.array,
49 | insertCss: React.PropTypes.func
50 | },
51 |
52 | getChildContext () {
53 | return {
54 | styles,
55 | insertCss (style) { styles[style] = style._getCss(); }
56 | };
57 | },
58 |
59 | render () {
60 | return
105 | In short: an excellent choice. 106 | Ready to start{'?'} 107 |
108 |
113 |