More Webpack

Following on from yesterdays experiments with webpack and react, I’ve changed things round a little today in an effort to make things even simpler.

webpack.ProvidePlugin

One of the annoying aspects of splitting the single file into smaller pieces was the need to add the require lines to every one. I thought there must be a simpler way – and there is! The ProvidePlugin allows you to tell webpack that certain requires should be added as needed. To use it a few changes are needed in the webpack configuration.

First you’ll need to make sure that webpack is available, so add the require at the top of the file.

var webpack = require('webpack');

Then add a plugins section with the plugin and it’s configuration.

  plugins: [
    new webpack.ProvidePlugin({
      React: "react",
      ReactDOM: "react-dom"
    }),
  ],

Following this change, the javascript files can be simplified by removing the require lines for react or react-dom, so /components/Main.js becomes just

module.exports = React.createClass({
  render: function() {
    return (
      <p>Hello World!</p>
    )
  }
});

This proved very useful for the project as a few components used jquery, but remembering which ones and to include the require line wasn’t an issue once this plugin had been added – with the appropriate configuration line ($: “jquery”).

Source Maps

It always seems like a good idea to create a .map file, so it’s as simple as adding a line telling webpack to do just that.

devtool: "source-map",

CSS

Handling CSS is something that never seems as simple as it should/could be, but initially webpack seems to offer a solution. 2 new loaders are needed, so install them via npm.

npm install --save-dev style-loader css-loader

Then we need to tell webpack that we can use them for css files, by adding a section to the loaders.

  module: {
    loaders: [
      {
        test: /components\/.+.jsx?$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
        query: {
          presets: ['react']
        }
      },
      {
        test: /.+.css$/,
        loader: 'style-loader!css-loader'
      }
    ]
  }

As we’re using the resolve to keep require usage simple, we should update that as well (I’ve added the css files in an inventively named css directory),

  resolve: {
    modulesDirectories: ['node_modules', 'components', 'css'],
    extensions: ['', '.js', '.jsx', '.css']
  },

After making these changes, we need to actually add a require for some css. This was done in App.js as follows,

require('style.css');

However, running webpack produced a small surprise and a touch of confusion.

$ webpack
Hash: f4a6a633c9be19bddd79
Version: webpack 1.12.13
Time: 1717ms
        Asset    Size  Chunks             Chunk Names
    bundle.js  689 kB       0  [emitted]  main
bundle.js.map  808 kB       0  [emitted]  main
    + 164 hidden modules

Where was the CSS? The answer is simple, but also, to my mind, not obvious. It’s merged into the bundle.js and if you open it in an editor you’ll find it there. However, I want the css in a seperate file for a number of reasons, so this solution only partly works. The answer turns out to be another plugin.

npm install --save-dev extract-text-webpack-plugin

Once installed there are quite a few changes required to use it. First off, we need to extract the css rather than let it be bundled with the javascript. To do this we need to change the css loader.

      {
        test: /.+.css$/,
        loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
      }

Before we can use the ExtractTextPlugin we need to require it, so this line needs to be added at the top of the file.

var ExtractTextPlugin = require("extract-text-webpack-plugin");

Finally we also need to output our extracted css, so we need to add this entry to the plugins.

new ExtractTextPlugin("bundle.css")

Running webpack now gives the expected results.

$ webpack
Hash: 55234e19dea7f3c8f04f
Version: webpack 1.12.13
Time: 1757ms
         Asset       Size  Chunks             Chunk Names
     bundle.js     679 kB       0  [emitted]  main
    bundle.css  107 bytes       0  [emitted]  main
 bundle.js.map     794 kB       0  [emitted]  main
bundle.css.map   87 bytes       0  [emitted]  main
    + 164 hidden modules
Child extract-text-webpack-plugin:
        + 2 hidden modules

With this approach I can simply add a require line into any component javascript file for required css and it will be automatically bundled. This works well but the ordering of things being added isn’t always as I’d wish it, so more care will need to be taken with how I write the css. This approach also opens up the prospect of moving to a LESS/SASS based approach as the css can be processed before being added to the bundle.

I’m reasonably sure I don’t need a map file for the css, but I haven’t found any simple solutions yet. Answers on a postcard.