diff --git a/src/Plugins/IdAttributePlugin.js b/src/Plugins/IdAttributePlugin.js index 63f1bf600..a55a13e21 100644 --- a/src/Plugins/IdAttributePlugin.js +++ b/src/Plugins/IdAttributePlugin.js @@ -4,6 +4,8 @@ import { decodeHTML } from "entities"; import slugifyFilter from "../Filters/Slugify.js"; import MemoizeUtil from "../Util/MemoizeFunction.js"; +const POSTHTML_PLUGIN_NAME = "11ty/eleventy/id-attribute"; + function getTextNodeContent(node) { if (node.attrs?.["eleventy:id-ignore"] === "") { delete node.attrs["eleventy:id-ignore"]; @@ -38,7 +40,7 @@ function IdAttributePlugin(eleventyConfig, options = {}) { eleventyConfig.htmlTransformer.addPosthtmlPlugin( "html", - function (pluginOptions = {}) { + function idAttributePosthtmlPlugin(pluginOptions = {}) { if (typeof options.filter === "function") { if (options.filter(pluginOptions) === false) { return function () {}; @@ -97,7 +99,11 @@ function IdAttributePlugin(eleventyConfig, options = {}) { return node; }); }; - } /* , {} // pluginOptions */, + }, + { + // pluginOptions + name: POSTHTML_PLUGIN_NAME, + }, ); } diff --git a/src/Util/ArrayUtil.js b/src/Util/ArrayUtil.js new file mode 100644 index 000000000..bcb61deef --- /dev/null +++ b/src/Util/ArrayUtil.js @@ -0,0 +1,24 @@ +export function arrayDelete(arr, match) { + if (!Array.isArray(arr)) { + return []; + } + + if (!match) { + return arr; + } + + // only mutates if found + if (typeof match === "function") { + if (arr.find(match)) { + return arr.filter((entry) => { + return !match(entry); + }); + } + } else if (arr.includes(match)) { + return arr.filter((entry) => { + return entry !== match; + }); + } + + return arr; +} diff --git a/src/Util/HtmlTransformer.js b/src/Util/HtmlTransformer.js index 1f9515a65..f28910fc6 100644 --- a/src/Util/HtmlTransformer.js +++ b/src/Util/HtmlTransformer.js @@ -2,10 +2,14 @@ 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 = {}; @@ -78,6 +82,8 @@ class HtmlTransformer { } 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), @@ -92,6 +98,15 @@ class HtmlTransformer { 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); } diff --git a/src/defaultConfig.js b/src/defaultConfig.js index eaf22b26a..fb1ab5dd2 100644 --- a/src/defaultConfig.js +++ b/src/defaultConfig.js @@ -70,6 +70,7 @@ export default function (config) { return this.existsCache.exists(filePath); }; + // Remember: the transform added here runs before the `htmlTransformer` transform config.addPlugin(bundlePlugin, { bundles: false, // no default bundles included—must be opt-in. immediate: true, @@ -128,6 +129,7 @@ export default function (config) { // Run the `htmlTransformer` transform config.addTransform("@11ty/eleventy/html-transformer", async function (content) { + // Runs **AFTER** the bundle plugin transform (except: delayed bundles) return ut.transformContent(this.outputPath, content, this); }); diff --git a/test/ArrayUtilTest.js b/test/ArrayUtilTest.js new file mode 100644 index 000000000..c72174a91 --- /dev/null +++ b/test/ArrayUtilTest.js @@ -0,0 +1,53 @@ +import test from "ava"; +import {arrayDelete} from "../src/Util/ArrayUtil.js"; + +test("ArrayUtil.arrayDelete empties", async (t) => { + t.deepEqual(arrayDelete(), []); + t.deepEqual(arrayDelete(undefined, 1), []); + + t.deepEqual(arrayDelete(null), []); + t.deepEqual(arrayDelete(1), []); + t.deepEqual(arrayDelete(true), []); + t.deepEqual(arrayDelete(false), []); +}); + +test("ArrayUtil.arrayDelete if array does not have value, it does not mutate", async (t) => { + let empty = []; + t.is(arrayDelete(empty), empty); + t.is(arrayDelete(empty, 1), empty); + t.is(arrayDelete(empty, true), empty); + t.is(arrayDelete(empty, undefined), empty); +}); + +test("ArrayUtil.arrayDelete if array does not have function matched value, it does not mutate", async (t) => { + let empty = []; + t.is(arrayDelete(empty, () => false), empty); +}); + + +test("ArrayUtil.arrayDelete mutates when array contains match", async (t) => { + let a = [1, 2]; + t.not(arrayDelete(a, 1), [2]); + t.deepEqual(arrayDelete(a, 1), [2]); +}); + +test("ArrayUtil.arrayDelete mutates when array contains function matched value", async (t) => { + let a = [1, 2]; + t.not(arrayDelete(a, entry => entry === 1), [2]); + t.deepEqual(arrayDelete(a, entry => entry === 1), [2]); +}); + +test("ArrayUtil.arrayDelete complex delete", async (t) => { + let a = [1,2,3,4,5,6,7,8]; + t.deepEqual(arrayDelete(a, 4), [1,2,3,5,6,7,8]); +}); + +test("ArrayUtil.arrayDelete function matched delete", async (t) => { + let a = [1,2,3,4,5,6,7,8]; + t.deepEqual(arrayDelete(a, entry => entry === 4), [1,2,3,5,6,7,8]); +}); + +test("ArrayUtil.arrayDelete double delete", async (t) => { + let a = [1,2,3,4,5,6,7,8]; + t.deepEqual(arrayDelete(arrayDelete(a, 4), 6), [1,2,3,5,7,8]); +});