Commit 7604da7b authored by Philipp Nowinski's avatar Philipp Nowinski
Browse files

[FEATURE] rewrite of the old gulp-plugin

parent 9f776cb0
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
charset = utf-8
# Get rid of whitespace to avoid diffs with a bunch of EOL changes
trim_trailing_whitespace = true
# Unix-style newlines with a newline ending every file
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
insert_final_newline = true
# CSS-Files
[*.css]
indent_style = tab
indent_size = 4
# HTML-Files
[*.html]
indent_style = tab
indent_size = 4
# TMPL-Files
[*.tmpl]
indent_style = tab
indent_size = 4
# LESS-Files
[*.less]
indent_style = tab
indent_size = 4
# JS-Files
[*.js]
indent_style = tab
indent_size = 4
# JSON-Files
[*.json]
indent_style = tab
indent_size = 4
# PHP-Files
[*.php]
indent_style = tab
indent_size = 4
# ReST-Files
[*.rst]
indent_style = space
indent_size = 3
# MD-Files
[*.md]
indent_style = space
indent_size = 4
# package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
# TypoScript
[*.ts]
indent_style = tab
indent_size = 4
# XLF-Files
[*.xlf]
indent_style = tab
indent_size = 4
# SQL-Files
[*.sql]
indent_style = tab
indent_size = 2
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"rules": {
"accessor-pairs": "error",
"array-bracket-spacing": "error",
"array-callback-return": "error",
"arrow-body-style": "error",
"arrow-parens": "error",
"arrow-spacing": "error",
"block-scoped-var": "error",
"block-spacing": "error",
"brace-style": [
"error",
"1tbs"
],
"callback-return": "error",
"camelcase": "error",
"capitalized-comments": "off",
"class-methods-use-this": "error",
"comma-dangle": "error",
"comma-spacing": [
"error",
{
"after": true,
"before": false
}
],
"comma-style": [
"error",
"last"
],
"complexity": "error",
"computed-property-spacing": [
"error",
"never"
],
"consistent-return": "error",
"consistent-this": "error",
"curly": "error",
"default-case": "error",
"dot-location": [
"error",
"property"
],
"dot-notation": "error",
"eol-last": "error",
"eqeqeq": "error",
"func-call-spacing": "error",
"func-name-matching": "error",
"func-names": [
"error",
"never"
],
"func-style": [
"error",
"declaration"
],
"generator-star-spacing": "error",
"global-require": "off",
"guard-for-in": "error",
"handle-callback-err": "error",
"id-blacklist": "error",
"id-length": "off",
"id-match": "error",
"indent": "off",
"init-declarations": "off",
"jsx-quotes": "error",
"key-spacing": "error",
"keyword-spacing": "off",
"line-comment-position": "error",
"linebreak-style": [
"error",
"unix"
],
"lines-around-comment": "error",
"lines-around-directive": "error",
"max-depth": "error",
"max-len": "off",
"max-lines": "error",
"max-nested-callbacks": "error",
"max-params": "error",
"max-statements": "off",
"max-statements-per-line": "error",
"multiline-ternary": "error",
"new-parens": "error",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-alert": "error",
"no-array-constructor": "error",
"no-await-in-loop": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-catch-shadow": "error",
"no-compare-neg-zero": "error",
"no-confusing-arrow": "error",
"no-continue": "error",
"no-div-regex": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-empty-function": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-label": "error",
"no-extra-parens": "off",
"no-floating-decimal": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-inline-comments": "error",
"no-inner-declarations": [
"error",
"functions"
],
"no-invalid-this": "off",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-mixed-operators": "error",
"no-mixed-requires": "off",
"no-multi-assign": "error",
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-multiple-empty-lines": "error",
"no-native-reassign": "error",
"no-negated-condition": "error",
"no-negated-in-lhs": "error",
"no-nested-ternary": "error",
"no-new": "off",
"no-new-func": "error",
"no-new-object": "error",
"no-new-require": "error",
"no-new-wrappers": "error",
"no-octal-escape": "error",
"no-param-reassign": "error",
"no-path-concat": "error",
"no-plusplus": [
"error",
{
"allowForLoopAfterthoughts": true
}
],
"no-process-env": "error",
"no-process-exit": "error",
"no-proto": "error",
"no-prototype-builtins": "off",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-modules": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-return-await": "error",
"no-script-url": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-shadow": "off",
"no-shadow-restricted-names": "error",
"no-spaced-func": "error",
"no-sync": "error",
"no-tabs": "off",
"no-template-curly-in-string": "error",
"no-ternary": "off",
"no-throw-literal": "error",
"no-trailing-spaces": "error",
"no-undef-init": "error",
"no-undefined": "error",
"no-underscore-dangle": [2, { "allowAfterThis": true }],
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unused-expressions": "error",
"no-use-before-define": "off",
"no-useless-call": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-escape": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "off",
"no-void": "error",
"no-warning-comments": "error",
"no-whitespace-before-property": "error",
"no-with": "error",
"nonblock-statement-body-position": "error",
"object-curly-newline": "off",
"object-curly-spacing": [
"error",
"never"
],
"object-property-newline": [
"error",
{
"allowMultiplePropertiesPerLine": true
}
],
"object-shorthand": "off",
"one-var": "off",
"one-var-declaration-per-line": "error",
"operator-assignment": [
"error",
"always"
],
"operator-linebreak": "error",
"padded-blocks": "off",
"prefer-arrow-callback": "off",
"prefer-const": "off",
"prefer-destructuring": "error",
"prefer-numeric-literals": "error",
"prefer-promise-reject-errors": "error",
"prefer-reflect": "off",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "off",
"quote-props": "off",
"quotes": [
"error",
"single"
],
"radix": [
"error",
"as-needed"
],
"require-await": "error",
"require-jsdoc": "off",
"rest-spread-spacing": "error",
"semi": "error",
"semi-spacing": [
"error",
{
"after": true,
"before": false
}
],
"sort-imports": "off",
"sort-keys": "off",
"sort-vars": "off",
"space-before-blocks": "error",
"space-before-function-paren": "off",
"space-in-parens": [
"error",
"never"
],
"space-infix-ops": "error",
"space-unary-ops": "error",
"spaced-comment": [
"error",
"always"
],
"strict": "off",
"symbol-description": "error",
"template-curly-spacing": "error",
"template-tag-spacing": "error",
"unicode-bom": [
"error",
"never"
],
"valid-jsdoc": "off",
"vars-on-top": "off",
"wrap-iife": "error",
"wrap-regex": "error",
"yield-star-spacing": "error",
"yoda": [
"error",
"never"
]
}
};
.vscode
node_modules
svgs
_svg.scss
# Inline SVG
This module is the successor of [gulp-inline-svg](https://gitlab.sgalinski.de/toolchain/gulp-inline-svg), rewritten as a standalone library.
The purpose of this moudle is to pass it a location to a folder containing SVG icons and get back a string containing inline-svg codes that can be used as ```background-image```. The output can be customized with a mustache-template. By default it will return a string that can be saved as a Sass-partial, offering a mixin and variables for each icon. By providing your own template, you can customize the output any way you want, though.
## Compatibility
The generated SVG inline code is URL encoded, so it should be safe to use starting with IE 9.
## Important notes
The generated mixin contains width and height values for each SVG. Those values are taken from the width and height attribute inside the SVG. If it does not provide them, they will be set to 0px. You can still overwrite them in your CSS.
## Usage
### Install the inline-svg module
```bash
npm i @sgalinski/inline-svg
```
### Example for usage with Sass
```js
const InlineSvg = require('@sgalinsk/inline-svg');
const fs = require('fs');
(async () => {
const sassTemplate = await new InlineSvg('./svgs');
fs.writeFileSync('./_svg.scss', sassTemplate);
})();
```
### Configuration
#### Use your own template
If you don't want to use the default Sass-template, you can esaily write your own and pass it as a configuration option:
```js
const InlineSvg = require('@sgalinsk/inline-svg');
const fs = require('fs');
(async () => {
const sassTemplate = await new InlineSvg('./svgs', {
template: './my-template.mustache'
});
fs.writeFileSync('./_svg.scss', sassTemplate);
})();
```
#### Pass additional variables
If needed, you can pass additional variables to the mustache template:
```js
const InlineSvg = require('@sgalinsk/inline-svg');
const fs = require('fs');
(async () => {
const sassTemplate = await new InlineSvg('./svgs', {
template: './my-template.mustache',
context: {
message: '// GENERATED BY gulpfile.js'
}
});
fs.writeFileSync('./_svg.scss', sassTemplate);
})();
```
And then use it in your template:
```mustache
{{{message}}}
{{#svgs}}
${{{name}}}: "{{{inline}}}" {{width}} {{height}};
{{/svgs}}
@mixin inline-svg($name) {
background: transparent url(nth($name, 1)) no-repeat 50% 50%;
background-size: 100%;
width: nth($name, 2);
height: nth($name, 3);
}
@function inline-svg-width($name) {
@return nth($name, 2);
}
@function inline-svg-height($name) {
@return nth($name, 3);
}
```
#### Customize variables
If you want to use your own naming convention for the SVG-variables, you can pass an interceptor-function:
```js
const InlineSvg = require('@sgalinsk/inline-svg');
const fs = require('fs');
(async () => {
const sassTemplate = await new InlineSvg('./svgs', {
interceptor: function (svgData) {
return Object.assign(svgData, { variableName: svgData.name.toLowerCase(), prefix: 'dso-icon' });
}
});
fs.writeFileSync('./_svg.scss', sassTemplate);
})();
```
const path = require('path');
const mustache = require('mustache');
const fs = require('fs');
const parseXmlStringSync = require('xml2js-parser').parseStringSync;
const globby = require('globby');
const _ = require('underscore');
const util = require('util');
class InlineSvg {
/**
* Kick things off
*
* @param {String} _svgFilePath The path to the folder containing the SVGs
* @param {Object} _options Configuration object
* @returns {Promise}
*/
constructor(_svgFilePath, _options) {
let defaultOptions = {
template: __dirname + '/template.mustache',
context: {},
interceptor: null
};
this._files = {};
this._svgs = [];
this._options = _.extend(defaultOptions, _options);
return this._process(_svgFilePath);
}
/**
* Process all SVG files
*
* @param {String} _svgFilePath The path to the folder containing the SVGs
*/
async _process(_svgFilePath) {
return new Promise(async (_resolve, _reject) => {
let svgs = await this._readFiles(_svgFilePath);
let templateContent = this._getTemplateContent();
svgs.forEach(svg => this._processSvg(svg))
let template = mustache.render(
templateContent,
_.extend(
{},
this._options.context,
{
svgs: this._svgs
}
)
);
_resolve(template);
});
}
/**
* Processes a single SVG icon
*
* _data is expected to carry two properties:
* data.content: A string containing the SVG source
* data.fileName: A string containing the file name
*
* @param {Object} _data Object containing info about the SVG
*/
_processSvg(_data) {
const xmlString = parseXmlStringSync(_data.content.toString(), {
attrNameProcessors: [name => name.toLowerCase()]
});
const svgDimensions = this._getSvgDimensions(xmlString);
const svgData = {
name: path.basename(_data.fileName, '.svg'),
inline: this._urlEncode(_data.content),
width: parseInt(svgDimensions.width) + 'px',
height: parseInt(svgDimensions.height) + 'px',
dimensions: svgDimensions
};
// store this svg data
this._svgs.push(
_.isFunction(this._options.interceptor)
? _.extend({}, svgData, this._options.interceptor(svgData) || svgData)
: svgData
);
}
/**
* Extracts the dimensions from the SVG source
*
* @param {String} xmlString The SVG source
* @returns {Object}
*/
_getSvgDimensions(xmlString) {
const hasWidthHeightAttr = xmlString.svg.$['width'] && xmlString.svg.$['width'];
let width;
let height;
if (hasWidthHeightAttr) {
width = parseInt(xmlString.svg.$['width']);
height = parseInt(xmlString.svg.$['height']);
} else {
width = parseInt(result.svg.$['viewbox'].toString().replace(/^\d+\s\d+\s(\d+\.?[\d])\s(\d+\.?[\d])/, "$1"));
height = parseInt(result.svg.$['viewbox'].toString().replace(/^\d+\s\d+\s(\d+\.?[\d])\s(\d+\.?[\d])/, "$2"));
}
return {
width,
height
};
}
/**
* Reads all the SVGs that are located in _path
*
* @param {String} _path The location to read from
* @returns {Promise}
*/
async _readFiles(_path) {
return new Promise(async (resolve, reject) => {
let files = await globby(path.join(_path, '*.svg'));
let svgs = [];
await files.forEach(async _file => {
let content = fs.readFileSync(_file);
svgs.push({
fileName: _file,
content: content
});
});
resolve(svgs);
})
}
/**
* Returns the mustache template as a string
*
* @returns {String}
*/
_getTemplateContent() {
return fs.readFileSync(this._options.template, 'utf-8');
}
/**
* Returns the SVG content as an URL encoded string
*
* @param {Buffer} _content The SVG content
* @returns {String}
*/
_urlEncode(_content) {
_content = _content.toString('utf8');
_content = _content.replace(/"/g, "'");
_content = _content.replace(/\s+/g, " ");
_content = _content.replace(/[{}\|\\\^~\[\]`"<>#%]/g, match => '%'