Skip to main content

React JS Development Setup & Workflow

Front-end Development
Back-end Development
Drupal

There are a variety of ways to develop with React.  In my case, I'm not building an exclusive React.js application.  I'm creating a page (or app) on an existing Drupal site.  Drupal is providing the data, and React handling the UI.  This changes slightly how one sets up an environment for development and distribution, especially when using "Hot loading" which is invaluable during development.  

To start we need to have npm (package manager, you can use homebrew for this)

The next thing to do is set up react and webpack, etc using npm.  Because I am creating a react app within a larger site (the site is NOT just the react app) I am going to install all the config and dependencies in a subdirectory of the site.  With Drupal specifically I do this be creating a custom module.  Then within the custom module I use a directory named 'js'.    Like so... 

sites/
  all/
    modules/
      custom/ 
        react_app/
          react_app.info
          react_app.module
          js/
            [App configuration and dependencies]

We will "kickoff" our app by using drupal_add_js() in our *.module file.  LIkely done in a menu callback of some kind...  There are some handy tricks to doing this when it comes to developing with the hot-loader.  I'll put those in another short blog post.

Let's get installing... in the js directory...

$ npm init
$ npm install --save react
$ npm install --save-dev webpack webpack-dev-server
$ npm install --save-dev react-hot-loader

Webpack will handle the building of our react distribution code, jsx-loader is handy for using *.jsx files for our various react components.

Next we'll set up webpack.  And we are going to create two files.  One will be used for development (webpack.config.js), using the "hot-loading" functionality that allows changes to seen immediately, without manual compalation every time.  The second webpack file webpack.dist.config.js will be used for generating the code you actually use on the site. 

Here is an example of webpack.config.js

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

module.exports = {
  entry: [
    'webpack/hot/dev-server',
    './src/index'
  ],
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'bundle.js',
    publicPath: "http://localhost:3000/"
  },
  plugins: [
    // new webpack.HotModuleReplacementPlugin(),
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  module: {
    loaders: [{
      test: /\.jsx?$/,
      loaders: ['react-hot'],
      include: path.join(__dirname, 'src')
    }]
  }
};

The entry point for our development setup will be through the hot-loader.  You'll also see that the public path is referencing a server on port 3000.  I'll show where that is specified later.  

As you can see, I've included the HotModuleReplacementPlugin but have it commented out.  This is because I will be invoking this functionality another way, and if we try to do it here we'll run into issues.

Here is an example of webpack.dist.config.js our config used for packaging up the app for deployment.

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

module.exports = {
  // devtool: 'eval',
  entry: [
    './src/index'
  ],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  plugins: [
    new webpack.NoErrorsPlugin()
  ],
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
  module: {
    loaders: [{
      test: /\.jsx?$/,
      loaders: ['babel?optional[]=runtime&stage=0'],
      include: path.join(__dirname, 'src')
    }]
  }
};

This one is straight forward, no extra plugins really, and a traditional entrypoint, our index (index.js) file.  A word about this.  I have my javascript in a src directory.  So an example hierarchy might look like this

~Drupal Module 
  react-app.info
  react-app.module
    js/
      package.js
      webpack.config.js
      webpack.dist.config.js
      src/
        index.js
        components/
          [jsx component files]

Note that the *js extension isn't needed in the config.

I'm not going to go into the structure of a react app much, but your index.js file may look something like this.. Where there is a div block with an id of #react-app somewhere on the page.

import React from 'react';
import App from './components/App';

React.render(
  <App />,
  document.getElementById('react-app')
);

Now, to handle these two different webpack setups, we will configure our package.js file created by npm and add a few 'script' elements.

Run Scripts

{  
  "name": "js",
  "version": "1.0.0",
  "description": "",
  "main": "bundle.js",
  "scripts": {
    "dev": "webpack-dev-server --devtool eval --progress --colors --hot --content-base build --port 3000",
    "dist": "NODE_ENV=production webpack -p --config webpack.dist.config.js"
  },
  "author": "",
  "license": "ISC",
  ...

There are two script elements.  One for development, and one for packaging for deployment.

The development line specifies a number of helpful option and more specifically the port we want to run webpack-dev-server on.

The distribution line sets an environment variable in case we want to reference it anywhere in our app to doing something unique for production.  We also specify that we want to use the 'dist' version of the webpack config.

To run these different environments we'll do 

npm run dev

Which kicks off the line specified in the package.json file named dev.  This spins up the dev server and webpack starts listening for changes, and "re-compiling" as necessary.

npm run dist

Runs our other script specification which won't include all the hot loader cruft, and simply give us our distribution bundle (in this case bundle.js, specified in package.json)  You can run this before committing or any other time you actually need a file to deploy.  Otherwise we will just let the dev script run, stopping it only to run the dist script periodically.

Hopefully I've not missed anything important.  Let me know otherwise or anything needs clarification.