All files / src plugin.js

0% Statements 0/17
0% Branches 0/6
0% Functions 0/6
0% Lines 0/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91                                                                                                                                                                                     
'use strict'
 
const { RawSource } = require('webpack-sources')
const { interpolateName } = require('loader-utils')
const HtmlWebpackPlugin = require('safe-require')('html-webpack-plugin')
 
const spriteStore = require('./sprite-store')
 
/**
 * The SVG symbol sprite plugin is responsible for generating the SVG sprite asset
 * and including it in the compilation assets.
 */
module.exports = class SVGSymbolSpritePlugin {
  /**
   * Merge default options with plugin configuration
   */
  constructor(options) {
    this.options = Object.assign(
      {
        /** The `filename` defines the name of the emitted asset */
        filename: 'icon-sprite.svg',
        /** By default inject sprite id into head */
        injectSpriteId: true,
      },
      options || {},
    )
  }
 
  /**
   * `apply` method is called once by webpack during plugin install, use it to set
   * event hooks on the compiler.
   */
  apply(compiler) {
    const { filename } = this.options
    let resourcePath
 
    // 🤔 The `compilation` hook is called twice and ends up producing an empty svg
    // sprite. The `thisCompiliation` hook is only called once. This seems to be the
    // correct hook to use ¯\_(ツ)_/¯
    compiler.hooks.thisCompilation.tap('SVGSymbolSprite', (compilation) => {
      // ℹ️ During additional assets hook, handle adding svg sprite to build assets
      compilation.hooks.additionalAssets.tap('SVGSymbolSprite', () => {
        // Get sprite content returns full svg symbol sprite ready for application
        const content = spriteStore.getSpriteContent()
 
        // Use interpolateName to allow for file name hashing based on content
        resourcePath = interpolateName(
          {},
          // CREATE A FILE NAME WITH HELLAS REGEX:
          // 1. Normalize to [hash]
          // 2. Inject default digest hash length if not there
          // loader-utils only supports [hash] interpolation, but that is confusing
          // because most other webpack filenames use either chunkhash or more
          // accurately in this case contenthash
          filename.replace(/contenthash|chunkhash/, 'hash').replace(
            // ℹ️ This will only match if a digest length hasn't been set
            /\[hash\]/,
            // see https://webpack.js.org/configuration/output/#output-hashdigestlength
            `[hash:${compilation.outputOptions.hashDigestLength}]`,
          ),
          { content },
        )
 
        compilation.emitAsset(resourcePath, new RawSource(content))
      })
    })
 
    if (this.options.injectSpriteId) {
      // The alter asset tags hook is only called once during the compilation ¯\_(ツ)_/¯
      compiler.hooks.compilation.tap('SVGSymbolSprite', (compilation) => {
        // HTML webpack plugin hook to alter the asset tags included in generated HTML
        HtmlWebpackPlugin.getHooks(compilation).alterAssetTagGroups.tapAsync(
          'SVGSymbolSprite',
          (data, cb) => {
            data.headTags.push({
              tagName: 'script',
              voidTag: false,
              attributes: { type: 'text/javascript' },
              innerHTML: `window.ICON_SPRITE_ID = "${
                compilation.outputOptions.publicPath || ''
              }${resourcePath}";`,
            })
 
            cb(null, data)
          },
        )
      })
    }
  }
}