├── .editorconfig ├── .gitignore ├── LICENSE.md ├── README.md ├── configs ├── webpack.client-watch.js ├── webpack.client.js ├── webpack.server-watch.js └── webpack.server.js ├── dist └── .gitignore ├── package.json ├── src ├── apis │ └── github.js ├── client.js ├── components │ ├── Avatar.js │ └── Avatar.scss ├── containers │ ├── Main.js │ └── routes.js └── server.js └── static ├── dist └── .gitignore └── favicon.ico /.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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | node_modules/ 3 | *.log 4 | *.map 5 | -------------------------------------------------------------------------------- /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 | ![screenshot](https://i.imgur.com/JgBEIMm.png?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 | ![version](https://img.shields.io/npm/v/react-isomorphic-starterkit.svg) ![license](https://img.shields.io/npm/l/react-isomorphic-starterkit.svg) [![Package Quality](http://npm.packagequality.com/shield/react-isomorphic-starterkit.svg)](http://packagequality.com/#?package=react-isomorphic-starterkit) ![installs](https://img.shields.io/npm/dt/react-isomorphic-starterkit.svg) ![downloads](https://img.shields.io/github/downloads/RickWong/react-isomorphic-starterkit/latest/total.svg) 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 | -------------------------------------------------------------------------------- /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 | {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.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 | -------------------------------------------------------------------------------- /dist/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* -------------------------------------------------------------------------------- /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/apis/github.js: -------------------------------------------------------------------------------- 1 | import fetch from "cross-fetch"; 2 | import fetchPlus from "fetch-plus"; 3 | import plusJson from "fetch-plus-json"; 4 | import plusBearerauth from "fetch-plus-bearerauth"; 5 | 6 | const githubServerUrl = () => { 7 | if (__SERVER__) { 8 | return "https://api.github.com"; 9 | } 10 | 11 | if (__CLIENT__) { 12 | const {protocol, hostname, port} = window.location; 13 | 14 | return `${protocol}//${hostname}:${port}/api/github`; 15 | } 16 | }; 17 | 18 | const endpoint = fetchPlus.connectEndpoint(githubServerUrl()); 19 | 20 | endpoint.addMiddleware(plusJson()); 21 | 22 | module.exports = endpoint; 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/components/Avatar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import withStyles from "isomorphic-style-loader/lib/withStyles"; 3 | import styles from "isomorphic-style!css?modules!sass!./Avatar.scss"; 4 | 5 | const avatarUrl = (id = 0, avatarSize = 32) => `https://avatars.githubusercontent.com/u/${id}?v=3&s=${avatarSize}`; 6 | 7 | export default withStyles(({user = {}}) => { 8 | const href = "https://github.com/" + (user.login || "RickWong/react-isomorphic-starterkit"); 9 | const title = user.login || "you here? star us!"; 10 | const alt = user.login || "you?"; 11 | 12 | return ( 13 | 14 | {alt} 15 | 16 | ); 17 | }, styles); 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/containers/Main.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import InlineCss from "react-inline-css"; 3 | import Transmit from "react-transmit"; 4 | 5 | import githubApi from "apis/github"; 6 | import Avatar from "components/Avatar"; 7 | import favicon from "favicon.ico"; 8 | 9 | const fetchStargazers = (page, per_page = 100) => { 10 | return githubApi.browse( 11 | ["repos", "RickWong/react-isomorphic-starterkit", "stargazers"], 12 | { query: { page, per_page } } 13 | ).then(json => { 14 | return (json || []).map(({id, login}) => ({id, login})); 15 | }).catch(error => { 16 | throw error; 17 | }); 18 | }; 19 | 20 | /** 21 | * Main React application entry-point for both the server and client. 22 | */ 23 | class Main extends React.Component { 24 | /** 25 | * componentWillMount() runs on server and client. 26 | */ 27 | componentWillMount () { 28 | if (__SERVER__) { 29 | console.log("Hello server"); 30 | } 31 | 32 | if (__CLIENT__) { 33 | console.log("Hello client"); 34 | } 35 | } 36 | 37 | /** 38 | * componentDidUpdate() only runs on the client. 39 | */ 40 | componentDidUpdate (prevProps, prevState) { 41 | if (!this.props.additionalStargazers) { 42 | return; 43 | } 44 | 45 | this.loadMoreStargazersOnClient(); 46 | } 47 | 48 | /** 49 | * Load more stargazers. 50 | */ 51 | loadMoreStargazersOnClient () { 52 | const {additionalStargazers = [], transmit} = this.props; 53 | let {nextPage, pagesToFetch} = transmit.variables; 54 | 55 | if (--pagesToFetch <= 0) { 56 | return; 57 | } 58 | 59 | ++nextPage; 60 | 61 | transmit.forceFetch({ 62 | nextPage, 63 | pagesToFetch, 64 | additionalStargazers 65 | }, "additionalStargazers"); 66 | } 67 | 68 | /** 69 | * Runs on server and client. 70 | */ 71 | render () { 72 | const repositoryUrl = "https://github.com/RickWong/react-isomorphic-starterkit"; 73 | const avatarSize = 32; 74 | const avatarUrl = (id) => `https://avatars.githubusercontent.com/u/${id}?v=3&s=${avatarSize}`; 75 | 76 | /** 77 | * This is a Transmit fragment. 78 | */ 79 | let {stargazers, additionalStargazers} = this.props; 80 | 81 | if (additionalStargazers) { 82 | stargazers = stargazers.concat(additionalStargazers); 83 | } 84 | 85 | return ( 86 | 87 | 88 | Fork me on GitHub 89 | 90 |

91 | icon 92 |
React Isomorphic Starterkit. Let's get you started! 93 |

94 |

All-You-Need Features

95 | 104 |

105 | In short: an excellent choice. 106 | Ready to start{'?'} 107 |

108 |

109 | Open Community 110 | 111 |

112 |

113 | 114 | {stargazers && stargazers.map(user => 115 | 116 | )} 117 | 118 |

119 |
120 | ); 121 | } 122 | /** 123 | * component allows you to write a CSS stylesheet for your component. Target 124 | * your component with `&` and its children with `& selectors`. Be specific. 125 | */ 126 | static css (avatarSize) { 127 | return (` 128 | & .github { 129 | position: absolute; 130 | top: 0; 131 | right: 0; 132 | border: 0; 133 | } 134 | & { 135 | font-family: sans-serif; 136 | color: #0df; 137 | padding: 10px 30px 30px; 138 | width: 443px; 139 | margin: 10px auto; 140 | background: #222; 141 | } 142 | `); 143 | } 144 | } 145 | 146 | /** 147 | * Use Transmit to query and return GitHub stargazers as a Promise. 148 | */ 149 | export default Transmit.createContainer(Main, { 150 | initialVariables: { 151 | nextPage: 2, 152 | pagesToFetch: 15, 153 | additionalStargazers: [] 154 | }, 155 | fragments: { 156 | /** 157 | * Load first stargazers. 158 | */ 159 | stargazers: () => fetchStargazers(1), 160 | /** 161 | * Load more stargazers deferred. 162 | */ 163 | additionalStargazers: ({nextPage, additionalStargazers}) => { 164 | return () => fetchStargazers(nextPage).then(newStargazers => { 165 | newStargazers = newStargazers.map(({id, login}) => { 166 | return { id, login }; 167 | }); 168 | 169 | return additionalStargazers.concat(newStargazers); 170 | }); 171 | } 172 | } 173 | }); 174 | -------------------------------------------------------------------------------- /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 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /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 ; 61 | } 62 | }); 63 | 64 | Transmit.renderToString(StyleProvider, renderProps).then(({reactString, reactData}) => { 65 | let cssModules = ""; 66 | 67 | Object.keys(styles).forEach((style) => { cssModules += styles[style]; }); 68 | 69 | let template = ( 70 | ` 71 | 72 | 73 | 74 | react-isomorphic-starterkit 75 | 76 | 77 | 78 | 79 |
${reactString}
80 | 81 | ` 82 | ); 83 | 84 | this.type = "text/html"; 85 | this.body = Transmit.injectIntoMarkup(template, reactData, [`${webserver}/dist/client.js`]); 86 | 87 | callback(null); 88 | }).catch(e => { 89 | callback(e); 90 | }); 91 | }); 92 | }); 93 | }); 94 | 95 | app.listen(port, () => { 96 | console.info("==> ✅ Server is listening"); 97 | console.info("==> 🌎 Go to http://%s:%s", hostname, port); 98 | }); 99 | 100 | if (__DEV__) { 101 | if (module.hot) { 102 | console.log("[HMR] Waiting for server-side updates"); 103 | 104 | module.hot.accept("containers/routes", () => { 105 | routes = require("containers/routes"); 106 | }); 107 | 108 | module.hot.addStatusHandler((status) => { 109 | if (status === "abort") { 110 | setTimeout(() => process.exit(0), 0); 111 | } 112 | }); 113 | } 114 | } 115 | } 116 | catch (error) { 117 | console.error(error.stack || error); 118 | 119 | throw error; 120 | } 121 | -------------------------------------------------------------------------------- /static/dist/.gitignore: -------------------------------------------------------------------------------- 1 | [^.]* -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RickWong/react-isomorphic-starterkit/1e0ed39145aa8b86699fe36a7dedbd0788fc0c93/static/favicon.ico --------------------------------------------------------------------------------