mirror of
https://github.com/svg/svgo.git
synced 2025-04-19 10:22:15 +03:00
I saw complaints about `extendDefaultPlugins` api - it cannot be used when svgo is installed globally - it requires svgo to be installed when using svgo-loader or svgo-jsx - it prevents using serializable config formats like json In this diff I introduced the new plugin which is a bundle of all default plugins. ```js module.exports = { plugins: [ 'preset_default', // or { name: 'preset_default', floatPrecision: 4, overrides: { convertPathData: { applyTransforms: false } } } ] } ```
301 lines
6.4 KiB
JavaScript
301 lines
6.4 KiB
JavaScript
'use strict';
|
|
|
|
exports.name = 'prefixIds';
|
|
|
|
exports.type = 'perItem';
|
|
|
|
exports.active = false;
|
|
|
|
exports.params = {
|
|
delim: '__',
|
|
prefixIds: true,
|
|
prefixClassNames: true,
|
|
};
|
|
|
|
exports.description = 'prefix IDs';
|
|
|
|
var csstree = require('css-tree'),
|
|
collections = require('./_collections.js'),
|
|
referencesProps = collections.referencesProps,
|
|
rxId = /^#(.*)$/, // regular expression for matching an ID + extracing its name
|
|
addPrefix = null;
|
|
|
|
const unquote = (string) => {
|
|
const first = string.charAt(0);
|
|
if (first === "'" || first === '"') {
|
|
if (first === string.charAt(string.length - 1)) {
|
|
return string.slice(1, -1);
|
|
}
|
|
}
|
|
return string;
|
|
};
|
|
|
|
// Escapes a string for being used as ID
|
|
var escapeIdentifierName = function (str) {
|
|
return str.replace(/[. ]/g, '_');
|
|
};
|
|
|
|
// Matches an #ID value, captures the ID name
|
|
var matchId = function (urlVal) {
|
|
var idUrlMatches = urlVal.match(rxId);
|
|
if (idUrlMatches === null) {
|
|
return false;
|
|
}
|
|
return idUrlMatches[1];
|
|
};
|
|
|
|
// Matches an url(...) value, captures the URL
|
|
var matchUrl = function (val) {
|
|
var urlMatches = /url\((.*?)\)/gi.exec(val);
|
|
if (urlMatches === null) {
|
|
return false;
|
|
}
|
|
return urlMatches[1];
|
|
};
|
|
|
|
// prefixes an #ID
|
|
var prefixId = function (val) {
|
|
var idName = matchId(val);
|
|
if (!idName) {
|
|
return false;
|
|
}
|
|
return '#' + addPrefix(idName);
|
|
};
|
|
|
|
// prefixes a class attribute value
|
|
const addPrefixToClassAttr = (element, name) => {
|
|
if (
|
|
element.attributes[name] == null ||
|
|
element.attributes[name].length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
element.attributes[name] = element.attributes[name]
|
|
.split(/\s+/)
|
|
.map(addPrefix)
|
|
.join(' ');
|
|
};
|
|
|
|
// prefixes an ID attribute value
|
|
const addPrefixToIdAttr = (element, name) => {
|
|
if (
|
|
element.attributes[name] == null ||
|
|
element.attributes[name].length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
element.attributes[name] = addPrefix(element.attributes[name]);
|
|
};
|
|
|
|
// prefixes a href attribute value
|
|
const addPrefixToHrefAttr = (element, name) => {
|
|
if (
|
|
element.attributes[name] == null ||
|
|
element.attributes[name].length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const idPrefixed = prefixId(element.attributes[name]);
|
|
if (!idPrefixed) {
|
|
return;
|
|
}
|
|
element.attributes[name] = idPrefixed;
|
|
};
|
|
|
|
// prefixes an URL attribute value
|
|
const addPrefixToUrlAttr = (element, name) => {
|
|
if (
|
|
element.attributes[name] == null ||
|
|
element.attributes[name].length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// url(...) in value
|
|
const urlVal = matchUrl(element.attributes[name]);
|
|
if (!urlVal) {
|
|
return;
|
|
}
|
|
|
|
const idPrefixed = prefixId(urlVal);
|
|
if (!idPrefixed) {
|
|
return;
|
|
}
|
|
|
|
element.attributes[name] = 'url(' + idPrefixed + ')';
|
|
};
|
|
|
|
// prefixes begin/end attribute value
|
|
const addPrefixToBeginEndAttr = (element, name) => {
|
|
if (
|
|
element.attributes[name] == null ||
|
|
element.attributes[name].length === 0
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const parts = element.attributes[name].split('; ').map((val) => {
|
|
val = val.trim();
|
|
|
|
if (val.endsWith('.end') || val.endsWith('.start')) {
|
|
const [id, postfix] = val.split('.');
|
|
|
|
let idPrefixed = prefixId(`#${id}`);
|
|
|
|
if (!idPrefixed) {
|
|
return val;
|
|
}
|
|
|
|
idPrefixed = idPrefixed.slice(1);
|
|
return `${idPrefixed}.${postfix}`;
|
|
} else {
|
|
return val;
|
|
}
|
|
});
|
|
|
|
element.attributes[name] = parts.join('; ');
|
|
};
|
|
|
|
const getBasename = (path) => {
|
|
// extract everything after latest slash or backslash
|
|
const matched = path.match(/[/\\]([^/\\]+)$/);
|
|
if (matched) {
|
|
return matched[1];
|
|
}
|
|
return '';
|
|
};
|
|
|
|
/**
|
|
* Prefixes identifiers
|
|
*
|
|
* @param {Object} node node
|
|
* @param {Object} opts plugin params
|
|
* @param {Object} extra plugin extra information
|
|
*
|
|
* @author strarsis <strarsis@gmail.com>
|
|
*/
|
|
exports.fn = function (node, opts, extra) {
|
|
// skip subsequent passes when multipass is used
|
|
if (extra.multipassCount && extra.multipassCount > 0) {
|
|
return;
|
|
}
|
|
|
|
// prefix, from file name or option
|
|
var prefix = 'prefix';
|
|
if (opts.prefix) {
|
|
if (typeof opts.prefix === 'function') {
|
|
prefix = opts.prefix(node, extra);
|
|
} else {
|
|
prefix = opts.prefix;
|
|
}
|
|
} else if (opts.prefix === false) {
|
|
prefix = false;
|
|
} else if (extra && extra.path && extra.path.length > 0) {
|
|
var filename = getBasename(extra.path);
|
|
prefix = filename;
|
|
}
|
|
|
|
// prefixes a normal value
|
|
addPrefix = function (name) {
|
|
if (prefix === false) {
|
|
return escapeIdentifierName(name);
|
|
}
|
|
return escapeIdentifierName(prefix + opts.delim + name);
|
|
};
|
|
|
|
// <style/> property values
|
|
|
|
if (node.type === 'element' && node.name === 'style') {
|
|
if (node.children.length === 0) {
|
|
// skip empty <style/>s
|
|
return;
|
|
}
|
|
|
|
var cssStr = '';
|
|
if (node.children[0].type === 'text' || node.children[0].type === 'cdata') {
|
|
cssStr = node.children[0].value;
|
|
}
|
|
|
|
var cssAst = {};
|
|
try {
|
|
cssAst = csstree.parse(cssStr, {
|
|
parseValue: true,
|
|
parseCustomProperty: false,
|
|
});
|
|
} catch (parseError) {
|
|
console.warn(
|
|
'Warning: Parse error of styles of <style/> element, skipped. Error details: ' +
|
|
parseError
|
|
);
|
|
return;
|
|
}
|
|
|
|
var idPrefixed = '';
|
|
csstree.walk(cssAst, function (node) {
|
|
// #ID, .class
|
|
if (
|
|
((opts.prefixIds && node.type === 'IdSelector') ||
|
|
(opts.prefixClassNames && node.type === 'ClassSelector')) &&
|
|
node.name
|
|
) {
|
|
node.name = addPrefix(node.name);
|
|
return;
|
|
}
|
|
|
|
// url(...) in value
|
|
if (
|
|
node.type === 'Url' &&
|
|
node.value.value &&
|
|
node.value.value.length > 0
|
|
) {
|
|
idPrefixed = prefixId(unquote(node.value.value));
|
|
if (!idPrefixed) {
|
|
return;
|
|
}
|
|
node.value.value = idPrefixed;
|
|
}
|
|
});
|
|
|
|
// update <style>s
|
|
node.children[0].value = csstree.generate(cssAst);
|
|
return;
|
|
}
|
|
|
|
// element attributes
|
|
|
|
if (node.type !== 'element') {
|
|
return;
|
|
}
|
|
|
|
// Nodes
|
|
|
|
if (opts.prefixIds) {
|
|
// ID
|
|
addPrefixToIdAttr(node, 'id');
|
|
}
|
|
|
|
if (opts.prefixClassNames) {
|
|
// Class
|
|
addPrefixToClassAttr(node, 'class');
|
|
}
|
|
|
|
// References
|
|
|
|
// href
|
|
addPrefixToHrefAttr(node, 'href');
|
|
|
|
// (xlink:)href (deprecated, must be still supported)
|
|
addPrefixToHrefAttr(node, 'xlink:href');
|
|
|
|
// (referenceable) properties
|
|
for (var referencesProp of referencesProps) {
|
|
addPrefixToUrlAttr(node, referencesProp);
|
|
}
|
|
|
|
addPrefixToBeginEndAttr(node, 'begin');
|
|
addPrefixToBeginEndAttr(node, 'end');
|
|
};
|