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

const { validate } = require('svg-mixer-utils'); const { name: packageName } = require('../package.json'); const schemas = require('../schemas'); const config = require('./config'); const SpriteCompiler = require('./utils/sprite-compiler'); const { configurator: configure, Replacer } = require('./utils'); const { isHtmlPluginCompilation } = require('./utils').helpers; let INSTANCE_COUNTER = 0; class ExtractSvgSpritePlugin { static get loader() { return config.LOADER_PATH; } static get cssLoader() { return config.CSS_LOADER_PATH; } constructor(cfg) { this.id = ++INSTANCE_COUNTER; this.config = configure(cfg); const errors = validate(schemas.plugin, this.config); if (errors.length) { throw new Error(`${packageName}: ${errors.join('\n')}`); } this.compiler = new SpriteCompiler(this.config); } get NAMESPACE() { return config.NAMESPACE; } apply(compiler) { const { NAMESPACE } = config; // TODO refactor this ugly way to avoid double compilation when using extract-text-webpack-plugin // TODO remove this when drop webpack 3 support // eslint-disable-next-line arrow-body-style const compileSprites = compilation => { return ( this.prevResult ? Promise.resolve(this.prevResult) : this.compiler.compile(compilation) ).then(result => { this.prevResult = result; return result; }); }; if (compiler.hooks) { compiler.hooks.thisCompilation.tap(NAMESPACE, compilation => { compilation.hooks.optimizeTree .tapPromise(NAMESPACE, () => compileSprites(compilation) .then(result => { result.forEach(({ sprite }) => { sprite.symbols.forEach(s => { if (s.cssModules && s.cssModules.length) { s.cssModules.forEach(m => { Replacer.replaceInModuleSource(m, s.replacements, compilation); }); } }); }); })); compilation.hooks.additionalAssets .tapPromise(NAMESPACE, () => compileSprites(compilation) .then(result => this.hookAdditionalAssets(compilation, result))); }); compiler.hooks.compilation.tap(NAMESPACE, compilation => { compilation.hooks.normalModuleLoader .tap(NAMESPACE, loaderCtx => this.hookNormalModuleLoader(loaderCtx)); if (compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration) { compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration .tapAsync(NAMESPACE, (htmlPluginData, done) => compileSprites(compilation) .then(result => { this.hookBeforeHtmlGeneration(htmlPluginData, result); done(null, htmlPluginData); })); } }); } else { compiler.plugin('compilation', compilation => { if (isHtmlPluginCompilation(compilation)) { return; } compilation.plugin( 'normal-module-loader', loaderCtx => this.hookNormalModuleLoader(loaderCtx) ); compilation.plugin('additional-assets', done => compileSprites(compilation).then(result => { this.hookAdditionalAssets(compilation, result); done(); })); compilation.plugin( 'html-webpack-plugin-before-html-generation', (htmlPluginData, done) => compileSprites(compilation).then(result => { this.hookBeforeHtmlGeneration(htmlPluginData, result); done(null, htmlPluginData); }) ); }); } } hookNormalModuleLoader(loaderContext) { loaderContext[config.NAMESPACE] = this; } hookAdditionalAssets(compilation, result) { result.forEach(({ filename, content, sprite }) => { sprite.symbols.forEach(s => { Replacer.replaceInModuleSource(s.module, s.replacements, compilation); Replacer.replaceInModuleSource(s.module.issuer, s.replacements, compilation); }); if (filename) { compilation.assets[filename.split('?')[0]] = { source: () => content, size: () => content.length }; } }); } hookBeforeHtmlGeneration(htmlPluginData, result) { htmlPluginData.assets.sprites = result .map(({ filename, content }) => ({ filename, content })); } } module.exports = ExtractSvgSpritePlugin;