packages/postcss-svg-mixer/lib/plugin.js (92 lines of code) (raw):

const postcss = require('postcss'); const merge = require('merge-options'); const { Compiler, Sprite, StackSprite } = require('svg-mixer'); const { parse: parseQuery, stringify: stringifyQuery } = require('query-string'); const { createMatcher } = require('svg-mixer-utils'); const { name: packageName } = require('../package.json'); const collectDeclarations = require('./find-declarations-to-process'); const transform = require('./transform-declaration'); function convertToCompilerOpts(opts) { // eslint-disable-next-line no-unused-vars const { match, selector, spriteFilename, userSprite, ...rest } = opts; return { spriteConfig: { filename: spriteFilename }, ...rest }; } /** * @typedef {Object} PluginConfig * @extends {CompilerConfig} * @property {string} spriteType * @property {string} spriteFilename * @property {RegExp|string|Array<RegExp|string>} match * @property {boolean} selector=null * @property {Sprite|StackSprite} userSprite */ const defaultConfig = { spriteType: Sprite.TYPE, spriteFilename: Sprite.defaultConfig.filename, match: /\.svg(\?.*)?$/, selector: null, userSprite: null }; module.exports = postcss.plugin(packageName, opts => { const { ctx, ...restOpts } = opts || {}; const cfg = merge(defaultConfig, restOpts); const { userSprite, spriteType } = cfg; const compiler = !userSprite ? new Compiler(convertToCompilerOpts(cfg)) : null; const matcher = createMatcher(cfg.match); const isWebpack = !!(ctx && ctx.webpack && ctx.webpack.emitFile); return async function plugin(root, result) { const declsAndPaths = await collectDeclarations(root, matcher); if (!declsAndPaths.length) { return; } let sprite; let spriteContent; let spriteFilename; if (userSprite) { sprite = userSprite; spriteContent = await sprite.render(); spriteFilename = sprite.config.filename; } else { for (const item of declsAndPaths) { const file = `${item.absolute}${item.query || ''}`; await compiler.addSymbolFromFile(file); } const res = await compiler.compile(); sprite = res.sprite; spriteContent = res.content; spriteFilename = res.filename; } declsAndPaths.forEach(item => { const { decl, path, absolute, query } = item; const symbol = sprite.symbols.find(({ image }) => { return image.path === absolute && image.query === query; }); if (!symbol) { return; } const position = sprite.calculateSymbolPosition(symbol, 'percent'); const parsedQuery = parseQuery(query || ''); let spriteUrl; if (spriteType === Sprite.TYPE) { // In webpack environment plugin produce `original_url?sprite_filename.svg`, and special loader // in pitching phase replace original url with sprite file name const q = stringifyQuery({ ...parsedQuery, spriteFilename }); spriteUrl = isWebpack ? `${path}?${q}` : spriteFilename; } else if (spriteType === StackSprite.TYPE) { spriteUrl = `${spriteFilename}#${symbol.id}`; } transform({ decl, selector: cfg.selector, position, spriteUrl, spriteType }); }); result.messages.push({ type: 'asset', kind: 'sprite', plugin: packageName, file: result.opts.from, sprite, filename: spriteFilename, content: spriteContent }); // Emit sprite file in webpack compilation assets if (isWebpack) { ctx.webpack.emitFile(spriteFilename, spriteContent); } }; });