gulp-stacksvg 1.0.1 → 1.0.2

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 +88 -74
  2. package/index.js +37 -24
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -1,14 +1,11 @@
1
1
  # gulp-stacksvg
2
2
 
3
- <img align="right" width="130" height="175" title="SVG Superman" src="https://raw.githubusercontent.com/firefoxic/gulp-stacksvg/master/svg-superman.png">
4
-
5
3
  [![Test Status][test-image]][test-url]
6
4
  [![License: MIT][license-image]][license-url]
7
5
  [![NPM version][npm-image]][npm-url]
8
6
  [![Vulnerabilities count][vulnerabilities-image]][vulnerabilities-url]
9
7
 
10
- Combine svg files into one with stack method.
11
- Read more about this in [the Simurai article](https://simurai.com/blog/2012/04/02/svg-stacks).
8
+ Combine svg icon files into one with stack method.
12
9
 
13
10
  ## Installation
14
11
 
@@ -16,14 +13,6 @@ Read more about this in [the Simurai article](https://simurai.com/blog/2012/04/0
16
13
  npm install gulp-stacksvg --save-dev
17
14
  ```
18
15
 
19
- ### Avalable options
20
-
21
- | Option | Description | Default |
22
- |-------------|--------------------------------------------------------------------------------------|-------------|
23
- | `output` | Sets the stack file name. Accepts values ​​both with and without the `.svg` extension. | `stack.svg` |
24
- | `separator` | Replaces the directory separator for the `id` attribute. | `_` |
25
- | `spacer` | Joins space-separated words for the `id` attribute. | `-` |
26
-
27
16
  ## Usage
28
17
 
29
18
  The following script will combine all svg sources into single svg file with stack method.
@@ -36,100 +25,125 @@ const { src, dest } = gulp
36
25
 
37
26
  function makeStack () {
38
27
  return src(`./src/icons/**/*.svg`)
39
- .pipe(stacksvg())
28
+ .pipe(stacksvg({ output: `sprite` }))
40
29
  .pipe(dest(`./dest/icons`))
41
30
  }
42
31
  ```
43
32
 
33
+ ### Avalable options
34
+
35
+ | Option | Description | Default |
36
+ |-------------|--------------------------------------------------------------------------------------|-------------|
37
+ | `output` | Sets the stack file name. Accepts values ​both with and without the `.svg` extension. | `stack.svg` |
38
+ | `separator` | Replaces the directory separator for the `id` attribute. | `_` |
39
+ | `spacer` | Joins space-separated words for the `id` attribute. | `-` |
40
+
44
41
  ### Inlining stacksvg result into markup
45
42
 
46
43
  You just don't have to want it.
47
44
 
48
- ### Editing id attributes
45
+ ## Why a stack?
49
46
 
50
- If you need to add prefix to each id, please use [gulp-rename](https://github.com/hparra/gulp-rename):
47
+ Unlike all other methods for assembling a sprite, the stack does not limit us in choosing how to insert a vector into a page. Take a look at [the results](https://demos.frontend-design.ru/sprite/src/) of different ways to display fragments of different types of sprites.
51
48
 
52
- ```js
53
- import { stacksvg } from "gulp-stacksvg"
54
- import rename from "gulp-rename"
55
- import gulp from "gulp"
49
+ We can use the stack in all four possible ways:
56
50
 
57
- const { src, dest } = gulp
51
+ - in markup:
52
+ - in `src` of `img` tag — static,
53
+ - in the `href` of the `use` tag — with the possibility of repainting,
54
+ - in styles:
55
+ - in `url()` properties `background` — static,
56
+ - in `url()` properties `mask` — with the possibility of repainting.
58
57
 
59
- function makeStack () {
60
- return src(`./src/icons/**/*.svg`, { base: `src/icons` })
61
- .pipe(rename({ prefix: `icon-` }))
62
- .pipe(stacksvg())
63
- .pipe(dest(`./dest/icons`))
64
- }
65
- ```
58
+ [Demo page](https://firefoxic.github.io/gulp-stacksvg/test/) to prove it.
66
59
 
67
- ### Transform svg sources or combined svg
60
+ ## Stack under the hood
68
61
 
69
- To transform either svg sources or combined svg you may pipe your files through [gulp-cheerio](https://github.com/KenPowers/gulp-cheerio).
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.
70
63
 
71
- An example below removes all fill attributes from svg sources before combining them.
72
- Please note that you have to set `xmlMode: true` to parse svgs as xml file.
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.
73
65
 
74
- ```js
75
- import { stacksvg } from "gulp-stacksvg"
76
- import cheerio from "gulp-cheerio"
77
- import gulp from "gulp"
66
+ ```svg
67
+ <?xml version="1.0" encoding="UTF-8"?>
68
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
78
69
 
79
- const { src, dest } = gulp
70
+ <style>
71
+ :root { visibility: hidden }
72
+ :target { visibility: visible }
73
+ </style>
74
+ ```
80
75
 
81
- function makeStack () {
82
- return src(`./src/icons/**/*.svg`)
83
- .pipe(cheerio({
84
- run: ($) => {
85
- $(`[fill]`).removeAttr(`fill`)
86
- },
87
- parserOptions: { xmlMode: true }
88
- }))
89
- .pipe(stacksvg())
90
- .pipe(dest(`./dest/icons`))
91
- }
76
+ <img align="left" width="90" height="90" title="sun" src="https://raw.githubusercontent.com/firefoxic/gulp-stacksvg/main/test/stack.svg#sun-alpha">
77
+
78
+ ```svg
79
+ <svg id="sun" viewBox="0 0 24 24">
80
+ <!-- Inner code of sun icon -->
81
+ </svg>
92
82
  ```
93
83
 
94
- ## Possible rendering issues with Clipping Paths in SVG
84
+ <img align="left" width="90" height="90" title="heart" src="https://raw.githubusercontent.com/firefoxic/gulp-stacksvg/main/test/stack.svg#heart-red">
95
85
 
96
- If you're running into issues with SVGs not rendering correctly in some browsers (see issue #47), the issue might be that clipping paths might not have been properly intersected in the SVG file. There are currently three ways of fixing this issue:
86
+ ```svg
87
+ <svg id="heart" viewBox="0 0 24 24">
88
+ <!-- Inner code of heart icon -->
89
+ </svg>
90
+ ```
91
+
92
+ <img align="left" width="90" height="90" title="thumbup" src="https://raw.githubusercontent.com/firefoxic/gulp-stacksvg/main/test/stack.svg#thumbup-alpha">
93
+
94
+ ```svg
95
+ <svg id="thumbup" viewBox="0 0 24 24">
96
+ <!-- Inner code of thumbup icon -->
97
+ </svg>
98
+ ```
97
99
 
98
- ### Correcting the Clipping Path in the SVG
100
+ ```svg
101
+ </svg>
102
+ ```
99
103
 
100
- If you have the source file, simply converting the clipping path to a nice coded shape will fix this issue. Select the object, open up the Pathfinder panel, and click the Intersect icon.
104
+ The magic is in the stack inner style:
101
105
 
102
- ### Editing the SVG Code
106
+ - `:root { visibility: hidden }` — hides the entire contents of the stack,
107
+ - `:target { visibility: visible }` — shows only the fragment that is requested by its link.
103
108
 
104
- If you don't have the source file or an SVG Editor (Adobe Illustrator etc.), you can manually edit the SVG code in the file. Wrapping the `<clipPath>` into a `<defs>` will fix this issue. Here's an example:
109
+ 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">
105
110
 
106
- ```diff
107
- <defs>
108
- <path d="M28.4 30.5l5.3 5c0-.1 7-6.9 7-6.9l-4-6.8-8.3 8.7z" id="a"/>
109
- + <clipPath id="b">
110
- + <use overflow="visible" href="#a"/>
111
- + </clipPath>
112
- </defs>
113
- -<clipPath id="b">
114
- - <use overflow="visible" xlink:href="#a"/>
115
- -</clipPath>
111
+ ```html
112
+ <button class="button button--icon_heart" type="button">
113
+ <span class="visually-hidden">Add to favorites</span>
114
+ </button>
116
115
  ```
117
116
 
118
- Or you can go further and reduce the size by removing the `<use>` element, like this:
117
+ ```css
118
+ .button {
119
+ display: inline-flex;
120
+ align-items: center;
121
+ gap: 0.5em;
122
+ }
119
123
 
120
- ```diff
121
- <defs>
122
- - <path d="M28.4 30.5l5.3 5c0-.1 7-6.9 7-6.9l-4-6.8-8.3 8.7z" id="a"/>
123
- <clipPath id="b">
124
- - <use overflow="visible" href="#a"/>
125
- + <path d="M28.4 30.5l5.3 5c0-.1 7-6.9 7-6.9l-4-6.8-8.3 8.7z"/>
126
- </clipPath>
127
- </defs>
124
+ .button--icon_heart {
125
+ --icon: url("../icons/stack.svg#heart");
126
+ }
127
+
128
+ .button:hover {
129
+ --fill: red;
130
+ }
131
+
132
+ .button::before {
133
+ content: "";
134
+ width: 1em;
135
+ height: 1em;
136
+ /* icon shape */
137
+ mask: var(--icon) no-repeat center / contain;
138
+ /* icon color */
139
+ background: var(--fill, orangered);
140
+ }
128
141
  ```
129
142
 
130
- ### Using gulp-cheerio to automate this
143
+ > ⚠️ Note:
144
+ > For the `mask` property, we still need an autoprefixer.
131
145
 
132
- Another possible solution would be to write a transformation with [gulp-cheerio](https://github.com/KenPowers/gulp-cheerio). Check this issue <https://github.com/firefoxic/gulp-stacksvg/issues/98> for the instructions.
146
+ 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.
133
147
 
134
148
  [test-url]: https://github.com/firefoxic/gulp-stacksvg/actions
135
149
  [test-image]: https://github.com/firefoxic/gulp-stacksvg/actions/workflows/test.yml/badge.svg?branch=main
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { load } from "cheerio"
1
+ import { parse } from "node-html-parser"
2
2
  import { basename, extname, sep } from "path"
3
3
  import { Transform } from "stream"
4
4
  import fancyLog from "fancy-log"
@@ -20,8 +20,8 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
20
20
  let isEmpty = true
21
21
  const ids = {}
22
22
  const namespaces = {}
23
- const $stack = load(`<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"><style>:root{visibility:hidden}:target{visibility:visible}</style></svg>`, { xmlMode: true })
24
- const $rootSvg = $stack(`svg`)
23
+ const stack = parse(`<?xml version="1.0" encoding="UTF-8"?><svg xmlns="http://www.w3.org/2000/svg"><style>:root{visibility:hidden}:target{visibility:visible}</style></svg>`)
24
+ const rootSvg = stack.querySelector(`svg`)
25
25
  const stream = new Transform({ objectMode: true })
26
26
 
27
27
  function transform (file, _, cb) {
@@ -30,43 +30,56 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
30
30
  return cb(new PluginError(`gulp-stacksvg`, `Streams are not supported!`))
31
31
  }
32
32
 
33
- if (file.isNull()) {
33
+ if (file.isNull() || !parse(file.contents.toString()).querySelector(`svg`)) {
34
34
  return cb()
35
35
  }
36
36
 
37
- const $icon = load(file.contents.toString(), { xmlMode: true })(`svg`)
38
-
39
- if ($icon.length === 0) {
40
- return cb()
41
- }
37
+ const icon = parse(file.contents.toString()).querySelector(`svg`)
42
38
 
43
39
  if (file && isEmpty) {
44
40
  isEmpty = false
45
41
  }
46
42
 
47
- const idAttr = basename(
43
+ const iconId = basename(
48
44
  file.relative.split(sep).join(separator).replace(/\s/g, spacer),
49
45
  extname(file.relative)
50
46
  )
51
47
 
52
- if (idAttr in ids) {
53
- return cb(new PluginError(`gulp-stacksvg`, `File name should be unique: ${idAttr}`))
48
+ if (iconId in ids) {
49
+ return cb(new PluginError(`gulp-stacksvg`, `File name should be unique: ${iconId}`))
54
50
  }
55
51
 
56
- ids[idAttr] = true
57
- $icon.attr(`id`, idAttr)
52
+ ids[iconId] = true
53
+ icon.setAttribute(`id`, iconId)
58
54
 
59
- const viewBoxAttr = $icon.attr(`viewBox`)
60
- const widthAttr = $icon.attr(`width`)?.replace(/[^0-9]/g, ``)
61
- const heightAttr = $icon.attr(`height`)?.replace(/[^0-9]/g, ``)
55
+ const viewBoxAttr = icon.getAttribute(`viewBox`)
56
+ const widthAttr = icon.getAttribute(`width`)?.replace(/[^0-9]/g, ``)
57
+ const heightAttr = icon.getAttribute(`height`)?.replace(/[^0-9]/g, ``)
62
58
 
63
59
  if (!viewBoxAttr && widthAttr && heightAttr) {
64
- $icon.attr(`viewBox`, `0 0 ${widthAttr} ${heightAttr}`)
60
+ icon.setAttribute(`viewBox`, `0 0 ${widthAttr} ${heightAttr}`)
65
61
  }
66
62
 
67
- excessAttrs.forEach((attr) => $icon.removeAttr(attr))
63
+ excessAttrs.forEach((attr) => icon.removeAttribute(attr))
64
+ icon.querySelectorAll(`[id]`).forEach(changeInnerId)
65
+
66
+ function changeInnerId (targetElem, suffix) {
67
+ let oldId = targetElem.id
68
+ let newId = `${iconId}_${suffix}`
69
+ targetElem.setAttribute(`id`, newId)
70
+ icon.querySelectorAll(`*`).forEach(updateUsingId)
71
+
72
+ function updateUsingId (elem) {
73
+ if (~elem.rawAttrs.search(`#${oldId}`)) {
74
+ for (let attr in elem._attrs) {
75
+ let attrValue = elem._attrs[attr].replace(`#${oldId}`, `#${newId}`)
76
+ elem.setAttribute(attr, attrValue)
77
+ }
78
+ }
79
+ }
80
+ }
68
81
 
69
- const attrs = $icon[0].attribs
82
+ const attrs = icon._attrs
70
83
 
71
84
  for (let attrName in attrs) {
72
85
  if (attrName.startsWith(`xmlns`)) {
@@ -86,11 +99,11 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
86
99
  namespaces[attrName] = attrNs
87
100
  }
88
101
 
89
- $icon.removeAttr(attrName)
102
+ icon.removeAttribute(attrName)
90
103
  }
91
104
  }
92
105
 
93
- $rootSvg.append($icon)
106
+ rootSvg.appendChild(icon)
94
107
  cb()
95
108
  }
96
109
 
@@ -100,12 +113,12 @@ export function stacksvg ({ output = `stack.svg`, separator = `_`, spacer = `-`
100
113
  }
101
114
 
102
115
  for (let nsName in namespaces) {
103
- $rootSvg.attr(nsName, namespaces[nsName])
116
+ rootSvg.setAttribute(nsName, namespaces[nsName])
104
117
  }
105
118
 
106
119
  output = output.endsWith(`.svg`) ? output : `${output}.svg`
107
120
 
108
- const file = new Vinyl({ path: output, contents: Buffer.from($stack.xml()) })
121
+ const file = new Vinyl({ path: output, contents: Buffer.from(stack.toString()) })
109
122
 
110
123
  this.push(file)
111
124
  cb()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gulp-stacksvg",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "type": "module",
5
5
  "description": "Combine svg files into one with stack method",
6
6
  "main": "index.js",
@@ -9,7 +9,7 @@
9
9
  ],
10
10
  "scripts": {
11
11
  "lint": "eslint ./",
12
- "test": "gulp build && mocha",
12
+ "test": "gulp test && mocha",
13
13
  "pretest": "npm run lint",
14
14
  "preversion": "npm test",
15
15
  "postversion": "npm publish",
@@ -29,8 +29,8 @@
29
29
  },
30
30
  "homepage": "https://github.com/firefoxic/gulp-stacksvg",
31
31
  "dependencies": {
32
- "cheerio": "^1.0.0-rc.12",
33
32
  "fancy-log": "^2.0.0",
33
+ "node-html-parser": "^5.4.2",
34
34
  "plugin-error": "^2.0.0",
35
35
  "vinyl": "^2.2.1"
36
36
  },