romanlex/app-manifest-webpack-plugin

View on GitHub
lib/compiler.js

Summary

Maintainability
A
1 hr
Test Coverage
const path = require('path')
const SingleEntryPlugin = require('webpack/lib/SingleEntryPlugin')

/**
 * Return path for compiler
 */
const getCompilerPath = (filename, outputPath) => {
  const absolutePath = path.resolve(outputPath, filename)
  const relativePath = path.relative(outputPath, filename)

  return filename.includes('/') ? relativePath : absolutePath
}

/**
 * Returns the child compiler name e.g. 'html-webpack-plugin for "index.html"'
 */
const getCompilerName = compilerPath => `app-manifest-webpack-plugin for "${compilerPath}"`

function getEntry(options, context) {
  return new SingleEntryPlugin(
    context,
    '!!' +
      require.resolve('./favicons.js') +
      '?' +
      JSON.stringify({
        outputFilePrefix: options.prefix,
        outputFilesDir: options.output,
        persistentCache: options.persistentCache,
        statsEncodeHtml: options.statsEncodeHtml,
        config: options.config,
      }) +
      '!' +
      options.logo,
  )
}

const compileProcessHandler = (compilation, chunks, callback) => {
  if (!chunks[0]) {
    return callback(compilation.errors[0] || 'Favicons and manifest generation failed')
  }
  var resultFile = chunks[0].files[0]
  var resultCode = compilation.assets[resultFile].source()
  var resultJson
  try {
    /* eslint no-eval:0 */
    var result = eval(resultCode)
    resultJson = JSON.stringify(result)
  } catch (e) {
    return callback(e)
  }
  compilation.assets[resultFile] = {
    source: function() {
      return resultJson
    },
    size: function() {
      return resultJson.length
    },
  }
  callback(null)
}

const compileProcess = (compilerName, compilation) => {
  if (compilation.cache) {
    if (!compilation.cache[compilerName]) {
      compilation.cache[compilerName] = {}
    }
    compilation.cache = compilation.cache[compilerName]
  }

  if (compilation.hooks) {
    compilation.hooks.optimizeChunkAssets.tapAsync(
      'AppManifestWebpackPluginOptimizeChunkAssets',
      (chunks, callback) => compileProcessHandler(compilation, chunks, callback),
    )
  } else {
    compilation.plugin('optimize-chunk-assets', (chunks, callback) =>
      compileProcessHandler(compilation, chunks, callback),
    )
  }
}

module.exports.compileTemplate = function compileTemplate(options, context, compilation) {
  const compilerPath = getCompilerPath(options.statsFilename, compilation.outputOptions.path)

  // The entry file is just an empty helper as the dynamic template
  // require is added in "loader.js"
  const outputOptions = {
    filename: options.statsFilename.includes('/') ? compilerPath : options.statsFilename,
    publicPath: compilation.outputOptions.publicPath,
  }

  // Create an additional child compiler which takes the template
  // and turns it into an Node.JS html factory.
  // This allows us to use loaders during the compilation
  const compilerName = getCompilerName(compilerPath)
  const childCompiler = compilation.createChildCompiler(compilerName, outputOptions)
  childCompiler.context = context

  if (compilation.hooks) {
    getEntry(options, context).apply(childCompiler)
  } else {
    childCompiler.apply(getEntry(options, context))
  }

  // Fix for "Uncaught TypeError: __webpack_require__(...) is not a function"
  // Hot module replacement requires that every child compiler has its own
  // cache. @see https://github.com/ampedandwired/html-webpack-plugin/pull/179
  if (compilation.hooks) {
    childCompiler.hooks.compilation.tap('AppManifestWebpackPluginCompilation', compilation =>
      compileProcess(compilerName, compilation),
    )
  } else {
    childCompiler.plugin('compilation', compilation => compileProcess(compilerName, compilation))
  }

  // Compile and return a promise
  return new Promise(function(resolve, reject) {
    childCompiler.runAsChild(function(err, entries, childCompilation) {
      if (err) {
        return reject(err)
      }

      const outputName = compilation.mainTemplate.getAssetPath
        ? compilation.mainTemplate.hooks.assetPath.call(outputOptions.filename, {
            hash: childCompilation.hash,
            chunk: entries[0],
          })
        : compilation.mainTemplate.applyPluginsWaterfall('asset-path', outputOptions.filename, {
            hash: childCompilation.hash,
            chunk: entries[0],
          })

      // Resolve / reject the promise
      if (childCompilation && childCompilation.errors && childCompilation.errors.length) {
        const errorDetails = childCompilation.errors
          .map(function(error) {
            return error.message + (error.error ? ':\n' + error.error : '')
          })
          .join('\n')
        reject(new Error('Child compilation failed:\n' + errorDetails))
      } else if (err) {
        reject(err)
      } else {
        resolve({
          outputName: outputName,
          stats: JSON.parse(childCompilation.assets[outputName].source()),
        })
      }
    })
  })
}