Lesson Weekend

Now that we know how to use import and export statements, we can separate our JavaScript logic into separate files. Once we do that, we'll use webpack to bundle our code. Here's the structure for our ping-pong project:

ping-pong/
├── dist
│   └── index.html
├── package-lock.json
├── package.json
├── src
│   ├── main.js
│   └── ping-pong.js
└── webpack.config.js

We have two directories inside our project: all our JavaScript source code will go inside src while all of our bundled code will go inside dist (short for distribution). index.html should be inside our dist folder.

Here's our package.json file so far:

package.json
{
  "name": "ping-pong",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "webpack": "4.19.1",
    "webpack-cli": "^2.0.9"
  }
}

And here's our basic webpack configuration:

webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

Now that our basic configurations are in place, we need some ping pong code. First, here's the HTML:

dist/index.html
<!DOCTYPE html>
<html>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    <script type="text/javascript" src="bundle.js"></script>
    <title>Ping Pong</title>
  </head>
  <body>
    <form id="ping-pong-form">
      <label for="goal">Enter a number:</label>
      <input id="goal" type="number">
      <button type="submit">Submit</button>
    </form>
    <ul id="solution"></ul>
  </body>
</html>

Note that we have two scripts, one for jQuery and one for bundle.js. We're using a CDN for jQuery instead of including it locally in our project. We'll use webpack to bundle our src JS files into bundle.js.

Here's our ping-pong code. We'll start with our business logic:

src/ping-pong.js
export function pingPong(goal) {
  var output = [];
  for (var i = 1; i <= goal; i++) {
    if (i % 15 === 0) {
      output.push("ping-pong");
    } else if (i % 3 === 0) {
      output.push("ping");
    } else if (i % 5 === 0) {
      output.push("pong");
    } else  {
      output.push(i);
    }
  }
  return output;
}

The code itself is straightforward. The key takeaway here is that we need to export our pingPong() function. That's the only thing we need to change about our function; adding export makes it available for importing into other JS files.

Here's our user interface logic:

src/main.js
import { pingPong } from './ping-pong';

$(document).ready(function() {
  $('#ping-pong-form').submit(function(event) {
    event.preventDefault();
    var goal = $('#goal').val();
    var output = pingPong(goal);
    output.forEach(function(element) {
      $('#solution').append("<li>" + element + "</li>");
    });
  });
});

All we need to do is import pingPong() from ping-pong.js. This import statement consists of a few key things: what we are importing from pingpong.js_ (pingPong) and where we are importing it from. We need the path and file name. Since the file is in the same directory, it's easy to access.

Bundling Code

Now we can $ npm run build in the root directory of the project. Webpack will access the entry point at src/main.js. Then webpack will recursively add any dependencies (anything that needs to be imported from elsewhere). This project only has one dependency: ping-pong.js.

If all goes well, your output will look similar to this:

> [email protected] build /Users/epicodus_student/Desktop/ping-pong
> webpack

Hash: b50b608532249685788d
Version: webpack 4.19.1
Time: 416ms
Built at: 01/08/2019 3:12:07 PM
    Asset      Size  Chunks             Chunk Names
bundle.js  1.22 KiB       0  [emitted]  main
Entrypoint main = bundle.js
[0] ./src/main.js + 1 modules 651 bytes {0} [built]
    | ./src/main.js 328 bytes [built]
    | ./src/ping-pong.js 323 bytes [built]

If you check the dist folder, you'll find bundle.js. The file includes all of our concatenated source code plus some code that webpack has added. Now we can open our index.html file and the code should work!

This may not be particularly impressive; all we've done is remove a single script tag from our code. However, it's a big deal when a project has hundreds of dependencies. More importantly, we were able to separate our logic into our different files (which is better for development), and then concatenate the files after we finished writing the code so browsers can read it.

Ignoring Build Files

As we can see, we can run $ npm run build to invoke webpack to create a bundle.js file. Because this one command can create our bundle at any time, we don't need to include dist in our repo. So we'll add it to our .gitignore file:

.gitignore
node_modules/
.DS_Store
dist/

And commit this change to .gitignore before committing anything in the dist directory. When our future selves or anyone else wants to clone and run our project, they can simply run $ npm install to download necessary dependencies, and $ npm run build to create their own dist folder with a bundle.js file.

It's common practice to ignore the directory containing your bundled code. But make sure to always include what instructions users must follow to install and bundle your project and its dependencies in your README.

We've barely scratched the surface of what webpack can do. In the next several lessons, we'll bundle our CSS, customize webpack for linting and set up a live development server.