Integrating with Express

(Without Codepen / JSbin / Glitch / create-react-app) Sooo… how do you think we could serve up our small React client side examples through Express? What would we minimally need to do to even start a React app?

  • include the react library in our client side code
  • serve any of the components that we create

Integrating With Express Continued

This would normally be a straightforward task:

  1. just serve up our client side JavaScript from the public/javascripts directory using express static
  2. do the same with the react library…
    • download it or use some sort of client side package manager like bower… and, again, serve it from our public/javascripts folder
    • or just include the file from someone else that's hosting it (like a content delivery network / cdnor from facebook)

Some Setup

  1. create an Express app
  2. create an html file in public
  3. include the appropriate scripts… using the examples for setting up a dev version of a react app from facebook:



Create and Mount a Test Component

Markup


	<div id="app"> </div>

Component


class MyComponent extends React.Component{
	render() {
		return React.createElement('h1', {}, 'hello');
	}
}

ReactDOM.render(
	React.createElement(MyComponent),
	document.getElementById('app')
);

Easy, Right?

Hey… wait, I thought we used JSX with react. Let's change our code…


class MyComponent extends React.Component({
	render() {
		return (
			<h1>Hello</h1>
		);
	}
}

ReactDOM.render(
	<MyComponent />
	document.getElementById('app')
);

Let's see what happens

Sad Face


Uncaught SyntaxError: Unexpected token <

This is valid JSX, right? It works on Glitch and Codepen… so what do you think happened here? →

  • Hmmm… looks like our browser doesn't understand JSX!
  • We need to somehow compile it…

Babel

Babel is a set of tools that allows you to use new JavaScript syntax, now, even if browsers don't support it yet!!! YES. THE FUTURE IS NOW!

  • it transforms syntax from ES6/ES2015, ES2016, ES2017, etc. to plain ol' ES5…
  • …so that you can write ES6 without having to wait for full browser support 🙌
  • …and, of course, it'll compile JSX to plain JavaScript

How About In-Browser?

So when/where do we use a transformer, like babel?

Things would be pretty easy if we could just use babel or some other transformer by including some client side JavaScript?

  • our client side JavaScript will include a JSX transformer script / library
  • which means that compilation of JSX into JavaScript will happen in the user's browser
  • so… it seems like we just find this thing, and magically, JSX will work in our browser, right?

But Wait

So… all signs point to the fact that compiling in browser is a bad idea, (sigh, yes, it is).


There's probably a reason why everyone says avoid in-browser transform… why

  • facebook docs say "(the JSX transformer) is fairly large and results in extraneous computation client-side that can be avoided - do not use it in production"
  • we really only want to transform once (not once per client!)
  • why transform on the client for every user (slowing down the user experience), when we can just transform once on the server by precompiling before deploy!?

Ohhh Kaaay. So Now What?

So here's where things get kind of complicated. We'll need:

  • babel to do the processing
  • and some tool to find the files that need processing, and apply the transformation to those files
  • why do you think we need a separate tool for this - why can't we just compile our one js file and call it a day?
  • your file may have dependencies which may also need to be compiled
  • dependencies can get complicated really fast, even for small projects

Enter Webpack

This is where webpack comes in. Webpack will do two things for us:

  1. it'll investigate our app using some entry point JavaScript file that we specify… and figure out all of that file's dependencies
    • for example, we may have a client side JavaScript file, client.js, that depends on React and socket.io
  2. then, it'll use additional libraries (loaders to run some processes) based on the extension of each dependency
    • for example, for all of our js files, it may compile any jsx syntax into regular JavaScript
    • there's a loader that integrates babel into the webpack workflow


Note that there's some intersection in functionality among webpack, grunt, and gulp (for example, they can all be used to concatenate files)

webpack vs grunt and gulp

So grunt and gulp are build tools. That is, you specify some tasks, and they'll run them automatically. For example, you want your build tools to:

  • minify your client side JavaScript and CSS
  • concatenate all of those files
  • even run your JSX transformations!


And… this will all be done without having to run separate commands. However, grunt and gulp are focused on files, while webpack is focused on a project. What does that exactly mean…

Webpack and Dependencies

Webpack requires that you specify a single entry point to your application.

  • typically, this is a single js file like main.js or client.js
  • then, it'll figure out what that file depends on by require statements (or import statements for ES6 syntax!)…
  • this will also work for assets like urls in CSS or hrefs in image tags!


It'll take all of these dependencies and output them as static assets based on your configuration.

Setting up Express with Webpack

Assuming we have a barebones Express app, the webpack docs recommend installing it both globally and locally in your project:

Globally (since you'll likely use for multiple projects)


npm install webpack -g

Then… install webpack as a development dependency in case you want to use a specific version.


npm install webpack --save-dev

This gives us a commandline tool, webpack (of course), that we'll use to bundle our JavaScript…


webpack --help

babel and webpack

Of course, we'll also need to integrate babel into the mix - that was the whole point. We'll want to install the following libraries:

  • babel-core - babel itself
  • babel-loader - webpack plugin for babel
  • babel-preset-react - a bunch of plugins for babel that are useful for react apps (most notably, JSX transform)


Install all of these with --save-dev since they're for building our project (not actual libraries that the app depends on).

webpack Everything

Since we're using webpack (which relies on requires), we'll download local versions of React rather than relying on a hosted / CDN version.

We can install with npm… remember to use --save)


react
react-dom

Setting up our App for webpack

Let's modify where we place our code so that we can use webpack.

  1. Move our code to external JavaScript
  2. Perhaps separate out our component, and the initial mounting of the component
  3. Designate one of those files as the entry point
  4. Add requires so that webpack knows what to put together

PROJECT_DIR 
|
+-webpack.config.js // config options for webpack
|
+-client.js // entry point into our client side code
|
+-components
  |
  +-MyComponent.js // a single react component

Our Bare Bones Page

So… let's get rid of that inline JavaScript…

  • and assume that we'll have a single js file available to us once webpack is done generating assets for us.
  • (bundle.js) doesn't exist yet!

<div id="app">
</div>
<script src="javascripts/bundle.js"></script>

Of course, we'll have to remember to tell webpack that our output should be javascripts/bundle.js.

Move the Component into a Separate File

Create a directory in our project folder that will contain all of our components. Drop our MyComponent code there. Remember to export it… to make it available when using require.

In components/MyComponent.js:


const React = require('react');
class MyComponent extends React.Component {
	render() {
		return (
			<h1>Hello</h1>
		);
	}
}
module.exports = MyComponent;

Our Entry Point

Our entry point will just be a file in the project directory called client.js. Within it, we can mount the component that we created to our DOM.

  • require the React libraries
  • require our component

const React = require('react');
const ReactDOM = require('react-dom');
const MyComponent = require('./components/MyComponent');
ReactDOM.render(<MyComponent />, document.getElementById('app'));

Webpack Configuration

The webpack configuration file is called webpack.config.js. This will be in the root of your project directory.


const path = require('path');
module.exports = {
  mode: 'development' // or production
  // configure the entry point
  // where to output the bundle of static assets
  // and configure the plugins / modules that we'll use
};

This is the entry point into our client side web app… (anything that this file requires will be bundled by webpack)


  entry: './client.js',

webpack Configuration Continued

Specify where the resulting JavaScript file should go… (javascripts/bundle.js)


  output: {
    path: path.join(__dirname, '/public', 'javascripts'),
    filename: 'bundle.js',
    publicPath:'/javascripts'
  },


  • path - where on the file system we're writing to
  • filename - name of resulting .js file
  • publicPath - the path where you're serving this file from (http://localhost:3000/javascripts <-- /javascripts)

Webpack / Babel Configs

Specify our loaders (the transformations we'll be using…)

  • this means… use babel for the transformation
  • and the default configuration will be react specific (for example, transpile JSX)



  module: {
    rules: [
      {
        test: /\.jsx?$/,
        exclude: [/node_modules/, /"app.js/],
        loader: "babel-loader",
        options: {
          presets: ["react"]
        }
      }
    ]
  },

Whew. Finally

In the root of your project folder, just run webpack… aaand hopefully we'll find a new js file in public/javascripts

Running webpack:


webpack
# magic

Checking if it created bundle.js:


ls public/javascripts
# hopefully bundle.js

Let's Try Changing Our Component

Ok… instead of hello, we'll change the text in our component. Let's see the output. What happened?

  • nothing changed! why
  • probably because bundle.js is still the same… what do we have to do?
    • ugh… we have to recompile with webpack
    • (wait, what? are we working with C or Java, here… I thought this was JavaScript!?)

Automatically Rebuilding on Change

Soooo… there are a lot of ways to automatically refresh the contents of bundle.js. We're going to use one that:

  • skips the actual compilation to a file on disk
  • and… instead, creates an in-memory version on file change
  • and intercepts requests to the file using middleware (so that it picks up the in memory version rather than the one on the file system)

Webpack Dev Middleware

We'll use webpack-dev-middleware to do this.

  • webpack-dev-middleware is express middleware that serves the files created from webpack
  • however, instead of writing the files to disk, it just stores the file in memory
  • by default, it'll monitor the file system for changes, and if it does, it automatically rebuilds the in-memory JavaScript bundle


Install via npm…


npm install --save-dev webpack-dev-middleware

Integrating Webpack Dev

In app.js add the code below before the express static middleware.


if(process.env.NODE_ENV === 'development') { 
    // configure webpack-dev-middlware with our original webpack config
    // then... "use" webpack-dev-middleware

    const webpackDevMiddleware = require("webpack-dev-middleware");
    const webpackConfig = require('./webpack.config.js')
    const webpack = require("webpack");
    const compiler = webpack(webpackConfig);
    app.use(webpackDevMiddleware(compiler, {
        publicPath:'/javascripts'
    }));
}

Webpack Dev Notes

A few things about the webpack-dev-middleware integration…

  • notice that we're essentially just reading our plain ol' webpack configuration
  • publicPath is the path in the url
    • (so localhost:3000/javascripts <– /javascripts)
    • should match what's in webpack.config.js
  • you can see it rebuild the javascript bundle on your server's output
  • we can even delete bundle.js, and we'll see that it all works out fine (since the in-memory version is being used)!
  • as mentioned previously, put this before the express static middleware
    • otherwise… you'll have to delete bundle.js

Dev Means Dev

The example code is only active when there's an environment variable called NODE_ENV present and equal to 'development':

  • if(process.env.NODE_ENV === 'development') { ... })
  • which means… only run the webpack dev middleware when we're in development, not production
  • in production we should build the javascript once only, and serve the compiled files
  • sooo … this means to start with the middelware enabled, we must use NODE_ENV=development nodemon bin/www