svgo-v2 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/bin/svgo +10 -0
- package/dist/svgo.browser.js +1 -0
- package/lib/css-tools.js +239 -0
- package/lib/parser.js +259 -0
- package/lib/path.js +347 -0
- package/lib/stringifier.js +326 -0
- package/lib/style.js +283 -0
- package/lib/svgo/coa.js +517 -0
- package/lib/svgo/config.js +138 -0
- package/lib/svgo/css-class-list.js +72 -0
- package/lib/svgo/css-select-adapter.d.ts +2 -0
- package/lib/svgo/css-select-adapter.js +120 -0
- package/lib/svgo/css-style-declaration.js +232 -0
- package/lib/svgo/jsAPI.d.ts +2 -0
- package/lib/svgo/jsAPI.js +443 -0
- package/lib/svgo/plugins.js +109 -0
- package/lib/svgo/tools.js +137 -0
- package/lib/svgo-node.js +106 -0
- package/lib/svgo.js +83 -0
- package/lib/types.ts +172 -0
- package/lib/xast.js +102 -0
- package/package.json +130 -0
- package/plugins/_applyTransforms.js +335 -0
- package/plugins/_collections.js +2168 -0
- package/plugins/_path.js +816 -0
- package/plugins/_transforms.js +379 -0
- package/plugins/addAttributesToSVGElement.js +87 -0
- package/plugins/addClassesToSVGElement.js +87 -0
- package/plugins/cleanupAttrs.js +55 -0
- package/plugins/cleanupEnableBackground.js +75 -0
- package/plugins/cleanupIDs.js +297 -0
- package/plugins/cleanupListOfValues.js +154 -0
- package/plugins/cleanupNumericValues.js +113 -0
- package/plugins/collapseGroups.js +135 -0
- package/plugins/convertColors.js +152 -0
- package/plugins/convertEllipseToCircle.js +39 -0
- package/plugins/convertPathData.js +1023 -0
- package/plugins/convertShapeToPath.js +175 -0
- package/plugins/convertStyleToAttrs.js +132 -0
- package/plugins/convertTransform.js +432 -0
- package/plugins/inlineStyles.js +379 -0
- package/plugins/mergePaths.js +104 -0
- package/plugins/mergeStyles.js +93 -0
- package/plugins/minifyStyles.js +148 -0
- package/plugins/moveElemsAttrsToGroup.js +130 -0
- package/plugins/moveGroupAttrsToElems.js +62 -0
- package/plugins/plugins.js +56 -0
- package/plugins/prefixIds.js +241 -0
- package/plugins/preset-default.js +80 -0
- package/plugins/removeAttributesBySelector.js +99 -0
- package/plugins/removeAttrs.js +159 -0
- package/plugins/removeComments.js +31 -0
- package/plugins/removeDesc.js +41 -0
- package/plugins/removeDimensions.js +43 -0
- package/plugins/removeDoctype.js +42 -0
- package/plugins/removeEditorsNSData.js +68 -0
- package/plugins/removeElementsByAttr.js +78 -0
- package/plugins/removeEmptyAttrs.js +33 -0
- package/plugins/removeEmptyContainers.js +58 -0
- package/plugins/removeEmptyText.js +57 -0
- package/plugins/removeHiddenElems.js +318 -0
- package/plugins/removeMetadata.js +29 -0
- package/plugins/removeNonInheritableGroupAttrs.js +38 -0
- package/plugins/removeOffCanvasPaths.js +138 -0
- package/plugins/removeRasterImages.js +33 -0
- package/plugins/removeScriptElement.js +29 -0
- package/plugins/removeStyleElement.js +29 -0
- package/plugins/removeTitle.js +29 -0
- package/plugins/removeUnknownsAndDefaults.js +218 -0
- package/plugins/removeUnusedNS.js +61 -0
- package/plugins/removeUselessDefs.js +65 -0
- package/plugins/removeUselessStrokeAndFill.js +144 -0
- package/plugins/removeViewBox.js +51 -0
- package/plugins/removeXMLNS.js +30 -0
- package/plugins/removeXMLProcInst.js +30 -0
- package/plugins/reusePaths.js +113 -0
- package/plugins/sortAttrs.js +113 -0
- package/plugins/sortDefsChildren.js +60 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('../lib/types').XastElement} XastElement
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const csso = require('csso');
|
|
8
|
+
|
|
9
|
+
exports.type = 'visitor';
|
|
10
|
+
exports.name = 'minifyStyles';
|
|
11
|
+
exports.active = true;
|
|
12
|
+
exports.description =
|
|
13
|
+
'minifies styles and removes unused styles based on usage data';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Minifies styles (<style> element + style attribute) using CSSO
|
|
17
|
+
*
|
|
18
|
+
* @author strarsis <strarsis@gmail.com>
|
|
19
|
+
*
|
|
20
|
+
* @type {import('../lib/types').Plugin<csso.MinifyOptions & Omit<csso.CompressOptions, 'usage'> & {
|
|
21
|
+
* usage?: boolean | {
|
|
22
|
+
* force?: boolean,
|
|
23
|
+
* ids?: boolean,
|
|
24
|
+
* classes?: boolean,
|
|
25
|
+
* tags?: boolean
|
|
26
|
+
* }
|
|
27
|
+
* }>}
|
|
28
|
+
*/
|
|
29
|
+
exports.fn = (_root, { usage, ...params }) => {
|
|
30
|
+
let enableTagsUsage = true;
|
|
31
|
+
let enableIdsUsage = true;
|
|
32
|
+
let enableClassesUsage = true;
|
|
33
|
+
// force to use usage data even if it unsafe (document contains <script> or on* attributes)
|
|
34
|
+
let forceUsageDeoptimized = false;
|
|
35
|
+
if (typeof usage === 'boolean') {
|
|
36
|
+
enableTagsUsage = usage;
|
|
37
|
+
enableIdsUsage = usage;
|
|
38
|
+
enableClassesUsage = usage;
|
|
39
|
+
} else if (usage) {
|
|
40
|
+
enableTagsUsage = usage.tags == null ? true : usage.tags;
|
|
41
|
+
enableIdsUsage = usage.ids == null ? true : usage.ids;
|
|
42
|
+
enableClassesUsage = usage.classes == null ? true : usage.classes;
|
|
43
|
+
forceUsageDeoptimized = usage.force == null ? false : usage.force;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* @type {Array<XastElement>}
|
|
47
|
+
*/
|
|
48
|
+
const styleElements = [];
|
|
49
|
+
/**
|
|
50
|
+
* @type {Array<XastElement>}
|
|
51
|
+
*/
|
|
52
|
+
const elementsWithStyleAttributes = [];
|
|
53
|
+
let deoptimized = false;
|
|
54
|
+
/**
|
|
55
|
+
* @type {Set<string>}
|
|
56
|
+
*/
|
|
57
|
+
const tagsUsage = new Set();
|
|
58
|
+
/**
|
|
59
|
+
* @type {Set<string>}
|
|
60
|
+
*/
|
|
61
|
+
const idsUsage = new Set();
|
|
62
|
+
/**
|
|
63
|
+
* @type {Set<string>}
|
|
64
|
+
*/
|
|
65
|
+
const classesUsage = new Set();
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
element: {
|
|
69
|
+
enter: (node) => {
|
|
70
|
+
// detect deoptimisations
|
|
71
|
+
if (node.name === 'script') {
|
|
72
|
+
deoptimized = true;
|
|
73
|
+
}
|
|
74
|
+
for (const name of Object.keys(node.attributes)) {
|
|
75
|
+
if (name.startsWith('on')) {
|
|
76
|
+
deoptimized = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// collect tags, ids and classes usage
|
|
80
|
+
tagsUsage.add(node.name);
|
|
81
|
+
if (node.attributes.id != null) {
|
|
82
|
+
idsUsage.add(node.attributes.id);
|
|
83
|
+
}
|
|
84
|
+
if (node.attributes.class != null) {
|
|
85
|
+
for (const className of node.attributes.class.split(/\s+/)) {
|
|
86
|
+
classesUsage.add(className);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// collect style elements or elements with style attribute
|
|
90
|
+
if (node.name === 'style' && node.children.length !== 0) {
|
|
91
|
+
styleElements.push(node);
|
|
92
|
+
} else if (node.attributes.style != null) {
|
|
93
|
+
elementsWithStyleAttributes.push(node);
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
root: {
|
|
99
|
+
exit: () => {
|
|
100
|
+
/**
|
|
101
|
+
* @type {csso.Usage}
|
|
102
|
+
*/
|
|
103
|
+
const cssoUsage = {};
|
|
104
|
+
if (deoptimized === false || forceUsageDeoptimized === true) {
|
|
105
|
+
if (enableTagsUsage && tagsUsage.size !== 0) {
|
|
106
|
+
cssoUsage.tags = Array.from(tagsUsage);
|
|
107
|
+
}
|
|
108
|
+
if (enableIdsUsage && idsUsage.size !== 0) {
|
|
109
|
+
cssoUsage.ids = Array.from(idsUsage);
|
|
110
|
+
}
|
|
111
|
+
if (enableClassesUsage && classesUsage.size !== 0) {
|
|
112
|
+
cssoUsage.classes = Array.from(classesUsage);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// minify style elements
|
|
116
|
+
for (const node of styleElements) {
|
|
117
|
+
if (
|
|
118
|
+
node.children[0].type === 'text' ||
|
|
119
|
+
node.children[0].type === 'cdata'
|
|
120
|
+
) {
|
|
121
|
+
const cssText = node.children[0].value;
|
|
122
|
+
const minified = csso.minify(cssText, {
|
|
123
|
+
...params,
|
|
124
|
+
usage: cssoUsage,
|
|
125
|
+
}).css;
|
|
126
|
+
// preserve cdata if necessary
|
|
127
|
+
// TODO split cdata -> text optimisation into separate plugin
|
|
128
|
+
if (cssText.indexOf('>') >= 0 || cssText.indexOf('<') >= 0) {
|
|
129
|
+
node.children[0].type = 'cdata';
|
|
130
|
+
node.children[0].value = minified;
|
|
131
|
+
} else {
|
|
132
|
+
node.children[0].type = 'text';
|
|
133
|
+
node.children[0].value = minified;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// minify style attributes
|
|
138
|
+
for (const node of elementsWithStyleAttributes) {
|
|
139
|
+
// style attribute
|
|
140
|
+
const elemStyle = node.attributes.style;
|
|
141
|
+
node.attributes.style = csso.minifyBlock(elemStyle, {
|
|
142
|
+
...params,
|
|
143
|
+
}).css;
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
};
|
|
148
|
+
};
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { visit } = require('../lib/xast.js');
|
|
4
|
+
const { inheritableAttrs, pathElems } = require('./_collections.js');
|
|
5
|
+
|
|
6
|
+
exports.type = 'visitor';
|
|
7
|
+
exports.name = 'moveElemsAttrsToGroup';
|
|
8
|
+
exports.active = true;
|
|
9
|
+
exports.description = 'Move common attributes of group children to the group';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Move common attributes of group children to the group
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* <g attr1="val1">
|
|
16
|
+
* <g attr2="val2">
|
|
17
|
+
* text
|
|
18
|
+
* </g>
|
|
19
|
+
* <circle attr2="val2" attr3="val3"/>
|
|
20
|
+
* </g>
|
|
21
|
+
* ⬇
|
|
22
|
+
* <g attr1="val1" attr2="val2">
|
|
23
|
+
* <g>
|
|
24
|
+
* text
|
|
25
|
+
* </g>
|
|
26
|
+
* <circle attr3="val3"/>
|
|
27
|
+
* </g>
|
|
28
|
+
*
|
|
29
|
+
* @author Kir Belevich
|
|
30
|
+
*
|
|
31
|
+
* @type {import('../lib/types').Plugin<void>}
|
|
32
|
+
*/
|
|
33
|
+
exports.fn = (root) => {
|
|
34
|
+
// find if any style element is present
|
|
35
|
+
let deoptimizedWithStyles = false;
|
|
36
|
+
visit(root, {
|
|
37
|
+
element: {
|
|
38
|
+
enter: (node) => {
|
|
39
|
+
if (node.name === 'style') {
|
|
40
|
+
deoptimizedWithStyles = true;
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
element: {
|
|
48
|
+
exit: (node) => {
|
|
49
|
+
// process only groups with more than 1 children
|
|
50
|
+
if (node.name !== 'g' || node.children.length <= 1) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// deoptimize the plugin when style elements are present
|
|
55
|
+
// selectors may rely on id, classes or tag names
|
|
56
|
+
if (deoptimizedWithStyles) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* find common attributes in group children
|
|
62
|
+
* @type {Map<string, string>}
|
|
63
|
+
*/
|
|
64
|
+
const commonAttributes = new Map();
|
|
65
|
+
let initial = true;
|
|
66
|
+
let everyChildIsPath = true;
|
|
67
|
+
for (const child of node.children) {
|
|
68
|
+
if (child.type === 'element') {
|
|
69
|
+
if (pathElems.includes(child.name) === false) {
|
|
70
|
+
everyChildIsPath = false;
|
|
71
|
+
}
|
|
72
|
+
if (initial) {
|
|
73
|
+
initial = false;
|
|
74
|
+
// collect all inheritable attributes from first child element
|
|
75
|
+
for (const [name, value] of Object.entries(child.attributes)) {
|
|
76
|
+
// consider only inheritable attributes
|
|
77
|
+
if (inheritableAttrs.includes(name)) {
|
|
78
|
+
commonAttributes.set(name, value);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// exclude uncommon attributes from initial list
|
|
83
|
+
for (const [name, value] of commonAttributes) {
|
|
84
|
+
if (child.attributes[name] !== value) {
|
|
85
|
+
commonAttributes.delete(name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// preserve transform on children when group has clip-path or mask
|
|
93
|
+
if (
|
|
94
|
+
node.attributes['clip-path'] != null ||
|
|
95
|
+
node.attributes.mask != null
|
|
96
|
+
) {
|
|
97
|
+
commonAttributes.delete('transform');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// preserve transform when all children are paths
|
|
101
|
+
// so the transform could be applied to path data by other plugins
|
|
102
|
+
if (everyChildIsPath) {
|
|
103
|
+
commonAttributes.delete('transform');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// add common children attributes to group
|
|
107
|
+
for (const [name, value] of commonAttributes) {
|
|
108
|
+
if (name === 'transform') {
|
|
109
|
+
if (node.attributes.transform != null) {
|
|
110
|
+
node.attributes.transform = `${node.attributes.transform} ${value}`;
|
|
111
|
+
} else {
|
|
112
|
+
node.attributes.transform = value;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
node.attributes[name] = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// delete common attributes from children
|
|
120
|
+
for (const child of node.children) {
|
|
121
|
+
if (child.type === 'element') {
|
|
122
|
+
for (const [name] of commonAttributes) {
|
|
123
|
+
delete child.attributes[name];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { pathElems, referencesProps } = require('./_collections.js');
|
|
4
|
+
|
|
5
|
+
exports.name = 'moveGroupAttrsToElems';
|
|
6
|
+
|
|
7
|
+
exports.type = 'perItem';
|
|
8
|
+
|
|
9
|
+
exports.active = true;
|
|
10
|
+
|
|
11
|
+
exports.description = 'moves some group attributes to the content elements';
|
|
12
|
+
|
|
13
|
+
const pathElemsWithGroupsAndText = [...pathElems, 'g', 'text'];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Move group attrs to the content elements.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* <g transform="scale(2)">
|
|
20
|
+
* <path transform="rotate(45)" d="M0,0 L10,20"/>
|
|
21
|
+
* <path transform="translate(10, 20)" d="M0,10 L20,30"/>
|
|
22
|
+
* </g>
|
|
23
|
+
* ⬇
|
|
24
|
+
* <g>
|
|
25
|
+
* <path transform="scale(2) rotate(45)" d="M0,0 L10,20"/>
|
|
26
|
+
* <path transform="scale(2) translate(10, 20)" d="M0,10 L20,30"/>
|
|
27
|
+
* </g>
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} item current iteration item
|
|
30
|
+
* @return {Boolean} if false, item will be filtered out
|
|
31
|
+
*
|
|
32
|
+
* @author Kir Belevich
|
|
33
|
+
*/
|
|
34
|
+
exports.fn = function (item) {
|
|
35
|
+
// move group transform attr to content's pathElems
|
|
36
|
+
if (
|
|
37
|
+
item.type === 'element' &&
|
|
38
|
+
item.name === 'g' &&
|
|
39
|
+
item.children.length !== 0 &&
|
|
40
|
+
item.attributes.transform != null &&
|
|
41
|
+
Object.entries(item.attributes).some(
|
|
42
|
+
([name, value]) =>
|
|
43
|
+
referencesProps.includes(name) && value.includes('url(')
|
|
44
|
+
) === false &&
|
|
45
|
+
item.children.every(
|
|
46
|
+
(inner) =>
|
|
47
|
+
pathElemsWithGroupsAndText.includes(inner.name) &&
|
|
48
|
+
inner.attributes.id == null
|
|
49
|
+
)
|
|
50
|
+
) {
|
|
51
|
+
for (const inner of item.children) {
|
|
52
|
+
const value = item.attributes.transform;
|
|
53
|
+
if (inner.attributes.transform != null) {
|
|
54
|
+
inner.attributes.transform = value + ' ' + inner.attributes.transform;
|
|
55
|
+
} else {
|
|
56
|
+
inner.attributes.transform = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
delete item.attributes.transform;
|
|
61
|
+
}
|
|
62
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// builtin presets
|
|
4
|
+
exports['preset-default'] = require('./preset-default.js');
|
|
5
|
+
|
|
6
|
+
// builtin plugins
|
|
7
|
+
exports.addAttributesToSVGElement = require('./addAttributesToSVGElement.js');
|
|
8
|
+
exports.addClassesToSVGElement = require('./addClassesToSVGElement.js');
|
|
9
|
+
exports.cleanupAttrs = require('./cleanupAttrs.js');
|
|
10
|
+
exports.cleanupEnableBackground = require('./cleanupEnableBackground.js');
|
|
11
|
+
exports.cleanupIDs = require('./cleanupIDs.js');
|
|
12
|
+
exports.cleanupListOfValues = require('./cleanupListOfValues.js');
|
|
13
|
+
exports.cleanupNumericValues = require('./cleanupNumericValues.js');
|
|
14
|
+
exports.collapseGroups = require('./collapseGroups.js');
|
|
15
|
+
exports.convertColors = require('./convertColors.js');
|
|
16
|
+
exports.convertEllipseToCircle = require('./convertEllipseToCircle.js');
|
|
17
|
+
exports.convertPathData = require('./convertPathData.js');
|
|
18
|
+
exports.convertShapeToPath = require('./convertShapeToPath.js');
|
|
19
|
+
exports.convertStyleToAttrs = require('./convertStyleToAttrs.js');
|
|
20
|
+
exports.convertTransform = require('./convertTransform.js');
|
|
21
|
+
exports.mergeStyles = require('./mergeStyles.js');
|
|
22
|
+
exports.inlineStyles = require('./inlineStyles.js');
|
|
23
|
+
exports.mergePaths = require('./mergePaths.js');
|
|
24
|
+
exports.minifyStyles = require('./minifyStyles.js');
|
|
25
|
+
exports.moveElemsAttrsToGroup = require('./moveElemsAttrsToGroup.js');
|
|
26
|
+
exports.moveGroupAttrsToElems = require('./moveGroupAttrsToElems.js');
|
|
27
|
+
exports.prefixIds = require('./prefixIds.js');
|
|
28
|
+
exports.removeAttributesBySelector = require('./removeAttributesBySelector.js');
|
|
29
|
+
exports.removeAttrs = require('./removeAttrs.js');
|
|
30
|
+
exports.removeComments = require('./removeComments.js');
|
|
31
|
+
exports.removeDesc = require('./removeDesc.js');
|
|
32
|
+
exports.removeDimensions = require('./removeDimensions.js');
|
|
33
|
+
exports.removeDoctype = require('./removeDoctype.js');
|
|
34
|
+
exports.removeEditorsNSData = require('./removeEditorsNSData.js');
|
|
35
|
+
exports.removeElementsByAttr = require('./removeElementsByAttr.js');
|
|
36
|
+
exports.removeEmptyAttrs = require('./removeEmptyAttrs.js');
|
|
37
|
+
exports.removeEmptyContainers = require('./removeEmptyContainers.js');
|
|
38
|
+
exports.removeEmptyText = require('./removeEmptyText.js');
|
|
39
|
+
exports.removeHiddenElems = require('./removeHiddenElems.js');
|
|
40
|
+
exports.removeMetadata = require('./removeMetadata.js');
|
|
41
|
+
exports.removeNonInheritableGroupAttrs = require('./removeNonInheritableGroupAttrs.js');
|
|
42
|
+
exports.removeOffCanvasPaths = require('./removeOffCanvasPaths.js');
|
|
43
|
+
exports.removeRasterImages = require('./removeRasterImages.js');
|
|
44
|
+
exports.removeScriptElement = require('./removeScriptElement.js');
|
|
45
|
+
exports.removeStyleElement = require('./removeStyleElement.js');
|
|
46
|
+
exports.removeTitle = require('./removeTitle.js');
|
|
47
|
+
exports.removeUnknownsAndDefaults = require('./removeUnknownsAndDefaults.js');
|
|
48
|
+
exports.removeUnusedNS = require('./removeUnusedNS.js');
|
|
49
|
+
exports.removeUselessDefs = require('./removeUselessDefs.js');
|
|
50
|
+
exports.removeUselessStrokeAndFill = require('./removeUselessStrokeAndFill.js');
|
|
51
|
+
exports.removeViewBox = require('./removeViewBox.js');
|
|
52
|
+
exports.removeXMLNS = require('./removeXMLNS.js');
|
|
53
|
+
exports.removeXMLProcInst = require('./removeXMLProcInst.js');
|
|
54
|
+
exports.reusePaths = require('./reusePaths.js');
|
|
55
|
+
exports.sortAttrs = require('./sortAttrs.js');
|
|
56
|
+
exports.sortDefsChildren = require('./sortDefsChildren.js');
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const csstree = require('css-tree-v2');
|
|
4
|
+
const { referencesProps } = require('./_collections.js');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef {import('../lib/types').XastElement} XastElement
|
|
8
|
+
* @typedef {import('../lib/types').PluginInfo} PluginInfo
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
exports.type = 'visitor';
|
|
12
|
+
exports.name = 'prefixIds';
|
|
13
|
+
exports.active = false;
|
|
14
|
+
exports.description = 'prefix IDs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* extract basename from path
|
|
18
|
+
* @type {(path: string) => string}
|
|
19
|
+
*/
|
|
20
|
+
const getBasename = (path) => {
|
|
21
|
+
// extract everything after latest slash or backslash
|
|
22
|
+
const matched = path.match(/[/\\]?([^/\\]+)$/);
|
|
23
|
+
if (matched) {
|
|
24
|
+
return matched[1];
|
|
25
|
+
}
|
|
26
|
+
return '';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* escapes a string for being used as ID
|
|
31
|
+
* @type {(string: string) => string}
|
|
32
|
+
*/
|
|
33
|
+
const escapeIdentifierName = (str) => {
|
|
34
|
+
return str.replace(/[. ]/g, '_');
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @type {(string: string) => string}
|
|
39
|
+
*/
|
|
40
|
+
const unquote = (string) => {
|
|
41
|
+
if (
|
|
42
|
+
(string.startsWith('"') && string.endsWith('"')) ||
|
|
43
|
+
(string.startsWith("'") && string.endsWith("'"))
|
|
44
|
+
) {
|
|
45
|
+
return string.slice(1, -1);
|
|
46
|
+
}
|
|
47
|
+
return string;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* prefix an ID
|
|
52
|
+
* @type {(prefix: string, name: string) => string}
|
|
53
|
+
*/
|
|
54
|
+
const prefixId = (prefix, value) => {
|
|
55
|
+
if (value.startsWith(prefix)) {
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
return prefix + value;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* prefix an #ID
|
|
63
|
+
* @type {(prefix: string, name: string) => string | null}
|
|
64
|
+
*/
|
|
65
|
+
const prefixReference = (prefix, value) => {
|
|
66
|
+
if (value.startsWith('#')) {
|
|
67
|
+
return '#' + prefixId(prefix, value.slice(1));
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Prefixes identifiers
|
|
74
|
+
*
|
|
75
|
+
* @author strarsis <strarsis@gmail.com>
|
|
76
|
+
*
|
|
77
|
+
* @type {import('../lib/types').Plugin<{
|
|
78
|
+
* prefix?: boolean | string | ((node: XastElement, info: PluginInfo) => string),
|
|
79
|
+
* delim?: string,
|
|
80
|
+
* prefixIds?: boolean,
|
|
81
|
+
* prefixClassNames?: boolean,
|
|
82
|
+
* }>}
|
|
83
|
+
*/
|
|
84
|
+
exports.fn = (_root, params, info) => {
|
|
85
|
+
const { delim = '__', prefixIds = true, prefixClassNames = true } = params;
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
element: {
|
|
89
|
+
enter: (node) => {
|
|
90
|
+
/**
|
|
91
|
+
* prefix, from file name or option
|
|
92
|
+
* @type {string}
|
|
93
|
+
*/
|
|
94
|
+
let prefix = 'prefix' + delim;
|
|
95
|
+
if (typeof params.prefix === 'function') {
|
|
96
|
+
prefix = params.prefix(node, info) + delim;
|
|
97
|
+
} else if (typeof params.prefix === 'string') {
|
|
98
|
+
prefix = params.prefix + delim;
|
|
99
|
+
} else if (params.prefix === false) {
|
|
100
|
+
prefix = '';
|
|
101
|
+
} else if (info.path != null && info.path.length > 0) {
|
|
102
|
+
prefix = escapeIdentifierName(getBasename(info.path)) + delim;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// prefix id/class selectors and url() references in styles
|
|
106
|
+
if (node.name === 'style') {
|
|
107
|
+
// skip empty <style/> elements
|
|
108
|
+
if (node.children.length === 0) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// parse styles
|
|
113
|
+
let cssText = '';
|
|
114
|
+
if (
|
|
115
|
+
node.children[0].type === 'text' ||
|
|
116
|
+
node.children[0].type === 'cdata'
|
|
117
|
+
) {
|
|
118
|
+
cssText = node.children[0].value;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* @type {null | csstree.CssNode}
|
|
122
|
+
*/
|
|
123
|
+
let cssAst = null;
|
|
124
|
+
try {
|
|
125
|
+
cssAst = csstree.parse(cssText, {
|
|
126
|
+
parseValue: true,
|
|
127
|
+
parseCustomProperty: false,
|
|
128
|
+
});
|
|
129
|
+
} catch {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
csstree.walk(cssAst, (node) => {
|
|
134
|
+
// #ID, .class selectors
|
|
135
|
+
if (
|
|
136
|
+
(prefixIds && node.type === 'IdSelector') ||
|
|
137
|
+
(prefixClassNames && node.type === 'ClassSelector')
|
|
138
|
+
) {
|
|
139
|
+
node.name = prefixId(prefix, node.name);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
// url(...) references
|
|
143
|
+
if (
|
|
144
|
+
node.type === 'Url' &&
|
|
145
|
+
node.value.value &&
|
|
146
|
+
node.value.value.length > 0
|
|
147
|
+
) {
|
|
148
|
+
const prefixed = prefixReference(
|
|
149
|
+
prefix,
|
|
150
|
+
unquote(node.value.value)
|
|
151
|
+
);
|
|
152
|
+
if (prefixed != null) {
|
|
153
|
+
node.value.value = prefixed;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// update styles
|
|
159
|
+
if (
|
|
160
|
+
node.children[0].type === 'text' ||
|
|
161
|
+
node.children[0].type === 'cdata'
|
|
162
|
+
) {
|
|
163
|
+
node.children[0].value = csstree.generate(cssAst);
|
|
164
|
+
}
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// prefix an ID attribute value
|
|
169
|
+
if (
|
|
170
|
+
prefixIds &&
|
|
171
|
+
node.attributes.id != null &&
|
|
172
|
+
node.attributes.id.length !== 0
|
|
173
|
+
) {
|
|
174
|
+
node.attributes.id = prefixId(prefix, node.attributes.id);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// prefix a class attribute value
|
|
178
|
+
if (
|
|
179
|
+
prefixClassNames &&
|
|
180
|
+
node.attributes.class != null &&
|
|
181
|
+
node.attributes.class.length !== 0
|
|
182
|
+
) {
|
|
183
|
+
node.attributes.class = node.attributes.class
|
|
184
|
+
.split(/\s+/)
|
|
185
|
+
.map((name) => prefixId(prefix, name))
|
|
186
|
+
.join(' ');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// prefix a href attribute value
|
|
190
|
+
// xlink:href is deprecated, must be still supported
|
|
191
|
+
for (const name of ['href', 'xlink:href']) {
|
|
192
|
+
if (
|
|
193
|
+
node.attributes[name] != null &&
|
|
194
|
+
node.attributes[name].length !== 0
|
|
195
|
+
) {
|
|
196
|
+
const prefixed = prefixReference(prefix, node.attributes[name]);
|
|
197
|
+
if (prefixed != null) {
|
|
198
|
+
node.attributes[name] = prefixed;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// prefix an URL attribute value
|
|
204
|
+
for (const name of referencesProps) {
|
|
205
|
+
if (
|
|
206
|
+
node.attributes[name] != null &&
|
|
207
|
+
node.attributes[name].length !== 0
|
|
208
|
+
) {
|
|
209
|
+
node.attributes[name] = node.attributes[name].replace(
|
|
210
|
+
/url\((.*?)\)/gi,
|
|
211
|
+
(match, url) => {
|
|
212
|
+
const prefixed = prefixReference(prefix, url);
|
|
213
|
+
if (prefixed == null) {
|
|
214
|
+
return match;
|
|
215
|
+
}
|
|
216
|
+
return `url(${prefixed})`;
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// prefix begin/end attribute value
|
|
223
|
+
for (const name of ['begin', 'end']) {
|
|
224
|
+
if (
|
|
225
|
+
node.attributes[name] != null &&
|
|
226
|
+
node.attributes[name].length !== 0
|
|
227
|
+
) {
|
|
228
|
+
const parts = node.attributes[name].split(/\s*;\s+/).map((val) => {
|
|
229
|
+
if (val.endsWith('.end') || val.endsWith('.start')) {
|
|
230
|
+
const [id, postfix] = val.split('.');
|
|
231
|
+
return `${prefixId(prefix, id)}.${postfix}`;
|
|
232
|
+
}
|
|
233
|
+
return val;
|
|
234
|
+
});
|
|
235
|
+
node.attributes[name] = parts.join('; ');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
};
|