Skip to content

Instantly share code, notes, and snippets.

@mzgoddard
Created February 29, 2016 15:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mzgoddard/3b66ff2119ebbddaa274 to your computer and use it in GitHub Desktop.
Save mzgoddard/3b66ff2119ebbddaa274 to your computer and use it in GitHub Desktop.
Webpacking an application and processing assets with it.

would love to hear how you're getting your hashed assets to your cdn, and how you're making those urls known to your app

I've been using webpack for a while now and in projects released online using webpack's idioms and some of it's options defaults to take my non-code assets and output them with hashed names. Setting another option and using a community plugin makes it easy to output the javascript files with hashed names and automatically include those in html files.

Using webpack getting the output hashed names into the app mostly goes through two parts, file-loader and webpack's loader context method emitFile.

Getting an asset included in the build and its hashed output name is like getting another javascript file in the build. Getting a javascript file included happens from requiring it with something like var someFileOutput = require('./some-file.js');. Getting an asset included uses the same pattern var someImageOutput = require('./some-image.png');. By default webpack treats every included file as javascript. It'll try to parse and statically analyze it for require and other statements. So we need to transform the image into javascript. That is where webpack loaders come in.

file-loader is one such loader used with assets. It takes any content from the given file, creates a output name and stores the name and content with webpack's emitFile which at the end of webpack's process writes to the file system. It then returns the stored name in a small commonjs style javascript module like module.exports = __webpack_public_path__ + "2292213b21b3957a330060c1ac80a543.png";.

Using a loader plainly looks like var someImageOutput = require('file-loader!./some-image.png');. In cases like images where we can easily determine what loaders will be used by the file path or in this case file extension, webpack's main configuration lets us define loaders to be used automatically with a regular expression on the resolved file path.

Such a webpack configuration would look like:

module.exports = {
  context: '.',
  entry: './index.js',
  output: {
    filename: 'build.js',
  },
  module: {
    loaders: [
      {
        test: /\.png$/,
        loader: 'file-loader',
      },
    ],
  },
};

We can also get images referred to by our style documents output with hash names and included properly in the output style documents.

The projects I've used webpack on, commonly use style-loader and css-loader for css files and for preprocessed css languages an appropriate loader. Loaders can work on the output of another loader. Thanks to this loaders act like functions and hold a single responsibility. For css that is split up with style-loader, css-loader, and for example stylus-loader. stylus-loader transforms stylus into css. css-loader transforms css into javascript. style-loader does something a little different. Instead of getting the raw output from the last loader it outputs a module to consume the runtime output of the css-loader. Basically style-loader takes the object with a toString method, calls the toString method and by default places a style tag into the head element with the toString output.

css-loader is the important step for getting assets included. Wherever there is a url('some-image.png') it'll output as example background-image: url(' + require('./some-image.png') + ');.

"But wait, I'm going to load all my css through the javascript?" We don't have to do that for production. Its recommend for development because it allows a faster development loop. For production I've used extract-text-webpack-plugin which was originally created for this purpose, to take css-loader's javascript output and turn it back into css. extract-text-webpack-plugin what is referred to as an optimization plugin. It doesn't optimize compile time, most of them deoptimize compile time, but it optimizes runtime. In the case for extract-text-webpack-plugin it evaluates css-loader's output javascript to emit as its own file. Since it evaluates that javascript it also does so for it's dependencies. Those javascript expressions concatenating the output from loader's like file-loader all turn into normal css with the paths statically set.

The best reason I can think of for including your style through webpack is integration. There are a number of levels of integration further that can be done but the first step here is build time errors. Instead of say copying a folder of assets and a separate build step to concatenate style documents, a url can refer to an asset that doesn't exist or is in the wrong folder, or just has a typo; webpack will emit a error however because it looks for the file and handles it. This has proved important on projects I've been on where pull requests had there continuous integration fail because an asset wasn't included with new css or other common human errors. Which naturally stopped us from breaking the build in those cases. Yay!

So far I've mentioned including single assets. Considering how you can copy a folder and knowing where the folder is append the name of a file needed at runtime, with webpack when it isn't economic to write a whole bunch of individual requires or css classes with url's there is require.context. A common use is to get all assets in a folder like var images = require.context('./images');. Using the returned value works like using require normally in webpack, like images('./some-image.png');. webpack while analyzing the javascript sees require.context like it sees require but in require.context's case resolves the target directory and creates a module that requires every file in the folder.

The last thing for tieing all the output files together with hashed names is html-webpack-plugin. By default it'll pick up on all output javascript entry chunks and css files and inject script and link tags into the given html file.

In the simple configurations below there are individual ExtractTextPlugin and HtmlWebpackPlugin's but its very possible to output multiple css files or html files in a build if need be.

Here is a simple build configuration with all the above considered:

'use strict';

var ExtractTextPlugin = require('extract-text-webpack-plugin');
var HtmlWepbackPlugin = require('html-webpack-plugin');
var UglifyJsPlugin = require('webpack').optimize.UglifyJsPlugin;

module.exports = {
  context: __dirname,
  entry: {
    main: './src',
  },
  output: {
    path: 'dist',
    filename: '[hash].js',
  },
  module: {
    loaders: [
      {
        test: /\.styl$/,
        loader: ExtractTextPlugin.extract(
          'style-loader',
          'css-loader!stylus-loader'
        ),
      },
      {
        test: /\.png$/,
        loader: 'file-loader',
      },
    ],
  },
  plugins: [
    new ExtractTextPlugin('[contenthash].css'),
    new HtmlWepbackPlugin({
      filename: 'index.html',
      template: './src/index.html',
    }),
    new UglifyJsPlugin(),
  ],
};

Here is a simple development configuration as well:

'use strict';

var HtmlWepbackPlugin = require('html-webpack-plugin');

module.exports = {
  context: __dirname,
  entry: {
    main: './src',
  },
  output: {
    path: 'dist',
    filename: '[hash].js',
  },
  devtool: 'source-map',
  module: {
    loaders: [
      {
        test: /\.styl$/,
        loader: 'style-loader!css-loader!stylus-loader',
      },
      {
        test: /\.png$/,
        loader: 'file-loader',
      },
    ],
  },
  plugins: [
    new HtmlWepbackPlugin({
      filename: 'index.html',
      template: './src/index.html',
    }),
  ],
};

All of my webpack configurations involve many more parts but these cover the common details for handling assets.

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