UNPKG

svg-inline-loader

Version:

Cleans up and inlines your SVG files into Webpack module.

192 lines (170 loc) 6.93 kB
var assign = require('object-assign'); var conditions = require('./conditions'); var defaultConfig = require('../config'); function removeSVGTagAttrs(tag) { if (conditions.isSVGToken(tag)) { tag.attributes = tag.attributes.filter(conditions.hasNoWidthHeight); } return tag; } function createRemoveTagAttrs(removingTagAttrs) { removingTagAttrs = removingTagAttrs || []; var hasNoAttributes = conditions.createHasNoAttributes(removingTagAttrs); return function removeTagAttrs(tag) { if (conditions.isStartTag(tag)) { tag.attributes = tag.attributes.filter(hasNoAttributes); } return tag; }; } function createWarnTagAttrs(warnTagAttrs) { warnTagAttrs = warnTagAttrs || []; var hasNoAttributes = conditions.createHasAttributes(warnTagAttrs); return function warnTagAttrs(tag) { if (conditions.isStartTag(tag)) { var attrs=tag.attributes.filter(hasNoAttributes); if(attrs.length > 0) { var attrList=[]; for(var i=0;i<attrs.length;i++){ var attr=attrs[i]; attrList.push(attr[0]); } console.warn('svg-inline-loader: tag ' + tag.tagName + ' has forbidden attrs: ' + attrList.join(', ')); } } return tag; }; } function isRemovingTag(removingTags, tag) { return removingTags.indexOf(tag.tagName) > -1; } function isWarningTag(warningTags, tag) { return warningTags.indexOf(tag.tagName) > -1; } // FIXME: Due to limtation of parser, we need to implement our // very own little state machine to express tree structure function createRemoveTags(removingTags) { removingTags = removingTags || []; var removingTag = null; return function removeTags(tag) { if (removingTag == null) { if (isRemovingTag(removingTags, tag)) { removingTag = tag.tagName; } else { return tag; } } else if (tag.tagName === removingTag && tag.type === 'EndTag') { // Reached the end tag of a removingTag removingTag = null; } }; } function createWarnTags(warningTags) { warningTags = warningTags || []; return function warnTags(tag) { if (conditions.isStartTag(tag) && isWarningTag(warningTags, tag)) { console.warn('svg-inline-loader: forbidden tag ' + tag.tagName); } return tag; }; } function getAttributeIndex (tag, attr) { if( tag.attributes !== undefined && tag.attributes.length > 0 ) { for(var i = 0; i < tag.attributes.length; i++) { if(tag.attributes[i][0] === attr) { return i; } } } return -1; } function createClassPrefix(classPrefix) { //http://stackoverflow.com/questions/12391760/regex-match-css-class-name-from-single-string-containing-multiple-classes var re = /\.(-?[_a-zA-Z]+[_a-zA-Z0-9-]*)(?![^\{]*\})/g; var inStyleTag = false; return function prefixClasses(tag) { if( inStyleTag ) { var string = tag.chars; // push matches to an array so we can operate in reverse var match; var matches = []; while(match = re.exec(string)) matches.push(match); // update the string in reverse so our matches indices don't get off for(var i = matches.length-1; i>=0; i--) { string = string.substring(0,matches[i].index+1) + classPrefix + string.substring(matches[i].index+1); } tag.chars = string; inStyleTag = false; } else if (conditions.isStyleToken(tag)) { inStyleTag = true; } else { var classIdx = getAttributeIndex(tag,'class'); if(classIdx >= 0) { //Prefix classes when multiple classes are present var classes = tag.attributes[classIdx][1]; var prefixedClassString = ""; classes = classes.replace(/[ ]+/,' '); classes = classes.split(' '); classes.forEach(function(classI){ prefixedClassString += classPrefix + classI + ' '; }); tag.attributes[classIdx][1] = prefixedClassString; } } return tag; }; } function createIdPrefix(idPrefix) { var url_pattern = /^url\(#.+\)$/i; return function prefixIds(tag) { var idIdx = getAttributeIndex(tag, 'id'); if (idIdx !== -1) { // prefix id definitions tag.attributes[idIdx][1] = idPrefix + tag.attributes[idIdx][1]; } if (tag.tagName == 'use') { // replace references via <use xlink:href='#foo'> var hrefIdx = getAttributeIndex(tag, 'xlink:href'); if (hrefIdx !== -1) { tag.attributes[hrefIdx][1] = '#' + idPrefix + tag.attributes[hrefIdx][1].substring(1); } } if (tag.attributes && tag.attributes.length > 0) { // replace instances of url(#foo) in attributes tag.attributes.forEach(function (attr) { if (attr[1].match(url_pattern)) { attr[1] = attr[1].replace(url_pattern, function (match) { var id = match.substring(5, match.length -1); return "url(#" + idPrefix + id + ")"; }); } }); } return tag; }; } function runTransform(tokens, configOverride) { var transformations = []; var config = conditions.isFilledObject(configOverride) ? assign({}, defaultConfig, configOverride) : defaultConfig; if (config.classPrefix !== false) transformations.push(createClassPrefix(config.classPrefix)); if (config.idPrefix !== false) transformations.push(createIdPrefix(config.idPrefix)); if (config.removeSVGTagAttrs === true) transformations.push(removeSVGTagAttrs); if (config.warnTags.length > 0) transformations.push(createWarnTags(config.warnTags)); if (config.removeTags === true) transformations.push(createRemoveTags(config.removingTags)); if (config.warnTagAttrs.length > 0) transformations.push(createWarnTagAttrs(config.warnTagAttrs)); if (config.removingTagAttrs.length > 0) transformations.push(createRemoveTagAttrs(config.removingTagAttrs)); transformations.forEach(function (transformation) { tokens = tokens.map(transformation); }); return tokens.filter(function (nonNull) { return nonNull; }); } module.exports = { removeSVGTagAttrs: removeSVGTagAttrs, createRemoveTags: createRemoveTags, createClassPrefix: createClassPrefix, runTransform: runTransform };