React App Best Practice(Under construction)

What is covered

This blog covers following topics

  • React
  • webpack
  • Sass
  • ES6
  • image
  • React router
  • Build local and pro environment
  • Babel

Packages

For React, I use react, react-dom, react-router with npm

npm install react react-dom react-router --save

For webpack
webpack, webpack-dev-server, webpack-merge

npm install webpack webpack-dev-server webpack-merge --save-dev

For webpack-loader
sass-loader, style-loader, css-loader, file-loader, html-loader, html-webpack-plugin, raw-loader, node-sass, extract-text-webpack-plugin

npm sass-loader style-loader css-loader file-loader html-loader html-webpack-plugin raw-loader node-sass extract-text-webpack-plugin --save-dev

For babel
babel-core, babel-loader, babel-preset-es2015, babel-preset-react

npm install babel-core babel-loader babel-preset-es2015 babel-preset-react

Project Structure

Project
|- .babelrc
|- index.html
|- package.json
|- webpack.config.js
|- images
|     |- riko.jpeg
|- styles
|     |- index.css
|     |- test.css
|     |- pack.scss
|- js
|    |- about.jsx
|    |- details.jsx
|    |- index.jsx
|    |- user.jsx 
|- config
     |- helpers.js
     |- webpack.common.js
     |- webpack.dev.js
     |- webpack.prod.js

Sources

.babelrc

{
  'presets': ['react', 'es2015']
}

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>React + Webpack</title>
</head>
<body>
<body>
<div id="content"></div>
</body>
</body>
</html>

All js and css are inserted by webpack html plugin

js/index.jsx

import React from 'react';
import ReactDOM from 'react-dom';
import '../styles/index.css';
import '../styles/pack.scss';
import '../styles/test.css';
import logo from '../images/riko.jpeg';
import { Router, Route, browserHistory } from 'react-router';
import { About } from './about.jsx';
import { User } from './user.jsx';
import { Detail } from './details.jsx';


class Home extends React.Component {
    render() {
        return (
            <div className="root">
                <div className="hello">
                    Hello React
                </div>
                <p className="scss">
                    SCSS
                </p>
                <img src={logo} />
            </div>

        );
    }
}

class App extends React.Component {
    render() {
        return (<Router history={browserHistory}>
            <Route path="/" component={Home}>
            </Route>
            <Route path="/about" component={About}></Route>
            <Route path="/user" component={User}>
                <Route path="/details" component={Detail} />
            </Route>
            <Route path="/user/details" component={Detail}></Route>
        </Router>
        );
    }
}

ReactDOM.render(
    <App />,
    document.getElementById("content")
);

js/about.jsx

import React from 'react';

export class About extends React.Component {
    render() {
        return (
            <div className="root">
                About
            </div>

        );
    }
}

js/user.jsx


js/details.jsx

import React from 'react';

export class Detail extends React.Component {
    render() {
        return (
            <div className="root">
                Detail
            </div>
        );
    }
}

webpack.config.js

module.exports = require('./config/webpack.dev.js');

config/helpers.js

var path = require('path');
var _root = path.resolve(__dirname, '..');

function root(args) {
    args = Array.prototype.slice.call(arguments, 0);
    return path.join.apply(path, [_root].concat(args));
}

exports.root = root;

config/webpack.common.js

var webpack = require('webpack');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var helpers = require('./helpers');

module.exports = {
    entry: {
        bundle: helpers.root('js') + '/index.jsx'
    },

    resolve: {
        extensions: ['', '.js', '.jsx']
    },

    module: {
        loaders: [
            {
                test: /\.jsx$/,
                exclude: /node_modules/,
                loader: 'babel-loader'
            },
            {
                test: /\.html$/,
                loader: 'html'
            },
            {
                test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
                loader: 'file?name=assets/[name].[hash].[ext]'
            },
            {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract("style-loader", "css-loader")
            },
            {
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract('css!sass')
            }
        ]
    },

    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            name: ['bundle']
        }),

        new HtmlWebpackPlugin({
            template: 'index.html'
        })
    ]
};

config/webpack.dev.js

var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

module.exports = webpackMerge(commonConfig, {
    devtool: 'cheap-module-eval-source-map',

    output: {
        path: helpers.root('dist'),
        publicPath: 'http://localhost:8080/',
        filename: '[name].js',
        chunkFilename: '[id].chunk.js'
    },

    plugins: [
        new ExtractTextPlugin('[name].css')
    ],

    devServer: {
        historyApiFallback: true,
        stats: 'minimal'
    }
});

config/webpack.prod.js

var webpack = require('webpack');
var webpackMerge = require('webpack-merge');
var ExtractTextPlugin = require('extract-text-webpack-plugin');
var commonConfig = require('./webpack.common.js');
var helpers = require('./helpers');

const ENV = process.env.NODE_ENV = process.env.ENV = 'production';

module.exports = webpackMerge(commonConfig, {
    devtool: 'source-map',

    output: {
        path: helpers.root('dist'),
        publicPath: '/',
        filename: '[name].[hash].js',
        chunkFilename: '[id].[hash].chunk.js'
    },

    plugins: [
        new webpack.NoErrorsPlugin(),
        new webpack.optimize.DedupePlugin(),
        new webpack.optimize.UglifyJsPlugin({ // https://github.com/angular/angular/issues/10618
            mangle: {
                keep_fnames: true
            }
        }),
        new ExtractTextPlugin('[name].[hash].css'),
        new webpack.DefinePlugin({
            'process.env': {
                'ENV': JSON.stringify(ENV)
            }
        })
    ]
});

Build and Run

Add following script for package.json

"scripts": {
    "clean": "rm -rf dist",
    "start": "webpack && webpack-dev-server",
    "build": "rm -rf dist && webpack --config config/webpack.prod.js"
}

start is just build and start as development environment with webpack-dev-server

Build for local and run

npm start

You can see build result under dist and can see localhost:8080
React router covers following
localhost:8080/about
localhost:8080/user
localhost:8080/details
localhost:8080/user/details

dist
|- assets
|- bundle.css
|- bundle.js
|- index.html

Build for PRO

npm run build
dist
|- assets
|- bundle.[hash].css
|- bundle.[hash].css.map
|- bundle.[hash].js
|- bundle.[hash].js.map
|- index.html