From 6de73b934aeee2ab941d82aec31470a2fc1c2717 Mon Sep 17 00:00:00 2001 From: vrugtehagel Date: Fri, 23 Feb 2024 19:21:24 +0900 Subject: [PATCH 1/3] Allow global variables in rendered liquid partials --- src/Engines/Liquid.js | 32 +++++++++++++++++++++++++++- src/Engines/TemplateEngine.js | 4 ++++ src/Template.js | 6 ++++++ test/TemplateRenderLiquidTest.js | 33 +++++++++++++++++++++++++++-- test/_testHelpers.js | 4 +++- test/stubs/_includes/globals.liquid | 1 + 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 test/stubs/_includes/globals.liquid diff --git a/src/Engines/Liquid.js b/src/Engines/Liquid.js index e52f51c23..2e4508b44 100644 --- a/src/Engines/Liquid.js +++ b/src/Engines/Liquid.js @@ -19,11 +19,13 @@ class Liquid extends TemplateEngine { constructor(name, eleventyConfig) { super(name, eleventyConfig); + this.globalsReference = this.config.globalData this.liquidOptions = this.config.liquidOptions || {}; this.setLibrary(this.config.libraryOverrides.liquid); this.argLexer = moo.compile(Liquid.argumentLexerOptions); + this.cacheable = true; } @@ -50,11 +52,30 @@ class Liquid extends TemplateEngine { }; let options = Object.assign(defaults, this.liquidOptions || {}); + + this.mergeGlobalData() + options.globals = this.globalsReference // debug("Liquid constructor options: %o", options); return options; } + mergeGlobalData(){ + for(let name in this.config.liquidOptions.globals){ + if(name in this.config.globalData) continue; + this.config.globalData[name] = this.config.liquidOptions.globals[name] + } + } + + /** + * Liquid needs to receive globals in order for {% render %} to have access to them + * + * @override + **/ + needsGlobals(){ + return true + } + static wrapFilter(fn) { return function (...args) { if (this.context && "get" in this.context) { @@ -111,6 +132,16 @@ class Liquid extends TemplateEngine { this.liquidLib.registerTag(name, tagObj); } + addGlobals(globals){ + for (let [name, value] of Object.entries(globals)) { + this.addGlobal(name, value); + } + } + + addGlobal(name, value){ + this.globalsReference[name] = value + } + addAllShortcodes(shortcodes) { for (let name in shortcodes) { this.addShortcode(name, shortcodes[name]); @@ -262,7 +293,6 @@ class Liquid extends TemplateEngine { return async function (data) { let tmpl = await tmplReady; - return engine.render(tmpl, data, options); }; } diff --git a/src/Engines/TemplateEngine.js b/src/Engines/TemplateEngine.js index 15f6999ac..e74b0906d 100644 --- a/src/Engines/TemplateEngine.js +++ b/src/Engines/TemplateEngine.js @@ -115,6 +115,10 @@ class TemplateEngine { return true; } + needsGlobals(){ + return false; + } + getExtraDataFromFile() { return {}; } diff --git a/src/Template.js b/src/Template.js index c45661420..2528de168 100755 --- a/src/Template.js +++ b/src/Template.js @@ -356,6 +356,12 @@ class Template extends TemplateContent { debugDev("%o getData mergedData", this.inputPath); this._dataCache = mergedData; + if(this.templateRender.engine.needsGlobals()){ + if(typeof this.templateRender.engine.addGlobals != 'function'){ + throw new Error("Internal error: the engine's `.needsGlobals()` method returned `true`, but it does not have an `.addGlobals()` method"); + } + this.templateRender.engine.addGlobals(globalData) + } return mergedData; } diff --git a/test/TemplateRenderLiquidTest.js b/test/TemplateRenderLiquidTest.js index 94a802f39..54e567e97 100644 --- a/test/TemplateRenderLiquidTest.js +++ b/test/TemplateRenderLiquidTest.js @@ -6,12 +6,12 @@ import EleventyExtensionMap from "../src/EleventyExtensionMap.js"; import { getTemplateConfigInstance } from "./_testHelpers.js"; -async function getNewTemplateRender(name, inputDir, userConfig = {}) { +async function getNewTemplateRender(name, inputDir, userConfig = {}, configCallback) { let eleventyConfig = await getTemplateConfigInstance({ dir: { input: inputDir } - }, null, userConfig); + }, null, userConfig, configCallback); let tr = new TemplateRender(name, eleventyConfig); tr.extensionMap = new EleventyExtensionMap([], eleventyConfig); @@ -1039,3 +1039,32 @@ test("Liquid Parse for Symbols", async (t) => { t.deepEqual(engine.parseForSymbols("{{ collections.mine | test }}>"), ["collections.mine"]); }); + +test("Issue 1541: global data in rendered templates passed through liquidOptions", async (t) => { + let tr = await getNewTemplateRender("liquid", "./test/stubs/", { + liquidOptions: { + globals: {globalVariable: "Hello world"} + }, + }); + let fn = await tr.getCompiledTemplate(`

{% render "globals" %}

`); + t.is(await fn(), "

Hello world

"); +}); + +test("Issue 1541: global data in rendered templates passed through addGlobalData", async (t) => { + let tr = await getNewTemplateRender("liquid", "./test/stubs/", null, eleventyConfig => { + eleventyConfig.userConfig.addGlobalData('globalVariable', 'Hello world'); + }); + let fn = await tr.getCompiledTemplate(`

{% render "globals" %}

`); + t.is(await fn(), "

Hello world

"); +}); + + + +// test("Liquid Render Scope Leak", async (t) => { +// let tr1 = await getNewTemplateRender("liquid", "./test/stubs/"); +// t.is(tr1.getEngineName(), "liquid"); + +// let tr2 = await getNewTemplateRender("liquid", "./test/stubs/"); +// let fn = await tr2.getCompiledTemplate("

{% render 'scopeleak' %}{{ test }}

"); +// t.is(await fn({ test: 1 }), "

21

"); +// }); diff --git a/test/_testHelpers.js b/test/_testHelpers.js index ae8be4b0d..a7bfcf38c 100644 --- a/test/_testHelpers.js +++ b/test/_testHelpers.js @@ -2,7 +2,7 @@ import { isPlainObject } from "@11ty/eleventy-utils"; import TemplateConfig from "../src/TemplateConfig.js"; import ProjectDirectories from "../src/Util/ProjectDirectories.js"; -async function getTemplateConfigInstance(configObj, dirs, configObjOverride = undefined) { +async function getTemplateConfigInstance(configObj, dirs, configObjOverride = undefined, configCallback = undefined) { let eleventyConfig; if(configObj instanceof TemplateConfig) { eleventyConfig = configObj; @@ -25,6 +25,8 @@ async function getTemplateConfigInstance(configObj, dirs, configObjOverride = un eleventyConfig.setDirectories(dirs); + configCallback?.(eleventyConfig); + await eleventyConfig.init(configObjOverride || configObj); // overrides return eleventyConfig; diff --git a/test/stubs/_includes/globals.liquid b/test/stubs/_includes/globals.liquid new file mode 100644 index 000000000..c8a583032 --- /dev/null +++ b/test/stubs/_includes/globals.liquid @@ -0,0 +1 @@ +{{ globalVariable }} \ No newline at end of file From 336f98bf6b7df2f76b9aa4787e6680535ae79c1e Mon Sep 17 00:00:00 2001 From: vrugtehagel Date: Fri, 23 Feb 2024 19:24:20 +0900 Subject: [PATCH 2/3] Cleanup whitespace and comments --- src/Engines/Liquid.js | 2 +- test/TemplateRenderLiquidTest.js | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/Engines/Liquid.js b/src/Engines/Liquid.js index 2e4508b44..0330b8fe5 100644 --- a/src/Engines/Liquid.js +++ b/src/Engines/Liquid.js @@ -25,7 +25,6 @@ class Liquid extends TemplateEngine { this.setLibrary(this.config.libraryOverrides.liquid); this.argLexer = moo.compile(Liquid.argumentLexerOptions); - this.cacheable = true; } @@ -293,6 +292,7 @@ class Liquid extends TemplateEngine { return async function (data) { let tmpl = await tmplReady; + return engine.render(tmpl, data, options); }; } diff --git a/test/TemplateRenderLiquidTest.js b/test/TemplateRenderLiquidTest.js index 54e567e97..e324ce93b 100644 --- a/test/TemplateRenderLiquidTest.js +++ b/test/TemplateRenderLiquidTest.js @@ -1057,14 +1057,3 @@ test("Issue 1541: global data in rendered templates passed through addGlobalData let fn = await tr.getCompiledTemplate(`

{% render "globals" %}

`); t.is(await fn(), "

Hello world

"); }); - - - -// test("Liquid Render Scope Leak", async (t) => { -// let tr1 = await getNewTemplateRender("liquid", "./test/stubs/"); -// t.is(tr1.getEngineName(), "liquid"); - -// let tr2 = await getNewTemplateRender("liquid", "./test/stubs/"); -// let fn = await tr2.getCompiledTemplate("

{% render 'scopeleak' %}{{ test }}

"); -// t.is(await fn({ test: 1 }), "

21

"); -// }); From a2110337f00f97a33c80ffc6c1f9397bbece43c3 Mon Sep 17 00:00:00 2001 From: vrugtehagel Date: Fri, 23 Feb 2024 19:29:57 +0900 Subject: [PATCH 3/3] Formatting --- src/Engines/Liquid.js | 26 +++++++++++++------------- src/Engines/TemplateEngine.js | 2 +- src/Template.js | 10 ++++++---- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/Engines/Liquid.js b/src/Engines/Liquid.js index 0330b8fe5..b4ea6471c 100644 --- a/src/Engines/Liquid.js +++ b/src/Engines/Liquid.js @@ -19,7 +19,7 @@ class Liquid extends TemplateEngine { constructor(name, eleventyConfig) { super(name, eleventyConfig); - this.globalsReference = this.config.globalData + this.globalsReference = this.config.globalData; this.liquidOptions = this.config.liquidOptions || {}; this.setLibrary(this.config.libraryOverrides.liquid); @@ -52,17 +52,17 @@ class Liquid extends TemplateEngine { let options = Object.assign(defaults, this.liquidOptions || {}); - this.mergeGlobalData() - options.globals = this.globalsReference + this.mergeGlobalData(); + options.globals = this.globalsReference; // debug("Liquid constructor options: %o", options); return options; } - mergeGlobalData(){ - for(let name in this.config.liquidOptions.globals){ - if(name in this.config.globalData) continue; - this.config.globalData[name] = this.config.liquidOptions.globals[name] + mergeGlobalData() { + for (let name in this.config.liquidOptions.globals) { + if (name in this.config.globalData) continue; + this.config.globalData[name] = this.config.liquidOptions.globals[name]; } } @@ -70,9 +70,9 @@ class Liquid extends TemplateEngine { * Liquid needs to receive globals in order for {% render %} to have access to them * * @override - **/ - needsGlobals(){ - return true + **/ + needsGlobals() { + return true; } static wrapFilter(fn) { @@ -131,14 +131,14 @@ class Liquid extends TemplateEngine { this.liquidLib.registerTag(name, tagObj); } - addGlobals(globals){ + addGlobals(globals) { for (let [name, value] of Object.entries(globals)) { this.addGlobal(name, value); } } - addGlobal(name, value){ - this.globalsReference[name] = value + addGlobal(name, value) { + this.globalsReference[name] = value; } addAllShortcodes(shortcodes) { diff --git a/src/Engines/TemplateEngine.js b/src/Engines/TemplateEngine.js index e74b0906d..14e5c9c52 100644 --- a/src/Engines/TemplateEngine.js +++ b/src/Engines/TemplateEngine.js @@ -115,7 +115,7 @@ class TemplateEngine { return true; } - needsGlobals(){ + needsGlobals() { return false; } diff --git a/src/Template.js b/src/Template.js index 2528de168..2ce02d009 100755 --- a/src/Template.js +++ b/src/Template.js @@ -356,11 +356,13 @@ class Template extends TemplateContent { debugDev("%o getData mergedData", this.inputPath); this._dataCache = mergedData; - if(this.templateRender.engine.needsGlobals()){ - if(typeof this.templateRender.engine.addGlobals != 'function'){ - throw new Error("Internal error: the engine's `.needsGlobals()` method returned `true`, but it does not have an `.addGlobals()` method"); + if (this.templateRender.engine.needsGlobals()) { + if (typeof this.templateRender.engine.addGlobals != "function") { + throw new Error( + "Internal error: the engine's `.needsGlobals()` method returned `true`, but it does not have an `.addGlobals()` method", + ); } - this.templateRender.engine.addGlobals(globalData) + this.templateRender.engine.addGlobals(globalData); } return mergedData; }