Skip to content

Instantly share code, notes, and snippets.

@thomasboyt
Last active February 5, 2019 02:20
Show Gist options
  • Select an option

  • Save thomasboyt/8bde87f15809c794cee18cb28cddcae7 to your computer and use it in GitHub Desktop.

Select an option

Save thomasboyt/8bde87f15809c794cee18cb28cddcae7 to your computer and use it in GitHub Desktop.

Why I think Webpack is the right approach to build pipelines

I was asked on Twitter why I think Webpack is the right approach to build tooling in JavaScript applications. My explanation is, uh, a bit longer than fit in a single tweet.

When I say "right approach," I'm specifically talking about the way Webpack's pipeline functions. There are certainly some deficiencies in various aspects of Webpack: it has a rather unintuitive API, and often requires quite a bit of boilerplate to set up. However, even with these issues, I think the core principles of how Webpack functions are sound.

I should also mention here this argument basically applies to SystemJS as well. I'm skeptical of various aspects of SystemJS, but I've only taken a very surface-level look at it, so I'm gonna withhold judgement until I've had a chance to work on a project with it.

So, pipelines. Webpack defines itself on its website as a "module bundler," but this sells it rather short. Webpack is, at its core, a pipeline that takes a JavaScript file as its input and outputs an entire application.

This is fundamentally different than how a build tool like Grunt, Gulp, or Broccoli functions. For example, using the example Brocfile on Broccoli's site:

/* Brocfile.js */

var compileSass = require('broccoli-sass');
var filterCoffeeScript = require('broccoli-coffee');
var mergeTrees = require('broccoli-merge-trees');

var sassDir = 'scss';
var coffeeDir = 'coffeescript';

var styles = compileSass([sassDir], 'app.scss', 'app.css');
var scripts = filterCoffeeScript(coffeeDir);

module.exports = mergeTrees([styles, scripts]);

Here, an application's various assets are defined as various input and output files and directories. While Broccoli gives you some powerful control over how this works, in this regard, it's not fundamentally different from how a Grunt or Gulp task's src and dest configuration works. This is the same approach used by Gulp or Grunt tasks, and I find it to be a really awkward way to think about assets at scale.

Webpack, instead, opts to use a JavaScript application as its only "src" configuration. The JavaScript source then handles importing various assets, which are automatically bundled by Webpack (assuming the right loader configuration).

For example, instead of the above Brocfile, you could do:

module.exports = {
  entry: {
    app: './src/main.coffee',
  },

  output: {
    path: './build/',
  },

  resolve: {
    extensions: ["", ".coffee", ".js"]
  },

  module: {
    loaders: [
      {
        test: /\.coffee$/,
        loaders: ['coffee']
      },
      {
        test: /\.sass$/,
        loaders: ['css', 'sass']
      },
      {
        test: /\.png$/,
        loaders: ['file']
      }
    ]
  }
}

At a glance, this seems similar to the Broccoli configuration. However, note that I didn't have to define where CoffeeScript or SASS originate or end up - I just had to tell Webpack which file extensions map to which files, and that it should be able to require() CoffeeScript files.

Now, in my CoffeeScript, I can do something like:

# main.coffee

require './styles/app.sass'

And the application will import my SASS files into my bundle. By default, this inlines the SASS code into my bundle, but a little extra configuration will split it out into a separate CSS bundle, if I desire.

Now, this, on its face, may not seem that powerful. But when you start getting even slightly complex, this becomes super useful. For example, let's say my SASS uses a few images in an assets/ folder:

body {
  background: url('../../assets/my-cool-bg.png');
}

In Broccoli, if you dropped this into your SASS file, it obviously wouldn't work out of the box unless your served asset paths matched your source's directory organization, which is pretty unlikely. Instead, you'd have to write a Broccoli task that copied and processed your images into an output folder, then update the paths in your SASS file to match the path when served.

However, with Webpack's CSS loader (which you'll note we chained the SASS loader into) and file loader (which is configured to bundle PNGs), that source just works. It will copy your image into the build folder (with a unique SHA filename), then rewrite the url() to be the new file path.

This is awesome! And this applies to all sorts of assets. For example, in a music game engine I worked on, a song's configuration file just requires its MP3, and that plus adding a couple characters to a file-loader configuration was all I needed to bundle MP3s in my application with proper paths.

In another game I made that had level data, I used raw-loader, which, as the name implies, just bundles a file as raw data inside your application bundle, to load some special level files which were parsed and loaded at runtime.

Again, you could totally do all this with Broccoli or other build tools, but you would have to define sources and outputs instead of just a single bundle, adding additional configuration overhead.

So, that's the Webpack "approach" that I like so much. Your entire application is the input, not its individual components. And again, Webpack isn't alone in this concept, SystemJS uses the same principles.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment