ReactJS only implements part of a Web application stack. We’ll take time now to refactor the Facebook tutorial application to support some additional development tools and application features.

Using Webpack

Webpack is a well-known module-bundling tool for ReactJS applications.

Bundling

We start by distinguishing between the application source code and the bundled application.

Exercise 9.1

Make a copy of your solution to the solution in the previous lab and store it under lab09. Note that some of the configuration in this lab is version-dependent so we suggest that you integrate the package.json file provided in the course repo as follows.

  1. Copy your lab 8 solution into your lab 9 directory and confirm that it still runs as it did last week.

  2. Copy in the new package.json file from the course lab 9 repo, delete and rebuild your node_modules directory, and confirm that your application still runs. This configuration files sets module versions that are known to work and provides some useful NPM run scripts.

Now, bundle the application as follows.

  1. Upgrade the environment by doing the following.

    1. You can skip this step if you’ve integrated the suggested lab 9 package.json file discussed above. If still necessary, install some new tools using the Node Package Manager (npm).
      npm install webpack \
          babel-loader babel-core babel-preset-react babel-preset-es2015 \
          style-loader css-loader postcss-loader \
          --save
      Note that we save a record of each of these tools in the npm environment. See package.json.
    2. Rename public/ as app/ and create a new directory dist/. You’re building an application now, not serving up public/static files, and this will separate the distributed application source from the bundled application being distributed. This change requires the following updates:

      • Modify server.js to serve up the static files from dist/ (rather than public/).
      • Add dist/ to your main .gitignore file.
    3. Rename app/scripts/example.js to index.js. This is a more common application name.

  2. Configure Webpack by creating webpack.config.js with the following contents.

    module.exports = {
        entry: [
            __dirname + '/app/scripts/index.js'
        ],
        output: {
            path: __dirname + '/dist',
            filename: '/bundle.js'
        },
        module: {
            loaders: [
                { test: /\.jsx?$/, exclude: /node_modules/, loader: "babel-loader" },
                { test: /\.css$/,  loader: 'style!css?modules!postcss' }
            ]
        }
    };
    This specifies the application’s source code modules (i.e., entry), the destination for the bundle (i.e., output) and the code pre-processors (i.e., loaders). The pre-processors grab all code files with JS, JSX and CSS extensions. For details on this configuration, see Configuring Webpack.
  3. Configure these new loaders as follows.

    • Create a Babel configuration file, .babelrc, with the following contents.

      {
        "presets": [
          "react",
          "es2015"
        ]
      }
      This specifies that when Webpack runs the Babel compilation (see the loaders specified above), it will support React JSX and ES2015. For details on this configuration, see Loaders: Babel.
    • Create a PostCSS configuration file, postcss.config.js, with the following contents.

      module.exports = {};
      This should be optional but it makes the PostCSS loader happy for some configurations. For details on this configuration, see PostCSS Loader.
  4. Run Webpack.

    node_modules/.bin/webpack

    Check out the size and contents of the new bundle file, dist/bundle.js. For details on this, see Running Your First Build.

  5. Modify index.html to load the new bundle file we’ll create (bundle.js rather than example.js).

    <script type="text/javascript" src="bundle.js"></script>

    Note that we’ve changed the script type to JavaScript; Webpack is now doing the required Babel compilations (see above) so the browser doesn’t need to do them anymore. Also, you can safely remove the loading of the Babel library in index.html.

  6. As a temporary measure, copy app/index.html and app/css/* to dist/. We’ll bundle these HTML and CSS files as well later in the lab.

The server and application should now run as they did before. When you’ve confirmed that they do, consider the following:

  1. What good has this bundling done? How big is the new bundle file? Check the Chrome DevTools network log to see what files are being loaded.
  2. What role did Babel play in the bundling?
  3. Can you just load file:///path…/index.html using the browser? Why or why not?
  4. In webpack.config.js, what does the loader test entry test: /\.jsx?$/ do?

Save your answers to these questions in a lab09.txt file in the root of your lab repo.

The bundling is now configured properly, but the application is still coded in one (overly) large source code file (as specified by the Facebook tutorial).

Modularization

Modularization is a fundamental computing practice that brings many well-known benefits and JavaScript modules are stored in separate files.

Exercise 9.2

Modularize the application code as follows.

  1. You can skip this step if you’ve integrated the suggested lab 9 package.json file discussed above. If still necessary, install a few more useful packages.

    npm install react react-dom jquery jquery-ui remarkable html-webpack-plugin --save
  2. Set up a template HTML file to be inflated by Webpack.

    1. Replace app/index.html with the following template, stored as app/index.tmpl.html.

      <!DOCTYPE html>
      <html>
          <head>
              <meta charset="utf-8">
              <title>Lab 9 - Webpack</title>
          </head>
          <body>
              <div id="content"></div>
          </body>
      </html>
      Note that this template does not load the CDN libraries or the CSS file; Webpack is already configured to handle this. Neither does it load the bundle; we’ll configure Webpack to take care of this next.
    2. Add this require statement and this plugin specification to webpack.config.js.

      var HtmlWebpackPlugin = require('html-webpack-plugin')
      
      module.exports = {
          entry: [ … ],
          output: { … },
          module: { … },
          plugins: [
      	new HtmlWebpackPlugin({template: __dirname + "/app/index.tmpl.html"})
          ]
      };
      This plugin instructs Webpack to inflate the template with an import of the bundle it creates and to load the result in the output directory, dist/.
    3. The webpage template defined above no longer loads the client-side libraries (e.g., React, react-dom, ...). Do this by modifying scripts/index.js as follows.

      import React from 'react';
      import ReactDOM from 'react-dom';
      import Remarkable from 'remarkable';
      import $ from 'jquery';
      
      import '../css/base.css';
      
      var CommentBox = React.createClass({ … });
      var CommentList = React.createClass({ … });
      var Comment = React.createClass({ … });
      var CommentForm = React.createClass({ … });
      ReactDOM.render( … );
      

      Note that this is actually a more appropriate place to load the libraries because this is where they’re actually being used.

    4. With this configuration, it’s safe to delete the contents of dist/. Do that now. Webpack will re-build and re-deploy all the required files.

    Re-run Webpack and restart your application now and make sure that things are still running properly. For details, see HtmlWebpackPlugin.
  3. Break index.js into separate JavaScript module files, one for each React component, leaving the main ReactDOM.render( … ) code in index.js. The new modules should each have the following structure:

    external imports required by this module…
    
    local imports required by this module…
    
    module.exports = React.createClass({
        class definition…
    });

    The external imports for each module should be selected, as needed, from the list of imports currently included in the index.js file. Include only those libraries that are needed for each individual module.

    Each class definition is exported using module.exports = class definition and must be included by other modules what use using the following form.

    import NewModuleName from './NewModuleFileName';

    Again, import only those local modules that are required. For example, the CommentBox module must import CommentList and CommentForm.

  4. Re-run Webpack and the server.

    • You can skip this step if you’ve integrated the suggested lab 9 package.json file discussed above. If still necessary, streamline this process by adding the following to package.json.

      {
        "name": "server",
        old settings...
        "scripts": {
          "build": "webpack --progress",
          "start": "node server.js"
        },
        old settings...
      }

      Note that NPM knows where to look for Node binaries, so you don’t need to include the full path for the Webpack command. Note also that non-standard npm scripts, e.g., build or dev, must be run with the run option, e.g., npm run script; standard scripts, e.g., start, don’t need the run directive.

    • You can now rebuild using npm run build and run, as before, using npm start.

The server and application should now run as they did before. When you’ve confirmed that they do, consider the following:

  1. Check out the new dist/index.html and dist/bundle.js. How are they different from the previous versions?
  2. What good is it to replace the CDN library loads with module imports?
  3. Check the network log. How many of the new modules you built are being loaded? How about the CSS file?

Save your answers to these questions in your lab09.txt file.

The application is now bundled.

Using Webpack Dev Server

The Webpack Dev Server supports a number of nice development features, including hot module replacement. Because this dev server can only serve up static files, we’ll configure the dev server to serve the application itself over port 3001 and use the original Express server to serve the data API (i.e., /api/comments) over port 3000.

Exercise 9.3

Deploy the Webpack Dev Server as follows.

  1. You can skip this step if you’ve integrated the suggested lab 9 package.json file discussed above. If still necessary, install the dev server, this time as a development-only tool.
    npm install webpack-dev-server --save-dev
  2. Add this require, plugin and devServer spec to webpack.config.js.
    var webpack = require('webpack');
    other requires...
    
    module.exports = {
                                
        entry: [ … ],
        output: { … },
        module: { … },
        plugins: [
    	other plugins...,
    	new webpack.HotModuleReplacementPlugin()
        ],
        devServer: {
            port: 3001,
            proxy: { '/api/*': 'http://localhost:3000' },
            colors: true,
            historyApiFallback: true,
            inline: true,
            hot: true
        }
    };
    This new plugin instructs Webpack to rebundle the application when source files are edited and refresh the browser automatically. The devserver specification runs the server on port 3001 and diverts all data API request through to server.js, which is assumed to be running on port 3000. See Webpack Development Server.
  3. You can skip this step if you’ve integrated the suggested lab 9 package.json file discussed above. If still necessary, add a development script to package.json.

    {
      "name": "server",
    
       old settings...
    
      "scripts": {
        "build": "webpack --progress",
        "dev": "node server.js & webpack-dev-server --progress",
        "start": "node server.js"
      },
    
       more old settings...
    
    }
    You can run the servers using npm run dev.

The server and application should now run as they did before. When you’ve confirmed that they do, consider the following:

  1. Try out both ports and explain the difference between them.
    • localhost:3001 — Try editing one of the React module files. Explain what happens, both to the displayed SPA webpage and to the bundle/index files.
    • localhost:3000 — Try loading /api/comments. Explain what you get. Does the SPA page running on this port change (due to the edits made above) as well?
    For more information on this two-server configuration, see Webpack’s HMR & React-Hot-Loader — The Missing Manual.
  2. Is it worth all this trouble to build a development configuration?

Save your answers to these questions in your lab09.txt file.

Checking in

We will grade your work according to the following criteria: