2

Reasoning

I'm bundling small minified SVGs (icons) with my CSS via LESS's data-uri method, to reduce HTTP requests similar to the purpose of icon fonts such as Octicons or Ye Olde CSS Sprites.

However, LESS encodes them in Base64.
This is sub-optimal in the case of SVG, which can be Data URI'd in UTF-8 (example).

There's three reasons why this is sub-optimal:

1: Base64 is silly for text

The purpose of Base64 is to encode binary data using only 6 bits per byte, making it safe to embed in text files. This is great for PNGs and JPEGs, but it makes any text file 33% larger for no reason. If you're now thinking "well gzip takes care of that", then keep in mind that...

2: Encoding text in Base64 makes gzip much less effective

To understand why this is the case, consider this:

btoa('width')   === 'd2lkdGg='
btoa(' width')  === 'IHdpZHRo'
btoa('  width') === 'ICB3aWR0aA=='

As a practical example, let's take an actual SVG and experiment with it:

$ wc -c *
68630 tiger.svg
25699 tiger.svg.gz
91534 tiger.txt
34633 tiger.txt.gz

Even after gzipping, it's still ~35% larger.

3: It disregards some free sources of redundancy

Think about the width example above. Every SVG will have this substring, and if you embed SVGs in a CSS, you'll probably have this keyword somewhere else (or others), which gzip could benefit from (because this is how Huffman Coding works), but not if it's hidden by Base64.


The Question

How can I embed SVGs in LESS as data: URIs using UTF-8 instead of Base64?

I can imagine a thousand ways to do this involving build tools like Grunt, but it breaks my workflow because I won't be able to do things like style: include:less all.less from a Jade view (I do this in development), or even just @import 'images.less'; from a less file.

James A Mohler
  • 11,060
  • 15
  • 46
  • 72
Camilo Martin
  • 37,236
  • 20
  • 111
  • 154
  • http://jsfiddle.net/estelle/SJjJb/ is not valid. You need to encode reserved URI characters like # for it to work properly. Base64 avoids that FWIW. – Robert Longson Sep 04 '14 at 10:29
  • @RobertLongson It works in Chrome. And [after escaping the `#`](http://jsfiddle.net/SJjJb/1510/) as you mention I could make it work in Firefox too. Also OK in Opera and Safari. Dunno what to think about IE though. Does it even support it? – Camilo Martin Sep 04 '14 at 10:51
  • By the way, one solution, which I don't like very much because it doesn't compress that much better either when you have many SVGs embedded in one stylesheet, is to first convert the SVGs to SVGZs. Also not supported by IE. – Camilo Martin Sep 04 '14 at 10:57
  • @RobertLongson "It's not a bug, it's a feature!". And Safari is still Webkit, it's Chrome and Opera that's Blink. (And Opera was Presto). – Camilo Martin Sep 04 '14 at 10:59
  • You can't use svgz in a data URI. IE does support svgz as an external file though. – Robert Longson Sep 04 '14 at 10:59
  • No, it's a **bug** The relevant specs are perfectly clear on how it's supposed to work and Chrome/Safari/Opera are in violation of that. Blink is a fork of Webkit. – Robert Longson Sep 04 '14 at 11:00
  • @RobertLongson IE doesn't support SVGZ; it just happens that SVGZ can be confused with regular SVG because of gzip being used on most web content already. But what do you mean violation of the spec? Wikipedia mentions you can `'data:text/csv;charset=UTF-8,' + encodeURIComponent(csv)`, which is not too far from that example either for the purposes of not ditching redundancy. – Camilo Martin Sep 04 '14 at 11:05
  • Edited with an example that works cross-browser. Still benefits largely from the third point I made. – Camilo Martin Sep 04 '14 at 11:10
  • @RobertLongson Yeah, but with encodeURIcomponent or similar you're fixing this problem. I posted a fiddle that works for IE, but TBH Webkit is just playing nicely (`data:` URIs don't need fragments, so there's no need to parse fragments, i.e., it's being permissive with its input despite the stringency of the spec). In any case, I found a way to do this! And URI-encoded or not, it still benefits from compression much more than Base64. – Camilo Martin Sep 04 '14 at 14:30
  • Per RFC 3986, Fragment ID should work regardless of the scheme. http://tools.ietf.org/html/rfc3986#section-3.5 – Robert Longson Sep 04 '14 at 15:25
  • @RobertLongson Yes, and Javascript was supposed to go with `CDATA`. But I see your point, and frankly, I agree that one should follow the spec simply because it's better than every one guessing with their own judgement and coming up with a mess of incompatible ideas. So yeah, you're right. – Camilo Martin Sep 04 '14 at 20:34

1 Answers1

4

I'm an idiot. This is simple:

data-uri('image/svg+xml;charset=UTF-8', 'path/to.svg')

I had to read LESS's source to figure this one out.

All the benefits I mention above are gained here, in particular that if you have tons of small SVGs, they will benefit from the redundancy between each other. And it works in all browsers.

Camilo Martin
  • 37,236
  • 20
  • 111
  • 154
  • You're not alone in fact I doubt any of Less team knows about this possibity too :) Though the fact that you can set the `mimetype` in the first parameter [is documented](http://lesscss.org/functions/#misc-functions-data-uri), it's quite rarely used feature and quite not so widely known format to be kept in mind... – seven-phases-max Sep 04 '14 at 23:02
  • @seven-phases-max What do you mean, I expect the LESS team to know that one can do this... I mean, they wrote the code. But even when I read that documentation it was only after seeing the source that it became clear. – Camilo Martin Sep 05 '14 at 03:43
  • >I mean, they wrote the code. - Not every line. Don't forget: in open source development *anybody* can submit a code. Obviously while core-team guys review the submitted code they may not know every-particular-hidden-perls of it :) – seven-phases-max Sep 05 '14 at 03:51
  • @seven-phases-max Well, indeed! I guess I need to practice more teamwork, lol. – Camilo Martin Sep 05 '14 at 04:16
  • 2
    Curiously it's revealed that (in modern browsers) `data-uri` SVG may not need any encoding at all (just subtle escaping) -> [codepen](http://codepen.io/seven-phases-max/pen/neAdb). – seven-phases-max Sep 07 '14 at 23:08
  • @seven-phases-max Oh my god, that's impressive! I didn't knew LESS could do that. But still, not working in IE is awful because just URL-encoding it could fix it. – Camilo Martin Sep 08 '14 at 13:46
  • 2
    Oh, thanks for reporting IE, currently there's [a discussion](https://github.com/less/less.js/issues/2184) if this "non-encoded" data-uri support for external SVG is to be included in Less. – seven-phases-max Sep 08 '14 at 18:58
  • @seven-phases-max I wish they would just fire up browsers to decide on this kind of stuff :P Like the awesome SVG generation thing in your codepen. – Camilo Martin Sep 09 '14 at 20:15