'use strict'; 
 | 
  
 | 
// use Polyfill for util.promisify in node versions < v8 
 | 
const promisify = require('util.promisify'); 
 | 
  
 | 
const vm = require('vm'); 
 | 
const fs = require('fs'); 
 | 
const _ = require('lodash'); 
 | 
const path = require('path'); 
 | 
const childCompiler = require('./lib/compiler.js'); 
 | 
const prettyError = require('./lib/errors.js'); 
 | 
const chunkSorter = require('./lib/chunksorter.js'); 
 | 
  
 | 
const fsStatAsync = promisify(fs.stat); 
 | 
const fsReadFileAsync = promisify(fs.readFile); 
 | 
  
 | 
class HtmlWebpackPlugin { 
 | 
  constructor (options) { 
 | 
    // Default options 
 | 
    this.options = _.extend({ 
 | 
      template: path.join(__dirname, 'default_index.ejs'), 
 | 
      templateParameters: templateParametersGenerator, 
 | 
      filename: 'index.html', 
 | 
      hash: false, 
 | 
      inject: true, 
 | 
      compile: true, 
 | 
      favicon: false, 
 | 
      minify: false, 
 | 
      cache: true, 
 | 
      showErrors: true, 
 | 
      chunks: 'all', 
 | 
      excludeChunks: [], 
 | 
      chunksSortMode: 'auto', 
 | 
      meta: {}, 
 | 
      title: 'Webpack App', 
 | 
      xhtml: false 
 | 
    }, options); 
 | 
  } 
 | 
  
 | 
  apply (compiler) { 
 | 
    const self = this; 
 | 
    let isCompilationCached = false; 
 | 
    let compilationPromise; 
 | 
  
 | 
    this.options.template = this.getFullTemplatePath(this.options.template, compiler.context); 
 | 
  
 | 
    // convert absolute filename into relative so that webpack can 
 | 
    // generate it at correct location 
 | 
    const filename = this.options.filename; 
 | 
    if (path.resolve(filename) === path.normalize(filename)) { 
 | 
      this.options.filename = path.relative(compiler.options.output.path, filename); 
 | 
    } 
 | 
  
 | 
    // setup hooks for webpack 4 
 | 
    if (compiler.hooks) { 
 | 
      compiler.hooks.compilation.tap('HtmlWebpackPluginHooks', compilation => { 
 | 
        const SyncWaterfallHook = require('tapable').SyncWaterfallHook; 
 | 
        const AsyncSeriesWaterfallHook = require('tapable').AsyncSeriesWaterfallHook; 
 | 
        compilation.hooks.htmlWebpackPluginAlterChunks = new SyncWaterfallHook(['chunks', 'objectWithPluginRef']); 
 | 
        compilation.hooks.htmlWebpackPluginBeforeHtmlGeneration = new AsyncSeriesWaterfallHook(['pluginArgs']); 
 | 
        compilation.hooks.htmlWebpackPluginBeforeHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']); 
 | 
        compilation.hooks.htmlWebpackPluginAlterAssetTags = new AsyncSeriesWaterfallHook(['pluginArgs']); 
 | 
        compilation.hooks.htmlWebpackPluginAfterHtmlProcessing = new AsyncSeriesWaterfallHook(['pluginArgs']); 
 | 
        compilation.hooks.htmlWebpackPluginAfterEmit = new AsyncSeriesWaterfallHook(['pluginArgs']); 
 | 
      }); 
 | 
    } 
 | 
  
 | 
    // Backwards compatible version of: compiler.hooks.make.tapAsync() 
 | 
    (compiler.hooks ? compiler.hooks.make.tapAsync.bind(compiler.hooks.make, 'HtmlWebpackPlugin') : compiler.plugin.bind(compiler, 'make'))((compilation, callback) => { 
 | 
      // Compile the template (queued) 
 | 
      compilationPromise = childCompiler.compileTemplate(self.options.template, compiler.context, self.options.filename, compilation) 
 | 
        .catch(err => { 
 | 
          compilation.errors.push(prettyError(err, compiler.context).toString()); 
 | 
          return { 
 | 
            content: self.options.showErrors ? prettyError(err, compiler.context).toJsonHtml() : 'ERROR', 
 | 
            outputName: self.options.filename 
 | 
          }; 
 | 
        }) 
 | 
        .then(compilationResult => { 
 | 
          // If the compilation change didnt change the cache is valid 
 | 
          isCompilationCached = compilationResult.hash && self.childCompilerHash === compilationResult.hash; 
 | 
          self.childCompilerHash = compilationResult.hash; 
 | 
          self.childCompilationOutputName = compilationResult.outputName; 
 | 
          callback(); 
 | 
          return compilationResult.content; 
 | 
        }); 
 | 
    }); 
 | 
  
 | 
    // Backwards compatible version of: compiler.plugin.emit.tapAsync() 
 | 
    (compiler.hooks ? compiler.hooks.emit.tapAsync.bind(compiler.hooks.emit, 'HtmlWebpackPlugin') : compiler.plugin.bind(compiler, 'emit'))((compilation, callback) => { 
 | 
      const applyPluginsAsyncWaterfall = self.applyPluginsAsyncWaterfall(compilation); 
 | 
      // Get chunks info as json 
 | 
      // Note: we're excluding stuff that we don't need to improve toJson serialization speed. 
 | 
      const chunkOnlyConfig = { 
 | 
        assets: false, 
 | 
        cached: false, 
 | 
        children: false, 
 | 
        chunks: true, 
 | 
        chunkModules: false, 
 | 
        chunkOrigins: false, 
 | 
        errorDetails: false, 
 | 
        hash: false, 
 | 
        modules: false, 
 | 
        reasons: false, 
 | 
        source: false, 
 | 
        timings: false, 
 | 
        version: false 
 | 
      }; 
 | 
      const allChunks = compilation.getStats().toJson(chunkOnlyConfig).chunks; 
 | 
      // Filter chunks (options.chunks and options.excludeCHunks) 
 | 
      let chunks = self.filterChunks(allChunks, self.options.chunks, self.options.excludeChunks); 
 | 
      // Sort chunks 
 | 
      chunks = self.sortChunks(chunks, self.options.chunksSortMode, compilation); 
 | 
      // Let plugins alter the chunks and the chunk sorting 
 | 
      if (compilation.hooks) { 
 | 
        chunks = compilation.hooks.htmlWebpackPluginAlterChunks.call(chunks, { plugin: self }); 
 | 
      } else { 
 | 
        // Before Webpack 4 
 | 
        chunks = compilation.applyPluginsWaterfall('html-webpack-plugin-alter-chunks', chunks, { plugin: self }); 
 | 
      } 
 | 
      // Get assets 
 | 
      const assets = self.htmlWebpackPluginAssets(compilation, chunks); 
 | 
      // If this is a hot update compilation, move on! 
 | 
      // This solves a problem where an `index.html` file is generated for hot-update js files 
 | 
      // It only happens in Webpack 2, where hot updates are emitted separately before the full bundle 
 | 
      if (self.isHotUpdateCompilation(assets)) { 
 | 
        return callback(); 
 | 
      } 
 | 
  
 | 
      // If the template and the assets did not change we don't have to emit the html 
 | 
      const assetJson = JSON.stringify(self.getAssetFiles(assets)); 
 | 
      if (isCompilationCached && self.options.cache && assetJson === self.assetJson) { 
 | 
        return callback(); 
 | 
      } else { 
 | 
        self.assetJson = assetJson; 
 | 
      } 
 | 
  
 | 
      Promise.resolve() 
 | 
        // Favicon 
 | 
        .then(() => { 
 | 
          if (self.options.favicon) { 
 | 
            return self.addFileToAssets(self.options.favicon, compilation) 
 | 
              .then(faviconBasename => { 
 | 
                let publicPath = compilation.mainTemplate.getPublicPath({hash: compilation.hash}) || ''; 
 | 
                if (publicPath && publicPath.substr(-1) !== '/') { 
 | 
                  publicPath += '/'; 
 | 
                } 
 | 
                assets.favicon = publicPath + faviconBasename; 
 | 
              }); 
 | 
          } 
 | 
        }) 
 | 
        // Wait for the compilation to finish 
 | 
        .then(() => compilationPromise) 
 | 
        .then(compiledTemplate => { 
 | 
          // Allow to use a custom function / string instead 
 | 
          if (self.options.templateContent !== undefined) { 
 | 
            return self.options.templateContent; 
 | 
          } 
 | 
          // Once everything is compiled evaluate the html factory 
 | 
          // and replace it with its content 
 | 
          return self.evaluateCompilationResult(compilation, compiledTemplate); 
 | 
        }) 
 | 
        // Allow plugins to make changes to the assets before invoking the template 
 | 
        // This only makes sense to use if `inject` is `false` 
 | 
        .then(compilationResult => applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-generation', false, { 
 | 
          assets: assets, 
 | 
          outputName: self.childCompilationOutputName, 
 | 
          plugin: self 
 | 
        }) 
 | 
      .then(() => compilationResult)) 
 | 
        // Execute the template 
 | 
        .then(compilationResult => typeof compilationResult !== 'function' 
 | 
        ? compilationResult 
 | 
        : self.executeTemplate(compilationResult, chunks, assets, compilation)) 
 | 
        // Allow plugins to change the html before assets are injected 
 | 
        .then(html => { 
 | 
          const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName}; 
 | 
          return applyPluginsAsyncWaterfall('html-webpack-plugin-before-html-processing', true, pluginArgs); 
 | 
        }) 
 | 
        .then(result => { 
 | 
          const html = result.html; 
 | 
          const assets = result.assets; 
 | 
          // Prepare script and link tags 
 | 
          const assetTags = self.generateHtmlTags(assets); 
 | 
          const pluginArgs = {head: assetTags.head, body: assetTags.body, plugin: self, chunks: chunks, outputName: self.childCompilationOutputName}; 
 | 
          // Allow plugins to change the assetTag definitions 
 | 
          return applyPluginsAsyncWaterfall('html-webpack-plugin-alter-asset-tags', true, pluginArgs) 
 | 
            .then(result => self.postProcessHtml(html, assets, { body: result.body, head: result.head }) 
 | 
            .then(html => _.extend(result, {html: html, assets: assets}))); 
 | 
        }) 
 | 
        // Allow plugins to change the html after assets are injected 
 | 
        .then(result => { 
 | 
          const html = result.html; 
 | 
          const assets = result.assets; 
 | 
          const pluginArgs = {html: html, assets: assets, plugin: self, outputName: self.childCompilationOutputName}; 
 | 
          return applyPluginsAsyncWaterfall('html-webpack-plugin-after-html-processing', true, pluginArgs) 
 | 
            .then(result => result.html); 
 | 
        }) 
 | 
        .catch(err => { 
 | 
          // In case anything went wrong the promise is resolved 
 | 
          // with the error message and an error is logged 
 | 
          compilation.errors.push(prettyError(err, compiler.context).toString()); 
 | 
          // Prevent caching 
 | 
          self.hash = null; 
 | 
          return self.options.showErrors ? prettyError(err, compiler.context).toHtml() : 'ERROR'; 
 | 
        }) 
 | 
        .then(html => { 
 | 
          // Replace the compilation result with the evaluated html code 
 | 
          compilation.assets[self.childCompilationOutputName] = { 
 | 
            source: () => html, 
 | 
            size: () => html.length 
 | 
          }; 
 | 
        }) 
 | 
        .then(() => applyPluginsAsyncWaterfall('html-webpack-plugin-after-emit', false, { 
 | 
          html: compilation.assets[self.childCompilationOutputName], 
 | 
          outputName: self.childCompilationOutputName, 
 | 
          plugin: self 
 | 
        }).catch(err => { 
 | 
          console.error(err); 
 | 
          return null; 
 | 
        }).then(() => null)) 
 | 
        // Let webpack continue with it 
 | 
        .then(() => { 
 | 
          callback(); 
 | 
        }); 
 | 
    }); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Evaluates the child compilation result 
 | 
   * Returns a promise 
 | 
   */ 
 | 
  evaluateCompilationResult (compilation, source) { 
 | 
    if (!source) { 
 | 
      return Promise.reject('The child compilation didn\'t provide a result'); 
 | 
    } 
 | 
  
 | 
    // The LibraryTemplatePlugin stores the template result in a local variable. 
 | 
    // To extract the result during the evaluation this part has to be removed. 
 | 
    source = source.replace('var HTML_WEBPACK_PLUGIN_RESULT =', ''); 
 | 
    const template = this.options.template.replace(/^.+!/, '').replace(/\?.+$/, ''); 
 | 
    const vmContext = vm.createContext(_.extend({HTML_WEBPACK_PLUGIN: true, require: require}, global)); 
 | 
    const vmScript = new vm.Script(source, {filename: template}); 
 | 
    // Evaluate code and cast to string 
 | 
    let newSource; 
 | 
    try { 
 | 
      newSource = vmScript.runInContext(vmContext); 
 | 
    } catch (e) { 
 | 
      return Promise.reject(e); 
 | 
    } 
 | 
    if (typeof newSource === 'object' && newSource.__esModule && newSource.default) { 
 | 
      newSource = newSource.default; 
 | 
    } 
 | 
    return typeof newSource === 'string' || typeof newSource === 'function' 
 | 
      ? Promise.resolve(newSource) 
 | 
      : Promise.reject('The loader "' + this.options.template + '" didn\'t return html.'); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Generate the template parameters for the template function 
 | 
   */ 
 | 
  getTemplateParameters (compilation, assets) { 
 | 
    if (typeof this.options.templateParameters === 'function') { 
 | 
      return this.options.templateParameters(compilation, assets, this.options); 
 | 
    } 
 | 
    if (typeof this.options.templateParameters === 'object') { 
 | 
      return this.options.templateParameters; 
 | 
    } 
 | 
    return {}; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Html post processing 
 | 
   * 
 | 
   * Returns a promise 
 | 
   */ 
 | 
  executeTemplate (templateFunction, chunks, assets, compilation) { 
 | 
    return Promise.resolve() 
 | 
      // Template processing 
 | 
      .then(() => { 
 | 
        const templateParams = this.getTemplateParameters(compilation, assets); 
 | 
        let html = ''; 
 | 
        try { 
 | 
          html = templateFunction(templateParams); 
 | 
        } catch (e) { 
 | 
          compilation.errors.push(new Error('Template execution failed: ' + e)); 
 | 
          return Promise.reject(e); 
 | 
        } 
 | 
        return html; 
 | 
      }); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Html post processing 
 | 
   * 
 | 
   * Returns a promise 
 | 
   */ 
 | 
  postProcessHtml (html, assets, assetTags) { 
 | 
    const self = this; 
 | 
    if (typeof html !== 'string') { 
 | 
      return Promise.reject('Expected html to be a string but got ' + JSON.stringify(html)); 
 | 
    } 
 | 
    return Promise.resolve() 
 | 
      // Inject 
 | 
      .then(() => { 
 | 
        if (self.options.inject) { 
 | 
          return self.injectAssetsIntoHtml(html, assets, assetTags); 
 | 
        } else { 
 | 
          return html; 
 | 
        } 
 | 
      }) 
 | 
      // Minify 
 | 
      .then(html => { 
 | 
        if (self.options.minify) { 
 | 
          const minify = require('html-minifier').minify; 
 | 
          return minify(html, self.options.minify); 
 | 
        } 
 | 
        return html; 
 | 
      }); 
 | 
  } 
 | 
  
 | 
  /* 
 | 
   * Pushes the content of the given filename to the compilation assets 
 | 
   */ 
 | 
  addFileToAssets (filename, compilation) { 
 | 
    filename = path.resolve(compilation.compiler.context, filename); 
 | 
    return Promise.all([ 
 | 
      fsStatAsync(filename), 
 | 
      fsReadFileAsync(filename) 
 | 
    ]) 
 | 
    .then(([size, source]) => { 
 | 
      return { 
 | 
        size, 
 | 
        source 
 | 
      }; 
 | 
    }) 
 | 
    .catch(() => Promise.reject(new Error('HtmlWebpackPlugin: could not load file ' + filename))) 
 | 
    .then(results => { 
 | 
      const basename = path.basename(filename); 
 | 
      if (compilation.fileDependencies.add) { 
 | 
        compilation.fileDependencies.add(filename); 
 | 
      } else { 
 | 
        // Before Webpack 4 - fileDepenencies was an array 
 | 
        compilation.fileDependencies.push(filename); 
 | 
      } 
 | 
      compilation.assets[basename] = { 
 | 
        source: () => results.source, 
 | 
        size: () => results.size.size 
 | 
      }; 
 | 
      return basename; 
 | 
    }); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Helper to sort chunks 
 | 
   */ 
 | 
  sortChunks (chunks, sortMode, compilation) { 
 | 
    // Custom function 
 | 
    if (typeof sortMode === 'function') { 
 | 
      return chunks.sort(sortMode); 
 | 
    } 
 | 
    // Check if the given sort mode is a valid chunkSorter sort mode 
 | 
    if (typeof chunkSorter[sortMode] !== 'undefined') { 
 | 
      return chunkSorter[sortMode](chunks, this.options, compilation); 
 | 
    } 
 | 
    throw new Error('"' + sortMode + '" is not a valid chunk sort mode'); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Return all chunks from the compilation result which match the exclude and include filters 
 | 
   */ 
 | 
  filterChunks (chunks, includedChunks, excludedChunks) { 
 | 
    return chunks.filter(chunk => { 
 | 
      const chunkName = chunk.names[0]; 
 | 
      // This chunk doesn't have a name. This script can't handled it. 
 | 
      if (chunkName === undefined) { 
 | 
        return false; 
 | 
      } 
 | 
      // Skip if the chunk should be lazy loaded 
 | 
      if (typeof chunk.isInitial === 'function') { 
 | 
        if (!chunk.isInitial()) { 
 | 
          return false; 
 | 
        } 
 | 
      } else if (!chunk.initial) { 
 | 
        return false; 
 | 
      } 
 | 
      // Skip if the chunks should be filtered and the given chunk was not added explicity 
 | 
      if (Array.isArray(includedChunks) && includedChunks.indexOf(chunkName) === -1) { 
 | 
        return false; 
 | 
      } 
 | 
      // Skip if the chunks should be filtered and the given chunk was excluded explicity 
 | 
      if (Array.isArray(excludedChunks) && excludedChunks.indexOf(chunkName) !== -1) { 
 | 
        return false; 
 | 
      } 
 | 
      // Add otherwise 
 | 
      return true; 
 | 
    }); 
 | 
  } 
 | 
  
 | 
  isHotUpdateCompilation (assets) { 
 | 
    return assets.js.length && assets.js.every(name => /\.hot-update\.js$/.test(name)); 
 | 
  } 
 | 
  
 | 
  htmlWebpackPluginAssets (compilation, chunks) { 
 | 
    const self = this; 
 | 
    const compilationHash = compilation.hash; 
 | 
  
 | 
    // Use the configured public path or build a relative path 
 | 
    let publicPath = typeof compilation.options.output.publicPath !== 'undefined' 
 | 
      // If a hard coded public path exists use it 
 | 
      ? compilation.mainTemplate.getPublicPath({hash: compilationHash}) 
 | 
      // If no public path was set get a relative url path 
 | 
      : path.relative(path.resolve(compilation.options.output.path, path.dirname(self.childCompilationOutputName)), compilation.options.output.path) 
 | 
        .split(path.sep).join('/'); 
 | 
  
 | 
    if (publicPath.length && publicPath.substr(-1, 1) !== '/') { 
 | 
      publicPath += '/'; 
 | 
    } 
 | 
  
 | 
    const assets = { 
 | 
      // The public path 
 | 
      publicPath: publicPath, 
 | 
      // Will contain all js & css files by chunk 
 | 
      chunks: {}, 
 | 
      // Will contain all js files 
 | 
      js: [], 
 | 
      // Will contain all css files 
 | 
      css: [], 
 | 
      // Will contain the html5 appcache manifest files if it exists 
 | 
      manifest: Object.keys(compilation.assets).filter(assetFile => path.extname(assetFile) === '.appcache')[0] 
 | 
    }; 
 | 
  
 | 
    // Append a hash for cache busting 
 | 
    if (this.options.hash) { 
 | 
      assets.manifest = self.appendHash(assets.manifest, compilationHash); 
 | 
      assets.favicon = self.appendHash(assets.favicon, compilationHash); 
 | 
    } 
 | 
  
 | 
    for (let i = 0; i < chunks.length; i++) { 
 | 
      const chunk = chunks[i]; 
 | 
      const chunkName = chunk.names[0]; 
 | 
  
 | 
      assets.chunks[chunkName] = {}; 
 | 
  
 | 
      // Prepend the public path to all chunk files 
 | 
      let chunkFiles = [].concat(chunk.files).map(chunkFile => publicPath + chunkFile); 
 | 
  
 | 
      // Append a hash for cache busting 
 | 
      if (this.options.hash) { 
 | 
        chunkFiles = chunkFiles.map(chunkFile => self.appendHash(chunkFile, compilationHash)); 
 | 
      } 
 | 
  
 | 
      // Webpack outputs an array for each chunk when using sourcemaps 
 | 
      // or when one chunk hosts js and css simultaneously 
 | 
      const js = chunkFiles.find(chunkFile => /.js($|\?)/.test(chunkFile)); 
 | 
      if (js) { 
 | 
        assets.chunks[chunkName].size = chunk.size; 
 | 
        assets.chunks[chunkName].entry = js; 
 | 
        assets.chunks[chunkName].hash = chunk.hash; 
 | 
        assets.js.push(js); 
 | 
      } 
 | 
  
 | 
      // Gather all css files 
 | 
      const css = chunkFiles.filter(chunkFile => /.css($|\?)/.test(chunkFile)); 
 | 
      assets.chunks[chunkName].css = css; 
 | 
      assets.css = assets.css.concat(css); 
 | 
    } 
 | 
  
 | 
    // Duplicate css assets can occur on occasion if more than one chunk 
 | 
    // requires the same css. 
 | 
    assets.css = _.uniq(assets.css); 
 | 
  
 | 
    return assets; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Generate meta tags 
 | 
   */ 
 | 
  getMetaTags () { 
 | 
    if (this.options.meta === false) { 
 | 
      return []; 
 | 
    } 
 | 
    // Make tags self-closing in case of xhtml 
 | 
    // Turn { "viewport" : "width=500, initial-scale=1" } into 
 | 
    // [{ name:"viewport" content:"width=500, initial-scale=1" }] 
 | 
    const selfClosingTag = !!this.options.xhtml; 
 | 
    const metaTagAttributeObjects = Object.keys(this.options.meta).map((metaName) => { 
 | 
      const metaTagContent = this.options.meta[metaName]; 
 | 
      return (typeof metaTagContent === 'object') ? metaTagContent : { 
 | 
        name: metaName, 
 | 
        content: metaTagContent 
 | 
      }; 
 | 
    }); 
 | 
    // Turn [{ name:"viewport" content:"width=500, initial-scale=1" }] into 
 | 
    // the html-webpack-plugin tag structure 
 | 
    return metaTagAttributeObjects.map((metaTagAttributes) => { 
 | 
      return { 
 | 
        tagName: 'meta', 
 | 
        voidTag: true, 
 | 
        selfClosingTag: selfClosingTag, 
 | 
        attributes: metaTagAttributes 
 | 
      }; 
 | 
    }); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Injects the assets into the given html string 
 | 
   */ 
 | 
  generateHtmlTags (assets) { 
 | 
    // Turn script files into script tags 
 | 
    const scripts = assets.js.map(scriptPath => ({ 
 | 
      tagName: 'script', 
 | 
      closeTag: true, 
 | 
      attributes: { 
 | 
        type: 'text/javascript', 
 | 
        src: scriptPath 
 | 
      } 
 | 
    })); 
 | 
    // Make tags self-closing in case of xhtml 
 | 
    const selfClosingTag = !!this.options.xhtml; 
 | 
    // Turn css files into link tags 
 | 
    const styles = assets.css.map(stylePath => ({ 
 | 
      tagName: 'link', 
 | 
      selfClosingTag: selfClosingTag, 
 | 
      voidTag: true, 
 | 
      attributes: { 
 | 
        href: stylePath, 
 | 
        rel: 'stylesheet' 
 | 
      } 
 | 
    })); 
 | 
    // Injection targets 
 | 
    let head = this.getMetaTags(); 
 | 
    let body = []; 
 | 
  
 | 
    // If there is a favicon present, add it to the head 
 | 
    if (assets.favicon) { 
 | 
      head.push({ 
 | 
        tagName: 'link', 
 | 
        selfClosingTag: selfClosingTag, 
 | 
        voidTag: true, 
 | 
        attributes: { 
 | 
          rel: 'shortcut icon', 
 | 
          href: assets.favicon 
 | 
        } 
 | 
      }); 
 | 
    } 
 | 
    // Add styles to the head 
 | 
    head = head.concat(styles); 
 | 
    // Add scripts to body or head 
 | 
    if (this.options.inject === 'head') { 
 | 
      head = head.concat(scripts); 
 | 
    } else { 
 | 
      body = body.concat(scripts); 
 | 
    } 
 | 
    return {head: head, body: body}; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Injects the assets into the given html string 
 | 
   */ 
 | 
  injectAssetsIntoHtml (html, assets, assetTags) { 
 | 
    const htmlRegExp = /(<html[^>]*>)/i; 
 | 
    const headRegExp = /(<\/head\s*>)/i; 
 | 
    const bodyRegExp = /(<\/body\s*>)/i; 
 | 
    const body = assetTags.body.map(this.createHtmlTag.bind(this)); 
 | 
    const head = assetTags.head.map(this.createHtmlTag.bind(this)); 
 | 
  
 | 
    if (body.length) { 
 | 
      if (bodyRegExp.test(html)) { 
 | 
        // Append assets to body element 
 | 
        html = html.replace(bodyRegExp, match => body.join('') + match); 
 | 
      } else { 
 | 
        // Append scripts to the end of the file if no <body> element exists: 
 | 
        html += body.join(''); 
 | 
      } 
 | 
    } 
 | 
  
 | 
    if (head.length) { 
 | 
      // Create a head tag if none exists 
 | 
      if (!headRegExp.test(html)) { 
 | 
        if (!htmlRegExp.test(html)) { 
 | 
          html = '<head></head>' + html; 
 | 
        } else { 
 | 
          html = html.replace(htmlRegExp, match => match + '<head></head>'); 
 | 
        } 
 | 
      } 
 | 
  
 | 
      // Append assets to head element 
 | 
      html = html.replace(headRegExp, match => head.join('') + match); 
 | 
    } 
 | 
  
 | 
    // Inject manifest into the opening html tag 
 | 
    if (assets.manifest) { 
 | 
      html = html.replace(/(<html[^>]*)(>)/i, (match, start, end) => { 
 | 
        // Append the manifest only if no manifest was specified 
 | 
        if (/\smanifest\s*=/.test(match)) { 
 | 
          return match; 
 | 
        } 
 | 
        return start + ' manifest="' + assets.manifest + '"' + end; 
 | 
      }); 
 | 
    } 
 | 
    return html; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Appends a cache busting hash 
 | 
   */ 
 | 
  appendHash (url, hash) { 
 | 
    if (!url) { 
 | 
      return url; 
 | 
    } 
 | 
    return url + (url.indexOf('?') === -1 ? '?' : '&') + hash; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Turn a tag definition into a html string 
 | 
   */ 
 | 
  createHtmlTag (tagDefinition) { 
 | 
    const attributes = Object.keys(tagDefinition.attributes || {}) 
 | 
      .filter(attributeName => tagDefinition.attributes[attributeName] !== false) 
 | 
      .map(attributeName => { 
 | 
        if (tagDefinition.attributes[attributeName] === true) { 
 | 
          return attributeName; 
 | 
        } 
 | 
        return attributeName + '="' + tagDefinition.attributes[attributeName] + '"'; 
 | 
      }); 
 | 
    // Backport of 3.x void tag definition 
 | 
    const voidTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag : !tagDefinition.closeTag; 
 | 
    const selfClosingTag = tagDefinition.voidTag !== undefined ? tagDefinition.voidTag && this.options.xhtml : tagDefinition.selfClosingTag; 
 | 
    return '<' + [tagDefinition.tagName].concat(attributes).join(' ') + (selfClosingTag ? '/' : '') + '>' + 
 | 
      (tagDefinition.innerHTML || '') + 
 | 
      (voidTag ? '' : '</' + tagDefinition.tagName + '>'); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Helper to return the absolute template path with a fallback loader 
 | 
   */ 
 | 
  getFullTemplatePath (template, context) { 
 | 
    // If the template doesn't use a loader use the lodash template loader 
 | 
    if (template.indexOf('!') === -1) { 
 | 
      template = require.resolve('./lib/loader.js') + '!' + path.resolve(context, template); 
 | 
    } 
 | 
    // Resolve template path 
 | 
    return template.replace( 
 | 
      /([!])([^/\\][^!?]+|[^/\\!?])($|\?[^!?\n]+$)/, 
 | 
      (match, prefix, filepath, postfix) => prefix + path.resolve(filepath) + postfix); 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Helper to return a sorted unique array of all asset files out of the 
 | 
   * asset object 
 | 
   */ 
 | 
  getAssetFiles (assets) { 
 | 
    const files = _.uniq(Object.keys(assets).filter(assetType => assetType !== 'chunks' && assets[assetType]).reduce((files, assetType) => files.concat(assets[assetType]), [])); 
 | 
    files.sort(); 
 | 
    return files; 
 | 
  } 
 | 
  
 | 
  /** 
 | 
   * Helper to promisify compilation.applyPluginsAsyncWaterfall that returns 
 | 
   * a function that helps to merge given plugin arguments with processed ones 
 | 
   */ 
 | 
  applyPluginsAsyncWaterfall (compilation) { 
 | 
    if (compilation.hooks) { 
 | 
      return (eventName, requiresResult, pluginArgs) => { 
 | 
        const ccEventName = trainCaseToCamelCase(eventName); 
 | 
        if (!compilation.hooks[ccEventName]) { 
 | 
          compilation.errors.push( 
 | 
            new Error('No hook found for ' + eventName) 
 | 
          ); 
 | 
        } 
 | 
  
 | 
        return compilation.hooks[ccEventName].promise(pluginArgs); 
 | 
      }; 
 | 
    } 
 | 
  
 | 
    // Before Webpack 4 
 | 
    const promisedApplyPluginsAsyncWaterfall = function (name, init) { 
 | 
      return new Promise((resolve, reject) => { 
 | 
        const callback = function (err, result) { 
 | 
          if (err) { 
 | 
            return reject(err); 
 | 
          } 
 | 
          resolve(result); 
 | 
        }; 
 | 
        compilation.applyPluginsAsyncWaterfall(name, init, callback); 
 | 
      }); 
 | 
    }; 
 | 
  
 | 
    return (eventName, requiresResult, pluginArgs) => promisedApplyPluginsAsyncWaterfall(eventName, pluginArgs) 
 | 
      .then(result => { 
 | 
        if (requiresResult && !result) { 
 | 
          compilation.warnings.push( 
 | 
            new Error('Using ' + eventName + ' without returning a result is deprecated.') 
 | 
          ); 
 | 
        } 
 | 
        return _.extend(pluginArgs, result); 
 | 
      }); 
 | 
  } 
 | 
} 
 | 
  
 | 
/** 
 | 
 * Takes a string in train case and transforms it to camel case 
 | 
 * 
 | 
 * Example: 'hello-my-world' to 'helloMyWorld' 
 | 
 * 
 | 
 * @param {string} word 
 | 
 */ 
 | 
function trainCaseToCamelCase (word) { 
 | 
  return word.replace(/-([\w])/g, (match, p1) => p1.toUpperCase()); 
 | 
} 
 | 
  
 | 
/** 
 | 
 * The default for options.templateParameter 
 | 
 * Generate the template parameters 
 | 
 */ 
 | 
function templateParametersGenerator (compilation, assets, options) { 
 | 
  return { 
 | 
    compilation: compilation, 
 | 
    webpack: compilation.getStats().toJson(), 
 | 
    webpackConfig: compilation.options, 
 | 
    htmlWebpackPlugin: { 
 | 
      files: assets, 
 | 
      options: options 
 | 
    } 
 | 
  }; 
 | 
} 
 | 
  
 | 
module.exports = HtmlWebpackPlugin; 
 |