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.
Files changed (3) hide show
  1. package/README.md +11 -7
  2. package/index.js +67 -27
  3. 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 svg icon files into one with stack method.
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 svg sources into single svg file with stack method.
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
- ### Avalable options
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 remain the `svg` tag, as in the original icon files) and with the addition of a tiny bit of style.
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" xmlns:xlink="http://www.w3.org/1999/xlink">
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 fancyLog from "fancy-log"
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 xmlns="http://www.w3.org/2000/svg"><style>:root svg:not(:target){display:none}</style></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 icon = parse(file.contents.toString()).querySelector(`svg`)
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
- icon.setAttribute(`id`, iconId)
54
+ iconSvg.setAttribute(`id`, iconId)
52
55
 
53
- const viewBoxAttr = icon.getAttribute(`viewBox`)
54
- const widthAttr = icon.getAttribute(`width`)?.replace(/[^0-9]/g, ``)
55
- const heightAttr = icon.getAttribute(`height`)?.replace(/[^0-9]/g, ``)
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
- icon.setAttribute(`viewBox`, `0 0 ${widthAttr} ${heightAttr}`)
61
+ iconSvg.setAttribute(`viewBox`, `0 0 ${widthAttr} ${heightAttr}`)
59
62
  }
60
63
 
61
- excessAttrs.forEach((attr) => icon.removeAttribute(attr))
62
- icon.querySelectorAll(`[id]`).forEach(changeInnerId)
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
- icon.querySelectorAll(`*`).forEach(updateUsingId)
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 = icon._attrs
83
+ const attrs = iconSvg._attrs
81
84
 
82
85
  for (let attrName in attrs) {
83
86
  if (attrName.startsWith(`xmlns`)) {
84
- const storedNs = namespaces[attrName]
85
- const attrNs = attrs[attrName]
86
-
87
- if (storedNs !== undefined) {
88
- if (storedNs !== attrNs) {
89
- fancyLog.info(`${attrName} namespace appeared multiple times with different value. Keeping the first one : "${storedNs}".\nEach namespace must be unique across files.`)
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 nsName in namespaces) {
93
- if (namespaces[nsName] === attrNs) {
94
- fancyLog.info(`Same namespace value under different names : ${nsName} and ${attrName}.\nKeeping both.`)
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
- namespaces[attrName] = attrNs
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
- icon.removeAttribute(attrName)
118
+ iconSvg.removeAttribute(attrName)
101
119
  }
102
120
  }
103
121
 
104
- rootSvg.appendChild(icon)
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 nsName in namespaces) {
114
- rootSvg.setAttribute(nsName, namespaces[nsName])
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": "1.0.6",
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
- "fancy-log": "^2.0.0",
33
- "node-html-parser": "^6.1.1",
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.26.0",
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.1.0",
42
- "puppeteer": "^19.2.0",
41
+ "mocha": "^10.2.0",
42
+ "puppeteer": "^19.4.1",
43
43
  "serve-static": "^1.15.0",
44
- "sinon": "^14.0.1"
44
+ "sinon": "^15.0.1"
45
45
  },
46
46
  "engines": {
47
47
  "node": ">=16"