// TODO refactor this smelly code! const loaderDefaults = require('../config').loader; const getAllModules = require('./get-all-modules'); const isModuleShouldBeExtracted = require('./is-module-should-be-extracted'); const getModuleChunk = require('./get-module-chunk'); const interpolate = require('./interpolate'); const getMatchedRule = require('./get-matched-rule'); class MappedListItem { /** * @param {SpriteSymbol} symbol * @param {NormalModule} module * @param {string} spriteFilename */ constructor(symbol, module, spriteFilename) { this.symbol = symbol; this.module = module; this.resource = symbol.request.file; this.spriteFilename = spriteFilename; } get url() { return `${this.spriteFilename}#${this.symbol.id}`; } get useUrl() { return `${this.spriteFilename}#${this.symbol.useId}`; } } class MappedList { /** * @param {SpriteSymbol[]} symbols * @param {Compilation} compilation */ constructor(symbols, compilation, shouldLog = false) { const { compiler } = compilation; this.symbols = symbols; this.rule = getMatchedRule(compiler); this.allModules = getAllModules(compilation); this.spriteModules = this.allModules.filter(isModuleShouldBeExtracted); this.shouldLog = shouldLog; this.items = this.create(); } /** * @param {MappedListItem[]} data * @return {Object} */ static groupItemsBySpriteFilename(data) { return data .map(item => item.spriteFilename) .filter((value, index, self) => self.indexOf(value) === index) .reduce((acc, spriteFilename) => { acc[spriteFilename] = data.filter(item => item.spriteFilename === spriteFilename); return acc; }, {}); } /** * @param {MappedListItem[]} data * @param {Function} [mapper] Custom grouper function * @return {Object} */ static groupItemsBySymbolFile(data, mapper) { return data.reduce((acc, item) => { if (mapper) { mapper(acc, item); } else { acc[item.resource] = item; } return acc; }, {}); } /** * @return {MappedListItem[]} */ create() { const { symbols, spriteModules, allModules, rule } = this; const data = symbols.reduce((acc, symbol) => { const resource = symbol.request.file; const module = spriteModules.find((m) => { return 'resource' in m ? m.resource.split('?')[0] === resource : false; }); let spriteFilename = rule.spriteFilename || loaderDefaults.spriteFilename; const chunk = module ? getModuleChunk(module, allModules) : null; if (typeof spriteFilename !== 'function' && chunk && chunk.name) { spriteFilename = spriteFilename.replace('[chunkname]', chunk.name); } else if (typeof spriteFilename === 'function') { spriteFilename = spriteFilename(resource); } if (rule && module) { acc.push(new MappedListItem(symbol, module, spriteFilename)); } return acc; }, []); // Additional pass to interpolate [hash] in spriteFilename const itemsBySpriteFilename = MappedList.groupItemsBySpriteFilename(data); const filenames = Object.keys(itemsBySpriteFilename); filenames.forEach((filename) => { if (!filename.includes('hash')) { return; } const items = itemsBySpriteFilename[filename]; const spriteSymbols = items.map(item => item.symbol); const content = spriteSymbols.map(s => s.render()).join(''); const interpolatedName = interpolate(filename, { resourcePath: filename, content }); items .filter(item => item.spriteFilename !== interpolatedName) .forEach(item => item.spriteFilename = interpolatedName); }); return data; } /** * @return {Object} */ groupItemsBySpriteFilename() { return MappedList.groupItemsBySpriteFilename(this.items); } /** * @param {Function} [mapper] Custom grouper function * @return {Object} */ groupItemsBySymbolFile(mapper) { return MappedList.groupItemsBySymbolFile(this.items, mapper); } } module.exports = MappedList;