packages/docpack-page-generator/lib/plugin.js (179 lines of code) (raw):
var path = require('path');
var Docpack = require('docpack');
var merge = require('merge-options');
var tools = require('webpack-toolkit');
var Page = require('docpack/lib/data/Page');
var slug = require('url-slug');
var isPlainObject = require('is-plain-object');
var defaultConfig = {
match: null,
select: null,
template: null,
url: '[path][name].[ext].html',
context: {},
recompileOnTemplateChange: false,
loader: null
};
var CONST = {
TEMPLATE_ASSET_NAME: 'docpack-page-generator-template',
FALLBACK_LOADER_NAME: 'twig-loader',
URL_ATTR_NAME: 'url'
};
/**
* @constructor
*/
var PageGeneratorPlugin = Docpack.createPlugin({
name: 'docpack-page-generator',
defaultConfig: defaultConfig,
init: function() {
if (!this.config.template) {
throw new Error('`template` is required');
}
}
});
module.exports = PageGeneratorPlugin;
module.exports.defaultConfig = defaultConfig;
module.exports.CONST = CONST;
/**
* @static
* @param {String} template
* @returns {String}
*/
PageGeneratorPlugin.getCompilerNameFor = function(template) {
return 'docpack-page-generator__' + slug(template);
};
/**
* @type {Function}
*/
PageGeneratorPlugin.prototype.renderer = null;
/**
* @param {Compiler} compiler
*/
PageGeneratorPlugin.prototype.configure = function(compiler) {
var config = this.config;
var tpl = config.template;
var tplExt = path.extname(tpl);
var tplAbsPath = path.resolve(tpl);
var resolveExtensions = compiler.options.resolve.extensions;
var moduleOptions = compiler.options.module;
var hasLoadersToProcessTemplate = resolveExtensions.indexOf(tplExt) >= 0 || tools.getMatchedLoaders(moduleOptions, tplAbsPath).length > 0;
var loadersProp = tools.getWebpackVersion(true) >= '2' ? 'rules' : 'loaders';
if (!Array.isArray(moduleOptions[loadersProp])) {
moduleOptions[loadersProp] = [];
}
// If loader specified explicitly, add it to config
if (config.loader) {
moduleOptions[loadersProp].push(config.loader);
}
else if (!hasLoadersToProcessTemplate) {
// If no loaders to process the template - add fallback loader to config
moduleOptions[loadersProp].push({
test: new RegExp('\\.' + tplExt.substr(1) + '$'),
loader: require.resolve(CONST.FALLBACK_LOADER_NAME),
include: path.dirname(tplAbsPath)
});
}
};
PageGeneratorPlugin.prototype.apply = function(compiler) {
var plugin = this;
var template = this.config.template;
var compilerName = PageGeneratorPlugin.getCompilerNameFor(template);
var assetFilename = CONST.TEMPLATE_ASSET_NAME;
compiler.plugin(Docpack.HOOKS.INIT, this.configure.bind(this));
compiler.plugin('compilation', function(compilation) {
compilation.plugin(Docpack.HOOKS.BEFORE_EXTRACT, function(sources, done) {
if (plugin.renderer) {
done(null, sources);
return;
}
tools.TemplateCompiler(compilation, {
template: template,
name: compilerName,
output: {
filename: assetFilename
}
})
.run()
.then(function (fn) {
plugin.renderer = fn;
done(null, sources);
});
});
compilation.plugin(Docpack.HOOKS.BEFORE_GENERATE, function (sources, done) {
plugin.generate(compilation, sources);
done(null, sources);
});
compilation.plugin(Docpack.HOOKS.GENERATE, function (sources, done) {
// TODO: refactor this ugly caching
plugin._assetsByChunkName = tools.getAssetsByChunkName(compilation);
plugin.generatePagesContent(compilation, sources);
done(null, sources);
});
});
};
/**
* @param {Array<Source>} sources
* @returns {Array<Source>}
*/
PageGeneratorPlugin.prototype.select = function(sources) {
var config = this.config;
var targets = sources;
if (config.match) {
targets = sources.filter(function (source) {
return tools.matcher(config.match, source.absolutePath);
});
}
if (typeof config.select == 'function') {
targets = config.select(sources);
if (!Array.isArray(targets)) {
throw new Error('`select` should return an array');
}
}
return targets;
};
/**
* @param {Source} target
* @param {String} compilationContext Compilation context
* @returns {String}
*/
PageGeneratorPlugin.prototype.generateURL = function(target, compilationContext) {
var config = this.config;
var url;
var typeofURL = typeof config.url;
if ('attrs' in target && CONST.URL_ATTR_NAME in target.attrs) {
url = target.attrs[CONST.URL_ATTR_NAME];
}
else if (typeofURL == 'string') {
url = config.url;
} else if (typeofURL == 'function') {
url = config.url(target);
if (typeof url != 'string') {
throw new Error('`url` function should return a string');
}
} else {
throw new Error('`url` option can be string or function');
}
return tools.interpolateName(url, {
path: target.absolutePath,
context: compilationContext,
content: target.content
});
};
/**
* @param {Compilation} compilation
* @param {Source} target
* @param {Array<Source>} targets
* @returns {String}
*/
PageGeneratorPlugin.prototype.render = function(compilation, target, targets) {
var config = this.config;
var defaultContext = {
source: target,
sources: targets,
publicPath: compilation.outputOptions.publicPath,
assetsByChunkName: this._assetsByChunkName
};
var context;
if (typeof config.context == 'function') {
context = merge(defaultContext, config.context(targets, target));
if (!isPlainObject(context)) {
throw new Error('`context` function should return an object');
}
} else {
context = merge(defaultContext, config.context);
}
return this.renderer(context);
};
/**
* @param {Compilation} compilation
* @param {Array<Source>} sources
*/
PageGeneratorPlugin.prototype.generate = function(compilation, sources) {
var plugin = this;
var targets = this.select(sources);
targets.forEach(function(source) {
var url = plugin.generateURL(source, compilation.compiler.context);
source.page = new Page({url: url});
});
};
PageGeneratorPlugin.prototype.generatePagesContent = function(compilation, sources) {
var plugin = this;
var targets = this.select(sources).filter(function (source) {
return 'page' in source;
});
targets
.forEach(function(source) {
var page = source.page;
if (page.url in compilation.assets) {
var msg = page.url + ' page already exist in assets. Check `url` option (and maybe make it more specific)';
compilation.errors.push(new Error(msg));
return;
}
page.content = plugin.render(compilation, source, targets);
tools.emitAsset(compilation, page.url, page.content);
});
};