dfreire's programming blog home

29 Jun 2015

A quick webapp project template

Let's create a minimal working webapp project template, using Bootstrap, React, React Router, NPM, and Webpack.

The directory layout looks like this:

js/
    about.jsx
    app.jsx
    home.jsx
index.html
package.json
webpack.config.json

All the code is pretty much self explanatory, but let's briefly describe each file separately.

js/about.jsx and js/home.jsx are two React (Router) pages. In this trivial example, each of them renders a heading:

import React from "react/addons";
import Router from "react-router";

export default React.createClass({
    mixins: [Router.State],

    render: function() {
        return (<h1>About</h1>);
    }
});

js/app.jx is the entry point of the application. It renders the website structure and configures the routes. The active page will be rendered in the place of the <RouteHandler/> component.

import React from 'react/addons';
import Router from 'react-router';
import {Route, Redirect, RouteHandler, Link} from 'react-router';

import Home  from './home.jsx'
import About from './about.jsx'

var pages = [
  { id: "home",  path: "/home",  title: "Home",  handler: Home  },
  { id: "about", path: "/about", title: "About", handler: About }
];

// hold the current active path
// used to identify which bootstrap's navbar <li> element
// should be declared with the "active" class
var activePath;

var App = React.createClass({
  render: function() {
    return (
      <div>
        <nav className="navbar navbar-default navbar-fixed-top">
          <div className="container">
            <div className="navbar-header">
              <button type="button" className="navbar-toggle collapsed"
                data-toggle="collapse"
                data-target="#navbar"
                aria-expanded="false" aria-controls="navbar">
                <span className="sr-only">Toggle navigation</span>
                <span className="glyphicon glyphicon-menu-hamburger"></span>
              </button>
              <a className="navbar-brand" href="/">App</a>
            </div>
            <div id="navbar" className="collapse navbar-collapse">
              <ul className="nav navbar-nav">
                {pages.map(function(page, i) {
                  var className = (activePath === page.path) ? "active" : "";
                  return (
                    <li key={i} className={className}>
                      <Link to={page.path}>{page.title}</Link>
                    </li>
                  );
                })}
              </ul>
            </div>
          </div>
        </nav>
        <div className="container">
          <div style= >
            <RouteHandler/>
          </div>
        </div>
      </div>
    )
  }
});

var routes = (
  <Route handler={App}>
    {pages.map(function(page, i) {
      return (
          <Route key={i}
            name={page.id}
            path={page.path}
            handler={page.handler}/>
      );
    })};
    <Redirect from="/" to="home" />
  </Route>
);

Router.run(routes, Router.HashLocation, function(Handler, state) {
  activePath = state.path.split("?")[0];
  React.render(<Handler />, document.getElementById('app'));
});

The webpack.config.json configuration file tells webpack to build js/app.jsx and all its dependencies into a bundle.js file. Webpack uses loaders to compile jsx, css, and scss files into bundle.js. Although in this minimal project template we haven't created any css (or scss) file, it is a good idea to have the build system ready for them.

var config = {
  entry: ['./js/app.jsx'],
  output: {
    filename: 'bundle.js'
  },
  module: {
    loaders: [{
      test: [/\.jsx?$/],
      loaders: ['babel']
    }, {
      test: [/\.css$/, /\.scss$/],
      loader: 'style!css!sass'
    }]
  }
};

module.exports = config;

In package.json we declare our production dependencies (React and React Router) and our development dependencies (Webpack's loaders).

{
  "name": "quick-webapp-template",
  "version": "0.0.1",
  "dependencies": {
    "react": "*",
    "react-router": "*"
  },
  "devDependencies": {
    "babel-loader": "*",
    "style-loader": "*",
    "css-loader": "*",
    "sass-loader": "*"
  }
}

Finally, the index.html page loads twitter bootstrap and our own bundle.js. Bootstrap depends on jquery, so we need to load it too.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>App</title>
  <link href="/bootstrap.min.css" rel="stylesheet">
  <!--[if lt IE 9]>
  <script src="/html5shiv.min.js"></script>
  <script src="/respond.min.js"></script>
  <![endif]-->
</head>
<body>
  <div id="app"></div>
  <script src="/jquery.min.js"></script>
  <script src="/bootstrap.min.js"></script>
  <script src="/bundle.js"></script>
</body>
</html>

If you remember js/app.jsx, you'll recognize that our app will be rendered inside the <div id="app"> element.

Assuming you have NPM and Webpack installed in your system, you can run:

To see the resulting website running, you should serve the project directory in a webserver.

A common way to do this is to run:

cd <project_dir>
python -m SimpleHTTPServer 3000

and then visit http://localhost:3000/

As an alternative, you can create your own static web server using Go, as explained here.

You can find and use the source code for this template here.