├── 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 |
27 | {
28 | feeds.map((feed, i) => {
29 | return ({feed})
30 | })
31 | }
32 |
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 |
--------------------------------------------------------------------------------