Developing Modularized Web Apps with ReactJS and Webpack

ReactJS is great. It allows you to create easily complex web applications and user interfaces, without worrying how all is going to update.

As any great technology it is very easy to start with. However, as soon as you begin writing more complex applications your React app begins to grow very quickly.

A complex application has many different React components, each requiring a lot of code to be described and to provide its functionality. It is no more practical to keep everything in the same file.

However, it is also not practical to include many JavaScript files as loading them takes way too much time, while preventing the rest of your page to render.

On the other hand if you know Node & NPM, you know how easy developing in JavaScript can be.

Wouldn’t it be nice if you had that same power while developing client side web applications?

Thankfully there is way to achieve that same kind of development, even using some npm modules, when developing React apps.

Before you start

You need to install NodeJS before you begin.

Webpack

Webpack is a great tool. It takes a bunch of files and as a result produces another bunch of files. Doesn’t sound like much, does it? :-)

In your case you would like that Webpack takes all your JavaScript files and produces a single file to be included in your web application.

Let’s install Webpack globally.

$ npm install webpack -g

Now, you should have the webpack executable on your path.

Let’s also install

$ npm install http-server -g

This is a simple but reliable http server for static files. It is ideal for serving your single page web applications during development.

Babel

Webpack can combine different JavaScript files, but you know that to use React and JSX you need more than that. Here comes Babel.

Babel takes files containing JSX and transforms them into regular JavaScript that all browser can understand.

Actually it is much more powerful than that. For example, it supports using the latest JavaScript standards like ES6 in your files. You can use lambda functions and other nice features in addition to JSX.

$ npm install babel-core babel-loader --save

Bare in mind that you install babel in your local project folder. It is a good idea to first create a project.json file with npm init.

babel-core is Babel itself and babel-loader will help us to use Babel together with Webpack.

React

Yes of course, you also need React, and you can use NPM for that, too.

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

react and react-dom are part of the main React project. The former contains the main react logic, while the latter is required for using React in the browser.

In addition, we also install react-router. A great component which helps when you like to build a single page app with natural navigation.

File Structure

Now that you have everything installed in your node_modules folder, let’s have a look how your app structure can look like.

build/
libs/
css/
  style.css
src/
  app.js
  login.js
  comments.js
index.html
package.json
webpack.config.js

This is the base structure which I use for all kind of single web apps, both simple and complex.

There are two critical files. First, there is index.html, which is the entry point of your application. It is actually very simple

<!DOCTYPE html>
<html>
  <head>
    <title>Reactive Application</title>
    <link rel="stylesheet" type="text/css" href="css/style.css">
  </head>
  <body>
    <div id="app"></div>
  </body>
  <script src="build/bundle.js"></script>
</html>

It is only 11 lines. Don’t worry about build/bundle.js, you will see what it is and how it is generated in a minute. It is all your javascript code combined in a single file.

This little html snippet loads your styles and your React application and that’s it.

Webpack Configuration

Before you begin developing you need to tell Webpack how to build your application. You tell it with the webpack.config.js file, which is in the root of your project.

var config = {
  entry: ['./src/app.js'],
  resolve: { alias: {} },
  output: {
    path: './build',
    filename: 'bundle.js'
  },
  module: {
    noParse: [],
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel'
      }
    ]
  }
}

module.exports = config

It tells which file is the entry point of your application, where the end result should be stored and then which files should be transformed by Babel.

In this case Babel will transform all .js files as long as they are not in node_modules.

Your first App

Now that you have all the structure in place, let’s make an example app to see how to use it.

It will consist of three files. The entry file, a login component and a component capable of storing comments.

Let’s look at your app.js file

var React = require('react')
  , ReactDom = require('react-dom')
  , ReactRouter = require('react-router')

var Comments = require('./comments')
  , Login = require('./login')

var Route = ReactRouter.Route
  , Router = ReactRouter.Router


var CommentsApp = React.createClass({
  mixins: [ReactRouter.History],

  signOut: function(e) {
    e.preventDefault()
    delete window.User
    this.history.pushState(null, "/login")
  },

  render: function() {
    if (window.User) {
      var navigation = (
        <nav className="App-navigation">
          <a href="#/">All</a>
          <a href="#" onClick={this.signOut}>Sign Out</a>
        </nav>
      )
    }

    return (
      <section className="CommentsApp">
        <h1>Comments</h1>
        <h2>Share what you think about the world</h2>

        {navigation}

        {this.props.children}
      </section>
    )
  }
})

function requireLogin(nextState, replaceState) {
  if (!window.User) {
    replaceState({ nextPathname: nextState.location.pathname }, '/login')
  }
}

function requireLogout(nextState, replaceState) {
  if (window.User) {
    replaceState({ nextPathname: nextState.location.pathname }, '/')
  }
}

ReactDom.render((
  <Router>
    <Route path="/" component={CommentsApp}>
      <Route path="login" component={Login} onEnter={requireLogout} />
      <Route path="comments" component={Comments} onEnter={requireLogin} />
    </Route>
  </Router>
), document.getElementById("app"))

In this simple application, there are only two screens. One will display a list of comments, the other one will display a login screen.

As you can see just like in Node, you can use require to load additional modules.

You can load local modules and also those installed with NPM. Of course, not all NPM libraries will work, but many do work.

Webpack is smart, and can follow all your require statements to include and load all the files that you need and then to produce a single bundle.js file.

Another interesting component is the React Router. The / path will show the comments and the /login path will display a login component.

The other part of the code from above insures that when a user is logged then the comments are shown and the login screen is shown otherwise.

Let’s see how our login component will look like

var React = require('react')
  , ReactRouter = require('react-router')

var Login = React.createClass({
  mixins: [ReactRouter.History],

  getInitialState: function() {
    return {email: "", password: ""}
  },

  handleSubmit: function(e) {
    e.preventDefault()

    if (this.state.email === '[email protected]' && this.state.password === 'secret') {
      window.User = {name: 'Stefan'}
      this.history.pushState(null, '/')
    } else {
      this.setState({errors: true})
    }
  },

  handleChange: function(event) {
    if (event.target.type === "password") {
      this.setState({password: event.target.value})
    } else if (event.target.type === "text") {
      this.setState({email: event.target.value})
    }
  },

  render: function() {
    if (this.state.errors) {
      var errors = (
        <div className="Login-errors">
          Wrong email or password. Please, try again.
        </div>
      )
    }

    return (
      <form className="Login" onSubmit={ this.handleSubmit }>
        { errors }

        <input type="text" placeholder="Email" onChange={ this.handleChange } />
        <input type="password" placeholder="Password" onChange={ this.handleChange } />
        <input type="submit" value="Sing In" />
      </form>
    )
  }
})

module.exports = Login

You can see that the login component is not too complex either. Again it uses require to get React, and then exports itself at the end of the file, just like in Node.

I am not going to show you how the comments component looks like, but it is pretty much the same.

You can use what you just saw with many more components and it will still work.

The main problem that you might encounter is what to do when you want use JS libraries which don’t work well with webpack, but there are solutions around them.

Puting it all together

To put everything together you can run the following command

$ webpack --progress --color --watch

This command will never exit but it will build continuously your app as you change it. In addition, it will provide you with a nice feedback.

You can run from the root of your project the http-server in another terminal

$ http-server

And then you can see your app by visiting http://127.0.0.1:8080.

Once you are ready for production stop webpack and just issue the following command

$ webpack -p

It will minify and optimize your build/bundle.js file.

Next

There are many places where you can go from this point. Here are a few ideas.

I just showed you a very simple app, which doesn’t use any other libraries besides React and ReactRouter. You can try to add more useful libraries and then use them with require instead of depending on global states.

Another place webpack can be very helpful is with CSS. You can configure it to transform your multiple Stylus or Less files into single CSS.

Maybe you saw that above I used the onEnter hook on the ReactRouter routes to implement a simple authorization mechanism. You can expand on that.

Did you like this article?

Please share it

We are Stefan Fidanov & Vasil Lyutskanov. We share actionable advice about development with Node, Express, React and other web & mobile technologies.

It is everything that we have learned from years of experience working with customers from all over the world on projects of all sizes.

Let's work together
© 2024 Terlici Ltd · Terms · Privacy