From f19ef55a796105f715cd20f2d9703ba2e5078df9 Mon Sep 17 00:00:00 2001 From: Zach Leatherman Date: Thu, 11 Jul 2024 17:23:04 -0500 Subject: [PATCH] Fixes #188 #3345 --- src/Template.js | 71 ++++++++++++++++++++++++++++++++++++++++++- src/TemplateConfig.js | 2 ++ src/UserConfig.js | 17 +++++++++++ test/EleventyTest.js | 20 ++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) diff --git a/src/Template.js b/src/Template.js index 56836384e..c5ad287a7 100755 --- a/src/Template.js +++ b/src/Template.js @@ -637,8 +637,77 @@ class Template extends TemplateContent { }); } + static async runPreprocessors(inputPath, content, data, preprocessors) { + let skippedVia = false; + for (let [name, preprocessor] of Object.entries(preprocessors)) { + let { filter, callback } = preprocessor; + + let filters; + if (Array.isArray(filter)) { + filters = filter; + } else if (typeof filter === "string") { + filters = filter.split(","); + } else { + throw new Error( + `Expected file extensions passed to "${name}" content preprocessor to be a string or array. Received: ${filter}`, + ); + } + + if (!filters.some((extension) => extension === "*" || inputPath.endsWith(extension))) { + // skip + continue; + } + + try { + let ret = await callback.call( + { + inputPath, + }, + data, + content, + ); + + // Returning explicit false is the same as ignoring the template + if (ret === false) { + skippedVia = name; + continue; + } + + // Different from transforms: returning falsy (not false) here does nothing (skips the preprocessor) + if (ret) { + content = ret; + } + } catch (e) { + throw new EleventyBaseError( + `Preprocessor \`${name}\` encountered an error when transforming ${inputPath}.`, + e, + ); + } + } + + return { + skippedVia, + content, + }; + } + async getTemplates(data) { - let rawInput = await this.getPreRender(); + let content = await this.getPreRender(); + let { skippedVia, content: rawInput } = await Template.runPreprocessors( + this.inputPath, + content, + data, + this.config.preprocessors, + ); + + if (skippedVia) { + debug( + "Skipping %o, the %o preprocessor returned an explicit `false`", + this.inputPath, + skippedVia, + ); + return []; + } // https://github.com/11ty/eleventy/issues/1206 data.page.rawInput = rawInput; diff --git a/src/TemplateConfig.js b/src/TemplateConfig.js index 4d82708ed..8a1ecd66f 100644 --- a/src/TemplateConfig.js +++ b/src/TemplateConfig.js @@ -333,6 +333,8 @@ class TemplateConfig { } this.userConfig.activeNamespace = storedActiveNamespace; + + this.userConfig._disablePluginExecution(); } /** diff --git a/src/UserConfig.js b/src/UserConfig.js index cd48b63a3..10095af5f 100644 --- a/src/UserConfig.js +++ b/src/UserConfig.js @@ -102,6 +102,8 @@ class UserConfig { this.linters = {}; this.transforms = {}; + this.preprocessors = {}; + this.activeNamespace = ""; this.DateTime = DateTime; this.dynamicPermalinks = true; @@ -544,6 +546,11 @@ class UserConfig { this.#pluginExecution = true; } + // Internal method + _disablePluginExecution() { + this.#pluginExecution = false; + } + /* Config is executed in two stages and plugins are the second stageā€”are we in the plugins stage? */ isPluginExecution() { return this.#pluginExecution; @@ -999,6 +1006,15 @@ class UserConfig { this.transforms[name] = this.#decorateCallback(`"${name}" Transform`, callback); } + addPreprocessor(name, fileExtensions, callback) { + name = this.getNamespacedName(name); + + this.preprocessors[name] = { + filter: fileExtensions, + callback: this.#decorateCallback(`"${name}" Preprocessor`, callback), + }; + } + addLinter(name, callback) { name = this.getNamespacedName(name); @@ -1055,6 +1071,7 @@ class UserConfig { // filters removed in 1.0 (use addTransform instead) transforms: this.transforms, linters: this.linters, + preprocessors: this.preprocessors, globalData: this.globalData, layoutAliases: this.layoutAliases, layoutResolution: this.layoutResolution, diff --git a/test/EleventyTest.js b/test/EleventyTest.js index 0573a3804..3d21578e7 100644 --- a/test/EleventyTest.js +++ b/test/EleventyTest.js @@ -1527,3 +1527,23 @@ test("#1419: Shortcode in a permalink", async (t) => { t.is(results.length, 1); t.is(results[0].url, `/url-slug/`); }); + +test("#188: Content preprocessing", async (t) => { + let elev = new Eleventy("./test/stubs-virtual/", undefined, { + config: eleventyConfig => { + eleventyConfig.addPreprocessor("drafts", ".njk", (data, content) => { + if(data.draft) { + return false; + } + return `Hello ${content}`; + }); + + eleventyConfig.addTemplate("index.njk", "Before"); + eleventyConfig.addTemplate("draft.njk", "Before", { draft: true }); + } + }); + + let results = await elev.toJSON(); + t.is(results.length, 1); + t.is(results[0].content, `Hello Before`); +});