5.8.8-SNAPSHOT • Published 6 years ago

interaction-player v5.8.8-SNAPSHOT

Weekly downloads
3
License
private
Repository
-
Last release
6 years ago

Redux-side-effects driven react boilerplate

This is a very simple example to illustrate the usage of redux-side-effects with react. Together with the pre-configured development tools like eslint, webpack, and testing frameworks like karma, mocha, and various other packages, it can be used as a basis for coding new React/Redux projects.

  1. Usage
  2. Development
  3. Using immutable for redux store state
  4. Passing environment variables to client
  5. Server source code
  6. Client source code
  7. Application URL router
  8. Side-effects
  9. URL history
  10. Quest list
  11. Hot reloading
  12. Clean up webpack configuration
  13. Better side-effects example
  14. Server tests
  15. package.json
  16. List of dependencies

Usage

git clone git@github.com:salsita/redux-boilerplate.git
cd redux-boilerplate
npm install
npm start

Navigate your browser to http://localhost:3000/

Development

npm run start:dev

Navigate your browser to http://localhost:3001/

Using immutable for redux store state

Immutable object is used for the store instead of a plain JavaScript object. This is against the redux specification. Because of this the combineReducers function from redux-side-effects can not be used out of the box.

The use of combineReducers is inevitable for instance for the use of redux-form in the project.

Either the developer writes their own combineReducers function (like the hive project does) or use the provided combineReducers function and relax the constraints from for instance this:

import { fromJS } from 'immutable';
export default fromJS({
  appState: {
    history: null
  },
  effects: []
});

to this:

import { fromJS } from 'immutable';
export default {
  main: fromJS({
    appState: {
      history: null
    },
    effects: []
  })
};

Passing environment variables to client

This is an example from the hive project on how an environment variable is passed to the client (browser-side) code. https://github.com/salsita/hive/blob/develop/web/webpack/webpack.frontend.config.js

const plugins = [
  new webpack.DefinePlugin({
    "process.env": {
      HIVE_LOGIN_PAGE: JSON.stringify(process.env.HIVE_LOGIN_PAGE),
      HIVE_LOGOUT_PAGE: JSON.stringify(process.env.HIVE_LOGOUT_PAGE)
    }
  })
];
...
module.exports = {
  ...
  plugins: plugins
};

On the client side, the variables are then accessed the same way as on the server side:

process.env.HIVE_LOGIN_PAGE;
process.env.HIVE_LOGOUT_PAGE;

Quest list

  • autoprefixer-loader is deprecated. postcss-loader should be used instead.
  • In npm run build-artefacts, the including of the node_modules directory should not be necessary because webpack tracks dependencies via require() statements and it is therefore suppossed to deliver them.
  • In router src/client/Routes.jsx, the default action should be to redirect to the root path (/) instead of displaying the not found page (via the NotFound component. Either check if the router allows it, or perhaps implement the component to dispatch an action (at some point) that will redirect to the root path.

Hot reloading

Hot reloading is not working properly (or at all). It may have to do with the use of devRunner.js, and maybe that the client does not connect back to the webpack-dev-server to listen for refresh events due to not being passed the port number.

Also react-hot-loader is going to be phased out so react-transform-hmr should be used instead. For instance the book SurviveJS - Webpack and React presents a working example.

Clean up webpack configuration

There are currently four webpack configuration files which is way to many. There is a separate configuration file for frontend (the client), the backend (the server), and the development and production environment.

The webpack.backend.config.js and webpack.frontend.config.js are utilized by webpack which is started by the babel-node in the start:dev package.json command (done via devRunner.js).

There should be a common file for both backend and frontend to get rid of the duplicate configuration. Then instead of having four webpack files, use the process.env.npm_lifecycle_event to make selection between a production and a development build.

For instance use this in webpack.conf:

var webpack = require('webpack');
var merge = require('webpack-merge');
const TARGET = process.env.npm_lifecycle_event;
const common = { // shared configuration for production and development builds };
// ...
if (TARGET === 'start') {
  module.exports = merge(common, { // production but not development configuration });  
}
if (TARGET === 'start:dev') {
  module.exports = merge(common, { // development but not production configuration });
}

Where the start and start:dev commands come from package.json

{
  "scripts": {
    "start":     // command to start the production build
    "start:dev": // command start the development build
  }
}

For example it usefull to have the webpack configuration to enable source maps for the development builds but not for the production builds. And also to have the production build source code minified (see -d and -p options) to decrease the network traffic and the browser application load time.

Better side-effects example

Better side-effects example is needed. Something to show handling of success and failure using store reducers when exchanging data with the server.

Server source code

The server src/server/main.js source code listens for http requests on port 3000, or port number configured by the environment variable PORT. It serves static files from the dist/client directory under url path /. It returns JSON data for http get request on path /hello. For every other url path, the server returns the default index.html page.

Client source code

Application URL router

The client router src/client/Router.jsx shows which React component implements which url path. For instance when the user types into a browser a url path that the server does not know and replies with the default index.html file content, the client NotFound React component will render the 404 Page Not Found in the browser.

Side-effects

This a simple example of a side effect from src/client/reducers/testingReducer.js. The side-effect here is the dispatch of the routerBootstrapped action.

export function* applicationMounting(state) {
  yield (dispatch) => {
    dispatch(TestingActions.routerBootstrapped(history));
  };

  return state;
}

export function* routerBootstrapped(state, _history) {
  return state.setIn(['appState', 'history'], _history);
}

URL history

Sometimes it is needed to be able to navigate to different url path from a current one. Regardless of how the url path is constructed, the pushState function of the history object can be used to instruct the browser to change to the given url path. The application router then gets to select the component to render the page based on the new url path. No request to the server is made.

Example from src/client/reducers/testingReducer.js:

import createBrowserHistory from 'history/lib/createBrowserHistory';
const history = createBrowserHistory();

export function* fooClicked(state) {
  history.pushState({}, '/foo');
  return state;
}

export function* barClicked(state) {
  history.pushState({}, '/bar');
  return state;
}

Favicon

Favicon did not always function properly. The trick that made the favicon to be picked up by the browser was to add the graphics in png format src/client/static-resources/favicon.png and to modify the src/client/static-resources/index.html to let the browser to choose from multiple formats. Like this:

<head>
  <link rel="icon" href="favicon.ico" type="image/x-icon" />
  <link rel="icon" href="favicon.png" type="image/png" /> ...
</head>

Server tests

There are two commands to run the same set of backend tests: npm run test_backend and npm run test_backend_cci. The reason is that mocha only allows for one test reporter. The test reporter nyan produces test reports readable by the user. The test reporter mocha-junit-reporter produces test reports for CircleCI (Circle Continuous Integration, hence the _cci suffix). The test reports are written to file test-results.xml.

package.json

If npm install command is to succeed on Windows (without cygwin), then the command must not use any syntax or programs specific to the Unix environment.

The npm run build-artifacts command produces production tarball for CircleCI which deploys it. Therefore it has to be named artifacts, not artefacts (interesting note).

Be prepared to do some configuration tinkering when placing shared source code outside the package.json directory, in order to make the eslint and babel to correctly work with these files.

List of dependencies

This is the list of dependencies taken from the package.json file with some short descriptions.

Development dependenciesSynopsis
autoprefixer-loaderMakes require('./file.css'); to compile and add the CSS to your page.
babel, babel-coreLatest (ES2015 and beyond) JavaScript transpiler/compiler.
babel-eslintAllows to lint all valid Babel code with ESLlint.
babel-loaderAllows transpiling JavaScript files using Babel and webpack.
css-loaderCSS loader for webpack.
eslintPluggable linting utility for JavaScript and JSX.
eslint-config-airbnbAirbnb JavaScript Style Guide.
eslint-loaderESLint loader for webpack
eslint-plugin-importESLint plugin with support for linting of ES2015+ (ES6+) import/export syntax.
eslint-plugin-reactReact specific linting rules for ESLint.
file-loadervar url = require("file!./file.png"); // => emits file.png as file in the output directory and returns the public url
font-awesomeScalable vector icons that can instantly be customized with CSS.
font-awesome-webpackFont awesome configuration and loading package for webpack, using font-awesome (Less).
karmaTesting environment to make test-driven development easy.
karma-chaiMake the Chai assertion library available in Karma.
karma-chrome-launcherx
karma-clix
karma-junit-reporterx
karma-mochax
karma-nyan-reporterNyan Cat style test results reporter.
karma-phantomjs-launcherx
karma-webpackx
mochaJavaScript test framework for Node.js and the browser.
mocha-junit-reporterProduces JUnit-style XML test results.
phantomjsScripted, headless browser used for automating web page interaction.
phantomjs-polyfillThis is a polyfill for function.prototype.bind which is missing from PhantomJS.
raw-loadervar fileContent = require("raw!./file.txt"); // => returns file.txt content as string
react-hot-loaderRe-render the source code changes automatically in the browser.
requestSimple way to make http calls with https and redirect support.
single-childSpawn a single child process which kills itself on restart.
sinonStandalone test spies, stubs and mocks for JavaScript.
sinon-chaiProvide sinon for use with the Chai assertion library.
source-map-supportSource map support for stack traces in node via the V8 stack trace API.
style-loaderStyle loader for webpack.
url-loaderUrl loader for webpack.
webpackModule bundler. The main purpose is to bundle JavaScript files for usage in a browser.
webpack-dev-serverServes a webpack application. Updates the browser on changes.
DependenciesSynopsis
babel-runtimeSelf-contained babel runtime.
bluebirdJavaScript promise library.
expressWeb application framework for Node.js.
historyJavaScript library to manage session history in browsers (and testing environments).
immutableImmutable collections for JavaScript. Immutable data cannot be changed once created.
invariantThrow exception if condition is false.
less, less-loaderCSS pre-processor. Adds variables, mixins, functions and other techniques.
reactJavaScript library for user interfaces.
react-document-metaHTML meta tags for React-based applications.
react-domEntry point of the DOM-related rendering paths (ReactDOM.render()).
react-reduxReact bindings for Redux.
react-routerRouter directs URLs in a single page application to specific handlers.
reduxPredictable state container for JavaScript applications.
redux-routerLibrary to keep the router state (current pathname, query, and params) inside the Redux store.
redux-side-effectsRedux store implementation with proper interface for asynchronous store operations.
serve-faviconNode.js middleware for serving a favicon.
serve-staticNode.js middleware to serve static files from withing a given root directory.