"use strict"; const Mustache = require("mustache"); const path = require("path"); /** * This plugin is used to generate an html file from a mustache template. * @param {object} options * - enabled {boolean} whether plugin is enabled * - outputFile {string} the relative path to the html file result * - templateFile {string} the absolute path to the html file result * - templateVars: {object} mustache view * - assetMatchers {object map str -> Regex} an object where each key K points to a regular expression that * is used to match asset files output by webpack during the build. We then set: * templateVars.assets[K] = <matched urls, made relative to the html file> {undefined | string | string[]} */ var HtmlPlugin = module.exports = function(options) { this.options = Object.assign({ enabled: true, templateVars: {}, assetMatchers: {} }, options); if (typeof this.options.outputFile !== "string") { throw new Error("HtmlPlugin: options.outputFile of type <string> is required!"); } this.options.outputFile = this.options.outputFile.replace(/\\/g, '/'); if (typeof this.options.templateFile !== "string") { throw new Error("HtmlPlugin: options.templateFile of type <string> is required!"); } }; HtmlPlugin.prototype.apply = function(compiler) { var options = this.options; if (options.enabled === false) { return; } compiler.plugin('emit', function(compilation, callback) { compilation.fileDependencies.push(options.templateFile); // note: those get deduped internally Promise.resolve().then(function() { return new Promise(function(resolve, reject) { compiler.inputFileSystem.stat(options.templateFile, function(err, statInfo) { // Check template file exists if(err) { return reject(err); } if (this._old_template_mtime && (statInfo.mtime === this._old_template_mtime)) { // use cached version return resolve(this._outputFileAsset); } compiler.inputFileSystem.readFile(options.templateFile, function(err, templateContent) { if(err) { return reject(err); } // TODO: generate relative paths var assetUrls = Object.keys(compilation.assets); var assets = Object.keys(options.assetMatchers).reduce((assetsMap, assetKey) => { var assetMatcher = options.assetMatchers[assetKey]; var matchedUrls = assetUrls.filter(url => assetMatcher.test(url)) .map(url => path.relative(path.dirname(options.outputFile), url).replace(/\\/g, '/')); assetsMap[assetKey] = matchedUrls.length > 1 ? matchedUrls : matchedUrls[0]; return assetsMap; }, {}); var outputContent; try { outputContent = Mustache.render(templateContent.toString(), Object.assign({}, options.templateVars, { assets: assets })); } catch(e) { e.message = "Cannot render mustache template: " + e.message; return reject(e); } this._old_template_mtime = statInfo.mtime; return resolve({ _content: outputContent, source: function() { return this._content; }, size: function() { return this._content.length; } }); }.bind(this)); }.bind(this)); }.bind(this)); }.bind(this)).then(function(assetSource) { this._outputFileAsset = assetSource; compilation.assets[options.outputFile] = assetSource; callback(); }.bind(this)).catch(function(err) { callback(err); }); }.bind(this)); };