gulp-stacksvg 1.0.6 → 2.0.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/README.md +11 -7
- package/index.js +67 -27
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[![NPM version][npm-image]][npm-url]
|
|
6
6
|
[![Vulnerabilities count][vulnerabilities-image]][vulnerabilities-url]
|
|
7
7
|
|
|
8
|
-
Combine
|
|
8
|
+
Combine SVG icon files into one with the stack method.
|
|
9
9
|
|
|
10
10
|
## Installation
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ npm install gulp-stacksvg --save-dev
|
|
|
15
15
|
|
|
16
16
|
## Usage
|
|
17
17
|
|
|
18
|
-
The following script will combine all
|
|
18
|
+
The following script will combine all SVG sources into a single SVG file with stack method.
|
|
19
19
|
|
|
20
20
|
```js
|
|
21
21
|
import { stacksvg } from "gulp-stacksvg"
|
|
@@ -30,7 +30,7 @@ function makeStack () {
|
|
|
30
30
|
}
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
###
|
|
33
|
+
### Available options
|
|
34
34
|
|
|
35
35
|
| Option | Description | Default |
|
|
36
36
|
|-------------|--------------------------------------------------------------------------------------|-------------|
|
|
@@ -61,10 +61,10 @@ We can use the stack in all four possible ways:
|
|
|
61
61
|
|
|
62
62
|
This method was first mentioned in a Simurai [article](https://simurai.com/blog/2012/04/02/svg-stacks) on April 2, 2012. But even it uses unnecessarily complex code transformations.
|
|
63
63
|
|
|
64
|
-
This can be done much easier. In general, the stack is arranged almost like a symbol sprite, but without changing the icon tag (it
|
|
64
|
+
This can be done much easier. In general, the stack is arranged almost like a symbol sprite, but without changing the icon tag (it remains the `svg` tag, as in the original icon files) and with the addition of a tiny bit of style.
|
|
65
65
|
|
|
66
66
|
```xml
|
|
67
|
-
<svg xmlns="http://www.w3.org/2000/svg"
|
|
67
|
+
<svg xmlns="http://www.w3.org/2000/svg">
|
|
68
68
|
|
|
69
69
|
<style>:root svg:not(:target) { display: none }</style>
|
|
70
70
|
```
|
|
@@ -103,8 +103,6 @@ The magic is in the stack inner style, which shows only the fragment requested b
|
|
|
103
103
|
:root svg:not(:target) { display: none }
|
|
104
104
|
```
|
|
105
105
|
|
|
106
|
-
It shows only the fragment that is requested by its link.
|
|
107
|
-
|
|
108
106
|
And now the icons from the external sprite are available in the styles <img width="16" height="16" title="heart" src="https://raw.githubusercontent.com/firefoxic/gulp-stacksvg/main/test/stack.svg#heart-red" alt="heart">
|
|
109
107
|
|
|
110
108
|
```html
|
|
@@ -144,6 +142,12 @@ And now the icons from the external sprite are available in the styles <img widt
|
|
|
144
142
|
|
|
145
143
|
For an icon inserted via `mask`, simply change the `background`. Moreover, unlike `use`, you can draw anything in the background under the mask, for example, a gradient.
|
|
146
144
|
|
|
145
|
+
## Useful links
|
|
146
|
+
|
|
147
|
+
- [Changelog](CHANGELOG.md)
|
|
148
|
+
- [License](LICENSE)
|
|
149
|
+
- [SVG sprites: old-school, modern, unknown, and forgotten](https://pepelsbey.dev/articles/svg-sprites/#forgotten-stacks) by [Vadim Makeev](https://mastodon.social/@pepelsbey)
|
|
150
|
+
|
|
147
151
|
[test-url]: https://github.com/firefoxic/gulp-stacksvg/actions
|
|
148
152
|
[test-image]: https://github.com/firefoxic/gulp-stacksvg/actions/workflows/test.yml/badge.svg?branch=main
|
|
149
153
|
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parse } from "node-html-parser"
|
|
2
2
|
import { basename, extname, sep } from "path"
|
|
3
3
|
import { Transform } from "stream"
|
|
4
|
-
import
|
|
4
|
+
import { createHmac } from "crypto"
|
|
5
5
|
import PluginError from "plugin-error"
|
|
6
6
|
import Vinyl from "vinyl"
|
|
7
7
|
|
|
@@ -15,12 +15,14 @@ const excessAttrs = [
|
|
|
15
15
|
`y`
|
|
16
16
|
]
|
|
17
17
|
|
|
18
|
+
const xlink = `http://www.w3.org/1999/xlink`
|
|
19
|
+
|
|
18
20
|
export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-` } = {}) {
|
|
19
21
|
|
|
20
22
|
let isEmpty = true
|
|
21
23
|
const ids = {}
|
|
22
|
-
const namespaces =
|
|
23
|
-
const stack = parse(`<svg
|
|
24
|
+
const namespaces = new Map([[`http://www.w3.org/2000/svg`, `xmlns`]])
|
|
25
|
+
const stack = parse(`<svg><style>:root svg:not(:target){display:none}</style></svg>`)
|
|
24
26
|
const rootSvg = stack.querySelector(`svg`)
|
|
25
27
|
const stream = new Transform({ objectMode: true })
|
|
26
28
|
|
|
@@ -34,7 +36,8 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
|
|
|
34
36
|
return cb()
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
const
|
|
39
|
+
const iconDom = parse(file.contents.toString()).removeWhitespace()
|
|
40
|
+
const iconSvg = iconDom.querySelector(`svg`)
|
|
38
41
|
|
|
39
42
|
isEmpty = false
|
|
40
43
|
|
|
@@ -48,24 +51,24 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
|
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
ids[iconId] = true
|
|
51
|
-
|
|
54
|
+
iconSvg.setAttribute(`id`, iconId)
|
|
52
55
|
|
|
53
|
-
const viewBoxAttr =
|
|
54
|
-
const widthAttr =
|
|
55
|
-
const heightAttr =
|
|
56
|
+
const viewBoxAttr = iconSvg.getAttribute(`viewBox`)
|
|
57
|
+
const widthAttr = iconSvg.getAttribute(`width`)?.replace(/[^0-9]/g, ``)
|
|
58
|
+
const heightAttr = iconSvg.getAttribute(`height`)?.replace(/[^0-9]/g, ``)
|
|
56
59
|
|
|
57
60
|
if (!viewBoxAttr && widthAttr && heightAttr) {
|
|
58
|
-
|
|
61
|
+
iconSvg.setAttribute(`viewBox`, `0 0 ${widthAttr} ${heightAttr}`)
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
excessAttrs.forEach((attr) =>
|
|
62
|
-
|
|
64
|
+
excessAttrs.forEach((attr) => iconSvg.removeAttribute(attr))
|
|
65
|
+
iconSvg.querySelectorAll(`[id]`).forEach(changeInnerId)
|
|
63
66
|
|
|
64
67
|
function changeInnerId (targetElem, suffix) {
|
|
65
68
|
let oldId = targetElem.id
|
|
66
69
|
let newId = `${iconId}_${suffix}`
|
|
67
70
|
targetElem.setAttribute(`id`, newId)
|
|
68
|
-
|
|
71
|
+
iconSvg.querySelectorAll(`*`).forEach(updateUsingId)
|
|
69
72
|
|
|
70
73
|
function updateUsingId (elem) {
|
|
71
74
|
if (~elem.rawAttrs.search(`#${oldId}`)) {
|
|
@@ -77,31 +80,46 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
|
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
|
|
80
|
-
const attrs =
|
|
83
|
+
const attrs = iconSvg._attrs
|
|
81
84
|
|
|
82
85
|
for (let attrName in attrs) {
|
|
83
86
|
if (attrName.startsWith(`xmlns`)) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
if (
|
|
89
|
-
|
|
87
|
+
let nsId = attrs[attrName]
|
|
88
|
+
let oldNsAlias = attrName.slice(6)
|
|
89
|
+
let newNsAlias = oldNsAlias
|
|
90
|
+
if (namespaces.has(nsId)) {
|
|
91
|
+
if (namespaces.get(nsId) !== attrName) {
|
|
92
|
+
newNsAlias = namespaces.get(nsId).slice(6)
|
|
93
|
+
changeNsAlias(iconDom, oldNsAlias, newNsAlias)
|
|
90
94
|
}
|
|
95
|
+
} else if (nsId === xlink) {
|
|
96
|
+
newNsAlias = ``
|
|
97
|
+
changeNsAlias(iconDom, oldNsAlias, newNsAlias)
|
|
91
98
|
} else {
|
|
92
|
-
for (let
|
|
93
|
-
if (
|
|
94
|
-
|
|
99
|
+
for (let ns of namespaces.values()) {
|
|
100
|
+
if (ns === attrName) {
|
|
101
|
+
newNsAlias = `${oldNsAlias}${getHash(nsId)}`
|
|
102
|
+
changeNsAlias(iconDom, oldNsAlias, newNsAlias)
|
|
103
|
+
break
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
|
-
|
|
106
|
+
iconDom.querySelectorAll(`*`).some((elem) => {
|
|
107
|
+
if (
|
|
108
|
+
elem.rawTagName.startsWith(`${newNsAlias}:`)
|
|
109
|
+
||
|
|
110
|
+
Object.keys(elem._attrs).some((attr) => attr.startsWith(`${newNsAlias}:`))
|
|
111
|
+
) {
|
|
112
|
+
namespaces.set(nsId, `xmlns:${newNsAlias}`)
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
})
|
|
98
116
|
}
|
|
99
117
|
|
|
100
|
-
|
|
118
|
+
iconSvg.removeAttribute(attrName)
|
|
101
119
|
}
|
|
102
120
|
}
|
|
103
121
|
|
|
104
|
-
rootSvg.appendChild(
|
|
122
|
+
rootSvg.appendChild(iconSvg)
|
|
105
123
|
cb()
|
|
106
124
|
}
|
|
107
125
|
|
|
@@ -110,8 +128,8 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
|
|
|
110
128
|
return cb()
|
|
111
129
|
}
|
|
112
130
|
|
|
113
|
-
for (let
|
|
114
|
-
rootSvg.setAttribute(
|
|
131
|
+
for (let [nsId, nsAttr] of namespaces) {
|
|
132
|
+
rootSvg.setAttribute(nsAttr, nsId)
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
output = output.endsWith(`.svg`) ? output : `${output}.svg`
|
|
@@ -127,3 +145,25 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
|
|
|
127
145
|
|
|
128
146
|
return stream
|
|
129
147
|
}
|
|
148
|
+
|
|
149
|
+
function changeNsAlias (elems, oldAlias, newAlias) {
|
|
150
|
+
elems.querySelectorAll(`*`).forEach((elem) => {
|
|
151
|
+
let prefix = newAlias === `` ? `` : `${newAlias}:`
|
|
152
|
+
if (elem.rawTagName.startsWith(`${oldAlias}:`)) {
|
|
153
|
+
elem.rawTagName = `${prefix}${elem.rawTagName.slice((oldAlias.length + 1))}`
|
|
154
|
+
}
|
|
155
|
+
for (let name of Object.keys(elem._attrs)) {
|
|
156
|
+
if (name.startsWith(`${oldAlias}:`)) {
|
|
157
|
+
elem.setAttribute(`${prefix}${name.slice((oldAlias.length + 1))}`, elem._attrs[name])
|
|
158
|
+
elem.removeAttribute(name)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function getHash (str) {
|
|
165
|
+
return createHmac(`sha1`, `xmlns`)
|
|
166
|
+
.update(str)
|
|
167
|
+
.digest(`hex`)
|
|
168
|
+
.slice(0, 7)
|
|
169
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gulp-stacksvg",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Combine svg files into one with stack method",
|
|
6
6
|
"main": "index.js",
|
|
@@ -29,19 +29,19 @@
|
|
|
29
29
|
},
|
|
30
30
|
"homepage": "https://github.com/firefoxic/gulp-stacksvg",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"plugin-error": "^2.0.0",
|
|
32
|
+
"node-html-parser": "^6.1.4",
|
|
33
|
+
"plugin-error": "^2.0.1",
|
|
35
34
|
"vinyl": "^3.0.0"
|
|
36
35
|
},
|
|
37
36
|
"devDependencies": {
|
|
38
|
-
"eslint": "^8.
|
|
37
|
+
"eslint": "^8.30.0",
|
|
38
|
+
"fancy-log": "^2.0.0",
|
|
39
39
|
"finalhandler": "^1.2.0",
|
|
40
40
|
"gulp": "^4.0.2",
|
|
41
|
-
"mocha": "^10.
|
|
42
|
-
"puppeteer": "^19.
|
|
41
|
+
"mocha": "^10.2.0",
|
|
42
|
+
"puppeteer": "^19.4.1",
|
|
43
43
|
"serve-static": "^1.15.0",
|
|
44
|
-
"sinon": "^
|
|
44
|
+
"sinon": "^15.0.1"
|
|
45
45
|
},
|
|
46
46
|
"engines": {
|
|
47
47
|
"node": ">=16"
|