Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
"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));
};
@MagicDuck

This comment has been minimized.

Copy link
Owner Author

@MagicDuck MagicDuck commented Jan 30, 2017

use like so:

    new HtmlPlugin({
        outputFile: 'app.html',
        templateFile: path.resolve(..., 'app-template.mustache'),
        templateVars: {
            myMustacheVar: "blah blah",
        }
        assetMatchers: {
            favicon: /^img\/favicon\.[^.]+\.ico$/,
            appEntryPoint: /^app\.main\..*js$/,
            appEntryPointCss: /^app\.main\..*css$/
        }
    })),

app-template.mustache

    {{#assets.favicon}}
    <link rel="shortcut icon" href="{{{assets.favicon}}}">
    {{/assets.favicon}}
    ...

    {{#myMustacheVar}}
    ...
    {{/myMustacheVar}}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.