From 5ed14aa538fe254151d04ac2d6f01df3538eeacd Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 10:53:26 -0700 Subject: [PATCH 1/5] pass global.data.js output to template vars Templates received only global.vars.js output in vars, while pages received both global.vars.js and global.data.js output via PageData.vars. This inconsistency forced every template that needed aggregated data to re-derive it from the pages array, duplicating logic from global.data.js. Merging globalDataVars into the globalVars object passed to templateBuilder makes template vars consistent with how page vars already work. --- lib/build-pages/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/build-pages/index.js b/lib/build-pages/index.js index 4f13858..6574f9d 100644 --- a/lib/build-pages/index.js +++ b/lib/build-pages/index.js @@ -276,7 +276,7 @@ export async function buildPagesDirect (src, dest, siteData, _opts) { const buildResult = await templateBuilder({ src, dest, - globalVars, + globalVars: { ...globalVars, ...globalDataVars }, template, pages, }) From 58d5ed24ba987bcbc3e52decefad809fb60283e3 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 12:01:23 -0700 Subject: [PATCH 2/5] Hoist templateGlobalVars merge, update JSDoc to reflect new contract Merges globalVars and globalDataVars once before the pMap loop instead of once per template render. Updates TemplateFunction and templateBuilder JSDoc to accurately describe that vars now includes global.data.js output. --- lib/build-pages/index.js | 5 ++++- lib/build-pages/page-builders/template-builder.js | 7 ++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/build-pages/index.js b/lib/build-pages/index.js index 6574f9d..38c1634 100644 --- a/lib/build-pages/index.js +++ b/lib/build-pages/index.js @@ -254,6 +254,9 @@ export async function buildPagesDirect (src, dest, siteData, _opts) { ? siteData.templates.filter(t => templateFilterSet.has(t.templateFile.filepath)) : siteData.templates + // Merge once — globalVars and globalDataVars are constant for this build. + const templateGlobalVars = { ...globalVars, ...globalDataVars } + await Promise.all([ pMap(pagesToRender, async (page) => { try { @@ -276,7 +279,7 @@ export async function buildPagesDirect (src, dest, siteData, _opts) { const buildResult = await templateBuilder({ src, dest, - globalVars: { ...globalVars, ...globalDataVars }, + globalVars: templateGlobalVars, template, pages, }) diff --git a/lib/build-pages/page-builders/template-builder.js b/lib/build-pages/page-builders/template-builder.js index cdd4bf4..6305bac 100644 --- a/lib/build-pages/page-builders/template-builder.js +++ b/lib/build-pages/page-builders/template-builder.js @@ -17,7 +17,7 @@ import { writeFile, mkdir } from 'fs/promises' * @template {Record} T - The type of variables for the template * @callback TemplateFunction * @param {object} params - The parameters for the template. - * @param {T} params.vars - All of the site globalVars. + * @param {T} params.vars - All of the site globalVars merged with global.data.js output. * @param {TemplateInfo} params.template - Info about the current template * @param {PageData[]} params.pages - An array of info about every page * @returns {Promise} @@ -46,12 +46,13 @@ import { writeFile, mkdir } from 'fs/promises' */ /** - * The template builder renders templates agains the globalVars variables + * The template builder renders templates against the globalVars variables. + * globalVars passed here already includes global.data.js output merged in. * @template {Record} T - The type of global variables for the template builder * @param {object} params * @param {string} params.src - The src path of the site build. * @param {string} params.dest - The dest path of the site build. - * @param {T} params.globalVars - The resolvedGlobal vars object. + * @param {T} params.globalVars - globalVars merged with global.data.js output. * @param {TemplateInfo} params.template - The TemplateInfo of the template. * @param {PageData[]} params.pages - The array of PageData object. */ From ed2f43eba23c8290a3fe395db575ab56c55c4561 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 12:52:43 -0700 Subject: [PATCH 3/5] Remove params.src from templateBuilder JSDoc - not in function signature --- lib/build-pages/page-builders/template-builder.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/build-pages/page-builders/template-builder.js b/lib/build-pages/page-builders/template-builder.js index 6305bac..d428ff4 100644 --- a/lib/build-pages/page-builders/template-builder.js +++ b/lib/build-pages/page-builders/template-builder.js @@ -50,7 +50,6 @@ import { writeFile, mkdir } from 'fs/promises' * globalVars passed here already includes global.data.js output merged in. * @template {Record} T - The type of global variables for the template builder * @param {object} params - * @param {string} params.src - The src path of the site build. * @param {string} params.dest - The dest path of the site build. * @param {T} params.globalVars - globalVars merged with global.data.js output. * @param {TemplateInfo} params.template - The TemplateInfo of the template. From 9a3a16fc4ea5dd000b4e2510eab070d3b73a075f Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 14:46:19 -0700 Subject: [PATCH 4/5] Add integration test proving global.data.js output reaches template vars global.data.js now returns a globalDataSentinel value. feeds.template.js reads it from vars and includes it as a custom extension field in feed.json. The test asserts the sentinel appears in the generated feed, which only passes when globalDataVars are correctly merged into templateGlobalVars. Co-Authored-By: Claude Sonnet 4.6 --- test-cases/general-features/index.test.js | 15 +++++++++++++++ test-cases/general-features/src/feeds.template.js | 5 ++++- test-cases/general-features/src/global.data.js | 4 ++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/test-cases/general-features/index.test.js b/test-cases/general-features/index.test.js index f876b3d..3c6e8e3 100644 --- a/test-cases/general-features/index.test.js +++ b/test-cases/general-features/index.test.js @@ -103,6 +103,21 @@ test.describe('general-features', () => { const jsChunkFiles = files.filter(f => f.relname.match(/chunks\/js\/chunk-.+\.js$/)) assert.ok(jsChunkFiles.length > 0, 'at least one shared JS chunk was produced with a hashed name') + // Verify that global.data.js output reaches template vars + const feedJsonPath = path.join(dest, 'feeds/feed.json') + try { + const feedContent = await readFile(feedJsonPath, 'utf8') + const feedData = JSON.parse(feedContent) + assert.strictEqual( + feedData._globalDataSentinel, + 'data-from-global-dot-data', + 'feeds template received globalDataSentinel from global.data.js via vars' + ) + } catch (err) { + const error = err instanceof Error ? err : new Error('Unknown error', { cause: err }) + assert.fail('Failed to verify global.data.js output in template vars: ' + error.message) + } + // Special test for global.data.js blogPostsHtml const indexPath = path.join(dest, 'index.html') try { diff --git a/test-cases/general-features/src/feeds.template.js b/test-cases/general-features/src/feeds.template.js index 5cb350b..6cf6154 100644 --- a/test-cases/general-features/src/feeds.template.js +++ b/test-cases/general-features/src/feeds.template.js @@ -12,7 +12,8 @@ import jsonfeedToAtom from 'jsonfeed-to-atom' * authorUrl: string, * authorImgUrl: string, * publishDate: string, - * siteDescription: string + * siteDescription: string, + * globalDataSentinel: string, * }>} */ export default async function * feedsTemplate ({ @@ -23,6 +24,7 @@ export default async function * feedsTemplate ({ authorUrl, authorImgUrl, siteDescription, + globalDataSentinel, }, pages, }) { @@ -39,6 +41,7 @@ export default async function * feedsTemplate ({ home_page_url: homePageUrl, feed_url: `${homePageUrl}/feed.json`, description: siteDescription, + _globalDataSentinel: globalDataSentinel, author: { name: authorName, url: authorUrl, diff --git a/test-cases/general-features/src/global.data.js b/test-cases/general-features/src/global.data.js index 0a38db4..db26d76 100644 --- a/test-cases/general-features/src/global.data.js +++ b/test-cases/general-features/src/global.data.js @@ -5,7 +5,7 @@ import { html } from 'htm/preact' import { render } from 'preact-render-to-string' -/** @type {AsyncGlobalDataFunction<{ blogPostsHtml: string }>} */ +/** @type {AsyncGlobalDataFunction<{ blogPostsHtml: string, globalDataSentinel: string }>} */ export default async function ({ pages }) { const blogPosts = pages .filter(page => page.vars?.layout === 'blog' && page.vars?.publishDate) @@ -36,5 +36,5 @@ export default async function ({ pages }) { `) - return { blogPostsHtml } + return { blogPostsHtml, globalDataSentinel: 'data-from-global-dot-data' } } From bf7426e9dbdc3d3408a26f1a170fbaf92dd31b51 Mon Sep 17 00:00:00 2001 From: Bret Comnes Date: Sat, 18 Apr 2026 19:49:44 -0700 Subject: [PATCH 5/5] Remove unused src param from templateBuilder call Co-Authored-By: Claude Sonnet 4.6 --- lib/build-pages/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/build-pages/index.js b/lib/build-pages/index.js index 38c1634..607469c 100644 --- a/lib/build-pages/index.js +++ b/lib/build-pages/index.js @@ -277,7 +277,6 @@ export async function buildPagesDirect (src, dest, siteData, _opts) { pMap(templatesToRender, async (template) => { try { const buildResult = await templateBuilder({ - src, dest, globalVars: templateGlobalVars, template,