Using Webpack to Bundle CSS with Images and Fonts

One of my teams at work is currently developing in Angular 2, and we use Webpack for all of our TypeScript transpilation, module loading, and bundling. Yesterday we ran into an issue where one of the developers had made references to CSS files located in the node_modules directory of one of the 3rd party libraries we're using. This presented a problem because we don't distribute the node_modules folder as part of our build and deployment process -- we use JavaScript bundles created by Webpack.

To correct the problem, I leveraged Webpack again, this time to bundle the required CSS files. The first step was to create a SASS file (.scss) which would be used as the entry point by Webpack. The reasoning behind using SASS was to allow us to do more than just reference the other stylesheets. But for the time being, it would do just that, and act as our entry point for Webpack to build its dependancy graph of our styles.

Here's what the SASS file looked like at the start:

@import '../js/node_modules/primeng/resources/primeng.min';

And here's my entry point in my Webpack config:

module.exports = {
  entry: {
    'polyfills''./ng/polyfills.ts',
    'vendor''./ng/vendor.ts',
    'app''./ng/main.ts',
    'primestyles''../sass/primestyles.scss'
  },

I started by just referencing one of the two stylesheets we were using. But once I got Webpack set up to include this new entry point, I received some errors concerning image files that the spreadsheet was referencing. This required setting up an additional loader in Webpack to take the image files into account and include them:

{
    test: /\.(png|gif)$/,
    loader: require.resolve("file-loader")
},

Notice in my loader assignment above, I'm using the require.resolve() method. This is because, for whatever reason, Webpack wasn't finding the file-loader module. It was installed, and I even re-installed it, to no avail. The fix was to use require.resolve().

I also had to make use of the resolve-url-loader, which requires sass-loader to specify the sourceMap parameter:

{
  test: /\.scss$/i, 
  exclude: /node_modules/, 
  loader: ExtractTextPlugin.extract('css-loader!resolve-url-loader!sass-loader?sourceMap')
},

To make sure that this new bundle had a CSS extension, I had to use the extract-text-webpack-plugin (as shown in the loader assignment above). This was getting a bit involved! This required me to require it in my Webpack config:

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

And to have it configured correctly:

plugins: [
  new ExtractTextPlugin('[name].css')
],

It's worth noting that the extract() method of the extract-text-webpack-plugin wouldn't work with the array syntax -- I needed to use the "single string with loaders separated by exclamation marks" approach, or else it gave me an error. Now that I had all that set up and working, I added an @import to the other CSS to my entry point:

@import '../js/node_modules/primeng/resources/themes/omega/theme.css';
@import '../js/node_modules/primeng/resources/primeng.min';

I expected everything to still work, but I was wrong, because this other CSS file had some font dependencies! To take care of that...I needed another loader (url-loader) and to specify 2 more loader specifications in my Webpack config:

{
    test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
    loader: "url-loader?limit=10000&minetype=application/font-woff"
},
{
    test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
    loader: "file-loader"
}

Running this caused Webpack to tell me it needed some additional Node modules. I needed to add font-awesome, font-awesome-webpack, and less.

And at this point, everything was working. The final result was that I had a new CSS bundle in my dist directory (where I have Webpack configured to place my output), and all of the required image and font files are copied there as well. The final change was to update the references in the HTML which had previously been referencing the CSS files from the node_modules folder. Done! It wasn't the simplest, or most straight-forward fix, but it got the job done.


Comments

Popular posts from this blog

How To Mock Out Child Components In Unit Tests of Angular 2 Code

A Generic Method Using HttpClient to Make a Synchronous Get Request

The Cause and Solution for the "System.Runtime.Serialization.InvalidDataContractException: Type 'System.Threading.Tasks.Task`1[YourTypeHere]' cannot be serialized." Exception