144 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			144 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
const valueParser = require('postcss-value-parser');
 | 
						|
const { optimize } = require('svgo');
 | 
						|
const { encode, decode } = require('./lib/url');
 | 
						|
 | 
						|
const PLUGIN = 'postcss-svgo';
 | 
						|
const dataURI = /data:image\/svg\+xml(;((charset=)?utf-8|base64))?,/i;
 | 
						|
const dataURIBase64 = /data:image\/svg\+xml;base64,/i;
 | 
						|
 | 
						|
// the following regex will globally match:
 | 
						|
// \b([\w-]+)       --> a word (a sequence of one or more [alphanumeric|underscore|dash] characters; followed by
 | 
						|
// \s*=\s*          --> an equal sign character (=) between optional whitespaces; followed by
 | 
						|
// \\"([\S\s]+?)\\" --> any characters (including whitespaces and newlines) between literal escaped quotes (\")
 | 
						|
const escapedQuotes = /\b([\w-]+)\s*=\s*\\"([\S\s]+?)\\"/g;
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {string} input the SVG string
 | 
						|
 * @param {Options} opts
 | 
						|
 * @return {{result: string, isUriEncoded: boolean}} the minification result
 | 
						|
 */
 | 
						|
function minifySVG(input, opts) {
 | 
						|
  let svg = input;
 | 
						|
  let decodedUri, isUriEncoded;
 | 
						|
  try {
 | 
						|
    decodedUri = decode(input);
 | 
						|
    isUriEncoded = decodedUri !== input;
 | 
						|
  } catch (e) {
 | 
						|
    // Swallow exception if we cannot decode the value
 | 
						|
    isUriEncoded = false;
 | 
						|
  }
 | 
						|
 | 
						|
  if (isUriEncoded) {
 | 
						|
    svg = /** @type {string} */ (decodedUri);
 | 
						|
  }
 | 
						|
 | 
						|
  if (opts.encode !== undefined) {
 | 
						|
    isUriEncoded = opts.encode;
 | 
						|
  }
 | 
						|
 | 
						|
  // normalize all escaped quote characters from svg attributes
 | 
						|
  // from <svg attr=\"value\"... /> to <svg attr="value"... />
 | 
						|
  // see: https://github.com/cssnano/cssnano/issues/1194
 | 
						|
  svg = svg.replace(escapedQuotes, '$1="$2"');
 | 
						|
 | 
						|
  const result = optimize(svg, opts);
 | 
						|
  if (result.error) {
 | 
						|
    throw new Error(result.error);
 | 
						|
  }
 | 
						|
 | 
						|
  return {
 | 
						|
    result: /** @type {import('svgo').OptimizedSvg}*/ (result).data,
 | 
						|
    isUriEncoded,
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * @param {import('postcss').Declaration} decl
 | 
						|
 * @param {Options} opts
 | 
						|
 * @param {import('postcss').Result} postcssResult
 | 
						|
 * @return {void}
 | 
						|
 */
 | 
						|
function minify(decl, opts, postcssResult) {
 | 
						|
  const parsed = valueParser(decl.value);
 | 
						|
 | 
						|
  const minified = parsed.walk((node) => {
 | 
						|
    if (
 | 
						|
      node.type !== 'function' ||
 | 
						|
      node.value.toLowerCase() !== 'url' ||
 | 
						|
      !node.nodes.length
 | 
						|
    ) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    let { value, quote } = /** @type {valueParser.StringNode} */ (
 | 
						|
      node.nodes[0]
 | 
						|
    );
 | 
						|
 | 
						|
    let optimizedValue;
 | 
						|
 | 
						|
    try {
 | 
						|
      if (dataURIBase64.test(value)) {
 | 
						|
        const url = new URL(value);
 | 
						|
        const base64String = `${url.protocol}${url.pathname}`.replace(
 | 
						|
          dataURI,
 | 
						|
          ''
 | 
						|
        );
 | 
						|
        const svg = Buffer.from(base64String, 'base64').toString('utf8');
 | 
						|
        const { result } = minifySVG(svg, opts);
 | 
						|
        const data = Buffer.from(result).toString('base64');
 | 
						|
        optimizedValue = 'data:image/svg+xml;base64,' + data + url.hash;
 | 
						|
      } else if (dataURI.test(value)) {
 | 
						|
        const svg = value.replace(dataURI, '');
 | 
						|
        const { result, isUriEncoded } = minifySVG(svg, opts);
 | 
						|
        let data = isUriEncoded ? encode(result) : result;
 | 
						|
        // Should always encode # otherwise we yield a broken SVG
 | 
						|
        // in Firefox (works in Chrome however). See this issue:
 | 
						|
        // https://github.com/cssnano/cssnano/issues/245
 | 
						|
        data = data.replace(/#/g, '%23');
 | 
						|
        optimizedValue = 'data:image/svg+xml;charset=utf-8,' + data;
 | 
						|
        quote = isUriEncoded ? '"' : "'";
 | 
						|
      } else {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
    } catch (error) {
 | 
						|
      decl.warn(postcssResult, `${error}`);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    node.nodes[0] = Object.assign({}, node.nodes[0], {
 | 
						|
      value: optimizedValue,
 | 
						|
      quote: quote,
 | 
						|
      type: 'string',
 | 
						|
      before: '',
 | 
						|
      after: '',
 | 
						|
    });
 | 
						|
 | 
						|
    return false;
 | 
						|
  });
 | 
						|
 | 
						|
  decl.value = minified.toString();
 | 
						|
}
 | 
						|
/** @typedef {{encode?: boolean, plugins?: object[]} & import('svgo').OptimizeOptions} Options */
 | 
						|
/**
 | 
						|
 * @type {import('postcss').PluginCreator<Options>}
 | 
						|
 * @param {Options} opts
 | 
						|
 * @return {import('postcss').Plugin}
 | 
						|
 */
 | 
						|
function pluginCreator(opts = {}) {
 | 
						|
  return {
 | 
						|
    postcssPlugin: PLUGIN,
 | 
						|
 | 
						|
    OnceExit(css, { result }) {
 | 
						|
      css.walkDecls((decl) => {
 | 
						|
        if (!dataURI.test(decl.value)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        minify(decl, opts, result);
 | 
						|
      });
 | 
						|
    },
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
pluginCreator.postcss = true;
 | 
						|
module.exports = pluginCreator;
 |