Using JavaScript Modules

A few weeks ago I spent some time breaking apart Sass files into smaller segments for a project at work, trying to go for a more modular approach for the CSS. Doing this in Sass is pretty simple. Pull out a section of styles into a new “partials” file, named something like _module.scss. Then in the main Sass file, import the module with @import "module";.

For quite a while I’ve wanted to be able to take a similar approach with JavaScript, breaking the main file into modules and using Sass-like imports to pull them all together. I knew this was supposed to be possible with ES6 modules, but every time I started doing the research, I was overwhelmed by new JavaScript techniques and tools (Babel? Webpack? Browserify? Rollup? RequireJS?). After my most recent attempt to find and understand a solution, I finally came up with something that works and I’m fairly happy with.

Module Loaders

I had heard quite a bit about Babel and how it was supposed to convert the new ES6 (ES2015) syntax into a format readable by today’s browsers. I was under the impression that all I needed to do was plug Babel into Grunt and let it process my JavaScript. However, as I tried to set up Babel, nothing seemed to work, and this is what the console told me:

Uncaught ReferenceError: require is not defined

Things started to go much better for me once I realized that I would need a few things in addition to Babel. The output from Babel is in CommonJS format by default, which defines a require function that browsers don’t understand by default. This is why you need a module loader to bridge the gap.

There are several popular options for module loaders, including the aforementioned Rollup and webpack. After doing a bit more research and seeing a fairly straightforward example, I decided to go with Browserify. Babel has created a “babelify” package for Babel + Browserify transforms.

Grunt Setup

Here are the packages I added to my npm package.json file to get my modules working with Grunt:

Once the correct packages were installed, I set up a browserify command in Grunt:

browserify: {
  build: {
    options: {
      browserifyOptions: {
        debug: false
      },
      transform: [
        [
          'babelify', {
            'presets': [
              [
                'es2015', {
                  'loose': true
                }
              ]
            ],
            'comments': false,
            'compact': false,
            'minified': false
          }
        ]
      ]
    },
    src: [
      'src/js/scripts.js'
    ],
    dest: 'build/js/scripts.js'
  }
}

Within the Babelify transform, you can set your Babel options, and within the es2015 preset, you can set options as well.

For Babel, I set comments to false. This can be nice for building a minified version of your file. However, the primary reason I remove the comments is that after Browserify finished building the final JavaScript file, the comments were out of order, making them completely useless. I’m not sure why this happens, and maybe it’s something I did wrong on my end. The comments are really more useful when looking at source anyway, so it doesn’t seem like a very big deal to remove them from the build file.

Module Example

Once Grunt is configured, you can start building modules. Here is an example module.js file that adds a new element to the page:

function helloWorld() {
  var messageElement = document.createElement('p')
  var messageContent = document.createTextNode('Hello, World! I’m a JavaScript module!')
  messageElement.appendChild(messageContent)

  document.body.appendChild(messageElement)

  console.log('The helloWorld function has executed.')
}

export { helloWorld }

There are a few ways to export functions, as highlighted in Wes Bos’ excellent intro to using ES6 modules. Here I have chosen to export them at the end of the file.

Now in the main script.js file, you can import the module, and then run the exported module function:

import { helloWorld } from './module'

helloWorld()

Again, there are a few different ways to import modules as well (take a look at that Wes Bos article again). In this example, I am importing a specific function from my module.

Check out the full example project code on GitHub.

Resources