├── app ├── lib │ ├── modules │ │ ├── feed │ │ │ ├── constants.js │ │ │ ├── actions.js │ │ │ ├── reducers.js │ │ │ └── index.js │ │ └── moduleCollector.js │ ├── utils │ │ └── devtools.js │ ├── index.js │ └── store │ │ └── storeBuilder.js ├── styles │ ├── general │ │ └── styles.scss │ └── components │ │ └── feed.scss ├── scripts │ ├── routes │ │ ├── index.js │ │ └── feed │ │ │ ├── components │ │ │ ├── Item.jsx │ │ │ └── Feed.jsx │ │ │ └── index.js │ ├── components │ │ └── Main.jsx │ ├── devtools.jsx │ └── app.js └── index.html ├── .gitignore ├── server ├── engines │ ├── index.js │ ├── renderIndex.js │ └── renderEngine.js ├── middleware │ ├── index.js │ ├── render.js │ └── webpack.js ├── utils │ └── environment.js ├── index.js └── server.js ├── .babelrc ├── README.md ├── ROADMAP.md ├── LICENSE ├── .eslintrc ├── package.json └── webpack └── webpack.dev.js /app/lib/modules/feed/constants.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/styles/general/styles.scss: -------------------------------------------------------------------------------- 1 | body { 2 | } 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .tmp/ 3 | dist/ 4 | .idea/ 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /app/styles/components/feed.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: green; 3 | } 4 | -------------------------------------------------------------------------------- /server/engines/index.js: -------------------------------------------------------------------------------- 1 | import renderEngine from './renderEngine'; 2 | import renderIndex from './renderIndex'; 3 | 4 | export { 5 | renderEngine, 6 | renderIndex 7 | } -------------------------------------------------------------------------------- /app/lib/modules/feed/actions.js: -------------------------------------------------------------------------------- 1 | export default function (config) { 2 | return { 3 | loadFeeds() { 4 | return {type: 'get', payload: ['feed 1', 'feed 2', 'feed 3']} 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /app/lib/modules/feed/reducers.js: -------------------------------------------------------------------------------- 1 | function feeds(state = [], action) { 2 | if (action.type === 'get') return action.payload; 3 | else return state; 4 | } 5 | 6 | export default { 7 | feeds 8 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "react", 4 | "es2015", 5 | "stage-0", 6 | "stage-1" 7 | ], 8 | "ignore": [ 9 | "*.scss", 10 | "*.json" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /server/middleware/index.js: -------------------------------------------------------------------------------- 1 | import webpack from './webpack'; 2 | import render from './render'; 3 | 4 | export default { 5 | render, 6 | webpack 7 | } 8 | 9 | export { 10 | render, 11 | webpack 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-isormorphic-kit 2 | An isormorphic kit for building ReactJS apps 3 | 4 | Links to parts of the medium article: 5 | [Part 1 - Basic setup](https://github.com/icapps/react-isormorphic-kit/tree/1_basic_setup) 6 | -------------------------------------------------------------------------------- /app/lib/modules/feed/index.js: -------------------------------------------------------------------------------- 1 | import actions from './actions'; 2 | import reducers from './reducers'; 3 | 4 | export default (config) => { 5 | return { 6 | actions: actions(config), 7 | reducers, 8 | name: 'feed' 9 | }; 10 | }; 11 | 12 | -------------------------------------------------------------------------------- /app/scripts/routes/index.js: -------------------------------------------------------------------------------- 1 | // Components 2 | import App from '../components/Main'; 3 | import feed from './feed'; 4 | 5 | export default { 6 | path: '/', 7 | component: App, 8 | childRoutes: [ 9 | feed.index, 10 | feed.item 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /app/scripts/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {Link} from 'react-router'; 3 | 4 | class Main extends Component { 5 | render() { 6 | return ( 7 |
8 | Feed 9 | {this.props.children || 'Home'} 10 |
11 | 12 | ) 13 | } 14 | } 15 | 16 | export default Main; 17 | -------------------------------------------------------------------------------- /app/scripts/devtools.jsx: -------------------------------------------------------------------------------- 1 | // General imports 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import SliderMonitor from 'redux-slider-monitor'; 5 | import DevTools from '../lib/utils/devtools'; 6 | 7 | if (process.env.feature.DEV) { 8 | const store = window.__STORE__; 9 | // ReactDOM.render(, document.getElementById('devtools')); 10 | } 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/scripts/routes/feed/components/Item.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mobinni on 08/12/15. 3 | */ 4 | import React, {Component} from 'react'; 5 | class Item extends Component { 6 | constructor(props, context) { 7 | super(props, context); 8 | } 9 | 10 | render() { 11 | const {params} = this.props; 12 | return ( 13 |
Item: {params.item}
14 | ) 15 | } 16 | } 17 | 18 | export default Item; -------------------------------------------------------------------------------- /server/utils/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Created by mobinni on 07/12/15. 4 | */ 5 | const env = process.env.NODE_ENV || 'DEVELOPMENT'; 6 | // set env variable 7 | const hasSSREnabled = (process.env.SSR || process.argv[2] === 'ssr') || false; 8 | 9 | export default { 10 | name: env, 11 | isProduction: env === 'PRODUCTION', 12 | isDevelopment: env === 'DEVELOPMENT', 13 | ssrEnabled: hasSSREnabled 14 | }; 15 | -------------------------------------------------------------------------------- /app/scripts/routes/feed/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mobinni on 08/12/15. 3 | */ 4 | import Feed from './components/Feed'; 5 | import Item from './components/Item'; 6 | 7 | export default { 8 | index: { 9 | path: 'feed', 10 | getComponent(location, cb) { 11 | cb(null, Feed); 12 | } 13 | }, 14 | item: { 15 | path: 'feed/:item', 16 | getComponent(location, cb) { 17 | cb(null, Item); 18 | } 19 | } 20 | }; -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /** 3 | * Created by mobinni on 07/12/15. 4 | */ 5 | 6 | require.extensions['.scss'] = function() { 7 | return; 8 | }; 9 | 10 | // compile ES6 to 5 11 | require('babel-register')(); 12 | 13 | /** 14 | * Environment configuration 15 | */ 16 | delete process.env.BROWSER; 17 | 18 | if (!process.env.hasOwnProperty('feature')) { 19 | process.env.feature = {}; 20 | } 21 | 22 | const server = require('./server.js'); 23 | 24 | server.boot(); 25 | -------------------------------------------------------------------------------- /server/engines/renderIndex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mobinni on 12/01/16. 3 | */ 4 | import ejs from 'ejs'; 5 | import webpackMw from '../middleware/webpack'; 6 | import {initialise} from '../../app/lib'; 7 | 8 | const {store, modules, middlewares} = initialise([]); 9 | 10 | export default function (callback) { 11 | webpackMw.query('index.html', function (err, body) { 12 | if (err) console.log(err); 13 | let output = ejs.render(body, { 14 | reactOutput: '', 15 | store: '{}' 16 | }); 17 | callback(output); 18 | }); 19 | } -------------------------------------------------------------------------------- /app/lib/utils/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 | import SliderMonitor from 'redux-slider-monitor'; 6 | 7 | 8 | const DevTools = createDevTools( 9 | 12 | 13 | 14 | ); 15 | 16 | export default DevTools; -------------------------------------------------------------------------------- /app/lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mobinni on 12/01/16. 3 | */ 4 | import ModuleCollector from './modules/moduleCollector'; 5 | import storeBuilder from './store/storeBuilder'; 6 | import feed from './modules/feed'; 7 | 8 | export function initialise(customMiddlewares = []) { 9 | const moduleCollector = new ModuleCollector(); 10 | moduleCollector.add(feed); 11 | 12 | const coreReducers = moduleCollector.getReducers(); 13 | 14 | const {store, middlewares} = storeBuilder( 15 | coreReducers, 16 | customMiddlewares 17 | ); 18 | 19 | const modules = moduleCollector.get(); 20 | 21 | return {store, modules, middlewares}; 22 | } 23 | 24 | export default { 25 | initialise 26 | } -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 11 | 12 | 13 | 14 |
<%- reactOutput %>
15 |
16 | 18 | 20 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/lib/store/storeBuilder.js: -------------------------------------------------------------------------------- 1 | import { combineReducers, createStore, compose, applyMiddleware } from 'redux'; 2 | import DevTools from '../utils/devtools'; 3 | 4 | function _buildStore(middlewares, reducers) { 5 | let functions = [applyMiddleware(...middlewares)]; 6 | if (process.env.feature.DEV) { 7 | functions.push(require('../utils/devtools').default.instrument()); 8 | } 9 | return compose( 10 | ...functions 11 | )(createStore)(combineReducers(reducers)); 12 | } 13 | 14 | export default function storeBuilder(reducers, customMiddlewares = []) { 15 | const middlewares = []; 16 | const store = _buildStore([...middlewares, ...customMiddlewares], reducers) 17 | 18 | return { 19 | store, 20 | middlewares 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /server/engines/renderEngine.js: -------------------------------------------------------------------------------- 1 | import {RoutingContext} from 'react-router'; 2 | import ejs from 'ejs'; 3 | import React from 'react'; 4 | import ReactDOMServer from 'react-dom/server'; 5 | import {Provider} from 'react-redux'; 6 | 7 | const _renderComponents = (props, store) => { 8 | return ReactDOMServer.renderToString( 9 | 10 | 11 | 12 | ); 13 | }; 14 | 15 | export default (renderProps, indexHtml, store) => { 16 | return new Promise((resolve, reject) => { 17 | let output = _renderComponents(renderProps, store); 18 | resolve( 19 | ejs.render(indexHtml, { 20 | reactOutput: output, 21 | store: JSON.stringify(store) 22 | }) 23 | ); 24 | }); 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /app/scripts/app.js: -------------------------------------------------------------------------------- 1 | // Import default Styles 2 | import '../styles/general/styles.scss'; 3 | 4 | // Import Modules 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import createBrowserHistory from 'history/lib/createBrowserHistory' 8 | import Router from 'react-router'; 9 | import routes from './routes'; 10 | import {initialise} from '../lib'; 11 | import createLogger from 'redux-logger'; 12 | import {Provider} from 'react-redux'; 13 | 14 | const history = createBrowserHistory(); 15 | 16 | 17 | const {store, modules, middlewares} = initialise([createLogger()]); 18 | 19 | if (process.env.feature.DEV) { 20 | window.__STORE__ = store; 21 | } 22 | 23 | ReactDOM.render( 24 | 25 | 27 | , 28 | document.getElementById('root') 29 | ); -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # 1.0.0 2 | - Dynamic webpack configuration for different build environments 3 | - Configuration files for environments, with environment specific assets, etc. 4 | - Integrate react-transform 5 | - Integrate custom provider to push modules and client-configuration to client 6 | - Integrate Redux promise middleware to allow server-side rendering to have data on render. 7 | - Integrate Redux actionHistory middleware, to build store based on action history 8 | - Implement force-check and force-execute properties (force check allows you to only do server-side rendering once, after which the client takes over, force execute forces the client to always refresh the action to get new data) 9 | - Integrate mocha testing suite 10 | - Implement data service for async calls 11 | - Split out core a separate module so it can be a basic redux starting point 12 | - Integrate CI compatibility? 13 | - Create build script: imageOptim, webpack build config, etc. 14 | - Localization integration 15 | -------------------------------------------------------------------------------- /app/scripts/routes/feed/components/Feed.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mobinni on 08/12/15. 3 | */ 4 | import React, {Component} from 'react'; 5 | import {Link} from 'react-router'; 6 | import feed from '../../../../lib/modules/feed'; 7 | import {connect} from 'react-redux'; 8 | 9 | import '../../../../styles/components/feed.scss'; 10 | 11 | class Feed extends Component { 12 | constructor(props, context) { 13 | super(props, context); 14 | } 15 | 16 | componentWillMount() { 17 | const dispatch = this.props.dispatch; 18 | dispatch(feed().actions.loadFeeds()); 19 | } 20 | 21 | render() { 22 | const {feeds} = this.props || []; 23 | console.log(feeds); 24 | return ( 25 |
26 | 33 |
34 | ) 35 | } 36 | } 37 | 38 | function mapStateToProps(state) { 39 | return {feeds: state.feeds} 40 | } 41 | 42 | 43 | export default connect(mapStateToProps)(Feed); 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 iCapps 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /app/lib/modules/moduleCollector.js: -------------------------------------------------------------------------------- 1 | class ModuleCollector { 2 | constructor(config) { 3 | this.actions = []; 4 | this.reducers = []; 5 | this.modules = {}; 6 | this.config = config; 7 | } 8 | 9 | add(newModule) { 10 | if (typeof newModule === 'function') { 11 | newModule = newModule(this.config); 12 | } 13 | 14 | if (!newModule) { 15 | throw new Error('Check if your module is an object or is returning an object'); 16 | } 17 | 18 | if (newModule.name) { 19 | if (this.modules.hasOwnProperty(newModule.name)) { 20 | throw new Error(`Module with name '${newModule.name}' was already registered.`); 21 | } 22 | 23 | this.modules[newModule.name] = newModule; 24 | } 25 | 26 | if (newModule.reducers) { 27 | this.reducers.push(newModule.reducers); 28 | } 29 | 30 | if (newModule.actions) { 31 | this.actions.push(newModule.actions); 32 | } 33 | } 34 | 35 | get(moduleName) { 36 | if (typeof moduleName === 'string') { 37 | return this.modules[moduleName]; 38 | } 39 | 40 | return this.modules; 41 | } 42 | 43 | getReducers() { 44 | return Object.assign({}, ...this.reducers); 45 | } 46 | } 47 | 48 | export default ModuleCollector; 49 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mobinni on 07/12/15. 3 | */ 4 | 5 | // Imports 6 | import env from './utils/environment'; 7 | import express from 'express'; 8 | import {webpack as webPackCustomMiddleware, render} from './middleware'; 9 | import compression from 'compression'; 10 | 11 | 12 | const app = express(); 13 | const {isProduction, ssrEnabled, isDevelopment} = env; 14 | 15 | export function boot() { 16 | // Configuration 17 | const port = isProduction ? process.env.PORT : 9000; 18 | 19 | 20 | // Environment setup 21 | if (isDevelopment) { 22 | app.use(function (req, res, next) { 23 | if (req.url !== '/') { 24 | // if you're not the root url, pass throught the webpack middleware 25 | webPackCustomMiddleware.WebPackMiddleware(req, res, next); 26 | } else { 27 | // Will pass through a middleware to server side render index.html 28 | next(); 29 | } 30 | }); 31 | 32 | app.use(webPackCustomMiddleware.HotReloadMiddleware); 33 | } 34 | 35 | 36 | // Other middlewares 37 | app.use(compression()); 38 | if (ssrEnabled) { 39 | app.use(render.route); 40 | } else { 41 | app.use(render.index); 42 | } 43 | 44 | app.listen(port, () => console.log('Server running on port ' + port)); 45 | }; -------------------------------------------------------------------------------- /server/middleware/render.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by mobinni on 08/12/15. 3 | */ 4 | import webpackMw from './webpack'; 5 | import { match } from 'react-router'; 6 | import createLocation from 'history/lib/createLocation'; 7 | import env from '../utils/environment'; 8 | import {renderEngine, renderIndex as renderStatic} from '../engines'; 9 | import routes from '../../app/scripts/routes'; 10 | import {initialise} from '../../app/lib'; 11 | 12 | const {store, modules, middlewares} = initialise([]); 13 | 14 | const query = (file, callback) => { 15 | if (!env.isProduction) { 16 | webpackMw.query(file, function (err, body) { 17 | callback(err, body); 18 | }); 19 | } else { 20 | // production read file from... 21 | callback(null, null); 22 | } 23 | }; 24 | 25 | // Router middleware 26 | function route(req, res, next) { 27 | query('index.html', function (err, body) { 28 | let location = createLocation(req.url); 29 | match({routes, location}, (error, redirectLocation, renderProps) => { 30 | if (error) { 31 | res.status(500).send(error.message); 32 | } else if (redirectLocation) { 33 | res.status(302).redirect(redirectLocation.pathname + redirectLocation.search); 34 | } else if (renderProps) { 35 | renderEngine( 36 | renderProps, 37 | body, 38 | store 39 | ).then(function (html) { 40 | res.status(200).send(html); 41 | }, function (error2) { 42 | console.error('failed to load', error2); 43 | res.status(500).send(JSON.stringify(error2)); 44 | }); 45 | } else { 46 | res.status(404).send(); 47 | } 48 | }); 49 | }); 50 | }; 51 | 52 | function index(req, res, next) { 53 | renderStatic(function (data) { 54 | res.status(200).send(data); 55 | }) 56 | } 57 | 58 | export default { 59 | route, 60 | index 61 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true 6 | }, 7 | "plugins": [ 8 | "react" 9 | ], 10 | "ecmaFeatures": { 11 | "arrowFunctions": true, 12 | "binaryLiterals": true, 13 | "blockBindings": true, 14 | "classes": true, 15 | "defaultParams": true, 16 | "destructuring": true, 17 | "forOf": true, 18 | "generators": true, 19 | "modules": true, 20 | "objectLiteralComputedProperties": true, 21 | "objectLiteralDuplicateProperties": true, 22 | "objectLiteralShorthandMethods": true, 23 | "objectLiteralShorthandProperties": true, 24 | "octalLiterals": true, 25 | "regexUFlag": true, 26 | "regexYFlag": true, 27 | "spread": true, 28 | "superInFunctions": true, 29 | "templateStrings": true, 30 | "unicodeCodePointEscapes": true, 31 | "globalReturn": true, 32 | "jsx": true 33 | }, 34 | "rules": { 35 | // 36 | // React specific linting rules for ESLint 37 | // 38 | "react/display-name": 0, 39 | // Prevent missing displayName in a React component definition 40 | "react/jsx-quotes": [ 41 | 2, 42 | "double", 43 | "avoid-escape" 44 | ], 45 | // Enforce quote style for JSX attributes 46 | "react/jsx-no-undef": 2, 47 | // Disallow undeclared variables in JSX 48 | "react/jsx-sort-props": 0, 49 | // Enforce props alphabetical sorting 50 | "react/jsx-uses-react": 2, 51 | // Prevent React to be incorrectly marked as unused 52 | "react/jsx-uses-vars": 2, 53 | // Prevent variables used in JSX to be incorrectly marked as unused 54 | "react/no-did-mount-set-state": 2, 55 | // Prevent usage of setState in componentDidMount 56 | "react/no-did-update-set-state": 2, 57 | // Prevent usage of setState in componentDidUpdate 58 | "react/no-multi-comp": 0, 59 | // Prevent multiple component definition per file 60 | "react/no-unknown-property": 2, 61 | // Prevent usage of unknown DOM property 62 | //"react/prop-types": 2, 63 | // Prevent missing props validation in a React component definition 64 | "react/react-in-jsx-scope": 2, 65 | // Prevent missing React when using JSX 66 | "react/self-closing-comp": 2, 67 | // Prevent extra closing tags for components without children 68 | "react/wrap-multilines": 2, 69 | // Prevent missing parentheses around multilines JSX 70 | } 71 | } -------------------------------------------------------------------------------- /server/middleware/webpack.js: -------------------------------------------------------------------------------- 1 | import WebPack from 'webpack'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import env from '../utils/environment'; 5 | import {StringDecoder} from 'string_decoder'; 6 | import webpackDevMiddleware from 'webpack-dev-middleware'; 7 | import HotReload from 'webpack-hot-middleware'; 8 | import chokidar from 'chokidar'; 9 | 10 | let webpackConfig = require( 11 | `${__dirname}/../../webpack/webpack.${env.isProduction ? 'prod' : 'dev'}.js` 12 | ); 13 | 14 | let bundleStart = null; 15 | let compiler = WebPack(webpackConfig); 16 | 17 | let requests = []; 18 | let webpackFs; 19 | 20 | let decoder = new StringDecoder('utf8'); 21 | 22 | // Webpack starts bundling 23 | compiler.plugin('compile', function () { 24 | bundleStart = Date.now(); 25 | }); 26 | 27 | var watcher = chokidar.watch(__dirname + '/../server'); 28 | 29 | watcher.on('ready', function () { 30 | watcher.on('all', function () { 31 | console.log("Clearing /server/ module cache from server"); 32 | Object.keys(require.cache).forEach(function (id) { 33 | if (/[\/\\]server[\/\\]/.test(id)) delete require.cache[id]; 34 | }); 35 | }); 36 | }); 37 | 38 | // Webpack is done compiling 39 | compiler.plugin('done', function () { 40 | console.log('Bundled in ' + (Date.now() - bundleStart) + 'ms!'); 41 | 42 | webpackFs = compiler.outputFileSystem; 43 | processRequests(); 44 | 45 | Object.keys(require.cache).forEach(function (id) { 46 | if (/[\/\\]client[\/\\]/.test(id)) delete require.cache[id]; 47 | }); 48 | }); 49 | 50 | const WebPackMiddleware = webpackDevMiddleware(compiler, { 51 | watchOptions: { 52 | aggregateTimeout: 300, 53 | poll: true 54 | }, 55 | hot: true, 56 | colors: true, 57 | headers: {'X-Webpack-Rendered': 'yes'} 58 | }); 59 | 60 | const query = function (path, cb) { 61 | requests.push({path, cb}); 62 | processRequests(); 63 | }; 64 | 65 | 66 | const HotReloadMiddleware = HotReload(compiler); 67 | 68 | function processRequests() { 69 | if (!webpackFs) { 70 | return; 71 | } 72 | 73 | let req = requests.pop(); 74 | 75 | if (!req) { 76 | return; 77 | } 78 | 79 | webpackFs.readFile(webpackConfig.output.path + '/' + req.path, function (err, data) { 80 | req.cb(err, decoder.write(data)); 81 | }); 82 | 83 | processRequests(); 84 | } 85 | 86 | export { 87 | WebPackMiddleware, 88 | query, 89 | HotReloadMiddleware 90 | } 91 | 92 | export default { 93 | WebPackMiddleware, 94 | query, 95 | HotReloadMiddleware 96 | } 97 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux_isomorphic_kit", 3 | "version": "0.0.1", 4 | "description": "A modern isomorphic setup containing: React, Redux, NodeJS, WebPack, Redux DevTools, Serverside rendering, React Router, etc.", 5 | "dependencies": { 6 | "babel": "^6.5.2", 7 | "babel-cli": "^6.5.1", 8 | "babel-core": "^6.5.2", 9 | "babel-fast-presets": "0.0.2", 10 | "babel-loader": "^6.2.3", 11 | "babel-preset-es2015": "^6.5.0", 12 | "babel-preset-react": "^6.5.0", 13 | "babel-preset-stage-0": "^6.5.0", 14 | "babel-preset-stage-1": "^6.5.0", 15 | "babel-preset-stage-2": "^6.5.0", 16 | "babel-preset-stage-3": "^6.5.0", 17 | "babel-register": "^6.5.2", 18 | "chai": "^3.5.0", 19 | "compression": "^1.6.1", 20 | "ejs": "^2.4.1", 21 | "express": "^4.13.4", 22 | "history": "^1.13.1", 23 | "lodash": "^3.10.1", 24 | "lodash.deburr": "^3.2.0", 25 | "lodash.repeat": "^3.2.0", 26 | "lodash.words": "^3.2.0", 27 | "nodemon": "^1.9.0", 28 | "postcss-modules-extract-imports": "^1.0.0", 29 | "postcss-modules-local-by-default": "^1.0.1", 30 | "postcss-modules-scope": "^1.0.0", 31 | "react": "^0.14.7", 32 | "react-dom": "^0.14.7", 33 | "react-redux": "4.0.6", 34 | "react-router": "^1.0.2", 35 | "react-tools": "^0.13.3", 36 | "redux": "^3.3.1", 37 | "redux-devtools": "^3.1.1", 38 | "redux-devtools-dock-monitor": "^1.1.0", 39 | "redux-devtools-log-monitor": "^1.0.4", 40 | "redux-logger": "^2.6.0", 41 | "redux-thunk": "^1.0.0", 42 | "sass-lint": "^1.5.0", 43 | "sasslint-loader": "git+https://github.com/alleyinteractive/sasslint-loader.git", 44 | "webpack": "^1.12.13", 45 | "webpack-dev-middleware": "^1.5.1", 46 | "webpack-hot-middleware": "^2.7.1" 47 | }, 48 | "devDependencies": { 49 | "babel-plugin-react-transform": "^2.0.0", 50 | "chai": "^3.4.1", 51 | "chai-immutable": "^1.5.3", 52 | "chokidar": "^1.4.2", 53 | "compression": "^1.6.0", 54 | "css-loader": "^0.19.0", 55 | "eslint": "^1.6.0", 56 | "eslint-loader": "^1.0.0", 57 | "eslint-plugin-react": "^3.5.1", 58 | "express": "^4.13.3", 59 | "extract-text-webpack-plugin": "^0.8.2", 60 | "file-loader": "^0.8.4", 61 | "html-webpack-plugin": "^1.6.1", 62 | "json-loader": "^0.5.2", 63 | "jsx-loader": "^0.13.2", 64 | "jsx-webpack-loader": "^0.1.2", 65 | "loader-utils": "^0.2.11", 66 | "nodemon": "^1.8.1", 67 | "piping": "^0.3.0", 68 | "react-tools": "^0.13.3", 69 | "react-transform": "0.0.3", 70 | "react-transform-catch-errors": "^1.0.1", 71 | "react-transform-hmr": "^1.0.1", 72 | "redbox-react": "1.2.0", 73 | "redux-devtools": "^3.0.1", 74 | "redux-devtools-dock-monitor": "^1.0.1", 75 | "redux-devtools-log-monitor": "^1.0.2", 76 | "redux-slider-monitor": "^1.0.2", 77 | "sass-loader": "^3.0.0", 78 | "style-loader": "^0.12.4", 79 | "webpack": "^1.12.2", 80 | "webpack-dev-hmr": "^1.0.0", 81 | "webpack-dev-middleware": "^1.2.0", 82 | "webpack-hot-middleware": "^2.4.1" 83 | }, 84 | "scripts": { 85 | "serve-ssr": "node ./server ssr", 86 | "serve": "babel-node ./server" 87 | }, 88 | "author": "Mo Binni", 89 | "license": "MIT" 90 | } 91 | -------------------------------------------------------------------------------- /webpack/webpack.dev.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | var ExtractTextPlugin = require("extract-text-webpack-plugin"); 4 | 5 | module.exports = { 6 | entry: { 7 | // entry point for your app 8 | app: ['webpack-hot-middleware/client', __dirname + '/../app/scripts/app.js'], 9 | // entry point for redux devtools 10 | tools: ['webpack-hot-middleware/client', __dirname + '/../app/scripts/devtools.jsx'], 11 | // vendors 12 | vendors: [ 13 | 'react', 14 | 'react-dom', 15 | 'react-router', 16 | 'react-redux', 17 | 'lodash', 18 | 'redux', 19 | 'history', 20 | 'redux-thunk' 21 | ] 22 | }, 23 | output: { 24 | path: __dirname + '/../dist', 25 | publicPath: '/', 26 | filename: "[name].js", 27 | sourceMapFilename: "[name].js.map" 28 | }, 29 | stats: { 30 | colors: true, 31 | reasons: true 32 | }, 33 | debug: true, // switch loaders to debug mode 34 | devtool: 'source-map', // important for debugging obfuscated from browser 35 | plugins: [ 36 | new ExtractTextPlugin('styles.css', { 37 | allChunks: true 38 | }), 39 | new webpack.DefinePlugin({ 40 | process: { 41 | env: { 42 | BROWSER: JSON.stringify(true), 43 | feature: { 44 | DEV: JSON.stringify(true) 45 | } 46 | } 47 | } 48 | }), 49 | new HtmlWebpackPlugin({ 50 | template: __dirname + '/../app/index.html', 51 | filename: 'index.html' 52 | }), 53 | new webpack.HotModuleReplacementPlugin() 54 | ], 55 | module: { 56 | 57 | preLoaders: [ 58 | { 59 | test: /\.json$/, 60 | exclude: /node_modules/, 61 | loader: 'json loader' 62 | } 63 | ], 64 | loaders: [ 65 | { 66 | test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$|\.wav$|\.mp3$|\.html$/, 67 | loader: "file" 68 | }, 69 | { 70 | test: /\.jsx$/, 71 | loader: 'babel', 72 | query: { 73 | env: { 74 | development: { 75 | plugins: [ 76 | ['react-transform', { 77 | transforms: [{ 78 | transform: 'react-transform-hmr', 79 | imports: ['react'], 80 | locals: ['module'] 81 | }, { 82 | transform: 'react-transform-catch-errors', 83 | imports: ['react', 'redbox-react'] 84 | }] 85 | }] 86 | ] 87 | } 88 | } 89 | }, 90 | exclude: [/node_modules/] 91 | }, 92 | { 93 | test: /\.js$/, 94 | loader: 'babel-loader', 95 | exclude: [/node_modules/] 96 | }, 97 | // generate css files for general styles 98 | { 99 | test: /\.scss$/, 100 | loader: ExtractTextPlugin.extract( 101 | "style-loader", 102 | "css-loader?minimize!sass-loader?outputStyle=compressed&linefeed=lfcr&indentedSyntax=false" 103 | ), 104 | include: [/app\/styles\/general/], 105 | exclude: [/node_modules/, /app\/core\/node_modules/] 106 | 107 | }, 108 | // generate inline styles for component files 109 | { 110 | test: /\.scss$/, 111 | loaders: [ 112 | "style-loader", 113 | "css-loader?minimize", 114 | "sass-loader?outputStyle=compressed&linefeed=lfcr&indentedSyntax=false" 115 | ], 116 | include: [/app\/styles\/component/], 117 | exclude: /node_modules/ 118 | } 119 | ] 120 | }, 121 | externals: {}, 122 | resolve: { 123 | extensions: ['', '.js', '.jsx', 'index.js', 'index.jsx', '.json', 'index.json'] 124 | }, 125 | node: {}, 126 | browser: {}, 127 | sasslint: { 128 | configFile: __dirname + '/../.sass-lint.yml' 129 | } 130 | }; 131 | 132 | --------------------------------------------------------------------------------