packages/extract-svg-sprite-webpack-plugin/lib/utils/sprite-compiler.js (104 lines of code) (raw):

const Path = require('path'); const { interpolateName } = require('loader-utils'); const generator = require('./replacement-generator'); const helpers = require('./helpers'); const MINI_EXTRACT_MODULE_TYPE = 'css/mini-extract'; class CompiledSprite { constructor({ sprite, content, filename }) { this.filename = filename; this.content = content; this.sprite = sprite; } } module.exports = class SpriteCompiler { constructor(config) { this.config = config; this.symbols = new Map(); } /** * @param {mixer.SpriteSymbol} symbol */ addSymbol(symbol) { this.symbols.set(symbol.key || symbol.request, symbol); } /** * @return {Replacement[]} */ getReplacements() { return Array.from(this.symbols.values()) .map(s => s.replacements) .reduce((acc, r) => acc.concat(r), []); } /** * @return {Array<{filename?: string, symbols: SpriteSymbol[]}>} */ groupBySpriteFileName(compilation) { const sprites = []; Array.from(this.symbols.keys()).forEach(path => { const symbol = this.symbols.get(path); const { config, module } = symbol; const compilationContext = helpers.getRootCompilation(compilation) .compiler.context; let filename; if (config.filename && config.emit) { filename = typeof config.filename === 'function' ? config.filename(module) : config.filename; if (filename.includes('[issuer-path]')) { filename = filename.replace( '[issuer-path]', module.issuer.resource .replace(compilationContext, '') .replace(Path.extname(module.issuer.resource), '') .replace(/^\//, '') ); } } let sprite = sprites.find(s => s.filename === filename); if (!sprite) { sprite = { symbols: [symbol] }; if (filename) { sprite.filename = filename; } sprites.push(sprite); } else { sprite.symbols.push(symbol); } }); return sprites; } /** * @param {Compilation} compilation * @return {Promise<CompiledSprite[]>} */ compile(compilation) { const { spriteClass, spriteConfig } = this.config; const promises = this.groupBySpriteFileName(compilation).map(spriteData => { const { filename, symbols } = spriteData; // eslint-disable-next-line new-cap const sprite = new spriteClass(spriteConfig, symbols); return sprite.render() .then(content => { const result = { sprite, content, filename }; if (filename && filename.includes('[contenthash')) { result.filename = interpolateName( process.cwd(), filename.replace('[contenthash', '[hash'), { content } ); } sprite.symbols.forEach(symbol => { const { config, request: symbolUrl } = symbol; const position = sprite.calculateSymbolPosition(symbol, 'percent'); symbol.cssModules = symbol.issuers .map(issuer => compilation.modules.find(m => m.type === MINI_EXTRACT_MODULE_TYPE && m.issuer.request.includes(issuer.request))) .filter(Boolean); symbol.replacements = [ generator.symbolUrl(symbol, { ...config, filename: result.filename }), generator.bgPosLeft(symbolUrl, position), generator.bgPosTop(symbolUrl, position), generator.bgSizeWidth(symbolUrl, position), generator.bgSizeHeight(symbolUrl, position), config.publicPath && new generator.Replacement( config.publicPath, compilation.getPath(config.publicPath) ) ].filter(replacement => replacement && replacement.replaceTo); }); return new CompiledSprite(result); }); }); return Promise.all(promises); } };