dr-svg-sprites
Version:
Create SVG sprites with PNG fallbacks at needed sizes
365 lines (329 loc) • 9.37 kB
JavaScript
var path = require("path");
var layout = require("layout");
var util = require("./util");
function Sprite (_config) {
// Humble defaults
var defaults = {
prefix: "",
cssPath: "",
cssSuffix: "css",
previewPath: "",
//cssSvgPrefix: ".svg",
//cssPngPrefix: "",
cssUnit: "px",
cssBaseFontSize: 16,
cssIncludeElementSizes: true,
layout: "horizontal",
template: "",
selector: function (filename, tokens) {
var state = "";
if (tokens.state) {
var index = filename.indexOf(tokens.state);
if (index > -1) {
state = ":" + filename.slice(index + tokens.state.length);
filename = filename.slice(0, index);
}
}
var parts = [filename];
if (tokens.prefix) {
parts.unshift(tokens.prefix);
}
if (tokens.size) {
parts.push(tokens.size);
}
return "." + parts.join("-") + state;
},
stateToken: null,
baseQuery: "@media only screen and (min-width: {{breakpoint}})"/*,
svgo: {
plugins: [
{moveGroupAttrsToElems: false},
{removeUselessStrokeAndFill: false},
{collapseGroups: false}
]
},
svgAttributes: {
baseProfile: "tiny",
xmlns: "http://www.w3.org/2000/svg",
preserveAspectRatio: "xMinYMin meet"
}
*/
};
// Merge defaults with user configuration
var config = Object.assign(defaults, _config);
if (config.prefix && !("cssPrefix" in config)) {
config.cssPrefix = config.prefix;
}
var layouts = {
"horizontal": "left-right",
"vertical": "top-down",
"packed": "binary-tree"
};
if (typeof config.spriteElementPath == "string" && !config.spriteElementPath.match(/[*+(){}|!]/)) {
config.spriteElementPath = path.join(config.spriteElementPath, "*.svg");
}
if (config.layout in layouts) {
config.layout = layouts[config.layout];
}
if (!config.template) {
config.template = path.join(__dirname, "../templates/stylesheet.hbs");
}
/*
if (config.spritePath.slice(-1) == "/") {
config.spritePath = config.spritePath.slice(0, -1);
}
*/
if ("cssPngPrefix" in config) {
if (!("cssSvgPrefix" in config)) {
config.cssSvgPrefix = "";
}
}
else {
if (!("cssSvgPrefix" in config)) {
config.cssSvgPrefix = ".svg";
}
config.cssPngPrefix = "";
}
if (config.cssPngPrefix && config.cssPngPrefix.slice(-1) != " ") {
config.cssPngPrefix += " ";
}
if (config.cssSvgPrefix && config.cssSvgPrefix.slice(-1) != " ") {
config.cssSvgPrefix += " ";
}
var cssPath;
// if the path includes filename just use the raw path - otherwise assemble path
if (path.basename(config.cssPath).indexOf(".") > -1) {
cssPath = config.cssPath;
}
else {
cssPath = path.join(config.cssPath, util.joinName(config.cssPrefix, config.name, "sprite") + "." + config.cssSuffix);
}
var svgPath;
// if the path includes filename just use the raw path - otherwise assemble path
if (config.spritePath.match(/\.svg$/)) {
svgPath = config.spritePath;
}
else {
svgPath = path.join(config.spritePath, util.joinName(config.prefix, config.name, "sprite") + ".svg");
}
var previewPath;
// if the path includes filename just use the raw path - otherwise assemble path
if (config.previewPath) {
if (config.previewPath.match(/\.html$/)) {
previewPath = config.previewPath;
}
else {
previewPath = path.join(config.previewPath, util.joinName(config.prefix, config.name, "sprite") + ".html");
}
}
var svgo = {
plugins: [
{moveGroupAttrsToElems: false},
{removeUselessStrokeAndFill: false},
{collapseGroups: false}
]
};
if (config.svgo && config.svgo.plugins) {
var plugins = config.svgo.plugins.map(function (plugin) {
return Object.keys(plugin)[0];
});
svgo.plugins.forEach(function (plugin) {
var name = Object.keys(plugin)[0];
if (plugins.indexOf(name) < 0) {
config.svgo.plugins.push(plugin);
}
});
}
else {
config.svgo = svgo;
}
var svgAttributes = {
baseProfile: "tiny",
xmlns: "http://www.w3.org/2000/svg",
preserveAspectRatio: "xMinYMin meet"
};
if (config.svgAttributes) {
Object.assign(svgAttributes, config.svgAttributes);
}
config.svgAttributes = svgAttributes;
if (typeof config.unit != "number") {
if (config.sizes) {
var usedSizes = [];
var refSize = (typeof config.refSize === "number") ? config.refSize : null;
if (Array.isArray(config.sizes)) {
[].push.apply(usedSizes, config.sizes);
}
else {
if (refSize in config.sizes) {
refSize = config.sizes[refSize];
if (Array.isArray(refSize)) {
refSize = refSize[0];
}
}
var size;
for (var label in config.sizes) {
size = config.sizes[label];
[].push.apply(usedSizes, Array.isArray(size) ? size : [size]);
}
}
if (refSize) {
usedSizes.push(config.refSize);
}
else if (typeof config.refSize === "string") {
refSize = config.sizes[config.refSize];
if (Array.isArray(refSize)) {
refSize = refSize[0];
}
}
var min = Math.min.apply(null, usedSizes);
if (min < refSize) {
min = refSize;
}
var gcd = util.gcdm(usedSizes);
config.unit = min / gcd;
}
else {
config.unit = 5;
}
}
if (config.breakpoints) {
config.breakpoints = config.breakpoints.map(function (breakpoint) {
var query;
if (typeof breakpoint == "number") {
breakpoint += "px";
}
if (breakpoint.indexOf("@") == 0) {
query = breakpoint;
}
else {
query = config.baseQuery.replace("{{breakpoint}}", breakpoint)
}
return {
query: query
};
});
}
this.name = config.name;
this.config = config;
this.items = [];
this.sizes = [];
this.cssPath = cssPath;
this.svgPath = svgPath;
this.previewPath = previewPath;
this.namespaces = [];
};
Sprite.prototype.prepare = function () {
// layout
var layoutData = layout(this.config.layout, {sort: false});
this.items.sort(function (a, b) {
return a.filename > b.filename ? 1 : -1;
});
this.items.forEach(function (element) {
layoutData.addItem(element);
});
Object.assign(this, layoutData.export());
// sizes
if (this.config.sizes && !Array.isArray(this.config.sizes) && Object.keys(this.config.sizes).length) {
for (var label in this.config.sizes) {
this.addSize(label);
}
}
else {
this.addSize("");
}
};
Sprite.prototype.addItem = function (file, source, namespaces, width, height) {
var filename = path.basename(file, path.extname(file));
Object.assign(this.namespaces, namespaces);
if (this.config.map) {
if (typeof this.config.map == "function") {
filename = this.config.map(filename);
}
else if (this.config.map[filename]) {
filename = this.config.map[filename];
}
}
this.items.push({
filename: filename,
source: source,
cssWidth: Math.ceil(width),
cssHeight: Math.ceil(height),
width: util.roundUpToUnit(width + this.config.unit, this.config.unit),
height: util.roundUpToUnit(height + this.config.unit, this.config.unit),
x: null,
y: null
});
};
Sprite.prototype.addSize = function (label) {
var config = this.config;
var breakpoints = [];
var breakpointSizes = null;
var size = 1;
if (config.sizes) {
if (Array.isArray(config.sizes)) {
size = config.sizes;
}
else if (label in config.sizes) {
size = config.sizes[label];
}
}
if (Array.isArray(size)) {
breakpointSizes = size.slice(1);
size = size[0];
}
var refSize = config.refSize || size;
if (typeof config.refSize == "string") {
refSize = config.sizes[refSize];
}
if (Array.isArray(refSize)) {
refSize = refSize[0];
}
var width = util.scaleValue(this.width, size, refSize);
var height = util.scaleValue(this.height, size, refSize);
var pngPath = this.svgPath.replace(/\.svg$/, ((label) ? "-" + label : "") + ".png");
var tokens = {
size: label,
prefix: config.prefix,
state: config.stateToken
};
var items = this.items.map(function (item, index) {
var selector = config.selector(item.filename, tokens);
return {
selector: selector,
className: selector, // legacy
width: util.scaleValue(item.cssWidth, size, refSize),
height: util.scaleValue(item.cssHeight, size, refSize),
x: util.scaleValue(item.x, size, refSize),
y: util.scaleValue(item.y, size, refSize)
};
});
if (config.breakpoints && breakpointSizes) {
breakpoints = config.breakpoints.map(function (breakpoint, index) {
var size = breakpointSizes[index];
return {
query: breakpoint.query,
width: util.scaleValue(this.width, size, refSize),
height: util.scaleValue(this.height, size, refSize),
items: this.items.map(function (item, index) {
return {
selector: items[index].selector,
className: items[index].className, // legacy
width: util.scaleValue(item.cssWidth, size, refSize),
height: util.scaleValue(item.cssHeight, size, refSize),
x: util.scaleValue(item.x, size, refSize),
y: util.scaleValue(item.y, size, refSize)
};
})
};
}, this);
}
this.sizes.push({
label: label,
items: items,
pngPath: pngPath,
width: width,
height: height,
breakpoints: breakpoints
});
};
module.exports = Sprite;