From 5b6817125e60f24432c8920b43c42fba002cb71d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Malthe=20J=C3=B8rgensen?= Date: Wed, 21 Jun 2017 14:18:57 -0700 Subject: [PATCH] [Add] `stripdeclarations` option Add the option `stripdeclarions` to the loader. If given will tell the loader to strip out any XML declaration, e.g. at the beginning of imported SVGs. Added because Internet Explorer (tested in Edge 14) cannot handle XML declarations in CSS data URLs (`content: url("data:image/svg...")`). --- README.md | 21 +++++-- index.js | 11 +++- test/input/icon-with-declaration.js | 2 + test/input/images/icon-with-declaration.svg | 7 +++ test/loader.spec.js | 61 ++++++++++++++++++++- 5 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 test/input/icon-with-declaration.js create mode 100644 test/input/images/icon-with-declaration.svg diff --git a/README.md b/README.md index dcbe57c5..a494c8b7 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ A webpack loader which loads SVG file as utf-8 encoded DataUrl string. -Existing [`url-loader`](https://github.com/webpack-contrib/url-loader) always does Base64 encoding for data-uri. As SVG content is a human-readable xml string, using base64 encoding is not mandatory. Instead, one may only escape [unsafe characters](http://www.ietf.org/rfc/rfc1738.txt) and replace `"` with `'` as described [in this article](http://codepen.io/Tigt/post/optimizing-svgs-in-data-uris). +Existing [`url-loader`](https://github.com/webpack-contrib/url-loader) always does Base64 encoding for data-uri. As SVG content is a human-readable xml string, using base64 encoding is not mandatory. Instead, one may only escape [unsafe characters](http://www.ietf.org/rfc/rfc1738.txt) and replace `"` with `'` as described [in this article](http://codepen.io/Tigt/post/optimizing-svgs-in-data-uris). -There are some benefits for choosing utf-8 encoding over base64. -1. Resulting string is shorter (can be ~2 times shorter for 2K-sized icons); -2. Resulting string will be compressed better when using gzip compression; +There are some benefits for choosing utf-8 encoding over base64. +1. Resulting string is shorter (can be ~2 times shorter for 2K-sized icons); +2. Resulting string will be compressed better when using gzip compression; 3. Browser parses utf-8 encoded string faster than its base64 equivalent. ## Supported parameters @@ -23,8 +23,8 @@ Passing this parameter (or setting to `true`) tells to loader *not to include* r ### `limit` -If given will tell the loader not to encode the source file if its content is greater than this limit. -Defaults to no limit. +If given will tell the loader not to encode the source file if its content is greater than this limit. +Defaults to no limit. If the file is greater than the limit the [`file-loader`](https://github.com/webpack-contrib/file-loader) is used and all query parameters are passed to it. ``` javascript @@ -36,6 +36,15 @@ require('svg-url-loader?prefix=img/!./file.svg'); // They are passed to the file-loader if used. ``` +### `stripdeclarations` + +If given will tell the loader to strip out any XML declaration, e.g. `` at the beginning of imported SVGs. +Internet Explorer (tested in Edge 14) cannot handle XML declarations in CSS data URLs (`content: url("data:image/svg...")`). + +``` javascript +require('svg-url-loader?stripdeclarations!./file.svg'); +``` + ## Usage [Documentation: Loaders](https://webpack.js.org/concepts/loaders/) diff --git a/index.js b/index.js index 3615488b..cd0b440c 100644 --- a/index.js +++ b/index.js @@ -8,11 +8,16 @@ module.exports = function(content) { this.cacheable && this.cacheable(); var query = loaderUtils.getOptions(this); - + var limit = query.limit ? parseInt(query.limit, 10) : 0; - - if(limit <= 0 || content.length < limit) { + + if (limit <= 0 || content.length < limit) { content = content.toString('utf8'); + + if (query.stripdeclarations) { + content = content.replace(/^\s*<\?xml [^>]*>\s*/i, ""); + } + content = content.replace(/"/g, "'"); content = content.replace(/\s+/g, " "); content = content.replace(/[{}\|\\\^~\[\]`"<>#%]/g, function(match) { diff --git a/test/input/icon-with-declaration.js b/test/input/icon-with-declaration.js new file mode 100644 index 00000000..73a0b674 --- /dev/null +++ b/test/input/icon-with-declaration.js @@ -0,0 +1,2 @@ +var icon = require('./images/icon-with-declaration.svg'); +module.exports = icon; diff --git a/test/input/images/icon-with-declaration.svg b/test/input/images/icon-with-declaration.svg new file mode 100644 index 00000000..fe6ad0fa --- /dev/null +++ b/test/input/images/icon-with-declaration.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/loader.spec.js b/test/loader.spec.js index 1862ff72..545e2697 100644 --- a/test/loader.spec.js +++ b/test/loader.spec.js @@ -6,7 +6,7 @@ var webpack = require('webpack'); describe('svg-url-loader', function() { 'use strict'; - + this.timeout(10000); var outputDir = path.resolve(__dirname, './output'), @@ -85,6 +85,65 @@ describe('svg-url-loader', function() { }); + describe('"stripdeclarations" option', function () { + it('if turned off - should do nothing to an SVG that has an XML declaration', function(done) { + var config = assign({}, globalConfig, { + entry: './test/input/icon-with-declaration.js' + }); + config.module.rules[0].use[0].options.stripdeclarations = false; + + webpack(config, function(err) { + expect(err).to.be(null); + fs.readFile(getBundleFile(), function(err, data) { + expect(err).to.be(null); + var encoded = (0,eval)(data.toString()); + expect(encoded.indexOf("%3C?xml version='1.0' encoding='UTF-8'?%3E")).to.be.greaterThan(-1); + return done(); + }); + }); + }); + + it('if turned on - should do nothing to an SVG that doesn\'t have an XML declaration', function(done) { + var config = assign({}, globalConfig, { + entry: './test/input/icon.js' + }); + config.module.rules[0].use[0].options.stripdeclarations = true; + + webpack(config, function(err) { + expect(err).to.be(null); + fs.readFile(getBundleFile(), function(err, data) { + expect(err).to.be(null); + var encoded = (0,eval)(data.toString()); + expect(encoded.indexOf('"')).to.be(0); + expect(encoded.lastIndexOf('"')).to.be(encoded.length - 1); + expect(encoded.indexOf('data:image/svg+xml,%3Csvg')).to.be(1); + return done(); + }); + }); + }); + + + it('if turned on - should remove XML declaration from a file that has one', function(done) { + var config = assign({}, globalConfig, { + entry: './test/input/icon-with-declaration.js' + }); + config.module.rules[0].use[0].options.stripdeclarations = true; + + webpack(config, function(err) { + expect(err).to.be(null); + fs.readFile(getBundleFile(), function(err, data) { + expect(err).to.be(null); + var encoded = (0,eval)(data.toString()); + expect(encoded.indexOf('%3C?xml version="1.0" encoding="UTF-8"?%3E')).to.be(-1); + expect(encoded.indexOf('data:image/svg+xml,%3Csvg')).to.be(1); + return done(); + }); + }); + }); + }); + + + describe('"limit" option and "url.dataUrlLimit" configuration', function () { it('should fall back to file-loader if the content of SVG file is longer than "limit" query parameter', function(done) { var config = assign({}, globalConfig, {