├── .gitattributes ├── .gitignore ├── .npmignore ├── .prettierrc.js ├── .travis.yml ├── .vscode └── settings.json ├── CHANGELOG.md ├── LICENCE ├── MIGRATING.md ├── README.md ├── TEMPLATES.md ├── examples ├── aspect-ratio-test.html ├── gulpfile.js ├── package.json ├── svg-with-masks-sources │ ├── ad-clip-path-donut.svg │ ├── ad-clip-path-heart.svg │ ├── ad-compose-path-variation.svg │ ├── ad-compose-path.svg │ ├── affinity-designer-source.afdesign │ ├── figma-output-1.svg │ └── figma-output-2.svg ├── vue │ ├── index.html │ ├── layout.vue │ └── main.js └── yarn.lock ├── index.js ├── lib ├── default-config.js ├── svg.js ├── templates.js └── utils.js ├── package.json ├── templates ├── svg-symbols-custom-properties.css ├── svg-symbols-demo-page.html ├── svg-symbols.css ├── svg-symbols.scss ├── svg-symbols.styl ├── svg-symbols.svg └── svg-symbols.vue ├── test ├── get-svg-datas.js ├── output │ ├── codepen-symbol.css │ ├── codepen-symbol.svg │ ├── gear_without_dimensions-symbol.svg │ ├── github-symbol.css │ ├── github-symbol.svg │ ├── mail-symbol-accessible.svg │ ├── mail-symbol.css │ ├── mail-symbol.svg │ ├── no-title.svg │ ├── skull-symbol.css │ ├── skull-symbol.svg │ ├── svg-symbols.css │ ├── svg-symbols.svg │ ├── template.html │ └── template.json ├── plugin.js ├── source │ ├── affinity-clip-path-outside-defs.svg │ ├── aspect-ratio.svg │ ├── chinese letter with styles.svg │ ├── crâne noir.svg │ ├── duplicated-defs-ids-1.svg │ ├── duplicated-defs-ids-2.svg │ ├── figma-mask-outside-defs.svg │ ├── gear_without_dimensions.svg │ ├── github.svg │ ├── gradient.svg │ ├── icon-with-percent-size.svg │ ├── instagram-black.svg │ ├── ios.svg │ ├── skull.svg │ ├── template.html │ ├── template.json │ ├── warning with styles and empty group.svg │ ├── xlink-href.svg │ └── zoom.svg ├── templates.js ├── transform-raw-data.js └── warnings.js └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | *.afdesign filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | tmp 4 | npm-debug.log 5 | examples/ex-* 6 | examples/dist 7 | examples/.cache 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | tmp 4 | npm-debug.log 5 | 6 | test 7 | examples 8 | .jshintrc 9 | .travis.yml 10 | CHANGELOG.md 11 | gulpfile.js 12 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | singleQuote: true, 4 | trailingComma: `es5`, 5 | } 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "6" 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "gulpfile" 4 | ] 5 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | - **3.2.0** 2 | - rename `default-sass` template to `default-scss` 3 | - add a `default-vue` template 4 | - **3.1.1** 5 | - add build-in templates for scss, stylus, and CSS custom properties 6 | - **3.1.0** 7 | - improve warning messages 8 | - **3.0.0** 9 | - fix demo page not showing gradients 10 | - **3.0.0** 11 | - remove gulp-util. Gulp-svg-symbols now work with the latest gulp4 12 | - add an `option.svgAttrs` 13 | - replace `option.className` by `options.class` 14 | - replace `option.svgClassname` by `options.svgAttrs.class` 15 | - update `defaultSvg` template. It now will add `xmlns:xlink="http://www.w3.org/1999/xlink"` if needed 16 | - use ES6 functionalities such as: Promise, arrow function & template strings 17 | - don't generated files if no SVG has been found 18 | - **2.0.1** 19 | - Update dependencies 20 | - Better _real situation_ example folder 21 | - **2.0.0** 22 | - Change default SVG template 23 | - Change id handling (moving from [slug](https://www.npmjs.com/package/slug) to [speakingurl](https://www.npmjs.com/package/speakingurl)) 24 | - **1.0.0** — Remove SVGO 25 | - **0.3.2** — Better demo page 26 | - **0.3.1** — Add .npmignore 27 | - **0.3.0** — Improve default option configuration 28 | - **0.2.1** — Fix SVG files without dimensions 29 | - **0.2.0** — Custom templates & data handling 30 | - **0.1.5** — Fix test 31 | - **0.1.4** — Fix viewbox issue 32 | - **0.1.3** — Custom `` & `<desc>` content 33 | - **0.1.2** — Config for optional CSS output 34 | - **0.1.1** — Add travis build 35 | - **0.1.0** — Publish to NPM and fix [watch issue](https://github.com/Hiswe/gulp-svg-symbols/issues/2) 36 | - **0.0.2** — Css can be generated with _em_ size 37 | - **0.0.1** — First release 38 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Hiswe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MIGRATING.md: -------------------------------------------------------------------------------- 1 | ### from v3.0+ to v3.2 2 | 3 | - if you were using the sass template, rename `default-sass` to `default-scss` in your gulpfile 4 | 5 | ### from v2.0+ to v3.0 6 | 7 | - Configuration deprecated options (still working but will issue a warning): 8 | - `option.className` has been replaced by `options.class`. 9 | - `option.svgClassname` has been replaced by `options.svgAttrs.class` 10 | - Datas in custom templates has been modified. This can break some templates. 11 | - `icon.className` is replaced by `icon.class` in the icons list 12 | - `svgClassname` is replaced by `svgAttrs.class` 13 | 14 | ### from v1.0+ to v2.0 15 | 16 | - Style attribute from the default SVG template has been removed. 17 | You will have to take care of it manually if you intend to inline the SVG symbols in your DOM. (see `svgClassname` option in the README) 18 | 19 | ### from v0.2+ to v1.0 20 | 21 | - SVGO is no more used. 22 | - `css: false` is removed. 23 | - svgSymbols.demoPage() method has been removed. See **Basics > demo page** 24 | 25 | ### from v0.1 to 0.2+ 26 | 27 | - `svgId` is replaced by `id`. 28 | - `accessibility` is replaced by `title`. 29 | - `css: false` is still working but is deprecated. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gulp-svg-symbols 2 | 3 | [![npm version](https://badge.fury.io/js/gulp-svg-symbols.svg)](https://badge.fury.io/js/gulp-svg-symbols) [![Build Status](https://travis-ci.org/Hiswe/gulp-svg-symbols.svg?branch=master)](https://travis-ci.org/Hiswe/gulp-svg-symbols) 4 | 5 | _gulp-svg-symbols_ is a minimal plugin for [gulp](http://gulpjs.com). 6 | It converts a bunch of svg files to a single svg file containing each one as a symbol. 7 | See [css-trick](http://css-tricks.com/svg-symbol-good-choice-icons/) for more details. 8 | 9 | <!-- START doctoc generated TOC please keep comment here to allow auto update --> 10 | <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> 11 | 12 | 13 | - [Install](#install) 14 | - [Example](#example) 15 | - [Options](#options) 16 | - [Basics](#basics) 17 | - [id and class](#id-and-class) 18 | - [fontSize](#fontsize) 19 | - [title](#title) 20 | - [svgAttrs](#svgattrs) 21 | - [slug](#slug) 22 | - [templates](#templates) 23 | - [CSS generation](#css-generation) 24 | - [warn](#warn) 25 | - [Advanced](#advanced) 26 | - [templates](#templates-1) 27 | - [transformData](#transformdata) 28 | - [Other observations](#other-observations) 29 | - [Other stuff](#other-stuff) 30 | - [Rendering caveats](#rendering-caveats) 31 | - [Migrating](#migrating) 32 | - [More examples](#more-examples) 33 | - [Usefull frontend lib](#usefull-frontend-lib) 34 | - [Thanks](#thanks) 35 | - [Credits](#credits) 36 | - [Alternatives](#alternatives) 37 | 38 | <!-- END doctoc generated TOC please keep comment here to allow auto update --> 39 | 40 | ## Install 41 | 42 | ``` 43 | npm install --save-dev gulp-svg-symbols 44 | ``` 45 | 46 | ## Example 47 | 48 | In your gulpfile.js: 49 | 50 | ```js 51 | const gulp = require('gulp') 52 | const svgSymbols = require('gulp-svg-symbols') 53 | 54 | gulp.task(`sprites`, function() { 55 | return gulp 56 | .src(`assets/svg/*.svg`) 57 | .pipe(svgSymbols()) 58 | .pipe(gulp.dest(`assets`)) 59 | }) 60 | ``` 61 | 62 | In your HTML, you first have to [reference the SVG](http://css-tricks.com/svg-sprites-use-better-icon-fonts/) 63 | then: 64 | 65 | ```html 66 | <svg role="img" class="github"> 67 | <use xlink:href="#github"></use> 68 | </svg> 69 | ``` 70 | 71 | - **class** is the one generated in the CSS file 72 | - **xlink:href** is the symbol id in the SVG file 73 | 74 | ## Options 75 | 76 | You can override the [default options](https://github.com/Hiswe/gulp-svg-symbols/blob/master/lib/default-config.js) by passing an object as an argument to `svgSymbols()` 77 | 78 | ### Basics 79 | 80 | #### id and class 81 | 82 | **type:** `function` or `string` 83 | **default:** `'%f'` and `'.%f'` 84 | 85 | Text templates for generating symbols id & icon class 86 | `%f` is the [speakingurled](https://www.npmjs.com/package/speakingurl) file name placeholder. 87 | See more about the name in the [slug option](#slug) 88 | 89 | #### fontSize 90 | 91 | **type:** `number` 92 | **default:** `0` 93 | 94 | This option lets you define a base font. 95 | If it's superior to 0, then the sizes in your CSS file will be in **em** else sizes are provided with **px**. 96 | 97 | #### title 98 | 99 | **type:** `boolean` or `function` or `string` 100 | **default:** `false` 101 | 102 | Specify whether or not you want to add a missing `title` tag in your SVG symbols. 103 | It should be better for _accessibility_. 104 | It takes a text template (like for [id/classname](https://github.com/Hiswe/gulp-svg-symbols#id--classname)): 105 | 106 | ```js 107 | title: `%f icon` 108 | ``` 109 | 110 | #### svgAttrs 111 | 112 | **type:** `object` 113 | **default:** `{class: null, xmlns: 'http://www.w3.org/2000/svg'}` 114 | 115 | Specify attributes for the `<svg>` container tag in the default SVG template. 116 | 117 | ```js 118 | { 119 | class: `svg-icon-lib`, 120 | 'aria-hidden': `true`, 121 | style: `position: absolute;`, 122 | 'data-enabled': true, 123 | } 124 | ``` 125 | 126 | output: 127 | 128 | ```html 129 | <svg xmlns="http://www.w3.org/2000/svg" class="svg-icon-lib" aria-hidden="true" style="position: absolute;" data-enabled> 130 | ``` 131 | 132 | _notes:_ 133 | 134 | - this is how you can add a `class` to the generated SVG 135 | - any string or numeric attribute will be rendered 136 | - boolean attributes will just toggle the attribute without any value. If you need to render the boolean as a value just pass it as a string 137 | - the attribute `xmlns:xlink="http://www.w3.org/1999/xlink"` will be added automatically if any `xlink:` is found in the SVG content 138 | 139 | #### slug 140 | 141 | **type:** `object` or `function` 142 | **default:** `{}` 143 | 144 | In order to have nice ids in the template and to keep the gulp task quite simple, gulp-svg-symbols use [speakingurl](https://www.npmjs.com/package/speakingurl). 145 | 146 | You can pass a [speakingurl's config](https://www.npmjs.com/package/speakingurl#getsluginput-options) here: 147 | 148 | ```js 149 | gulp.src(`*.svg`).pipe( 150 | svgSymbols({ 151 | slug: { 152 | separator: `_`, 153 | }, 154 | }) 155 | ) 156 | ``` 157 | 158 | You can also provide a custom function which should return a `string`: 159 | 160 | ```js 161 | gulp.src(`*.svg`).pipe( 162 | svgSymbols({ 163 | slug: function(name) { 164 | return name.replace(/\s/g, `-`) 165 | }, 166 | }) 167 | ) 168 | ``` 169 | 170 | Or if you want to use [gulp-rename](https://www.npmjs.com/package/gulp-rename): 171 | 172 | ```js 173 | gulp 174 | .src(`*.svg`) 175 | .pipe(rename(/* gulp rename options*/)) 176 | .pipe( 177 | svgSymbols({ 178 | slug: name => name, 179 | }) 180 | ) 181 | ``` 182 | 183 | #### templates 184 | 185 | **type:** `array of string` 186 | **default:** `['default-svg', 'default-css']` 187 | 188 | _gulp-svg-symbols_ comes with some default templates. 189 | 190 | You can control which file are generated by specifying only the templates to keep: 191 | 192 | ```js 193 | templates: [`default-svg`] 194 | ``` 195 | 196 | will output **only** the SVG file. 197 | 198 | Here is the list of all provided templates: 199 | 200 | - [**default-svg**](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md#default-svg): the bundle of SVG 201 | - [**default-css**](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md#default-css): a CSS file gathering all sizes and additional styles 202 | - [**default-demo**](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md#default-demo): a demo page which provide an overview of every symbols + a way to copy/paste easily the symbol SVG code 203 | - [**default-vue**](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md#default-vue): a vue component 204 | - [**default-css-var**](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md#default-css-var): same as the CSS, but all sizes will be also declared as CSS Custom Properties 205 | - [**default-scss**](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md#default-scss): same as the CSS, but sizes will be declared as SCSS variables 206 | - [**default-stylus**](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md#default-stylus): same as the CSS, but sizes will be declared as Stylus variables 207 | 208 | More details about the build-in templates can be found in the [TEMPLATES.md](https://github.com/Hiswe/gulp-svg-symbols/blob/master/TEMPLATES.md) file 209 | 210 | ##### CSS generation 211 | 212 | You can deactivate CSS output by removing the CSS template from the template array. 213 | See [templates option](https://github.com/Hiswe/gulp-svg-symbols#templates) for more details. 214 | 215 | #### warn 216 | 217 | **default:** `true` 218 | 219 | Disable plugin warn messages (like: missing viewBox & depreciation warnings). 220 | 221 | ### Advanced 222 | 223 | #### templates 224 | 225 | Specify your own templates by providing an absolute path: 226 | 227 | ```js 228 | templates: [ 229 | path.join(__dirname, `path/to/my/template.less`), 230 | path.join(__dirname, `path/to/another/template.js`), 231 | // You can still access to default templates by providing: 232 | `default-svg`, 233 | `default-css`, 234 | `default-demo`, 235 | ] 236 | ``` 237 | 238 | - template engine is [lodash](http://lodash.com/docs#template). 239 | - the output files will have the same name & extension as your files. 240 | - every template will have acces to those datas: 241 | 242 | ```js 243 | { 244 | svgAttrs: {/* the same object you can pass in configuration */ }, 245 | defs: `string`, 246 | icons: [{ 247 | id: `string`, 248 | class: `.string`, 249 | width: `a number as a string with a unit`, 250 | height: `a number as a string with a unit`, 251 | style: `string if exists`, 252 | svg: { 253 | name: `string (svg filename without extension)`, 254 | id: `string`, 255 | width: `number`, 256 | height: `number`, 257 | content: `the svg markup as a string`, 258 | viewBox: `string`, 259 | originalAttributes: { 260 | /* every attributes before processing them */ 261 | }, 262 | }, 263 | }, {/*…*/}, ], 264 | } 265 | ``` 266 | 267 | - and also 2 helpers functions 268 | - `attributesToString( object )` render an object as a string of attributes 269 | - `svgdataToSymbol( iconData )` render an icon data object to a stringed symbol 270 | 271 | #### transformData 272 | 273 | With the ability to provide custom templates, you also have the ability to configure custom data. 274 | 275 | ```js 276 | transformData: function(svg, defaultData, options) { 277 | /****** 278 | svg is same object as the one passed to the templates (see above) 279 | 280 | defaultData are the ones needed by default templates 281 | see /lib/get-default-data.js 282 | 283 | options are the one you have set in your gulpfile, 284 | minus templates & transformData 285 | *******/ 286 | 287 | return { 288 | // Return every datas you need 289 | id: defaultData.id, 290 | class: defaultData.class, 291 | width: `${svg.width}em`, 292 | height: `${svg.height}em` 293 | }; 294 | } 295 | ``` 296 | 297 | In your templates, svg original data are accessible in `icon.svg`. 298 | Of course default templates need `defaultData`. 299 | 300 | ### Other observations 301 | 302 | - If you want to manipulate your icons files, use [gulp-cheerio](https://www.npmjs.com/package/gulp-cheerio) 303 | - If you want to optimize your icons files or the SVG output, use [gulp-svgmin](https://www.npmjs.org/package/gulp-svgmin) (using SVGO) 304 | - If you want to change the generated files name, again use [gulp-rename](https://www.npmjs.org/package/gulp-rename) 305 | - If you want different destination for the files, use [gulp-if](https://www.npmjs.org/package/gulp-if) 306 | - Unlike [gulp-svg-sprites](https://www.npmjs.org/package/gulp-svg-sprites) there is no way to add padding to SVG files. 307 | 308 | If you want to include the SVG symbols directly in the DOM (i.e. no external reference) and mask it, a secure way of hiding it could be achieved in this way: 309 | 310 | ```css 311 | .svg-icon-lib { 312 | border: 0 !important; 313 | clip: rect(0 0 0 0) !important; 314 | height: 1px !important; 315 | margin: -1px !important; 316 | overflow: hidden !important; 317 | padding: 0 !important; 318 | position: absolute !important; 319 | width: 1px !important; 320 | } 321 | ``` 322 | 323 | A simple `display: none` will mess with defs rendering (gradients and so on…) 324 | 325 | ## Other stuff 326 | 327 | ### Rendering caveats 328 | 329 | SVG can have rendering issues if: 330 | 331 | - multiple `<defs>` have the same ids. 332 | Use [gulp-svgmin](https://github.com/ben-eb/gulp-svgmin#per-file-options) to fix that. 333 | - `<clipPath>` and `<mask>` aren't staying inside `<defs>` tags. 334 | Move those tags **inside** the `<defs>` tags. Manually or programmatically (easy to do with [gulp-cheerio](https://www.npmjs.com/package/gulp-cheerio)) 335 | 336 | An example has been made to show all those issues resolved inside the [svgContainingIdenticalId](https://github.com/Hiswe/gulp-svg-symbols/blob/master/examples/gulpfile.js#L198-L282). 337 | 338 | `npm run svg-containing-identical-id` to test. 339 | 340 | ### Migrating 341 | 342 | See [MIGRATING.md](https://github.com/Hiswe/gulp-svg-symbols/blob/master/MIGRATING.md) 343 | 344 | ### More examples 345 | 346 | Go in the [examples folder](https://github.com/Hiswe/gulp-svg-symbols/blob/master/examples), then `npm install && npm run list`. 347 | You will have a list of all task examples there 348 | 349 | ### Usefull frontend lib 350 | 351 | - [svg4everybody](https://www.npmjs.com/package/svg4everybody) leverage external SVG for browser which doesn't support it 352 | 353 | ### Thanks 354 | 355 | - [Florens Verschelde](https://github.com/fvsch) for the usefull insights and PR 356 | 357 | ### Credits 358 | 359 | - [Chris Coyier](http://css-tricks.com/) for the [trick](http://css-tricks.com/svg-symbol-good-choice-icons/) 360 | - [Shaky Shane](https://www.npmjs.org/~shakyshane) for the [gulp-svg-sprites](https://www.npmjs.org/package/gulp-svg-sprites) plugin 361 | - [FWeinb](https://github.com/FWeinb) for the [grunt-svgstore](https://github.com/FWeinb/grunt-svgstore) plugin 362 | 363 | ### Alternatives 364 | 365 | - [gulp-svg-sprite](https://www.npmjs.com/package/gulp-svg-sprite) 366 | - [gulp-svg-store](https://www.npmjs.com/package/gulp-svgstore) 367 | - [gulp-svg-sprites](https://www.npmjs.org/package/gulp-svg-sprites) 368 | -------------------------------------------------------------------------------- /TEMPLATES.md: -------------------------------------------------------------------------------- 1 | # gulp-svg-symbols build-in templates 2 | 3 | Here are all the templates provided by the plugin and the result you can expect from them: 4 | 5 | <!-- START doctoc generated TOC please keep comment here to allow auto update --> 6 | <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> 7 | 8 | 9 | - [default-svg](#default-svg) 10 | - [default-css](#default-css) 11 | - [default-demo](#default-demo) 12 | - [default-vue](#default-vue) 13 | - [usage](#usage) 14 | - [props](#props) 15 | - [default-css-var](#default-css-var) 16 | - [default-scss](#default-scss) 17 | - [default-stylus](#default-stylus) 18 | 19 | <!-- END doctoc generated TOC please keep comment here to allow auto update --> 20 | 21 | ## default-svg 22 | 23 | > responsible of generating the bundled SVG file 24 | 25 | file name: 26 | 27 | ``` 28 | svg-symbols.svg 29 | ``` 30 | 31 | ```xml 32 | <svg xmlns="http://www.w3.org/2000/svg"> 33 | <symbol id="github" viewBox="0 0 22 24"> 34 | <!-- SVG content --> 35 | </symbol> 36 | </svg> 37 | ``` 38 | 39 | ## default-css 40 | 41 | > responsible of generating the CSS file containing the symbols sizes and the CSS rules coming from your SVG files 42 | 43 | file name: 44 | 45 | ``` 46 | svg-symbols.css 47 | ``` 48 | 49 | ```css 50 | .github { 51 | width: 22px; 52 | height: 24px; 53 | } 54 | ``` 55 | 56 | ## default-demo 57 | 58 | > the demo page with the snippets you can copy & paste in your HTML 59 | 60 | file name: 61 | 62 | ``` 63 | svg-symbols-demo-page.html 64 | ``` 65 | 66 | A single HTML file with all the right styles and a nice presentations 😀 67 | 68 | ## default-vue 69 | 70 | > generate a vue component 71 | 72 | file name: 73 | 74 | ``` 75 | svg-symbols.vue 76 | ``` 77 | 78 | ### usage 79 | 80 | gulpfile configuration 81 | 82 | ```js 83 | var gulp = require('gulp') 84 | var svgSymbols = require('gulp-svg-symbols') 85 | 86 | function sprites() { 87 | return gulp 88 | .src(`svg/*.svg`) 89 | .pipe( 90 | svgSymbols({ 91 | // optional: define a global class for every SVG 92 | svgAttrs: { class: `svg-symbol` }, 93 | // optional: customize another class for each SVG 94 | class: `.svg-symbol--%f`, 95 | // choose the vue template 96 | templates: [`default-vue`], 97 | }) 98 | ) 99 | .pipe(gulp.dest(`components`)) 100 | } 101 | exports.sprites = sprites 102 | ``` 103 | 104 | Register the component in your Vue application (or Vue component) 105 | 106 | ```js 107 | import Vue from 'vue' 108 | import SvgSymbol from '.svg-symbols' 109 | Vue.component(`svg-symbol`, SvgSymbol) 110 | ``` 111 | 112 | Use it in your own components 113 | 114 | ```html 115 | <template> 116 | <div> 117 | <svg-symbol class="github-icon" name="github" :scale="3"></svg-symbol> 118 | </div> 119 | </template> 120 | 121 | <style> 122 | .github-icon { 123 | fill: red; 124 | } 125 | </style> 126 | ``` 127 | 128 | ### props 129 | 130 | | Name | | Default | Type | | 131 | | ----- | -------- | ------- | ------ | -------------------------------------------------------------- | 132 | | name | Required | none | String | icon name | 133 | | scale | | 1 | Number | Scale icon size by recomputing SVG's width & height attributes | 134 | 135 | ## default-css-var 136 | 137 | > generate a CSS file containing the symbols sizes as custom properties and the CSS rules coming from your SVG files 138 | 139 | file name: 140 | 141 | ``` 142 | svg-symbols-custom-properties.css 143 | ``` 144 | 145 | ```css 146 | :root { 147 | --icon-github-width: 22px; 148 | --icon-github-height: 24px; 149 | } 150 | .github { 151 | width: var(--icon-github-width); 152 | height: var(--icon-github-height); 153 | } 154 | ``` 155 | 156 | ## default-scss 157 | 158 | > generate a SCSS file containing the symbols sizes as variables and the CSS rules coming from your SVG files 159 | 160 | file name: 161 | 162 | ``` 163 | svg-symbols.scss 164 | ``` 165 | 166 | ```scss 167 | $icon-github-width: 22px; 168 | $icon-github-height: 24px; 169 | 170 | .github { 171 | width: $icon-github-width; 172 | height: $icon-github-height; 173 | } 174 | ``` 175 | 176 | ## default-stylus 177 | 178 | > generate a stylus file containing the symbols sizes as variables and the CSS rules coming from your SVG files 179 | 180 | file name: 181 | 182 | ``` 183 | svg-symbols.styl 184 | ``` 185 | 186 | ```styl 187 | $icon-github-width = 22px; 188 | $icon-github-height = 24px; 189 | 190 | .github { 191 | width: $icon-github-width; 192 | height: $icon-github-height; 193 | } 194 | ``` 195 | -------------------------------------------------------------------------------- /examples/aspect-ratio-test.html: -------------------------------------------------------------------------------- 1 | <!DOCTYPE html> 2 | <html> 3 | <head> 4 | <meta charset="utf-8" /> 5 | <meta http-equiv="X-UA-Compatible" content="IE=edge" /> 6 | <title>Gulp SVG Symbols test page 7 | 8 | 9 | 25 | 33 | 34 | 35 | 36 | 37 | <%= defs %> 38 | 39 | <% _.forEach( icons, function( icon ){ %> 40 | preserveAspectRatio="<%= icon.svg.originalAttributes.preserveAspectRatio %>" <% }%>><% if (icon.title) {%> 41 | <%= icon.title %><% }%> 42 | <%= icon.svg.content %> 43 | <% 44 | }); %> 45 |

line whould take 100%
octocat should stay same sized

46 |
47 |

untouched SVG

48 | 49 | 50 | 51 |
52 | 53 | 54 | 55 |

SVG with width at 100%

56 | 57 | 58 | 59 | 60 | 61 | 62 |
63 | 64 | 65 | -------------------------------------------------------------------------------- /examples/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require(`path`) 4 | const gulp = require(`gulp`) 5 | const gulpif = require(`gulp-if`) 6 | const rename = require(`gulp-rename`) 7 | const svgmin = require(`gulp-svgmin`) 8 | const cheerio = require(`gulp-cheerio`) 9 | const gulpSass = require(`gulp-sass`) 10 | const gulpStylus = require(`gulp-stylus`) 11 | const beautify = require('gulp-beautify') 12 | // need to reference the real module 13 | // https://github.com/Hiswe/gulp-svg-symbols/issues/35#issuecomment-254494474 14 | const svgSymbols = require(`gulp-svg-symbols`) 15 | // // for test purpose 16 | // const svgSymbols = require(`../index.js`) 17 | 18 | const svgGlob = `../test/source/*.svg` 19 | 20 | //////// 21 | // DEFAULT OPTIONS OUTPUT 22 | //////// 23 | 24 | function svg() { 25 | return gulp 26 | .src(svgGlob) 27 | .pipe(svgSymbols()) 28 | .pipe(gulp.dest(`ex-default`)) 29 | } 30 | svg.description = `default use of the plugin` 31 | 32 | //////// 33 | // ADDING THE DEMO PAGE 34 | //////// 35 | 36 | function demoPage() { 37 | return gulp 38 | .src(svgGlob) 39 | .pipe( 40 | svgSymbols({ 41 | templates: [`default-svg`, `default-css`, `default-demo`], 42 | }) 43 | ) 44 | .pipe(gulp.dest(`ex-demo-page`)) 45 | } 46 | demoPage.description = `Generating the demo page along with the default templates` 47 | 48 | //////// 49 | // ALL TEMPLATES 50 | //////// 51 | 52 | function allBuildInTemplates() { 53 | return gulp 54 | .src(`../test/source/github.svg`) 55 | .pipe( 56 | svgSymbols({ 57 | templates: [ 58 | `default-svg`, 59 | `default-css`, 60 | `default-css-var`, 61 | `default-scss`, 62 | `default-stylus`, 63 | `default-demo`, 64 | `default-vue`, 65 | ], 66 | }) 67 | ) 68 | .pipe(gulp.dest(`ex-build-in-templates`)) 69 | } 70 | demoPage.description = `Generating all default templates` 71 | 72 | //////// 73 | // SCSS 74 | //////// 75 | 76 | function scss() { 77 | return gulp 78 | .src(svgGlob) 79 | .pipe( 80 | svgSymbols({ 81 | templates: [`default-scss`], 82 | }) 83 | ) 84 | .pipe(gulp.dest(`ex-scss`)) 85 | .pipe(gulpSass()) 86 | .pipe(gulp.dest(`ex-scss`)) 87 | } 88 | scss.description = `Generating scss file` 89 | 90 | //////// 91 | // STYLUS 92 | //////// 93 | 94 | function stylus() { 95 | return gulp 96 | .src(svgGlob) 97 | .pipe( 98 | svgSymbols({ 99 | templates: [`default-stylus`], 100 | }) 101 | ) 102 | .pipe(gulp.dest(`ex-stylus`)) 103 | .pipe(gulpStylus()) 104 | .pipe(gulp.dest(`ex-stylus`)) 105 | } 106 | stylus.description = `Generating styl file` 107 | 108 | //////// 109 | // CSS VAR 110 | //////// 111 | 112 | function cssVar() { 113 | return gulp 114 | .src(svgGlob) 115 | .pipe( 116 | svgSymbols({ 117 | templates: [`default-css-var`], 118 | }) 119 | ) 120 | .pipe(gulp.dest(`ex-css-var`)) 121 | } 122 | cssVar.description = `Generating css file with custom properties` 123 | 124 | //////// 125 | // SVG GLOBAL CLASS NAME 126 | //////// 127 | 128 | // add a class name to the outputted SVG in case of SVG being included in the DOM 129 | function svgClassname() { 130 | return gulp 131 | .src(svgGlob) 132 | .pipe( 133 | svgSymbols({ 134 | svgAttrs: { class: `custom-name` }, 135 | }) 136 | ) 137 | .pipe(gulp.dest(`ex-svg-classname`)) 138 | } 139 | svgClassname.description = `Adding add a class to the generated SVG file` 140 | 141 | //////// 142 | // CUSTOM TEMPLATES 143 | //////// 144 | 145 | const customCSSTemplate = path.join(__dirname, `../test/source/template.json`) 146 | const customHTMLTemplate = path.join(__dirname, `../test/source/template.html`) 147 | 148 | function customTemplate() { 149 | return gulp 150 | .src(svgGlob) 151 | .pipe( 152 | svgSymbols({ 153 | id: `icon-%f`, 154 | className: `.icon-%f`, 155 | title: false, 156 | fontSize: 16, 157 | templates: [ 158 | `default-svg`, 159 | `default-demo`, 160 | customCSSTemplate, 161 | customHTMLTemplate, 162 | ], 163 | }) 164 | ) 165 | .pipe( 166 | rename(function(path) { 167 | path.basename = `icon-files` 168 | }) 169 | ) 170 | .pipe(gulpif(/[.]svg$/, gulp.dest(`ex-custom-template/views`))) 171 | .pipe(gulpif(/[.]json$/, gulp.dest(`ex-custom-template/front`))) 172 | .pipe(gulpif(/[.]html$/, gulp.dest(`ex-custom-template/tmp`))) 173 | } 174 | customTemplate.description = `Using custom templates` 175 | 176 | //////// 177 | // CUSTOM TEMPLATES with ASPECT RATIO 178 | //////// 179 | 180 | function aspectRatio() { 181 | return gulp 182 | .src([ 183 | `../test/source/aspect-ratio.svg`, 184 | `../test/source/chinese letter with styles.svg`, 185 | `../test/source/github.svg`, 186 | ]) 187 | .pipe( 188 | svgSymbols({ 189 | templates: [ 190 | path.join(__dirname, `/aspect-ratio-test.html`), 191 | `default-svg`, 192 | ], 193 | }) 194 | ) 195 | .pipe(gulp.dest(`ex-aspect-ratio`)) 196 | } 197 | aspectRatio.description = `A custom template with aspect ratios` 198 | 199 | //////// 200 | // OUTPUT WITH SVG INCLUDING SAME MASK IDS 201 | //////// 202 | 203 | function svgContainingIdenticalId() { 204 | return ( 205 | gulp 206 | .src(`./svg-with-masks-sources/*.svg`) 207 | .pipe( 208 | svgmin(file => { 209 | const { relative } = file 210 | const prefix = path.basename(relative, path.extname(relative)) 211 | return { 212 | js2svg: { 213 | pretty: true, 214 | }, 215 | plugins: [ 216 | { 217 | // this prevent duplicated IDs when bundled in the same file 218 | cleanupIDs: { prefix: `${prefix}-` }, 219 | }, 220 | { 221 | // some cleaning 222 | removeDoctype: true, 223 | }, 224 | { 225 | removeXMLProcInst: true, 226 | }, 227 | { 228 | removeTitle: true, 229 | }, 230 | { 231 | removeDesc: { removeAny: true }, 232 | }, 233 | { 234 | convertTransform: {}, 235 | }, 236 | ], 237 | } 238 | }) 239 | ) 240 | // We need to move and to the defs… 241 | // …in order for Firefox to render the SVG correctly 242 | .pipe( 243 | cheerio({ 244 | run: ($, file) => { 245 | const $clipPath = $(`clipPath`) 246 | const $mask = $(`mask`) 247 | let $defs = $(`defs`) 248 | const hasClipPath = $clipPath.length > 0 249 | const hasMask = $mask.length > 0 250 | const hasDefs = $defs.length > 0 251 | if (!hasClipPath && !hasMask) return 252 | if (!hasDefs) { 253 | $defs = $(``) 254 | $defs.prependTo(`svg`) 255 | } 256 | function copyToDefs(i, el) { 257 | const $el = $(el) 258 | const $clone = $el.clone() 259 | $clone.appendTo($defs) 260 | $el.remove() 261 | } 262 | if (hasClipPath) $clipPath.each(copyToDefs) 263 | if (hasMask) $mask.each(copyToDefs) 264 | }, 265 | parserOptions: { 266 | xmlMode: true, 267 | }, 268 | }) 269 | ) 270 | // reformat 271 | .pipe(beautify.html({ indent_char: ` `, indent_size: 2 })) 272 | .pipe(gulp.dest(`ex-svg-with-masks`)) 273 | // everything is ready for gulp-svg-symbols! 274 | .pipe( 275 | svgSymbols({ 276 | templates: [`default-demo`], 277 | }) 278 | ) 279 | .pipe(gulp.dest(`ex-svg-with-masks`)) 280 | ) 281 | } 282 | svgContainingIdenticalId.description = `How to handle SVGs with masks IDs` 283 | 284 | //////// 285 | // VUE 286 | //////// 287 | 288 | function vue() { 289 | return gulp 290 | .src([ 291 | `../test/source/chinese letter with styles.svg`, 292 | `../test/source/gradient.svg`, 293 | `../test/source/xlink-href.svg`, 294 | `../test/source/aspect-ratio.svg`, 295 | `../test/source/crâne noir.svg`, 296 | `../test/source/github.svg`, 297 | ]) 298 | .pipe( 299 | svgSymbols({ 300 | svgAttrs: { class: `svg-symbol` }, 301 | class: `.svg-symbol--%f`, 302 | templates: [`default-vue`, `default-demo`], 303 | }) 304 | ) 305 | .pipe(gulp.dest(`ex-vue`)) 306 | } 307 | vue.description = `vue template` 308 | 309 | //////// 310 | // EXPORTING TASKS 311 | //////// 312 | 313 | gulp.task(`svg`, svg) 314 | gulp.task(`scss`, scss) 315 | gulp.task(`all-build-in-templates`, allBuildInTemplates) 316 | gulp.task(`stylus`, stylus) 317 | gulp.task(`css-var`, cssVar) 318 | gulp.task(`demo-page`, demoPage) 319 | gulp.task(`svg-classname`, svgClassname) 320 | gulp.task(`custom-template`, customTemplate) 321 | gulp.task(`aspect-ratio`, aspectRatio) 322 | gulp.task(`svg-containing-identical-id`, svgContainingIdenticalId) 323 | gulp.task(`vue`, vue) 324 | gulp.task( 325 | `all`, 326 | gulp.parallel(svg, demoPage, svgClassname, customTemplate, aspectRatio, vue) 327 | ) 328 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-svg-symbols-examples", 3 | "version": "1.0.0", 4 | "description": "A bunch of gulpfile examples using gulp-svg-symbols", 5 | "main": "gulpfile.js", 6 | "scripts": { 7 | "list": "gulp -T", 8 | "all": "gulp all", 9 | "all-build-in": "gulp all-build-in-templates", 10 | "svg": "gulp svg", 11 | "scss": "gulp scss", 12 | "stylus": "gulp stylus", 13 | "css-var": "gulp css-var", 14 | "demo-page": "gulp demo-page", 15 | "svg-classname": "gulp svg-classname", 16 | "custom-template": "gulp custom-template", 17 | "aspect-ratio": "gulp aspect-ratio", 18 | "svg-containing-identical-id": "gulp svg-containing-identical-id", 19 | "vue": "gulp vue && parcel vue/index.html" 20 | }, 21 | "author": "Hiswe", 22 | "license": "MIT", 23 | "devDependencies": { 24 | "@vue/component-compiler-utils": "^3.0.0", 25 | "gulp": "^4.0.0", 26 | "gulp-beautify": "^3.0.0", 27 | "gulp-cheerio": "^0.6.3", 28 | "gulp-if": "^2.0.1", 29 | "gulp-rename": "^1.2.2", 30 | "gulp-sass": "^4.0.1", 31 | "gulp-stylus": "^2.7.0", 32 | "gulp-svg-symbols": "^3.0.2", 33 | "gulp-svgmin": "^2.2.0", 34 | "parcel-bundler": "^1.9.7", 35 | "vue": "^2.5.16", 36 | "vue-template-compiler": "^2.5.16" 37 | }, 38 | "dependencies": { 39 | "vue-hot-reload-api": "^2.3.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /examples/svg-with-masks-sources/ad-clip-path-donut.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/svg-with-masks-sources/ad-clip-path-heart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/svg-with-masks-sources/ad-compose-path-variation.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/svg-with-masks-sources/ad-compose-path.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/svg-with-masks-sources/affinity-designer-source.afdesign: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:abde83c7f7318425a825684ee8f88744cb436c1d398aa699cb94c59c933206ed 3 | size 25239 4 | -------------------------------------------------------------------------------- /examples/svg-with-masks-sources/figma-output-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 2 3 | Created using Figma 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/svg-with-masks-sources/figma-output-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 3 | Created using Figma 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /examples/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue icons 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/vue/layout.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 84 | 85 | 90 | 91 | -------------------------------------------------------------------------------- /examples/vue/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | import Layout from './layout' 4 | import SvgSymbol from '../ex-vue/svg-symbols' 5 | 6 | Vue.component(`svg-symbol`, SvgSymbol) 7 | 8 | new Vue({ 9 | el: `#vue-root`, 10 | render: h => h(Layout), 11 | }) 12 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = require(`lodash`) 4 | const path = require(`path`) 5 | const PluginError = require(`plugin-error`) 6 | const through = require(`through2`) 7 | 8 | const defaults = require(`./lib/default-config`) 9 | const svg = require(`./lib/svg`) 10 | const templates = require(`./lib/templates.js`) 11 | const utils = require(`./lib/utils.js`) 12 | 13 | const PLUGIN_NAME = utils.name 14 | 15 | // prettier-ignore 16 | const templatesPath = { 17 | 'default-svg': path.join(__dirname, `./templates/svg-symbols.svg`), 18 | 'default-css': path.join(__dirname, `./templates/svg-symbols.css`), 19 | 'default-css-var': path.join(__dirname, `./templates/svg-symbols-custom-properties.css`), 20 | 'default-scss': path.join(__dirname, `./templates/svg-symbols.scss`), 21 | 'default-stylus': path.join(__dirname, `./templates/svg-symbols.styl`), 22 | 'default-demo': path.join(__dirname, `./templates/svg-symbols-demo-page.html`), 23 | 'default-vue': path.join(__dirname, `./templates/svg-symbols.vue`), 24 | }; 25 | 26 | function gulpSvgSymbols(opts = {}) { 27 | const buffer = [] 28 | let defs = [] 29 | 30 | // clone everything as we don't want to mutate anything 31 | const options = _.defaultsDeep(_.cloneDeep(opts), _.cloneDeep(defaults)) 32 | // restore templates array as it will be messed up by _.defaultsDeep 33 | options.templates = opts.templates || defaults.templates 34 | 35 | // expand path to default templates 36 | options.templates = options.templates.map(pathName => { 37 | if (pathName in templatesPath) return templatesPath[pathName] 38 | return pathName 39 | }) 40 | 41 | // Handle deprecation warnings for old options and fix the config 42 | // `className` option is now just `class` 43 | if (typeof options.className !== `undefined`) { 44 | utils.logWarn( 45 | options, 46 | `options.className is deprecated. Please replace it with options.class` 47 | ) 48 | options.class = options.className 49 | } 50 | // svgClassname option is now living inside svgAttrs 51 | if (typeof options.svgClassname !== `undefined`) { 52 | utils.logWarn( 53 | options, 54 | `options.svgClassname is deprecated. Please replace it with options.svgAttrs.class` 55 | ) 56 | options.svgAttrs.class = options.svgClassname 57 | } 58 | 59 | // buffer and transform every files 60 | return through.obj( 61 | function transform(file, encoding, cb) { 62 | if (file.isNull()) { 63 | return cb(null, file) 64 | } 65 | 66 | // we don't handle streams :,( 67 | // use https://github.com/nfroidure/gulp-streamify if you're reading this 68 | // next versions should use https://www.npmjs.com/package/bufferstreams 69 | if (file.isStream()) { 70 | const errorReason = `Streaming is not supported` 71 | this.emit(`error`, new PluginError(PLUGIN_NAME, errorReason)) 72 | return cb() 73 | } 74 | 75 | svg.parseFile(file, options).then(result => { 76 | buffer.push(result) 77 | return cb(null) 78 | }) 79 | 80 | // put all generated files back in the stream 81 | }, 82 | function flush(cb) { 83 | const that = this 84 | 85 | // don't produce any file if no datas 86 | if (buffer.length === 0) { 87 | return cb() 88 | } 89 | 90 | const defsIdList = {} 91 | const svgData = buffer.map(svgRawData => { 92 | // defs are not at an SVG level 93 | // they should be handled globally to the new SVG file 94 | if (svgRawData.defs) defs.push(svgRawData.defs) 95 | // control IDs laying in defs 96 | if (svgRawData.__gatheredIds__) { 97 | _.toPairs(svgRawData.__gatheredIds__).forEach(([key, value]) => { 98 | if (!defsIdList[key]) return (defsIdList[key] = [value]) 99 | defsIdList[key].push(value) 100 | }) 101 | } 102 | return svg.formatDataForTemplate(svgRawData, options) 103 | }) 104 | 105 | // make a warn about duplicated IDs inside defs 106 | const defsIdWarn = [] 107 | _.toPairs(defsIdList).forEach(([key, value]) => { 108 | if (value.length < 2) return 109 | const warn = `id “${key}” found in different files (${value.join( 110 | `, ` 111 | )})` 112 | defsIdWarn.push(` • ${warn}`) 113 | }) 114 | if (defsIdWarn.length) { 115 | utils.logWarn( 116 | options, 117 | ` has some duplicated ids:\n${defsIdWarn.join( 118 | `\n` 119 | )}\nsee https://github.com/Hiswe/gulp-svg-symbols#rendering-caveats` 120 | ) 121 | } 122 | 123 | // force defs to have a value. 124 | // better for templates to check if `false` rather than length… 125 | defs = defs.length > 0 ? defs.join(`\n`) : false 126 | 127 | // automatically insert xlink if needed 128 | // even if it's deprecated in SVG2 most software will still produce SVG 1.1 129 | // and I can't find find any good website talking about SVG2 support in browsers… 130 | const haystack = 131 | svgData.map(templates.svgdataToSymbol).join(``) + (defs || ``) 132 | if (/\sxlink:[a-z]+=/.test(haystack)) { 133 | options.svgAttrs[`xmlns:xlink`] = `http://www.w3.org/1999/xlink` 134 | } 135 | 136 | const files = templates.renderAll(options.templates, { 137 | svgAttrs: options.svgAttrs, 138 | icons: svgData, 139 | defs: defs, 140 | }) 141 | 142 | function outputFiles(files) { 143 | files.forEach(file => that.push(file)) 144 | cb() 145 | } 146 | 147 | Promise.all(files) 148 | .then(outputFiles) 149 | .catch(err => { 150 | this.emit( 151 | `error`, 152 | new PluginError(PLUGIN_NAME, err, { showStack: true }) 153 | ) 154 | cb() 155 | }) 156 | } 157 | ) 158 | } 159 | 160 | module.exports = gulpSvgSymbols 161 | -------------------------------------------------------------------------------- /lib/default-config.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | // attributes that will be be pushed in the SVG root node 5 | svgAttrs: { 6 | // class that will be added in default template root SVG (deprecated) 7 | class: null, 8 | xmlns: `http://www.w3.org/2000/svg`, 9 | }, 10 | // this will be used for generating SVG id and associated class names 11 | id: `%f`, 12 | class: `.%f`, 13 | // Determine size in CSS (em/px) 14 | fontSize: 0, 15 | // debug log 16 | warn: true, 17 | // accessibility title 18 | title: false, 19 | // templates to render 20 | templates: [`default-svg`, `default-css`], 21 | // slug params or custom function 22 | slug: {}, 23 | // datas passed to templates while rendering 24 | transformData: function(svg, defaultData, options) { 25 | return defaultData 26 | }, 27 | } 28 | -------------------------------------------------------------------------------- /lib/svg.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = require(`lodash`) 4 | const slug = require(`speakingurl`) 5 | const path = require(`path`) 6 | const cheerio = require(`cheerio`) 7 | 8 | const utils = require(`./utils`) 9 | 10 | // 11 | // GATHER ELEMENTARY INFORMATIONS ABOUT THE SVG FILE 12 | // 13 | 14 | function parseFile(file, options = {}) { 15 | // TODO what if cheerio can't load a content? 16 | const $ = cheerio.load(file.contents.toString(), { 17 | normalizeWhitespace: true, 18 | xmlMode: true, 19 | }) 20 | const $svg = $(`svg`) 21 | const attr = $svg[0].attribs 22 | const viewBox = utils.viewboxToArray(attr.viewBox, file.path, options) 23 | const name = /(.*)\.svg/.exec(path.basename(file.path))[1] 24 | 25 | const result = { 26 | name: name, 27 | viewBox: viewBox.join(` `), 28 | originalAttributes: attr, 29 | // SVG files might not have size 30 | // https://github.com/Hiswe/gulp-svg-symbols/issues/10 31 | width: utils.sizeOrViewboxFallback(attr.width, viewBox[2]), 32 | height: utils.sizeOrViewboxFallback(attr.height, viewBox[3]), 33 | } 34 | 35 | // ID generation 36 | // spaces in IDs or Classes are never a good idea 37 | if (_.isFunction(options.slug)) { 38 | // let user add his custom parsing function… 39 | result.id = options.slug(name) 40 | } else if (_.isPlainObject(options.slug)) { 41 | // …or pass custom option to speakingurl 42 | result.id = slug(name, options.slug) 43 | } else { 44 | result.id = slug(name) 45 | } 46 | 47 | // STYLE handling 48 | const $style = $svg.find(`style`) 49 | if ($style.length) { 50 | result.style = $style.html().trim() 51 | // don't format more than adding newlines after each rules end 52 | result.style = result.style.replace(/}\s*(?!\n)/g, `}\n`) 53 | } 54 | $style.remove() 55 | 56 | // DEFS handling 57 | const $defs = $svg.find(`defs`) 58 | if ($defs.children().length) { 59 | result.defs = $defs.html() 60 | // gather all ids inside defs 61 | // this will be used in the plugin to check if ids are not shared among files 62 | $defs.find(`[id]`).each((i, el) => { 63 | result.__gatheredIds__ = result.__gatheredIds__ || {} 64 | const id = $(el).attr(`id`) 65 | result.__gatheredIds__[id] = result.name 66 | }) 67 | } 68 | $defs.remove() 69 | 70 | // CONTENT 71 | // only optim is to remove empty group 72 | // but shouldn't be done: SVG Symbol should only do concat SVG files 73 | $svg.find(`g`).each(function() { 74 | if (!$(this).children().length) $(this).remove() 75 | }) 76 | 77 | // & check 78 | // if they are not inside a tag it can result in wrong display on Firefox 79 | ;[`clipPath`, `mask`].forEach(tagName => { 80 | const $el = $(tagName) 81 | if (!$el.length) return 82 | const warn = `<${tagName}> tag found outside a tag in file ${ 83 | file.path 84 | }. This can lead to unexpected results.\nsee https://github.com/Hiswe/gulp-svg-symbols#rendering-caveats` 85 | utils.logWarn(options, warn) 86 | }) 87 | 88 | result.content = $svg.html() 89 | 90 | return Promise.resolve(result) 91 | } 92 | 93 | // 94 | // MODIFY DATAS BEFORE GIVING IT TO TEMPLATES 95 | // 96 | 97 | function formatDataForTemplate(svgRawData, options) { 98 | let result = {} 99 | // this can be overrided by user transformData function 100 | const tmplDatas = { 101 | id: utils.dynamicText(options.id, svgRawData.id), 102 | class: utils.dynamicText(options.class, svgRawData.id), 103 | width: utils.cssSize(svgRawData.width, options.fontSize), 104 | height: utils.cssSize(svgRawData.height, options.fontSize), 105 | } 106 | // It should be handled by a custom template or custom transformData 107 | if (options.title !== false && !//.test(svgRawData.content)) { 108 | tmplDatas.title = utils.dynamicText(options.title, svgRawData.name) 109 | } 110 | 111 | // Styles coming from <style /> are kept in the SVG file 112 | // we don't take care of duplicated styles or anything else 113 | if (svgRawData.style) tmplDatas.style = svgRawData.style 114 | 115 | // Apply TransformData option 116 | // no need to be able to call transformData inside transformData %) 117 | result = options.transformData( 118 | svgRawData, 119 | tmplDatas, 120 | _.omit(options, [`transformData`, `templates`]) 121 | ) 122 | // Always keep a reference of the original datas 123 | result.svg = svgRawData 124 | 125 | return result 126 | } 127 | 128 | module.exports = { 129 | parseFile, 130 | formatDataForTemplate, 131 | } 132 | -------------------------------------------------------------------------------- /lib/templates.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require(`path`) 4 | const consolidate = require(`consolidate`) 5 | consolidate.requires.lodash = require(`lodash`) 6 | const tmpl = consolidate.lodash 7 | const utils = require(`./utils`) 8 | 9 | function attributesToString(obj) { 10 | return Object.getOwnPropertyNames(obj).reduce((str, key) => { 11 | const attr = key.replace(/[^a-zA-Z-:]/g, ``) 12 | const raw = obj[key] 13 | const rawEscaped = String(raw).replace(/"/g, `"`) 14 | const t = typeof raw 15 | if (t === `boolean` && raw === true) { 16 | return `${str} ${attr}` 17 | } 18 | if (t === `string` || (t === `number` && !isNaN(raw))) { 19 | return `${str} ${attr}="${rawEscaped}"` 20 | } 21 | return str 22 | }, ``) 23 | } 24 | 25 | function svgdataToSymbol(icon) { 26 | const symbolAttrs = attributesToString({ 27 | id: icon.id, 28 | viewBox: icon.svg.viewBox, 29 | preserveAspectRatio: icon.svg.originalAttributes.preserveAspectRatio, 30 | }) 31 | const title = icon.title ? `<title>${icon.title}` : `` 32 | return `${title}${icon.svg.content}` 33 | } 34 | 35 | function render(template, datas) { 36 | const name = path.basename(template) 37 | // add some utilities function to lodash template engine 38 | // this avoid bloating the template with javascript 39 | // and keep the use of it optionnal 40 | datas.imports = { 41 | attributesToString, 42 | svgdataToSymbol, 43 | } 44 | return tmpl(template, datas).then(result => utils.createFile(name, result)) 45 | } 46 | 47 | function renderAll(templates, datas) { 48 | return templates.map(template => render(template, datas)) 49 | } 50 | 51 | module.exports = { 52 | attributesToString, 53 | svgdataToSymbol, 54 | renderAll, 55 | render, 56 | } 57 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const _ = require(`lodash`) 4 | const log = require(`fancy-log`) 5 | const yellow = require(`ansi-yellow`) 6 | const grey = require(`ansi-grey`) 7 | const Vinyl = require(`vinyl`) 8 | const PLUGIN_NAME = `gulp-svg-symbols` 9 | 10 | function logWarn(options, ...message) { 11 | if (options.warn) { 12 | log(yellow(PLUGIN_NAME), ...message) 13 | } 14 | } 15 | 16 | // Format a size to px or em 17 | function cssSize(size, fontSize) { 18 | let unit = `px` 19 | if (_.isNumber(fontSize) && fontSize > 0) { 20 | unit = `em` 21 | } 22 | if (unit === `px`) { 23 | return size + `px` 24 | } 25 | return _.round(size / fontSize, 3) + `em` 26 | } 27 | 28 | function dynamicText(template, name) { 29 | return template.replace(`%f`, name) 30 | } 31 | 32 | function viewboxToArray(viewbox, name, options) { 33 | if (_.isUndefined(viewbox)) { 34 | logWarn(options, grey(`viewbox missing in file`), name) 35 | return [0, 0, 100, 100] 36 | } 37 | 38 | return viewbox.split(` `).map(function(value) { 39 | return parseFloat(value, 10) 40 | }) 41 | } 42 | 43 | function sizeOrViewboxFallback(size, fallback) { 44 | // no size -> get viewbox fallback 45 | if (_.isUndefined(size)) { 46 | return fallback 47 | } 48 | // handle percent svg size -> get viewbox fallback 49 | // https://github.com/Hiswe/gulp-svg-symbols/issues/24 50 | if (/\d+%/.test(size)) { 51 | return (parseInt(size, 10) * fallback) / 100 52 | } 53 | return parseInt(size, 10) 54 | } 55 | 56 | function createFile(name, contents) { 57 | return new Vinyl({ 58 | cwd: `./`, 59 | base: `./`, 60 | path: name, 61 | contents: new Buffer(contents), 62 | }) 63 | } 64 | 65 | module.exports = { 66 | logWarn, 67 | cssSize, 68 | dynamicText, 69 | viewboxToArray, 70 | sizeOrViewboxFallback, 71 | createFile, 72 | name: PLUGIN_NAME, 73 | } 74 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-svg-symbols", 3 | "version": "3.2.3", 4 | "description": "Convert SVG files to symbols with gulp", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "ava test/*.js", 8 | "lint": "prettier --write \"{{lib,test}/*,index,examples/gulpfile}.js\"", 9 | "toc": "doctoc README.md TEMPLATES.md --github", 10 | "release": "np --no-yarn" 11 | }, 12 | "repository": "https://github.com/Hiswe/gulp-svg-symbols.git", 13 | "keywords": [ 14 | "gulpplugin", 15 | "svg", 16 | "symbols" 17 | ], 18 | "author": "Yannick Aïvayan (http://hiswe.net)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/Hiswe/gulp-svg-symbols/issues" 22 | }, 23 | "homepage": "https://github.com/Hiswe/gulp-svg-symbols", 24 | "dependencies": { 25 | "ansi-grey": "^0.1.1", 26 | "ansi-yellow": "^0.1.1", 27 | "cheerio": "^1.0.0-rc.3", 28 | "consolidate": "^0.15.1", 29 | "fancy-log": "^1.3.3", 30 | "lodash": "^4.17.11", 31 | "plugin-error": "^1.0.1", 32 | "speakingurl": "^14.0.1", 33 | "through2": "^3.0.1", 34 | "vinyl": "^2.2.0" 35 | }, 36 | "devDependencies": { 37 | "ava": "^1.4.1", 38 | "doctoc": "^1.4.0", 39 | "event-stream": "^4.0.1", 40 | "gulp": "^4.0.2", 41 | "intercept-stdout": "^0.1.2", 42 | "prettier": "^1.17.1" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /templates/svg-symbols-custom-properties.css: -------------------------------------------------------------------------------- 1 | :root {<% _.forEach( icons, function( icon ){ %> 2 | --icon-<%= icon.id %>-width: <%= icon.width %>; 3 | --icon-<%= icon.id %>-height: <%= icon.height %>;<% }); %> 4 | } 5 | 6 | <% _.forEach( icons, function( icon ){ %><%= icon.class %> { 7 | width: var(--icon-<%= icon.id %>-width); 8 | height: var(--icon-<%= icon.id %>-height); 9 | } 10 | <%= icon.style %> 11 | <% }); %> 12 | -------------------------------------------------------------------------------- /templates/svg-symbols-demo-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <%= icons.length + ' Symbols' %> 7 | 8 | 376 | 385 | 386 | 387 | 388 | 389 | 390 | <%= defs %> 391 | 392 | <% _.forEach( icons, function( icon ){ %> 393 | preserveAspectRatio="<%= icon.svg.originalAttributes.preserveAspectRatio %>"<% }%>><% if (icon.title) {%> 394 | <%= icon.title %><% }%> 395 | <%= icon.svg.content %> 396 | <% 397 | }); %> 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 |
406 |

<%= icons.length %> Symbols

407 | 411 | 414 | 415 | 416 |
417 |
418 | 419 |
<% 420 | icons.forEach(function(icon) { 421 | // Approximate size = content + id + with viewBox markup 422 | var size = icon.svg.content.length + icon.id.length + 50; 423 | var hrSize = Math.max(Math.round(size/100), 1) / 10; 424 | var dimensions = [ 425 | Math.round(icon.svg.width), 426 | Math.round(icon.svg.height) 427 | ]; 428 | var search = [ 429 | icon.svg.name.trim().toLowerCase(), 430 | icon.id.trim().toLowerCase(), 431 | dimensions.join('x') 432 | ].concat([1, 2, 3, 4, 5, 6, 7, 8, 9].map(function(s){ 433 | return (hrSize >= s ? '>' : '<') + s; 434 | })); 435 | var example = '\n' + 436 | ' \n'; 437 | %> 438 |
441 |
442 |

<%= icon.id %>

443 | <%= hrSize %> kB 444 |
445 |

446 | <%= icon.svg.name %>.svg 447 | <%= dimensions.join(' × ') %> 448 | <%= size %> B 449 |

450 |
451 |
452 | 453 | 454 | 455 |
456 |
457 | <%= _.escape(example) %> 458 |
459 |
460 |
461 | 462 | 463 |
464 |
<% }); %> 465 |
466 |
467 | 671 | 672 | 673 | -------------------------------------------------------------------------------- /templates/svg-symbols.css: -------------------------------------------------------------------------------- 1 | <% _.forEach( icons, function( icon ){ %><%= icon.class %> { 2 | width: <%= icon.width %>; 3 | height: <%= icon.height %>; 4 | } 5 | <%= icon.style %> 6 | <% }); %> 7 | -------------------------------------------------------------------------------- /templates/svg-symbols.scss: -------------------------------------------------------------------------------- 1 | <% _.forEach( icons, function( icon ){ %> 2 | $icon-<%= icon.id %>-width: <%= icon.width %>; 3 | $icon-<%= icon.id %>-height: <%= icon.height %>;<% }); %> 4 | 5 | <% _.forEach( icons, function( icon ){ %><%= icon.class %> { 6 | width: $icon-<%= icon.id %>-width; 7 | height: $icon-<%= icon.id %>-height; 8 | } 9 | <%= icon.style %> 10 | <% }); %> 11 | -------------------------------------------------------------------------------- /templates/svg-symbols.styl: -------------------------------------------------------------------------------- 1 | <% _.forEach( icons, function( icon ){ %> 2 | $icon-<%= icon.id %>-width = <%= icon.width %>; 3 | $icon-<%= icon.id %>-height = <%= icon.height %>;<% }); %> 4 | 5 | <% _.forEach( icons, function( icon ){ %><%= icon.class %> { 6 | width: $icon-<%= icon.id %>-width; 7 | height: $icon-<%= icon.id %>-height; 8 | } 9 | <%= icon.style %> 10 | <% }); %> 11 | -------------------------------------------------------------------------------- /templates/svg-symbols.svg: -------------------------------------------------------------------------------- 1 | > 2 | <% if (defs) { %> 3 | <%= defs.trim() %> 4 | 5 | <% } %><%= icons.map(svgdataToSymbol).join('\n') %> 6 | 7 | -------------------------------------------------------------------------------- /templates/svg-symbols.vue: -------------------------------------------------------------------------------- 1 | 85 | -------------------------------------------------------------------------------- /test/get-svg-datas.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import fs from 'fs' 3 | import Vinyl from 'vinyl' 4 | import test from 'ava' 5 | 6 | import { parseFile as formatSvgData } from '../lib/svg.js' 7 | 8 | //////// 9 | // BASIC 10 | //////// 11 | 12 | // Use the skull files for that 13 | const svgFile = new Vinyl({ 14 | base: `test/source`, 15 | cwd: `test/`, 16 | path: `test/source/skull.svg`, 17 | contents: fs.readFileSync(`test/source/skull.svg`), 18 | }) 19 | 20 | const authorizedInfo = [ 21 | `name`, 22 | `viewBox`, 23 | `originalAttributes`, 24 | `id`, 25 | `width`, 26 | `height`, 27 | `content`, 28 | ].sort() 29 | 30 | const expectedInfo = { 31 | content: fs.readFileSync(`test/output/skull-symbol.svg`).toString(), 32 | width: 150, 33 | height: 150, 34 | name: `skull`, 35 | viewBox: `-50 0 150 150`, 36 | } 37 | 38 | const basicTitle = `get SVG datas - Gather basic info from SVG` 39 | 40 | test(`${basicTitle} - should be an object`, async t => { 41 | const result = await formatSvgData(svgFile) 42 | t.true(_.isPlainObject(result)) 43 | t.deepEqual(Object.keys(result).sort(), authorizedInfo) 44 | }) 45 | 46 | test(`${basicTitle} - should have the right width & height`, async t => { 47 | const result = await formatSvgData(svgFile) 48 | t.is(result.width, expectedInfo.width, `width`) 49 | t.is(result.height, expectedInfo.height, `height`) 50 | }) 51 | 52 | test(`${basicTitle} - should have the right viewbox`, async t => { 53 | const result = await formatSvgData(svgFile) 54 | t.is(result.viewbox, expectedInfo.viewbox) 55 | }) 56 | 57 | test(`${basicTitle} - output the right name`, async t => { 58 | const result = await formatSvgData(svgFile) 59 | t.is(result.name, expectedInfo.name) 60 | }) 61 | 62 | //////// 63 | // NO DIMENSIONS 64 | //////// 65 | 66 | const noDimensionSvgFile = new Vinyl({ 67 | base: `test/source`, 68 | cwd: `test/`, 69 | path: `test/source/skull.svg`, 70 | contents: fs.readFileSync(`test/source/gear_without_dimensions.svg`), 71 | }) 72 | 73 | const noDimensionExpectedInfo = { 74 | content: fs 75 | .readFileSync(`test/output/gear_without_dimensions-symbol.svg`) 76 | .toString(), 77 | width: 229.6, 78 | height: 259.9, 79 | name: `gear_without_dimensions`, 80 | viewBox: `0 0 229.6 259.9`, 81 | } 82 | 83 | const noDimensionTitle = `get SVG datas - Handle SVG without dimensions` 84 | 85 | test(`${noDimensionTitle} - should have the right width & height`, async t => { 86 | const result = await formatSvgData(noDimensionSvgFile) 87 | t.is(result.width, noDimensionExpectedInfo.width, `width`) 88 | t.is(result.height, noDimensionExpectedInfo.height, `height`) 89 | }) 90 | 91 | test(`${noDimensionTitle} - should have the right viewbox`, async t => { 92 | const result = await formatSvgData(noDimensionSvgFile) 93 | t.is(result.viewbox, noDimensionExpectedInfo.viewbox) 94 | }) 95 | 96 | //////// 97 | // PERCENT DIMENSIONS 98 | //////// 99 | 100 | // https://github.com/Hiswe/gulp-svg-symbols/issues/24 101 | 102 | const percentSvgFile = new Vinyl({ 103 | base: `test/source`, 104 | cwd: `test/`, 105 | path: `test/source/skull.svg`, 106 | contents: fs.readFileSync(`test/source/icon-with-percent-size.svg`), 107 | }) 108 | 109 | const percentExpectedInfo = { 110 | width: 20, 111 | height: 26, 112 | name: `icon-with-percent-size`, 113 | viewBox: `0 0 20 26`, 114 | } 115 | 116 | const percentTitle = `get SVG datas - Handle SVG with percent dimensions` 117 | 118 | test(`${percentTitle} - should have the right width & height`, async t => { 119 | const result = await formatSvgData(percentSvgFile) 120 | t.is(result.width, percentExpectedInfo.width, `width`) 121 | t.is(result.height, percentExpectedInfo.height, `height`) 122 | }) 123 | 124 | test(`${percentTitle} - should have the right viewbox`, async t => { 125 | const result = await formatSvgData(percentSvgFile) 126 | t.is(result.viewbox, percentExpectedInfo.viewbox) 127 | }) 128 | -------------------------------------------------------------------------------- /test/output/codepen-symbol.css: -------------------------------------------------------------------------------- 1 | .icon-codepen { 2 | width: 1.5em; 3 | height: 1.5em; 4 | } -------------------------------------------------------------------------------- /test/output/codepen-symbol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/output/gear_without_dimensions-symbol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/output/github-symbol.css: -------------------------------------------------------------------------------- 1 | .github { 2 | width: 22px; 3 | height: 24px; 4 | } -------------------------------------------------------------------------------- /test/output/github-symbol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | github icon 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/output/mail-symbol-accessible.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | envelope 4 | a stylised envelope 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/output/mail-symbol.css: -------------------------------------------------------------------------------- 1 | .icon-mail { 2 | width: 1.75em; 3 | height: 1.5em; 4 | } -------------------------------------------------------------------------------- /test/output/mail-symbol.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/output/no-title.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /test/output/skull-symbol.css: -------------------------------------------------------------------------------- 1 | .skull { 2 | width: 150px; 3 | height: 150px; 4 | } -------------------------------------------------------------------------------- /test/output/skull-symbol.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/output/svg-symbols.css: -------------------------------------------------------------------------------- 1 | .chinese-letter-with-styles { 2 | width: 36px; 3 | height: 36px; 4 | } 5 | .cls-1 { fill: #fff45c; stroke: #fc0; stroke-width: 2px; } 6 | .cls-2, .cls-3 { font-size: 20px; } 7 | .cls-3 { fill: #fc0; font-family: "Microsoft YaHei"; } 8 | 9 | .gear_without_dimensions { 10 | width: 229px; 11 | height: 259px; 12 | } 13 | 14 | .github { 15 | width: 22px; 16 | height: 24px; 17 | } 18 | 19 | .gradient { 20 | width: 100px; 21 | height: 100px; 22 | } 23 | 24 | .ios { 25 | width: 32px; 26 | height: 32px; 27 | } 28 | 29 | .skull { 30 | width: 150px; 31 | height: 150px; 32 | } 33 | 34 | .warning-with-styles-and-empty-group { 35 | width: 64px; 36 | height: 64px; 37 | } 38 | .alert{fill:#C40000;} 39 | .white{fill:#FFFFFF;} 40 | .st0{fill:#808285;} 41 | .st1{fill:#AEAFAF;} 42 | .st2{fill:#231F20;} 43 | .st3{opacity:0.5;fill:#231F20;} 44 | .st4{opacity:0.8;fill:#231F20;} 45 | .st5{opacity:0.1;fill:#231F20;} 46 | .st6{opacity:0.55;fill:#231F20;} 47 | .st7{opacity:0.3;fill:#231F20;} 48 | .st8{fill:#FFFFFF;stroke:#AEAFAF;} 49 | .st9{fill-rule:evenodd;clip-rule:evenodd;fill:#82B82D;} 50 | 51 | .zoom { 52 | width: 20px; 53 | height: 20px; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /test/output/svg-symbols.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | chinese letter with styles icon 7 | 8 | 9 | 10 | gear_without_dimensions icon 11 | 21 | 22 | 23 | github icon 24 | 37 | 38 | 39 | gradient icon 40 | 42 | 43 | 44 | ios icon 45 | 64 | 65 | 66 | skull icon 67 | 102 | 103 | 104 | warning with styles and empty group icon 105 | 107 | 108 | 109 | zoom icon 110 | 111 | -------------------------------------------------------------------------------- /test/output/template.html: -------------------------------------------------------------------------------- 1 |

pouic

2 |

clapou

-------------------------------------------------------------------------------- /test/output/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "icons": [ 3 | {"id": "pouic"}, 4 | {"id": "clapou"} 5 | 6 | ] 7 | } -------------------------------------------------------------------------------- /test/plugin.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import es from 'event-stream' 3 | import test from 'ava' 4 | 5 | import svgSymbols from '../index.js' 6 | 7 | //////// 8 | // BASIC 9 | //////// 10 | 11 | const basicTitle = `Plugin – basic` 12 | 13 | test.cb(`${basicTitle} - should produce two files`, t => { 14 | gulp 15 | .src([ 16 | `test/source/*.svg`, 17 | `!test/source/crâne noir.svg`, 18 | `!test/source/aspect-ratio.svg`, 19 | ]) 20 | .pipe(svgSymbols({ warn: false })) 21 | .pipe( 22 | es.writeArray((err, output) => { 23 | t.is(output.length, 2) 24 | t.is(output[0].path, `svg-symbols.svg`) 25 | t.is(output[1].path, `svg-symbols.css`) 26 | t.end() 27 | }) 28 | ) 29 | }) 30 | 31 | // control duplicate attributes in a watch case 32 | // https://github.com/Hiswe/gulp-svg-symbols/issues/2 33 | 34 | test.cb(`${basicTitle} - have the right output if called many times`, t => { 35 | gulp 36 | .src(`test/source/github.svg`) 37 | .pipe(svgSymbols({ warn: false })) 38 | .pipe( 39 | es.wait(function() { 40 | gulp 41 | .src(`test/source/github.svg`) 42 | .pipe(svgSymbols({ warn: false })) 43 | .pipe( 44 | es.writeArray((err, output) => { 45 | const svg = output[0].contents.toString() 46 | const css = output[1].contents.toString() 47 | t.is(output.length, 2) 48 | t.regex(svg, / { 58 | gulp 59 | .src(`test/source/github.svg`) 60 | .pipe( 61 | svgSymbols({ 62 | templates: [`default-demo`], 63 | warn: false, 64 | }) 65 | ) 66 | .pipe( 67 | es.writeArray((err, output) => { 68 | t.is(output.length, 1) 69 | t.is(output[0].path, `svg-symbols-demo-page.html`) 70 | t.end() 71 | }) 72 | ) 73 | }) 74 | 75 | test.cb( 76 | `${basicTitle} - shouldn't generate any file if no svg are passed in`, 77 | t => { 78 | gulp 79 | .src(`this/path/doesnt/match/any/*.svg`) 80 | .pipe(svgSymbols({ warn: false })) 81 | .pipe( 82 | es.writeArray((err, output) => { 83 | t.is(output.length, 0) 84 | t.end() 85 | }) 86 | ) 87 | } 88 | ) 89 | 90 | //////// 91 | // CONCAT DEFS 92 | //////// 93 | 94 | const defsTitle = `Plugin – defs` 95 | 96 | test.cb(`${defsTitle} - should handle svg with defs`, t => { 97 | gulp 98 | .src(`test/source/gradient.svg`) 99 | .pipe(svgSymbols({ warn: false })) 100 | .pipe( 101 | es.writeArray((err, output) => { 102 | const svgContent = output[0].contents.toString() 103 | t.regex(svgContent, //g) 104 | t.end() 105 | }) 106 | ) 107 | }) 108 | 109 | test.cb(`${defsTitle} - should handle svg withouts defs`, t => { 110 | gulp 111 | .src(`test/source/gear_without_dimensions.svg`) 112 | .pipe(svgSymbols({ warn: false })) 113 | .pipe( 114 | es.writeArray((err, output) => { 115 | const svgContent = output[0].contents.toString() 116 | t.notRegex(svgContent, //g) 117 | t.end() 118 | }) 119 | ) 120 | }) 121 | 122 | test.cb(`${defsTitle} - should handle svg with empty defs`, t => { 123 | gulp 124 | .src(`test/source/chinese letter with styles.svg`) 125 | .pipe(svgSymbols({ warn: false })) 126 | .pipe( 127 | es.writeArray((err, output) => { 128 | const svgContent = output[0].contents.toString() 129 | t.notRegex(svgContent, //g) 130 | t.end() 131 | }) 132 | ) 133 | }) 134 | 135 | //////// 136 | // CONCAT STYLES 137 | //////// 138 | 139 | test.cb( 140 | `Plugin - style tags - should remove style attributes and put content in another file`, 141 | t => { 142 | gulp 143 | .src(`test/source/warning with styles and empty group.svg`) 144 | .pipe(svgSymbols({ warn: false })) 145 | .pipe( 146 | es.writeArray((err, output) => { 147 | const svgContent = output[0].contents.toString() 148 | const cssContent = output[1].contents.toString() 149 | t.notRegex(svgContent, / 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /test/source/crâne noir.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 15 | 22 | 25 | 27 | 30 | 33 | 35 | 37 | 40 | 43 | 46 | 49 | 51 | 53 | 55 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /test/source/duplicated-defs-ids-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/source/duplicated-defs-ids-2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/source/figma-mask-outside-defs.svg: -------------------------------------------------------------------------------- 1 | 2 | 2 3 | Created using Figma 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/source/gear_without_dimensions.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | -------------------------------------------------------------------------------- /test/source/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 19 | 20 | -------------------------------------------------------------------------------- /test/source/gradient.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/source/icon-with-percent-size.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/source/instagram-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | instagram-black 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/source/ios.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 16 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /test/source/skull.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 15 | 22 | 25 | 27 | 30 | 33 | 35 | 37 | 40 | 43 | 46 | 49 | 51 | 53 | 55 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /test/source/template.html: -------------------------------------------------------------------------------- 1 | <% _.each( icons, function( icon ){ %>

<%= icon.id %>

2 | <% }); %> -------------------------------------------------------------------------------- /test/source/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "icons": [ 3 | <% _.forEach( icons, function( icon, index, collection ) 4 | { %>{"id": "<%= icon.id %>"}<% if (index !== collection.length -1) {%>,<% }%> 5 | <% }); %> 6 | ] 7 | } -------------------------------------------------------------------------------- /test/source/warning with styles and empty group.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/source/xlink-href.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | An example link. 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/source/zoom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/templates.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import fs from 'fs' 3 | import path from 'path' 4 | import es from 'event-stream' 5 | import test from 'ava' 6 | 7 | import svgSymbols from '../index.js' 8 | import templates from '../lib/templates.js' 9 | 10 | //////// 11 | // DEFAULT SVG TEMPLATE 12 | //////// 13 | 14 | const defaultSvgTitle = `Attributes handling in default-svg` 15 | 16 | test.cb(`${defaultSvgTitle} - should add xmlns attribute`, t => { 17 | gulp 18 | .src(`test/source/*.svg`) 19 | .pipe( 20 | svgSymbols({ 21 | warn: false, 22 | templates: [`default-svg`], 23 | }) 24 | ) 25 | .pipe( 26 | es.writeArray((err, output) => { 27 | const svg = output[0].contents.toString() 28 | t.regex(svg, /xmlns="http:\/\/www.w3.org\/2000\/svg"/g) 29 | t.end() 30 | }) 31 | ) 32 | }) 33 | 34 | test.cb(`${defaultSvgTitle} - add a class to root SVG when wanted`, t => { 35 | gulp 36 | .src(`test/source/*.svg`) 37 | .pipe( 38 | svgSymbols({ 39 | warn: false, 40 | svgAttrs: { class: `foobar` }, 41 | templates: [`default-svg`], 42 | }) 43 | ) 44 | .pipe( 45 | es.writeArray((err, output) => { 46 | const svg = output[0].contents.toString() 47 | t.regex(svg, /class="foobar"/g) 48 | t.end() 49 | }) 50 | ) 51 | }) 52 | 53 | test.cb(`${defaultSvgTitle} - handle deprecated svgClassname`, t => { 54 | gulp 55 | .src(`test/source/*.svg`) 56 | .pipe( 57 | svgSymbols({ 58 | warn: false, 59 | svgClassname: `foobar`, 60 | templates: [`default-svg`], 61 | }) 62 | ) 63 | .pipe( 64 | es.writeArray((err, output) => { 65 | const svg = output[0].contents.toString() 66 | t.regex(svg, /class="foobar"/g) 67 | t.end() 68 | }) 69 | ) 70 | }) 71 | 72 | test.cb(`${defaultSvgTitle} - should add any string attributes`, t => { 73 | gulp 74 | .src(`test/source/*.svg`) 75 | .pipe( 76 | svgSymbols({ 77 | warn: false, 78 | svgAttrs: { foo: `bar`, pouic: `clapou` }, 79 | templates: [`default-svg`], 80 | }) 81 | ) 82 | .pipe( 83 | es.writeArray((err, output) => { 84 | const svg = output[0].contents.toString() 85 | t.regex(svg, /foo="bar"/g) 86 | t.regex(svg, /pouic="clapou"/g) 87 | t.end() 88 | }) 89 | ) 90 | }) 91 | 92 | test.cb( 93 | `${defaultSvgTitle} - add any string attributes with double quotes`, 94 | t => { 95 | gulp 96 | .src(`test/source/*.svg`) 97 | .pipe( 98 | svgSymbols({ 99 | warn: false, 100 | svgAttrs: { foo: `"bar"` }, 101 | templates: [`default-svg`], 102 | }) 103 | ) 104 | .pipe( 105 | es.writeArray((err, output) => { 106 | const svg = output[0].contents.toString() 107 | t.regex(svg, /foo=""bar""/g) 108 | t.end() 109 | }) 110 | ) 111 | } 112 | ) 113 | 114 | test.cb(`${defaultSvgTitle} - handle any boolean attributes`, t => { 115 | gulp 116 | .src(`test/source/*.svg`) 117 | .pipe( 118 | svgSymbols({ 119 | warn: false, 120 | svgAttrs: { foo: true, bar: false }, 121 | templates: [`default-svg`], 122 | }) 123 | ) 124 | .pipe( 125 | es.writeArray((err, output) => { 126 | const svg = output[0].contents.toString() 127 | t.regex(svg, /\sfoo/g) 128 | t.notRegex(svg, /\sbar/g) 129 | t.end() 130 | }) 131 | ) 132 | }) 133 | 134 | test.cb(`${defaultSvgTitle} - remove xmlns attribute if setted to false`, t => { 135 | gulp 136 | .src(`test/source/*.svg`) 137 | .pipe( 138 | svgSymbols({ 139 | warn: false, 140 | svgAttrs: { xmlns: false }, 141 | templates: [`default-svg`], 142 | }) 143 | ) 144 | .pipe( 145 | es.writeArray((err, output) => { 146 | const svg = output[0].contents.toString() 147 | t.notRegex(svg, /xmlns="http:\/\/www.w3.org\/2000\/svg"/g) 148 | t.end() 149 | }) 150 | ) 151 | }) 152 | 153 | test.cb(`${defaultSvgTitle} - should handle any number attributes`, t => { 154 | gulp 155 | .src(`test/source/*.svg`) 156 | .pipe( 157 | svgSymbols({ 158 | warn: false, 159 | svgAttrs: { foo: 300 }, 160 | templates: [`default-svg`], 161 | }) 162 | ) 163 | .pipe( 164 | es.writeArray((err, output) => { 165 | const svg = output[0].contents.toString() 166 | t.regex(svg, /\sfoo="300"/g) 167 | t.end() 168 | }) 169 | ) 170 | }) 171 | 172 | test.cb( 173 | `${defaultSvgTitle} - add the xlink namespace if found inside the file`, 174 | t => { 175 | gulp 176 | .src(`test/source/xlink-href.svg`) 177 | .pipe( 178 | svgSymbols({ 179 | warn: false, 180 | svgAttrs: { foo: 300 }, 181 | templates: [`default-svg`], 182 | }) 183 | ) 184 | .pipe( 185 | es.writeArray((err, output) => { 186 | const svg = output[0].contents.toString() 187 | t.regex(svg, /\sxmlns:xlink="http:\/\/www.w3.org\/1999\/xlink"/g) 188 | t.end() 189 | }) 190 | ) 191 | } 192 | ) 193 | 194 | test.cb(`${defaultSvgTitle} - keep the options`, t => { 195 | gulp 196 | .src(`test/source/xlink-href.svg`) 197 | .pipe( 198 | svgSymbols({ 199 | warn: false, 200 | svgAttrs: { foo: 300 }, 201 | templates: [`default-svg`], 202 | }) 203 | ) 204 | .pipe( 205 | es.writeArray((err, output) => { 206 | const svg = output[0].contents.toString() 207 | t.regex(svg, /\sxmlns:xlink="http:\/\/www.w3.org\/1999\/xlink"/g) 208 | t.end() 209 | }) 210 | ) 211 | }) 212 | 213 | //////// 214 | // CUSTOM TEMPLATE 215 | //////// 216 | 217 | const htmlOutput = fs.readFileSync(`test/output/template.html`).toString() 218 | const jsonOutput = fs.readFileSync(`test/output/template.json`).toString() 219 | 220 | const datas = { 221 | icons: [{ id: `pouic` }, { id: `clapou` }], 222 | } 223 | const tmpl = [ 224 | path.join(__dirname, `./source/template.html`), 225 | path.join(__dirname, `./source/template.json`), 226 | ] 227 | 228 | const customTmplTitle = `Render custom templates` 229 | 230 | test(`${customTmplTitle} - render a custom template`, async t => { 231 | const file = await templates.render(tmpl[0], datas) 232 | t.is(file.contents.toString(), htmlOutput) 233 | }) 234 | 235 | test(`${customTmplTitle} - render an array of templates`, async t => { 236 | const files = await Promise.all(templates.renderAll(tmpl, datas)) 237 | t.is(files.length, 2) 238 | t.is(files[0].contents.toString(), htmlOutput) 239 | t.is(files[1].contents.toString(), jsonOutput) 240 | }) 241 | -------------------------------------------------------------------------------- /test/transform-raw-data.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import fs from 'fs' 3 | import test from 'ava' 4 | 5 | import { formatDataForTemplate as transformSvgData } from '../lib/svg.js' 6 | import defaultOptions from '../lib/default-config.js' 7 | 8 | const svgRawData = { 9 | content: fs.readFileSync(`test/output/codepen-symbol.svg`).toString(), 10 | width: 24, 11 | height: 24, 12 | name: `codepen square`, 13 | id: `codepen-square`, 14 | viewBox: `0 0 24 24`, 15 | } 16 | 17 | //////// 18 | // DEFAULT 19 | //////// 20 | 21 | const resultDefault = { 22 | svg: svgRawData, 23 | id: `codepen-square`, 24 | class: `.codepen-square`, 25 | title: `codepen square icon`, 26 | width: `24px`, 27 | height: `24px`, 28 | } 29 | 30 | const defaultTitle = `Transform data - default` 31 | 32 | test(`${defaultTitle} - should be an object`, t => { 33 | const result = transformSvgData(svgRawData, defaultOptions) 34 | t.true(_.isPlainObject(result)) 35 | }) 36 | 37 | test(`${defaultTitle} - should have the raw datas`, t => { 38 | const result = transformSvgData(svgRawData, defaultOptions) 39 | t.is(result.svg, resultDefault.svg) 40 | }) 41 | 42 | test(`${defaultTitle} - should have the right id`, t => { 43 | const result = transformSvgData(svgRawData, defaultOptions) 44 | t.is(result.id, resultDefault.id) 45 | }) 46 | 47 | test(`${defaultTitle} - should have the right title`, t => { 48 | const result = transformSvgData(svgRawData, defaultOptions) 49 | t.true(_.isUndefined(result.title)) 50 | }) 51 | 52 | test(`${defaultTitle} - should have the right width & height`, t => { 53 | const result = transformSvgData(svgRawData, defaultOptions) 54 | t.is(result.width, resultDefault.width, `width`) 55 | t.is(result.height, resultDefault.height, `height`) 56 | }) 57 | 58 | //////// 59 | // USER OPTIONS 60 | //////// 61 | 62 | const userOptions = _.defaults( 63 | { 64 | id: `icon-%f`, 65 | class: `.icon-%f`, 66 | fontSize: 16, 67 | title: `logo of %f`, 68 | }, 69 | defaultOptions 70 | ) 71 | 72 | const resultDefaultOptions = { 73 | svg: svgRawData, 74 | id: `icon-codepen-square`, 75 | class: `.icon-codepen-square`, 76 | title: `logo of codepen square`, 77 | width: `1.5em`, 78 | height: `1.5em`, 79 | } 80 | 81 | const optionsTitle = `Transform data - default & options` 82 | 83 | test(`${optionsTitle} - should be an object`, t => { 84 | const result = transformSvgData(svgRawData, userOptions) 85 | t.true(_.isPlainObject(result)) 86 | }) 87 | 88 | test(`${optionsTitle} - should have the raw datas`, t => { 89 | const result = transformSvgData(svgRawData, userOptions) 90 | t.is(result.svg, resultDefaultOptions.svg) 91 | }) 92 | 93 | test(`${optionsTitle} - should have the right id`, t => { 94 | const result = transformSvgData(svgRawData, userOptions) 95 | t.is(result.id, resultDefaultOptions.id) 96 | }) 97 | 98 | test(`${optionsTitle} - should have the right class`, t => { 99 | const result = transformSvgData(svgRawData, userOptions) 100 | t.is(result.class, resultDefaultOptions.class) 101 | }) 102 | 103 | test(`${optionsTitle} - should have the right title`, t => { 104 | const result = transformSvgData(svgRawData, userOptions) 105 | t.is(result.title, resultDefaultOptions.title) 106 | }) 107 | 108 | test(`${optionsTitle} - should have the right width & height`, t => { 109 | const result = transformSvgData(svgRawData, userOptions) 110 | t.is(result.width, resultDefaultOptions.width, `width`) 111 | t.is(result.height, resultDefaultOptions.height, `height`) 112 | }) 113 | 114 | //////// 115 | // CUSTOM OPTIONS 116 | //////// 117 | 118 | const customOptions = _.defaults( 119 | { 120 | id: `svg-icon-%f`, 121 | transformData: (svg, options) => { 122 | return { 123 | svg: false, 124 | id: options.id.replace(`%f`, svg.id), 125 | } 126 | }, 127 | }, 128 | defaultOptions 129 | ) 130 | 131 | const resultCustomOptions = { 132 | svg: svgRawData, 133 | id: `svg-icon-codepen-square`, 134 | } 135 | 136 | const customOptionsTitle = `Transform - custom & options` 137 | 138 | test(`${customOptionsTitle} - should be an object`, t => { 139 | const result = transformSvgData(svgRawData, customOptions) 140 | t.true(_.isPlainObject(result)) 141 | }) 142 | 143 | test(`${customOptionsTitle} - should have only user keys`, t => { 144 | const result = transformSvgData(svgRawData, customOptions) 145 | const keys = Object.keys(result).sort() 146 | t.deepEqual(keys, [`svg`, `id`].sort()) 147 | }) 148 | 149 | test(`${customOptionsTitle} - raw datas aren't overwritten`, t => { 150 | const result = transformSvgData(svgRawData, customOptions) 151 | t.is(result.svg, resultCustomOptions.svg) 152 | }) 153 | 154 | test(`${customOptionsTitle} - should have the right id`, t => { 155 | const result = transformSvgData(svgRawData, customOptions) 156 | t.is(result.id, resultCustomOptions.id) 157 | }) 158 | 159 | test(`${customOptionsTitle} - title should be removable`, t => { 160 | const result = transformSvgData(svgRawData, customOptions) 161 | t.true(_.isUndefined(result.title)) 162 | }) 163 | -------------------------------------------------------------------------------- /test/warnings.js: -------------------------------------------------------------------------------- 1 | import gulp from 'gulp' 2 | import es from 'event-stream' 3 | import test from 'ava' 4 | import intercept from 'intercept-stdout' 5 | 6 | import svgSymbols from '../index.js' 7 | // import templates from '../lib/templates.js'; 8 | 9 | test.beforeEach(t => { 10 | t.context.stdout = [] 11 | t.context.unhookIntercept = intercept(txt => { 12 | t.context.stdout.push(txt) 13 | // mute stdout 14 | // https://github.com/sfarthin/intercept-stdout/issues/8#issuecomment-250521176 15 | return `` 16 | }) 17 | }) 18 | 19 | test.afterEach(t => { 20 | t.context.unhookIntercept() 21 | }) 22 | 23 | const title = `Warning` 24 | 25 | test.serial.cb(`${title} - className deprecation notice`, t => { 26 | gulp 27 | .src(`test/source/ios.svg`) 28 | .pipe(svgSymbols({ className: `foobar` })) 29 | .pipe( 30 | es.writeArray((err, output) => { 31 | const messageRegex = /options\.className\sis\sdeprecated/ 32 | const warnMessage = t.context.stdout.find(e => messageRegex.test(e)) 33 | t.truthy(warnMessage, `has the className deprecation warning`) 34 | t.end() 35 | }) 36 | ) 37 | }) 38 | 39 | test.serial.cb(`${title} - svgClassname deprecation notice`, t => { 40 | gulp 41 | .src(`test/source/ios.svg`) 42 | .pipe(svgSymbols({ svgClassname: `foobar` })) 43 | .pipe( 44 | es.writeArray((err, output) => { 45 | const messageRegex = /options\.svgClassname\sis\sdeprecated/ 46 | const warnMessage = t.context.stdout.find(e => messageRegex.test(e)) 47 | t.truthy(warnMessage, `has the svgClassname deprecation warning`) 48 | t.end() 49 | }) 50 | ) 51 | }) 52 | 53 | test.serial.cb(`${title} - Missing viewbox warning`, t => { 54 | gulp 55 | .src(`test/source/gradient.svg`) 56 | .pipe(svgSymbols()) 57 | .pipe( 58 | es.writeArray((err, output) => { 59 | const { stdout } = t.context 60 | const reasonRegex = /viewbox\smissing\sin\sfile/ 61 | const filenameRegex = /test\/source\/gradient\.svg/ 62 | const reasonMessage = stdout.find(e => reasonRegex.test(e)) 63 | const filenameMessage = stdout.find(e => filenameRegex.test(e)) 64 | t.truthy(reasonMessage, `has the missing viewbox warning`) 65 | t.truthy(filenameMessage, `has the right filename`) 66 | t.end() 67 | }) 68 | ) 69 | }) 70 | 71 | test.serial.cb(`${title} - outside defs`, t => { 72 | gulp 73 | .src(`test/source/figma-mask-outside-defs.svg`) 74 | .pipe(svgSymbols()) 75 | .pipe( 76 | es.writeArray((err, output) => { 77 | const { stdout } = t.context 78 | const reasonRegex = /\stag\sfound\soutside\sa\s\stag/ 79 | const filenameRegex = /test\/source\/figma-mask-outside-defs\.svg/ 80 | const reasonMessage = stdout.find(e => reasonRegex.test(e)) 81 | const filenameMessage = stdout.find(e => filenameRegex.test(e)) 82 | t.truthy(reasonMessage, `has the mask warning`) 83 | t.truthy(filenameMessage, `has the right filename`) 84 | t.end() 85 | }) 86 | ) 87 | }) 88 | 89 | test.serial.cb(`${title} - outside defs`, t => { 90 | gulp 91 | .src(`test/source/affinity-clip-path-outside-defs.svg`) 92 | .pipe(svgSymbols()) 93 | .pipe( 94 | es.writeArray((err, output) => { 95 | const { stdout } = t.context 96 | /* eslint-disable max-len */ 97 | const reasonRegex = /\stag\sfound\soutside\sa\s\stag/ 98 | const filenameRegex = /test\/source\/affinity-clip-path-outside-defs\.svg/ 99 | const reasonMessage = stdout.find(e => reasonRegex.test(e)) 100 | const filenameMessage = stdout.find(e => filenameRegex.test(e)) 101 | /* eslint-enable max-len */ 102 | t.truthy(reasonMessage, `has the mask warning`) 103 | t.truthy(filenameMessage, `has the right filename`) 104 | t.end() 105 | }) 106 | ) 107 | }) 108 | 109 | test.serial.cb(`${title} - duplicated ids`, t => { 110 | gulp 111 | .src(`test/source/duplicated-defs-ids-*.svg`) 112 | .pipe(svgSymbols()) 113 | .pipe( 114 | es.writeArray((err, output) => { 115 | const { stdout } = t.context 116 | /* eslint-disable max-len */ 117 | const reasonRegex = /\shas\ssome\sduplicated\sids:/ 118 | const firstDuplicatedRegex = /id\s“gradient-background”\sfound\sin\sdifferent\sfiles\s\(duplicated-defs-ids-1,\sduplicated-defs-ids-2\)/ 119 | const secondDuplicatedRegex = /id\s“shape-clip”\sfound\sin\sdifferent\sfiles\s\(duplicated-defs-ids-1,\sduplicated-defs-ids-2\)/ 120 | const reasonMessage = stdout.find(e => reasonRegex.test(e)) 121 | const firstDuplicatedMessage = stdout.find(e => 122 | firstDuplicatedRegex.test(e) 123 | ) 124 | const secondDuplicatedMessage = stdout.find(e => 125 | secondDuplicatedRegex.test(e) 126 | ) 127 | /* eslint-enable max-len */ 128 | t.truthy(reasonMessage, `has the mask warning`) 129 | t.truthy(firstDuplicatedMessage, `has the right filename`) 130 | t.truthy(secondDuplicatedMessage, `has the right filename`) 131 | t.end() 132 | }) 133 | ) 134 | }) 135 | --------------------------------------------------------------------------------