| /* eslint-disable import/no-extraneous-dependencies */ | 
| const merge = require('deepmerge'); | 
| const Promise = require('bluebird'); | 
| const SVGCompiler = require('svg-baker'); | 
| const spriteFactory = require('svg-baker/lib/sprite-factory'); | 
| const Sprite = require('svg-baker/lib/sprite'); | 
| const { NAMESPACE } = require('./config'); | 
| const { | 
|   MappedList, | 
|   replaceInModuleSource, | 
|   replaceSpritePlaceholder, | 
|   getMatchedRule, | 
|   getWebpackVersion | 
| } = require('./utils'); | 
|   | 
| const defaultConfig = { | 
|   plainSprite: false, | 
|   spriteAttrs: {} | 
| }; | 
|   | 
| class SVGSpritePlugin { | 
|   constructor(cfg = {}) { | 
|     const config = merge.all([defaultConfig, cfg]); | 
|     this.config = config; | 
|   | 
|     const spriteFactoryOptions = { | 
|       attrs: config.spriteAttrs | 
|     }; | 
|   | 
|     if (config.plainSprite) { | 
|       spriteFactoryOptions.styles = false; | 
|       spriteFactoryOptions.usages = false; | 
|     } | 
|   | 
|     this.factory = ({ symbols }) => { | 
|       const opts = merge.all([spriteFactoryOptions, { symbols }]); | 
|       return spriteFactory(opts); | 
|     }; | 
|   | 
|     this.svgCompiler = new SVGCompiler(); | 
|     this.rules = {}; | 
|   } | 
|   | 
|   /** | 
|    * This need to find plugin from loader context | 
|    */ | 
|   // eslint-disable-next-line class-methods-use-this | 
|   get NAMESPACE() { | 
|     return NAMESPACE; | 
|   } | 
|   | 
|   getReplacements() { | 
|     const isPlainSprite = this.config.plainSprite === true; | 
|     const replacements = this.map.groupItemsBySymbolFile((acc, item) => { | 
|       acc[item.resource] = isPlainSprite ? item.url : item.useUrl; | 
|     }); | 
|     return replacements; | 
|   } | 
|   | 
|   // TODO optimize MappedList instantiation in each hook | 
|   apply(compiler) { | 
|     this.rules = getMatchedRule(compiler); | 
|   | 
|     const path = this.rules.outputPath ? this.rules.outputPath : this.rules.publicPath; | 
|     this.filenamePrefix = path | 
|       ? path.replace(/^\//, '') | 
|       : ''; | 
|   | 
|     if (compiler.hooks) { | 
|       compiler.hooks | 
|         .thisCompilation | 
|         .tap(NAMESPACE, (compilation) => { | 
|           compilation.hooks | 
|             .normalModuleLoader | 
|             .tap(NAMESPACE, loaderContext => loaderContext[NAMESPACE] = this); | 
|   | 
|           compilation.hooks | 
|             .afterOptimizeChunks | 
|             .tap(NAMESPACE, () => this.afterOptimizeChunks(compilation)); | 
|           if (!getWebpackVersion.IS_5) { | 
|             compilation.hooks | 
|               .optimizeExtractedChunks | 
|               .tap(NAMESPACE, chunks => this.optimizeExtractedChunks(chunks)); | 
|           } | 
|           compilation.hooks | 
|             .additionalAssets | 
|             .tapPromise(NAMESPACE, () => { | 
|               return this.additionalAssets(compilation); | 
|             }); | 
|         }); | 
|   | 
|       compiler.hooks | 
|         .compilation | 
|         .tap(NAMESPACE, (compilation) => { | 
|           if (compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) { | 
|             compilation.hooks | 
|               .htmlWebpackPluginBeforeHtmlGeneration | 
|               .tapAsync(NAMESPACE, (htmlPluginData, callback) => { | 
|                 htmlPluginData.assets.sprites = this.beforeHtmlGeneration(compilation); | 
|   | 
|                 callback(null, htmlPluginData); | 
|               }); | 
|           } | 
|   | 
|           if (compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing) { | 
|             compilation.hooks | 
|               .htmlWebpackPluginBeforeHtmlProcessing | 
|               .tapAsync(NAMESPACE, (htmlPluginData, callback) => { | 
|                 htmlPluginData.html = this.beforeHtmlProcessing(htmlPluginData); | 
|   | 
|                 callback(null, htmlPluginData); | 
|               }); | 
|           } | 
|         }); | 
|     } else { | 
|       // Handle only main compilation | 
|       compiler.plugin('this-compilation', (compilation) => { | 
|         // Share svgCompiler with loader | 
|         compilation.plugin('normal-module-loader', (loaderContext) => { | 
|           loaderContext[NAMESPACE] = this; | 
|         }); | 
|   | 
|         // Replace placeholders with real URL to symbol (in modules processed by svg-sprite-loader) | 
|         compilation.plugin('after-optimize-chunks', () => this.afterOptimizeChunks(compilation)); | 
|   | 
|         // Hook into extract-text-webpack-plugin to replace placeholders with real URL to symbol | 
|         compilation.plugin('optimize-extracted-chunks', chunks => this.optimizeExtractedChunks(chunks)); | 
|   | 
|         // Hook into html-webpack-plugin to add `sprites` variable into template context | 
|         compilation.plugin('html-webpack-plugin-before-html-generation', (htmlPluginData, done) => { | 
|           htmlPluginData.assets.sprites = this.beforeHtmlGeneration(compilation); | 
|   | 
|           done(null, htmlPluginData); | 
|         }); | 
|   | 
|         // Hook into html-webpack-plugin to replace placeholders with real URL to symbol | 
|         compilation.plugin('html-webpack-plugin-before-html-processing', (htmlPluginData, done) => { | 
|           htmlPluginData.html = this.beforeHtmlProcessing(htmlPluginData); | 
|           done(null, htmlPluginData); | 
|         }); | 
|   | 
|         // Create sprite chunk | 
|         compilation.plugin('additional-assets', (done) => { | 
|           return this.additionalAssets(compilation) | 
|             .then(() => { | 
|               done(); | 
|               return true; | 
|             }) | 
|             .catch(e => done(e)); | 
|         }); | 
|       }); | 
|     } | 
|   } | 
|   | 
|   additionalAssets(compilation) { | 
|     const itemsBySprite = this.map.groupItemsBySpriteFilename(); | 
|     const filenames = Object.keys(itemsBySprite); | 
|   | 
|     return Promise.map(filenames, (filename) => { | 
|       const spriteSymbols = itemsBySprite[filename].map(item => item.symbol); | 
|   | 
|       return Sprite.create({ | 
|         symbols: spriteSymbols, | 
|         factory: this.factory | 
|       }) | 
|         .then((sprite) => { | 
|           const content = sprite.render(); | 
|   | 
|           compilation.assets[`${this.filenamePrefix}${filename}`] = { | 
|             source() { return content; }, | 
|             size() { return content.length; } | 
|           }; | 
|         }); | 
|     }); | 
|   } | 
|   | 
|   afterOptimizeChunks(compilation) { | 
|     const { symbols } = this.svgCompiler; | 
|     this.map = new MappedList(symbols, compilation); | 
|     const replacements = this.getReplacements(); | 
|     this.map.items.forEach(item => replaceInModuleSource(item.module, replacements)); | 
|   } | 
|   | 
|   optimizeExtractedChunks(chunks) { | 
|     const replacements = this.getReplacements(); | 
|   | 
|     chunks.forEach((chunk) => { | 
|       let modules; | 
|   | 
|       if (chunk.modulesIterable) { | 
|         modules = Array.from(chunk.modulesIterable); | 
|       } else { | 
|         modules = chunk.modules; | 
|       } | 
|   | 
|       modules | 
|         // dirty hack to identify modules extracted by extract-text-webpack-plugin | 
|         // TODO refactor | 
|         .filter(module => '_originalModule' in module) | 
|         .forEach(module => replaceInModuleSource(module, replacements)); | 
|     }); | 
|   } | 
|   | 
|   beforeHtmlGeneration(compilation) { | 
|     const itemsBySprite = this.map.groupItemsBySpriteFilename(); | 
|   | 
|     const sprites = Object.keys(itemsBySprite).reduce((acc, filename) => { | 
|       acc[this.filenamePrefix + filename] = compilation.assets[this.filenamePrefix + filename].source(); | 
|       return acc; | 
|     }, {}); | 
|   | 
|     return sprites; | 
|   } | 
|   | 
|   beforeHtmlProcessing(htmlPluginData) { | 
|     const replacements = this.getReplacements(); | 
|     return replaceSpritePlaceholder(htmlPluginData.html, replacements); | 
|   } | 
| } | 
|   | 
| module.exports = SVGSpritePlugin; |