React/Redux Node/Express Ecosystem

Basic techniques for building the ecosystem

React/Redux Node/Express Ecosystem

by John Vincent


Posted on May 03, 2018



The React/Redux Node/Express Ecosystem is extremely difficult to understand and so everyone has built one.

The Facebook create react app is the closest I have seen to being a standard but even this I found lacking. For example, I could not change the port, and the whole ecosystem is so full of magic that I found it to be impossible to do anything or to change anything.

Thus I set out to build an ecosystem from scratch. It turned out the React/Redux Node/Express Ecosystem is extremely difficult to build. The rewards of persisting are an understanding of how the ecosystem works and the ability to make useful changes to that ecosystem.

I quickly found that I needed a build system to build the build system. To do this I reached back to an old technique; Makefiles.

A benefit of this approach is that a breaking change to the current ecosystem can be researched and fixed very quickly using these tools.

My Ecosystem

  • React
  • Redux
  • Node
  • Express
  • Visual Studio Code
  • ESLint
  • Airbnb
  • Babel
  • Webpack
  • Sass
  • Mocha (Express)
  • Jest (React)

Visual Studio Code

For details, see Visual Studio Code

Unix Make Script

/Users/jv/Desktop/MyDevelopment/github/repo_shell_scripts/mac/make-webpack

#!/bin/sh
#
# script to execute a Makefile to create a webpack project
#
echo "Script to execute a Makefile to create a webpack project"
echo ""
if [ "$#" -lt 2 ]; then
    echo "Usage: make-webpack target port"
    exit 1
fi
TARGET=$1
PORT=$2
#
SCRIPTS="/Users/jv/Desktop/MyDevelopment/github/repo_shell_scripts/mac/unix-scripts"
echo "Creating target: $TARGET to use Port: $PORT"
make -f $SCRIPTS/webpack/Makefile port=$PORT $TARGET

Notice the port, this is the client port.

Make Script Usage

Targets:

  • react
  • express

To create React app

cd client
make-webpack react {port}

To create Node/Express app

cd server
make-webpack express {port]

The systems created are starter systems, the philosophy being that is easier to delete code than it is to add it.

Makefile


MAKEFILE_DIR := $(dir $(lastword $(MAKEFILE_LIST)))

include $(MAKEFILE_DIR)/babel.mk $(MAKEFILE_DIR)/react.mk

FOO = $PATH

PORT = $(port)

define ESLINTRC
{
  "extends": "airbnb",
  "env": {
    "browser": true
	},
	"globals": {
	},
	"rules": {
		"indent": [2, "tab"],
		"no-tabs": 0,
		"react/jsx-indent": ["off", 2],
		"react/jsx-indent-props": ["off", 2]
	},
	"plugins": [
		"react",
		"jsx-a11y",
		"import"
  ]
}
endef
export ESLINTRC

#
# Targets:
#
# react
# express
# react-express
#

always: init npm_init README gitignore eslint

react: always webpack babel webpack_dev_server react_config html scss react-jest

express: always express-config

react-express: react express-config

#

init:
	@echo "Initialization"
	@echo "makefile_dir $(MAKEFILE_DIR)"
	@echo "Path: $$PATH"

npm_init:
	npm init -y
	sed -i '' $$'/"test":/d' package.json
	sed -i '' $$'/scripts/a\\\n\\\t"USELESS": "nothing"\\\n' package.json

README:
	touch README.md

gitignore:
	touch .gitignore
	echo "node_modules" > .gitignore
	echo "dist/bundle.js" >> .gitignore
	echo ".env" >> .gitignore

simplest_source:
	@echo "Create source code"
	mkdir -p src
	echo "console.log('Hello World!');" > src/main.js

webpack_dev_server:
	@echo "Handle webpack_dev_server"
	npm install webpack-dev-server --save-dev
	sed -i '' $$'/scripts/a\\\n\\\t"start": "webpack-dev-server --hot --inline",\\\n' package.json

webpack:
	@echo "Handle webpack"
	cat $(MAKEFILE_DIR)/webpack.config.js | sed 's/8080/$(PORT)/'> webpack.config.js

	npm install webpack --save-dev

	echo "(webpack) Update package.json"
	sed -i '' $$'/scripts/a\\\n\\\t"prod": "NODE_ENV=production npm run build",\\\n' package.json
	sed -i '' $$'/scripts/a\\\n\\\t"build": "webpack",\\\n' package.json
	sed -i '' $$'/scripts/a\\\n\\\t"help": "webpack --help",\\\n' package.json

eslint_versions:
	@echo "List eslint version dependencies"
	npm info eslint-config-airbnb@latest peerDependencies

eslint:
	@echo "Handle eslint"
	npm i eslint@4.9.0 eslint-plugin-import@2.7.0 --save-dev
	npm i eslint-plugin-jsx-a11y@6.0.2 eslint-plugin-react@7.4.0 --save-dev

	npm i eslint-loader --save-dev
	npm i eslint-config-airbnb --save-dev

	@echo "$$ESLINTRC" > .eslintrc.json

	echo "(eslint) Update package.json"
	sed -i '' $$'/scripts/a\\\n\\\t"eslint_versions": "npm info eslint-config-airbnb@latest peerDependencies",\\\n' package.json

scss:
	@echo "Handle scss"
	npm i css-loader node-sass sass-loader style-loader extract-text-webpack-plugin --save-dev

html:
	@echo "Handle Html"
	npm i copy-webpack-plugin --save-dev

express-config:
	@echo "Handle Express"
	npm install express --save
	npm install morgan winston --save
	npm install body-parser cookie-parser  --save
	npm install serve-favicon --save
	npm install dotenv --save
	npm install uuid --save
	npm install mongoose --save
	npm install joi --save

	npm install express-handlebars --save
	npm install express-jwt jsonwebtoken --save

	npm install chai --save-dev
	npm install chai-http --save-dev
	npm install mocha --save-dev
	npm install faker --save-dev
	npm install esdoc --save-dev

	echo "(express) Update package.json"
	sed -i '' $$'/scripts/a\\\n\\\t"start": "node server.js",\\\n' package.json
	sed -i '' $$'/scripts/a\\\n\\\t"nodemon": "nodemon server.js",\\\n' package.json
	sed -i '' $$'/scripts/a\\\n\\\t"test": "LOG_LEVEL=info mocha ./test --exit",\\\n' package.json

	echo "(express) Update .esdoc.json"
	@echo '{"source": "./api", "destination": "./esdoc"}' > .esdoc.json

	echo "(express) Update .travis.yml"
	@echo 'language: node_js' > .travis.yml
	@echo 'node_js: node' >> .travis.yml
	@echo 'services:' >> .travis.yml
	@echo '- mongodb' >> .travis.yml

	echo "(express) Update .env"
	@echo 'DATABASE_URL=mongodb://localhost/CHANGE_DB' > .env

	@echo "Create Express"
	mkdir -p api config scripts
	mkdir -p views/layouts views/pages views/partials
	mkdir -p test
	mkdir -p public/assets/css public/assets/images public/assets/js public/assets/scss

	@echo '<html><head></head><body>say something</body></html>' > public/assets/test.html

simple: always webpack babel webpack_dev_server simple_config html scss

simple_config:
	@echo "Create simple source code"
	mkdir -p src
	cp -r $(MAKEFILE_DIR)/simple/src/* src
	mkdir -p dist
	cp -r $(MAKEFILE_DIR)/simple/index.html .

babel.mk

define BABELRC_4
{
  "presets": [
		"env", "react"
	]
}
endef
export BABELRC_4

babel:
	@echo "Handle babel"
	touch .babelrc

	@echo "$$BABELRC_4" > .babelrc

	npm install babel-core babel-loader --save-dev

	npm install babel-preset-env babel-preset-react --save-dev

react.mk

MAKEFILE_DIR := $(dir $(lastword $(MAKEFILE_LIST)))

react_config:
	@echo "Create source code"
	mkdir -p src
	cp -r $(MAKEFILE_DIR)/react/src/* src
	mkdir -p test
	cp -r $(MAKEFILE_DIR)/react/test/* test
	mkdir -p dist
	cp -r $(MAKEFILE_DIR)/react/index.html .

	@echo "Handle npm react"
	npm install react react-dom react-router-dom --save
	npm i prop-types --save

	@echo "Handle npm redux"
	npm i react-redux redux redux-thunk --save

	@echo "Handle npm isomorphic-fetch"
	npm i isomorphic-fetch --save

react-mocha:
	echo "(mocha) Update package.json"
	sed -i '' $$'/scripts/a\\\n\\\t"test": "mocha --recursive --compilers js:babel-register test/**/*.js",\\\n' package.json

	@echo "Handle Mocha/Chai Unit tests"
	npm i mocha chai chai-http react-test-renderer --save-dev

react-jest:
	echo "(jest) Update package.json"
	sed -i '' $$'/scripts/a\\\n\\\t"test": "jest",\\\n' package.json
	sed -i '' $$'/scripts/a\\\n\\\t"test-help": "jest --help",\\\n' package.json

	@echo "Handle Jest"
	npm i jest react-test-renderer --save-dev

	@echo "Handle Test files"
	mv test/test-image.js test/image.test.js

webpack.config.js


const webpack = require('webpack');
const path = require('path');

const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');

const APP_FOLDER = path.resolve(__dirname, './src');
const DIST_FOLDER = path.resolve(APP_FOLDER, './dist');
const DIST_FOLDER_STYLE = path.resolve(DIST_FOLDER, './css');

const config = {
  entry: ['./src/index.js', './src/scss/index.scss'],
  output: {
    path: path.resolve('dist'),
    filename: 'bundle.js',
  },

	devtool: 'source-map',
  devServer: {
    contentBase: path.join(__dirname, 'dist'),
    compress: true,
    inline: true,
    port: 8080,
  },

  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
			},
			{
        test: /\.jsx$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      { // regular css files
        test: /\.css$/,
        loader: ExtractTextPlugin.extract({
          fallback: 'style-loader/url!file-loader',
          use: ['css-loader'],
          publicPath: DIST_FOLDER_STYLE,
        }),
      },
      { // sass / scss loader for webpack
        test: /\.(sass|scss)$/,
        loader: ExtractTextPlugin.extract(['css-loader', 'sass-loader']),
      },
    ],
	},
	resolve: {
    extensions: ['.js', '.jsx'],
  },
  plugins: [
    new ExtractTextPlugin({ // define where to save the file
      filename: '[name].bundle.css',
      allChunks: true,
    }),
    new CopyWebpackPlugin([
      { from: 'index.html', to: '.' },
    ],
    { debug: 'info' }),
  ],
};

module.exports = config;