- 5 minutes read

This is just a little tool I wrote instead of reading the manual. Inlining images as base64 images is one of the core features of webpack. However, I didn't want to set up a full-blown webpack project for a task I need exactly once. Actually, I tried, but my project is an Angular 6 project. In theory, Angular offers a nice feature: it can eject the webpack configuration of the project. However, in Angular 6.0.0 this feature is temporarily deactivated.

I ended up with merely 24 lines (plus comments and blank lines). Node.js is a nice tool for such ad-hoc tasks!

Excursion: why and when should you inline an image?

It's always a question of latency and download speed. Inlining an image means to convert it into a long, encoded string (the base64 encoding) and to put that string into the CSS file. Mind you: the resulting file is larger than the combined size of the CSS file and the image.

As it turns out, this pays if the latency exceeds the additional download time. In my case, I observed that my blog loaded blazingly fast, except for a single image. The browser can't load images in advance. It has to load and parse the CSS file and the HMTL code first. Only then it knows which image to load.

In my case, that adds between 0.3 seconds and 0.7 seconds, depending on the quality of your internet connection and where you are. The rest of the page took roughly a second to load, so inlining the image made the page noticeably faster.

But doesn't Angular inline images by default?

Yes, Angular inlines images automatically, at least in the production build. But it doesn't inline images above 10 Kb. In general, that's a good heuristic. Only in rare cases does it pay to override it.

The natural choice would have been to eject the webpack code the Angular CLI uses internally. I suppose it's a matter of a few minutes to find and modify the line responsible for inline in the webpack code. But, as I mentioned above, the eject functionality is currently disabled.

Another disadvantage of ejecting the webpack code is that it makes updating to a new version of Angular more difficult. As long as the Angular CLI manages all the magic internally without showing you, the Angular team can offer a smooth migration path, even if they've changed a lot of things internally. But it's unlikely they'll be able to update the webpack code you've ejected and modified.

How to do it with a few lines of node.js code

If you haven't done so already, this is a good time to install node.js. Navigate to the node.js download page and select one of the LTS versions.

After that, create a new folder, open your terminal or (if you're using Windows) your CMD in the new folder and create the project:

npm init npm install base64-img --save-dev

Next, create a file called inline.js and put this code in it:

const base64Img = require('base64-img'); const fs = require('fs'); const lines = fs.readFileSync('./my-css-file.css', 'utf8').split('\n'); // <<< put your filename here let result = ''; lines.forEach(line => { let start = line.indexOf('url'); if (start >= 0) { start = line.indexOf('(', start); let end = line.indexOf(')'); let url = line.substring(start + 1, end); if (url.startsWith("'")) { // quotes are optional in CSS - let's get rid of them if they're there url = url.substring(1, url.length - 1); } if (!url.startsWith('data:')) { if (url.startsWith('/assets')) { <<< put your resource path here url = './' + url; // <<< put your image path here } const data = base64Img.base64Sync(url); line = line.substring(0, start) + "('" + data + "'" + line.substring(end); } } result += line + '\n'; }); fs.writeFileSync('my-css-file-with-inlined-image.css', result); // <<< put your filename here

Copy the CSS file and the image into the same folder and start the program with the command

node ./inline.js

How the program works

Simple: it reads the CSS file and splits it into individual lines (line 4). After that, it parses the CSS file line by line, looking for the pattern url="/assets/someFile.png". If it finds such a pattern, it excises it, calls the base64 library to convert the image into a long data64 string (line 23), and inserts it back into the CSS source code (line 24). The last step is to write the file back to disk (line 29).

I suppose the program gets even shorter if you're familiar with regular expressions.

Wrapping it up

Granted - I should have read the manual. Conventional wisdom has it that there's no point in re-inventing the wheel.

Be that as it may, like so often, it takes some time to understand the webpack manual. It's well written. There's nothing to complain about. But if you're new to the topic, you have to spend a couple of hours to get started. Truth to tell, I estimate it takes one or two days.

That's why I didn't consider re-inventing the wheel a waste of time. On the long-term, this investment is going to pay, but my project wasn't a long-term project. It was a leisure-time fun project.

Come to think of it: it's a bit weird I feel compelled to vindicate myself for 24 lines. As much as I value ideas like "DRY" and "don't re-invent the wheel" - let's take these rules with a grain of salt.


Comments