import posthtml from "posthtml"; import urls from "@11ty/posthtml-urls"; import { FilePathUtil } from "./FilePathUtil.js"; import { arrayDelete } from "./ArrayUtil.js"; class HtmlTransformer { // feature test for Eleventy Bundle Plugin static SUPPORTS_PLUGINS_ENABLED_CALLBACK = true; static TYPES = ["callbacks", "plugins"]; constructor() { // execution order is important (not order of addition/object key order) this.callbacks = {}; this.posthtmlProcessOptions = {}; this.plugins = {}; } get aggregateBench() { if (!this.userConfig) { throw new Error("Internal error: Missing `userConfig` in HtmlTransformer."); } return this.userConfig.benchmarkManager.get("Aggregate"); } setUserConfig(config) { this.userConfig = config; } static prioritySort(a, b) { if (b.priority > a.priority) { return 1; } if (a.priority > b.priority) { return -1; } return 0; } // context is important as it is used in html base plugin for page specific URL static _getPosthtmlInstance(callbacks = [], plugins = [], context = {}) { let inst = posthtml(); // already sorted by priority when added for (let { fn: plugin, options } of plugins) { inst.use(plugin(Object.assign({}, context, options))); } // Run the built-ins last if (callbacks.length > 0) { inst.use( urls({ eachURL: (url, attrName, tagName) => { for (let { fn: callback } of callbacks) { // already sorted by priority when added url = callback.call(context, url, { attribute: attrName, tag: tagName }); } return url; }, }), ); } return inst; } _add(extensions, addType, value, options = {}) { options = Object.assign( { priority: 0, }, options, ); let extensionsArray = (extensions || "").split(","); for (let ext of extensionsArray) { let target = this[addType]; if (!target[ext]) { target[ext] = []; } target[ext].push({ // *could* fallback to function name, `value.name` name: options.name, // for `remove` and debugging fn: value, // callback or plugin priority: options.priority, // sorted in descending order enabled: options.enabled || (() => true), options: options.pluginOptions, }); target[ext].sort(HtmlTransformer.prioritySort); } } addPosthtmlPlugin(extensions, plugin, options = {}) { this._add(extensions, "plugins", plugin, options); } // match can be a plugin function or a filter callback(plugin => true); remove(extensions, match) { for (let removeType of HtmlTransformer.TYPES) { for (let ext of (extensions || "").split(",")) { this[removeType][ext] = arrayDelete(this[removeType][ext], match); } } } addUrlTransform(extensions, callback, options = {}) { this._add(extensions, "callbacks", callback, options); } setPosthtmlProcessOptions(options) { Object.assign(this.posthtmlProcessOptions, options); } isTransformable(extension, context) { return ( this.getCallbacks(extension, context).length > 0 || this.getPlugins(extension).length > 0 ); } getCallbacks(extension, context) { let callbacks = this.callbacks[extension] || []; return callbacks.filter(({ enabled }) => { if (!enabled || typeof enabled !== "function") { return true; } return enabled(context); }); } getPlugins(extension) { let plugins = this.plugins[extension] || []; return plugins.filter(({ enabled }) => { if (!enabled || typeof enabled !== "function") { return true; } return enabled(); }); } static async transformStandalone(content, callback, posthtmlProcessOptions = {}) { let posthtmlInstance = this._getPosthtmlInstance([ { fn: callback, enabled: () => true, }, ]); let result = await posthtmlInstance.process(content, posthtmlProcessOptions); return result.html; } async transformContent(outputPath, content, context) { let extension = FilePathUtil.getFileExtension(outputPath); if (!this.isTransformable(extension, context)) { return content; } let bench = this.aggregateBench.get(`Transforming \`${extension}\` with posthtml`); bench.before(); let callbacks = this.getCallbacks(extension, context); let plugins = this.getPlugins(extension); let posthtmlInstance = HtmlTransformer._getPosthtmlInstance(callbacks, plugins, context); let result = await posthtmlInstance.process(content, this.posthtmlProcessOptions); bench.after(); return result.html; } } export { HtmlTransformer };