├── .babelrc
├── .editorconfig
├── .env
├── .eslintignore
├── .eslintrc
├── .gitignore
├── Dockerfile
├── README.md
├── package.json
├── public
└── favicon.ico
├── src
├── actions
│ └── user.js
├── client.js
├── components
│ ├── Home
│ │ ├── Home.js
│ │ ├── Home.scss
│ │ └── funyee.png
│ └── Html
│ │ └── Html.js
├── constants
│ └── user.js
├── containers
│ └── HomeContainer
│ │ ├── HomeContainer.js
│ │ └── HomeContainer.scss
├── reducers
│ ├── index.js
│ └── user.js
├── routes
│ ├── index.js
│ └── routes.js
├── server.js
└── store
│ └── create.js
└── tools
├── webpack-isomorphic-tools.js
├── webpack.dev.config.js
└── webpack.prod.config.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | presets: [ "es2015", "stage-3", "react" ]
3 | }
4 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root=true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 |
7 | [*.js]
8 | indent_style = tab
9 |
10 | [*.json]
11 | indent_style = space
12 | indent_size = 2
13 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | API=http://localhost:3000
2 | NODE_APP_PORT=3000
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | tools/webpack.dev.config.js
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "eslint-config-airbnb",
3 | "env": {
4 | "browser": true,
5 | "node": true,
6 | "mocha": true
7 | },
8 | "plugins": [
9 | "react", "import"
10 | ],
11 | "settings": {
12 | "import/default": /\*.js/
13 | },
14 | "rules": {
15 | "indent": [2, "tab"],
16 | "no-console": 0
17 | },
18 | "globals": {
19 | "console": true
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | public/dist
2 | npm-debug.log
3 | .DS_Store
4 | node_modules
5 | webpack-assets.json
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:latest
2 |
3 | RUN mkdir -p /usr/src/app
4 | WORKDIR /usr/src/app
5 |
6 | COPY package.json package.json
7 | RUN npm install
8 |
9 | COPY . .
10 |
11 | CMD [ "npm", "run", "start:prod" ]
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-universal-redux-starter-kit
2 | It's the starter kit to build up universal structure of react with redux.
3 | We use as less code as we can to build up universal, I hope you will like it!
4 |
5 | ## Before staring it, something you must to know
6 |
7 | 1. Of course, React, Redux, Isomorphic(Universal), ES6, eslint, npm, node.
8 | 2. [redux-thunk](https://github.com/gaearon/redux-thunk) => Help Redux do asynchronized actions.
9 | 3. [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) => Help react to do hot reload on client side.
10 | 4. [webpack-hot-middleware](https://github.com/glenjamin/webpack-hot-middleware) => Help you do hot reload in server-side.
11 | 5. [isomorphic-fetch](https://github.com/matthew-andrews/isomorphic-fetch) => Use fetch in both client and server side.
12 | 6. [webpack-isomorphic-tools](https://github.com/halt-hammerzeit/webpack-isomorphic-tools) => Help you deal with some files in isomorphic structure, make it avaible on both client and server side.
13 | 7. [dotenv](https://github.com/bkeepers/dotenv) => Help you easily write configs.
14 | 8. [extract-text-webpack-plugin](https://github.com/webpack/extract-text-webpack-plugin) Extract all your css in one file for server-side use.
15 | 9. Container Design Pattern of React Redux
16 |
17 |
18 | ## Clone repository
19 | Clone the repository
20 |
21 | ```bash
22 | https://github.com/DumDumGeniuss/react-universal-redux-starter-kit.git react-universal-redux-starter-kit
23 | ```
24 | Go into the directory
25 |
26 | ```bash
27 | cd react-universal-redux-starter-kit
28 | ```
29 |
30 | ## Make it yours
31 | Delete the .git directory
32 |
33 | ```bash
34 | rm -rf .git
35 | ```
36 | Re-initialize the repository
37 |
38 | ```bash
39 | git init
40 | ```
41 |
42 | ## INSTALL packages
43 |
44 | ```bash
45 | npm install
46 | ```
47 |
48 | ## DEVELOPMENT MODE
49 |
50 | ```bash
51 | npm run start:dev
52 | ```
53 |
54 | ## PRODUCTION MODE
55 |
56 | ```bash
57 | npm run start:prod
58 | ```
59 |
60 | ## ESLINT CHECK
61 |
62 | ```bash
63 | npm run eslint
64 | ```
65 |
66 | ## DOCKER (optional)
67 |
68 | Build up your react application image
69 |
70 | ```bash
71 | docker build -t react-universal-redux-starter-kit .
72 | ```
73 | Create container by the image, and run it, expose port
74 |
75 | ```bash
76 | docker run -p 3000:3000 -d react-universal-redux-starter-kit
77 | ```
78 |
79 | Then you can find your app on localhost:3000 (maybe few seconds)
80 |
81 | ###_If you use Mac, sometimes you need to find your docekr vm ip rather than localhost_
82 |
83 | ```bash
84 | docker-machine ip
85 | ```
86 |
87 | ## REFERENCE
88 |
89 | [erikras/react-redux-universal-hot-example](https://github.com/erikras/react-redux-universal-hot-example)
90 |
91 | [kriasoft/react-starter-kit
92 | ](https://github.com/kriasoft/react-starter-kit)
93 |
94 | ## LICENSE
95 | Free, welcome to use it.
96 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-universal-redux-starter-kit",
3 | "version": "1.0.0",
4 | "description": "It's the react-start-kit for you to quickly build application of React Isomorphic Structure.",
5 | "scripts": {
6 | "eslint": "better-npm-run eslint",
7 | "start:dev": "better-npm-run start:dev",
8 | "start:prod": "better-npm-run start:prod"
9 | },
10 | "betterScripts": {
11 | "eslint": {
12 | "command": "eslint src; exit 0"
13 | },
14 | "start:dev": {
15 | "command": "webpack --config tools/webpack.dev.config.js && babel-node src/server.js",
16 | "env": {
17 | "NODE_ENV": "development"
18 | }
19 | },
20 | "start:prod": {
21 | "command": "webpack --config tools/webpack.prod.config.js && babel-node src/server.js",
22 | "env": {
23 | "NODE_ENV": "production"
24 | }
25 | }
26 | },
27 | "author": "DumDumGeniuss",
28 | "license": "MIT",
29 | "dependencies": {
30 | "babel-loader": "^6.2.8",
31 | "babel-polyfill": "^6.16.0",
32 | "body-parser": "^1.15.2",
33 | "cookie-parser": "^1.4.3",
34 | "dotenv": "^4.0.0",
35 | "es6-promise": "^4.0.5",
36 | "express": "^4.14.0",
37 | "history": "^1.17.0",
38 | "isomorphic-fetch": "^2.2.1",
39 | "react": "^15.4.0",
40 | "react-dom": "^15.4.0",
41 | "react-redux": "^4.4.6",
42 | "react-router": "^3.0.0",
43 | "redux": "^3.6.0",
44 | "redux-thunk": "^2.1.0",
45 | "serialize-javascript": "^1.3.0",
46 | "serve-favicon": "^2.3.2",
47 | "webpack-isomorphic-tools": "^2.2.18"
48 | },
49 | "devDependencies": {
50 | "autoprefixer-loader": "^3.2.0",
51 | "babel-cli": "^6.18.0",
52 | "babel-eslint": "^7.1.1",
53 | "babel-preset-es2015": "^6.18.0",
54 | "babel-preset-react": "^6.16.0",
55 | "babel-preset-stage-3": "^6.17.0",
56 | "better-npm-run": "0.0.13",
57 | "css-loader": "^0.26.0",
58 | "eslint": "^1.10.3",
59 | "eslint-config-airbnb": "^0.1.0",
60 | "eslint-loader": "^1.0.0",
61 | "eslint-plugin-import": "^0.8.1",
62 | "eslint-plugin-react": "^3.16.1",
63 | "extract-text-webpack-plugin": "^1.0.1",
64 | "file-loader": "^0.9.0",
65 | "json-loader": "^0.5.4",
66 | "node-sass": "^3.13.0",
67 | "sass-loader": "^4.0.2",
68 | "style-loader": "^0.13.1",
69 | "url-loader": "^0.5.7",
70 | "webpack": "^1.13.3",
71 | "webpack-dev-middleware": "^1.8.4",
72 | "webpack-hot-middleware": "^2.13.2"
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/messi-yang/react-universal-redux-starter-kit/587a32e0df6eb180e2194443a531a9000d22701e/public/favicon.ico
--------------------------------------------------------------------------------
/src/actions/user.js:
--------------------------------------------------------------------------------
1 | import * as userConstant from '../constants/user.js';
2 | import es6Promise from 'es6-promise';
3 | es6Promise.polyfill();
4 | import fetch from 'isomorphic-fetch';
5 |
6 | export function addUser(user) {
7 | return {
8 | type: userConstant.ADD_USER,
9 | result: user,
10 | };
11 | }
12 |
13 | /*
14 | Here's example of how to do asynchronization in Redux Actions ,
15 | Before doing that, you need to add 'Thunk' middleware in createStore function.
16 | */
17 | export function addUserAsync(user) {
18 | // You will need dispatch to dispatch the object returned by addUser
19 | return dispatch => {
20 | const headers = new Headers();
21 | headers.append('Content-Type', 'text/plain');
22 |
23 | //process.env.API comes from webpack config file and .env file
24 | fetch(process.env.API + '/getUserTitle', {headers: headers})
25 | .then((res) => {
26 | return res.text();
27 | })
28 | .then((res) => {
29 | user.name = res + user.name;
30 | dispatch(addUser(user));
31 | });
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/client.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Routes from './routes/index.js';
4 |
5 | import { Provider } from 'react-redux';
6 | import storeCreator from './store/create.js';
7 |
8 | const app = document.getElementById('app');
9 |
10 | /**
11 | * Hydrate the server side State.
12 | */
13 | const store = storeCreator(window.__INITIAL_STATE__);
14 |
15 | ReactDOM.render(
16 |
17 |
18 |
19 | , app);
20 |
21 | // Let you do the hot-reload while in development mode, google 'webpack-dev-middleware' and 'webpack-hot-middleware'
22 | if (module.hot) {
23 | module.hot.accept();
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class Home extends React.Component {
4 | static get propTypes() {
5 | return {
6 | users: React.PropTypes.array,
7 | };
8 | }
9 | render() {
10 | const { users } = this.props;
11 | const style = require('./Home.scss');
12 | // In this place, funyee represents the file path.
13 | const funyee = require('./funyee.png');
14 | return (
15 |
16 | {
17 | users.map( (item, index) => {
18 | return (
19 |
20 |

21 |
{item.name}
22 |
23 | );
24 | })
25 | }
26 |
27 | );
28 | }
29 | }
30 |
31 | export default Home;
32 |
--------------------------------------------------------------------------------
/src/components/Home/Home.scss:
--------------------------------------------------------------------------------
1 | .box {
2 | display: inline-block;
3 | border: 2px solid black;
4 | border-radius: 10px;
5 | margin: 5px;
6 | padding: 4px;
7 | }
8 | .name {
9 | display: inline-block;
10 | color: #5d108e;
11 | vertical-align: middle;
12 | font-size: 20px;
13 | }
14 | .image {
15 | display: inline-block;
16 | vertical-align: middle;
17 | width: 20px;
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/Home/funyee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/messi-yang/react-universal-redux-starter-kit/587a32e0df6eb180e2194443a531a9000d22701e/src/components/Home/funyee.png
--------------------------------------------------------------------------------
/src/components/Html/Html.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import serialize from 'serialize-javascript';
3 |
4 | class Html extends React.Component {
5 | static get propTypes() {
6 | return {
7 | assets: React.PropTypes.object,
8 | reactHtml: React.PropTypes.string,
9 | store: React.PropTypes.object,
10 | };
11 | }
12 | render() {
13 | // The assets are providde by webpack-isomorphic-tools
14 | const { assets, reactHtml, store } = this.props;
15 | return (
16 |
17 |
18 |
19 | {Object.keys(assets.styles).map((style, key) =>
20 |
22 | )}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 | }
33 |
34 | export default Html;
35 |
--------------------------------------------------------------------------------
/src/constants/user.js:
--------------------------------------------------------------------------------
1 | export const ADD_USER = 'ADD_USER';
2 |
--------------------------------------------------------------------------------
/src/containers/HomeContainer/HomeContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Home from '../../components/Home/Home.js';
3 |
4 | import { connect } from 'react-redux';
5 | import { bindActionCreators } from 'redux';
6 | import * as userActions from '../../actions/user.js';
7 |
8 | class HomeContainer extends React.Component {
9 | static get propTypes() {
10 | return {
11 | state: React.PropTypes.object,
12 | actions: React.PropTypes.object,
13 | };
14 | }
15 | constructor(props) {
16 | super(props);
17 | this.state = {
18 | name: 'John',
19 | };
20 | }
21 | componentDidMount() {
22 | }
23 | addUser() {
24 | const { actions } = this.props;
25 | const state = this.state;
26 | actions.addUserAsync({name: state.name});
27 | }
28 | handNameChange(e) {
29 | this.setState({
30 | name: e.target.value,
31 | });
32 | }
33 | render() {
34 | /*
35 | * The user is one of the datas stored in Redux and is stored in props, check the reducers
36 | * and the function mapStateToProps() and mapDispatchToProps() below.
37 | */
38 | const { user } = this.props.state;
39 | const state = this.state;
40 | const style = require('./HomeContainer.scss');
41 | return (
42 |
43 |
Hello World ! Add Some Names !
44 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
52 |
53 | function mapStateToProps(state) {
54 | return {
55 | state: state,
56 | };
57 | }
58 |
59 | function mapDispatchToProps(dispatch) {
60 | return {
61 | actions: bindActionCreators(userActions, dispatch),
62 | };
63 | }
64 |
65 | /**
66 | * Connect Redux with this Component. (If you want to why this is called container, google 'redux container')
67 | */
68 | export default connect(mapStateToProps, mapDispatchToProps)(HomeContainer);
69 |
--------------------------------------------------------------------------------
/src/containers/HomeContainer/HomeContainer.scss:
--------------------------------------------------------------------------------
1 | .h1Title {
2 | color: #FF9343;
3 | }
4 |
--------------------------------------------------------------------------------
/src/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import user from './user.js';
3 |
4 | const reducers = combineReducers({
5 | user,
6 | });
7 |
8 | export default reducers;
9 |
--------------------------------------------------------------------------------
/src/reducers/user.js:
--------------------------------------------------------------------------------
1 | import * as userConstant from '../constants/user.js';
2 |
3 | const initState = {
4 | users: [],
5 | };
6 |
7 | export default function user(state = initState, action) {
8 | switch (action.type) {
9 | case userConstant.ADD_USER:
10 | return Object.assign({}, state, {users: [...state.users, action.result]});
11 | default:
12 | return state;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router, browserHistory } from 'react-router';
3 | import routes from './routes.js';
4 |
5 | export default class Routes extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 | render() {
10 | return (
11 |
12 | {routes}
13 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/routes/routes.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, IndexRoute } from 'react-router';
3 | import HomeContainer from '../containers/HomeContainer/HomeContainer.js';
4 |
5 | export default (
6 |
7 |
8 |
9 | );
10 |
--------------------------------------------------------------------------------
/src/server.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import path from 'path';
3 | import cookieParser from 'cookie-parser';
4 | import bodyParser from 'body-parser';
5 | import favicon from 'serve-favicon';
6 |
7 | import webpackDevMiddleware from 'webpack-dev-middleware';
8 | import webpackHotMiddleware from 'webpack-hot-middleware';
9 | import webpack from 'webpack';
10 | import webpackConfig from '../tools/webpack.dev.config.js';
11 | import WebpackIsomorphicTools from 'webpack-isomorphic-tools';
12 |
13 | import createLocation from 'history/lib/createLocation';
14 | import React from 'react';
15 | import { match, RouterContext } from 'react-router';
16 | import { createStore, applyMiddleware } from 'redux';
17 | import { Provider } from 'react-redux';
18 | import thunk from 'redux-thunk';
19 | import ReactDomServer from 'react-dom/server';
20 |
21 | import reducers from './reducers/index.js';
22 | import routes from './routes/routes.js';
23 | import Html from './components/Html/Html.js';
24 |
25 | import dotenv from 'dotenv';
26 |
27 | dotenv.config();
28 |
29 | // import { port } from './config.js';
30 |
31 | // The two following lines comes from https://github.com/halt-hammerzeit/webpack-isomorphic-tools, see it on Github
32 | const rootDir = path.resolve(__dirname, '..');
33 | global.webpackIsomorphicTools = new WebpackIsomorphicTools(require('../tools/webpack-isomorphic-tools.js'))
34 | .development(process.env.NODE_ENV === 'development')
35 | .server(rootDir, () => {
36 | require('./server.js');
37 | });
38 |
39 | const app = express();
40 |
41 | app.use(favicon(path.join(__dirname, '..', 'public', 'favicon.ico')));
42 | app.use(express.static(path.join(__dirname, '..', 'public')));
43 | app.use(cookieParser());
44 | app.use(bodyParser.urlencoded({ extended: true }));
45 | app.use(bodyParser.json());
46 |
47 | if (process.env.NODE_ENV === 'development') {
48 | const compiler = webpack(webpackConfig);
49 |
50 | app.use(webpackDevMiddleware(compiler, {
51 | publicPath: '/dist',
52 | hot: true,
53 | filename: 'bundle.js',
54 | stats: {
55 | colors: true,
56 | },
57 | historyApiFallback: true,
58 | }));
59 |
60 | app.use(webpackHotMiddleware(compiler, {
61 | log: console.log,
62 | path: '/__webpack_hmr',
63 | heartbeat: 10 * 1000,
64 | }));
65 | }
66 |
67 | function handleReactRender(req, res, initState = {}, assets = {}) {
68 | const store = createStore(reducers, initState, applyMiddleware(thunk));
69 | const location = createLocation(req.originalUrl);
70 |
71 | match({ routes, location }, (error, redirectLocation, renderProps) => {
72 | if (error) {
73 | console.error(error);
74 | return res.status(500).end('Server Error');
75 | }
76 | if (!renderProps) {
77 | return res.status(404).end('Not Found');
78 | }
79 |
80 | const reactHtml = ReactDomServer.renderToString(
81 |
82 |
83 |
84 | );
85 | const html = ReactDomServer.renderToStaticMarkup(
86 |
87 | );
88 | res.send(`${html}`);
89 | });
90 | }
91 |
92 | // It's created for redux's action, just to show how to call asynchronized action in redux.
93 | app.get('/getUserTitle', (req, res) => {
94 | res.send('Mr\.');
95 | });
96 |
97 | app.use((req, res) => {
98 | // Do hot reload in development mode
99 | if (process.env.NODE_ENV === 'development') {
100 | global.webpackIsomorphicTools.refresh();
101 | }
102 | /**
103 | * This is the initState to be dehydrated on server side and hydrated on client side.
104 | * It's just an easy example that show you how to create store in server-side.
105 | */
106 | const initState = {
107 | user: {
108 | users: [{name: 'John'}, {name: 'Lennon'}],
109 | },
110 | };
111 | handleReactRender(req, res, initState, global.webpackIsomorphicTools.assets());
112 | });
113 |
114 | app.listen(process.env.NODE_APP_PORT, () => {
115 | console.log(`app\'s running on port ${process.env.NODE_APP_PORT}!`);
116 | });
117 |
118 | export default app;
119 |
--------------------------------------------------------------------------------
/src/store/create.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import reducers from '../reducers';
3 | import thunk from 'redux-thunk';
4 |
5 |
6 | export default function configureStore(preloadedState) {
7 | // Thunk is import, it help you to do asynchronization in redux's action
8 | const store = createStore(reducers, preloadedState, applyMiddleware(thunk));
9 |
10 | if (module.hot) {
11 | module.hot.accept('../reducers', () => {
12 | store.replaceReducer(require('../reducers').default);
13 | });
14 | }
15 |
16 | return store;
17 | }
18 |
--------------------------------------------------------------------------------
/tools/webpack-isomorphic-tools.js:
--------------------------------------------------------------------------------
1 | var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
2 |
3 | // see this link for more info on what all of this means
4 | // https://github.com/halt-hammerzeit/webpack-isomorphic-tools
5 | module.exports = {
6 |
7 | // when adding "js" extension to asset types
8 | // and then enabling debug mode, it may cause a weird error:
9 | //
10 | // [0] npm run start-prod exited with code 1
11 | // Sending SIGTERM to other processes..
12 | //
13 | // debug: true,
14 |
15 | assets: {
16 | images: {
17 | extensions: [
18 | 'jpeg',
19 | 'jpg',
20 | 'png',
21 | 'gif'
22 | ],
23 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
24 | },
25 | fonts: {
26 | extensions: [
27 | 'woff',
28 | 'woff2',
29 | 'ttf',
30 | 'eot'
31 | ],
32 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
33 | },
34 | svg: {
35 | extension: 'svg',
36 | parser: WebpackIsomorphicToolsPlugin.url_loader_parser
37 | },
38 | // this whole "bootstrap" asset type is only used once in development mode.
39 | // the only place it's used is the Html.js file
40 | // where a tag is created with the contents of the
41 | // './src/theme/bootstrap.config.js' file.
42 | // (the aforementioned tag can reduce the white flash
43 | // when refreshing page in development mode)
44 | //
45 | // hooking into 'js' extension require()s isn't the best solution
46 | // and I'm leaving this comment here in case anyone finds a better idea.
47 | bootstrap: {
48 | extension: 'js',
49 | include: ['./src/theme/bootstrap.config.js'],
50 | filter: function(module, regex, options, log) {
51 | function is_bootstrap_style(name) {
52 | return name.indexOf('./src/theme/bootstrap.config.js') >= 0;
53 | }
54 | if (options.development) {
55 | return is_bootstrap_style(module.name) && WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
56 | }
57 | // no need for it in production mode
58 | },
59 | // in development mode there's webpack "style-loader",
60 | // so the module.name is not equal to module.name
61 | path: WebpackIsomorphicToolsPlugin.style_loader_path_extractor,
62 | parser: WebpackIsomorphicToolsPlugin.css_loader_parser
63 | },
64 | style_modules: {
65 | extensions: ['less','scss'],
66 | filter: function(module, regex, options, log) {
67 | if (options.development) {
68 | // in development mode there's webpack "style-loader",
69 | // so the module.name is not equal to module.name
70 | return WebpackIsomorphicToolsPlugin.style_loader_filter(module, regex, options, log);
71 | } else {
72 | // in production mode there's no webpack "style-loader",
73 | // so the module.name will be equal to the asset path
74 | return regex.test(module.name);
75 | }
76 | },
77 | path: function(module, options, log) {
78 | if (options.development) {
79 | // in development mode there's webpack "style-loader",
80 | // so the module.name is not equal to module.name
81 | return WebpackIsomorphicToolsPlugin.style_loader_path_extractor(module, options, log);
82 | } else {
83 | // in production mode there's no webpack "style-loader",
84 | // so the module.name will be equal to the asset path
85 | return module.name;
86 | }
87 | },
88 | parser: function(module, options, log) {
89 | if (options.development) {
90 | return WebpackIsomorphicToolsPlugin.css_modules_loader_parser(module, options, log);
91 | } else {
92 | // in production mode there's Extract Text Loader which extracts CSS text away
93 | return module.source;
94 | }
95 | }
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tools/webpack.dev.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | // var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
5 | var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'));
6 | var dotenv = require('dotenv');
7 |
8 | dotenv.config();
9 |
10 | var config = {
11 | devtool: 'cheap-module-source-map',
12 | context: path.join(__dirname, '..'),
13 | entry: [
14 | 'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000',
15 | './src/client.js',
16 | ],
17 | output: {
18 | path: path.join(__dirname, '..', 'public', 'dist'),
19 | publicPath: 'http://localhost:3000/dist/',
20 | filename: 'bundle.js',
21 | },
22 | module: {
23 | loaders: [
24 | {
25 | test:/\.jsx?$/,
26 | exclude: /(node_modules)/,
27 | loader: 'babel-loader',
28 | query: {
29 | presets: ['es2015', 'react']
30 | },
31 | },
32 | {
33 | test: /\.json$/,
34 | loader: 'json-loader'
35 | },
36 | {
37 | test: /\.scss$/,
38 | loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap',
39 | },
40 | {
41 | test: /\.(eot|ttf|wav|mp3)$/,
42 | loader: 'file-loader',
43 | },
44 | {
45 | test: webpackIsomorphicToolsPlugin.regular_expression('images'),
46 | loader: 'url-loader?limit=10240'
47 | },
48 | ]
49 | },
50 | plugins: [
51 | new webpack.optimize.OccurenceOrderPlugin(),
52 | new webpack.HotModuleReplacementPlugin(),
53 | new webpack.NoErrorsPlugin(),
54 | // process.env.API comes from the file '.env', want to learn more, google dotenv
55 | new webpack.DefinePlugin({
56 | 'process.env.NODE_ENV': JSON.stringify('development'),
57 | 'process.env.API': JSON.stringify(process.env.API)
58 | }),
59 | webpackIsomorphicToolsPlugin.development(),
60 | ]
61 | };
62 |
63 | module.exports = config;
64 |
--------------------------------------------------------------------------------
/tools/webpack.prod.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require('extract-text-webpack-plugin');
4 | var WebpackIsomorphicToolsPlugin = require('webpack-isomorphic-tools/plugin');
5 | var webpackIsomorphicToolsPlugin = new WebpackIsomorphicToolsPlugin(require('./webpack-isomorphic-tools'));
6 | var dotenv = require('dotenv');
7 |
8 | dotenv.config();
9 |
10 | var config = {
11 | context: path.join(__dirname, '..'),
12 | entry: [
13 | './src/client.js',
14 | ],
15 | output: {
16 | path: path.join(__dirname, '..', 'public', 'dist'),
17 | publicPath: '/dist/',
18 | filename: 'bundle-[hash].js',
19 | },
20 | module: {
21 | loaders: [
22 | {
23 | test:/\.jsx?$/,
24 | exclude: /(node_modules)/,
25 | loader: 'babel-loader',
26 | query: {
27 | presets: ['es2015', 'react']
28 | },
29 | },
30 | {
31 | test: /\.json$/,
32 | loader: 'json-loader'
33 | },
34 | {
35 | test: /\.scss$/,
36 | loader: ExtractTextPlugin.extract('style', 'css?modules&importLoaders=2&sourceMap!autoprefixer?browsers=last 2 version!sass?outputStyle=expanded&sourceMap=true&sourceMapContents=true'),
37 | },
38 | {
39 | test: /\.(eot|ttf|wav|mp3)$/,
40 | loader: 'file-loader',
41 | },
42 | {
43 | test: webpackIsomorphicToolsPlugin.regular_expression('images'),
44 | loader: 'url-loader?limit=10240'
45 | },
46 | ]
47 | },
48 | plugins: [
49 | new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}),
50 | // process.env.API comes from the file '.env', want to learn more, google dotenv
51 | new webpack.DefinePlugin({
52 | 'process.env.NODE_ENV': JSON.stringify('production'),
53 | 'process.env.API': JSON.stringify(process.env.API)
54 | }),
55 | // We want to compress all the css into one file to be loaded early on client-side in production, so we do it
56 | new ExtractTextPlugin('style-[hash].css'),
57 | webpackIsomorphicToolsPlugin,
58 | ]
59 | };
60 |
61 | module.exports = config;
62 |
--------------------------------------------------------------------------------