svg-symbol-sprite
Version:
A script to generate a symbol sprite from SVG files
134 lines (107 loc) • 4.2 kB
text/typescript
import { existsSync, promises as fs } from 'fs';
import { join, extname, resolve, basename } from 'path';
import { load } from 'cheerio';
import { Command } from 'commander';
import { optimize, loadConfig, Config } from 'svgo';
const { rm, readdir, readFile, writeFile } = fs;
const cli = new Command();
const SVG_PROPS = 'xmlns="http://www.w3.org/2000/svg" aria-hidden="true"';
const SVG_STYLE = 'width: 0; height: 0; position: absolute;';
const DEFAULT_CONFIG = join(__dirname, '..', 'config', 'svgo.config.js');
const CHEERIO_OPTIONS = {
lowerCaseTags: true,
lowerCaseAttributeNames: true,
_useHtmlParser2: true
};
cli.option('-i, --input [input]', 'Specifies input dir (current dir by default)', '.')
.option('-o, --output [output]', 'Specifies output file ("./sprite.svg" by default)', 'sprite.svg')
.option('-v, --viewbox [viewbox]', 'Specifies viewBox attribute (parsed by default)', '')
.option('-p, --prefix [prefix]', 'Specifies prefix for id attribute ("icon-" by default)', 'icon-')
.option('-c, --config [config]', 'Absolute path to the SVGO config file or "false"', './config/svgo.config.js')
.option('-a, --attrs [attributes]', 'Attributes for the SVG element', SVG_PROPS)
.option('-s, --style [style]', 'Inline style for the SVG element', SVG_STYLE)
.parse(process.argv);
type OptionValues = {
input: string;
output: string;
viewbox: string;
prefix: string;
config: string | 'false';
attrs: string;
style: string;
};
const {
input: INPUT,
output: OUTPUT,
viewbox: VIEWBOX,
prefix: PREFIX,
config: CONFIG,
attrs: ATTRS,
style: STYLE
} = cli.opts<OptionValues>();
const onEnd = (): void => console.log(`File ‘${OUTPUT}’ successfully generated.`);
const getSvg = (content: string) => load(content, CHEERIO_OPTIONS)('svg').first();
const filterFile = (file: string) => extname(file) === '.svg';
const processFiles = (files: string[]) => Promise.all(files.map(processFile));
const removeOutput = async () => (existsSync(OUTPUT) ? await rm(OUTPUT) : undefined);
const getSvgContent = (content: string) => getSvg(content).html();
const readSrcFolder = () => readdir(INPUT);
const writeDestFile = (content: string) => writeFile(OUTPUT, content, 'utf8');
const getSpriteContent = (contents: string[]) => {
return `<${['svg', ATTRS, `style="${STYLE}"`].join(' ').replace('style=""', '').trim()}>${contents.join('')}</svg>`;
};
const getSymbol = (content: string, attrs: Record<string, unknown>) => {
return `<symbol${getAttributes(attrs)}>${getSvgContent(content)}</symbol>`;
};
const getAttributes = (attrs: Record<string, unknown>) =>
Object.keys(attrs).reduce((acc, key) => {
const value = attrs[key];
return value ? `${acc} ${key}="${value}"` : acc;
}, '');
const wrapFile = (fileName: string, content: string) => {
const attrs = {
id: (PREFIX + fileName).replace(/\s/g, '-'),
viewBox: VIEWBOX || getSvg(content).attr('viewbox'),
preserveAspectRatio: getSvg(content).attr('preserveaspectratio')
};
return getSymbol(content, attrs);
};
const getName = (file: string) => basename(file, extname(file));
const processFile = (file: string) => {
const path = resolve(INPUT, file);
const name = getName(file);
const wrapContent = wrapFile.bind(null, name);
return readFile(path, 'utf8').then(wrapContent);
};
const onError = (err: Error) => {
throw err;
};
removeOutput()
.then(readSrcFolder)
.then(async (files: string[]) => {
const matchingFiles = files.filter(filterFile);
if (CONFIG === 'false') {
return processFiles(matchingFiles);
}
let svgoConfig: Config = await loadConfig(DEFAULT_CONFIG);
try {
svgoConfig = await loadConfig(CONFIG);
} catch (e: any) {
console.log('SVG Symbol Sprite: SVGO configuration file not found. Using default SVGO configuration.');
}
const processedFiles = [];
for (const file of matchingFiles) {
const content = await fs.readFile(join(INPUT, file), {
encoding: 'utf-8'
});
const name = getName(file);
const optimizedSVG = optimize(content, svgoConfig).data;
processedFiles.push(wrapFile(name, optimizedSVG));
}
return processedFiles;
})
.then(getSpriteContent)
.then(writeDestFile)
.then(onEnd)
.catch(onError);