Skip to content

Commit b15def1

Browse files
committed
Remove postVars and implement global.data.{j,t}s
1 parent 6d194c5 commit b15def1

14 files changed

Lines changed: 694 additions & 250 deletions

File tree

.claude/settings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
{
22
"permissions": {
33
"allow": [
4-
"Bash(node --test:*)"
4+
"Bash(node --test:*)",
5+
"Bash(git checkout:*)",
6+
"Bash(npm test:*)"
57
]
68
}
79
}

README.md

Lines changed: 85 additions & 97 deletions
Large diffs are not rendered by default.

index.js

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/**
22
* @import { DomStackOpts as DomStackOpts, Results } from './lib/builder.js'
33
* @import { FSWatcher, Stats } from 'node:fs'
4-
* @import { PostVarsFunction, AsyncPostVarsFunction, AsyncLayoutFunction, LayoutFunction } from './lib/build-pages/page-data.js'
4+
* @import { AsyncLayoutFunction, LayoutFunction } from './lib/build-pages/page-data.js'
55
* @import { PageFunction, AsyncPageFunction } from './lib/build-pages/page-builders/page-writer.js'
66
* @import { TemplateFunction } from './lib/build-pages/page-builders/template-builder.js'
77
* @import { TemplateAsyncIterator } from './lib/build-pages/page-builders/template-builder.js'
88
* @import { TemplateOutputOverride } from './lib/build-pages/page-builders/template-builder.js'
9+
* @import { GlobalDataFunction, AsyncGlobalDataFunction } from './lib/build-pages/index.js'
910
* @import { BuildOptions } from 'esbuild'
1011
*/
1112
import { once } from 'events'
@@ -44,17 +45,13 @@ import { DomStackAggregateError } from './lib/helpers/dom-stack-aggregate-error.
4445
*/
4546

4647
/**
47-
* @template {Record<string, any>} Vars - The type of variables for the post vars function
48-
* @template [PageReturn=any] PageReturn - The return type of the page function (defaults to any)
49-
* @template [LayoutReturn=string] LayoutReturn - The return type of the layout function (defaults to string)
50-
* @typedef {PostVarsFunction<Vars, PageReturn, LayoutReturn>} PostVarsFunction
48+
* @template {Record<string, any>} [T=Record<string, any>] - The shape of the derived vars object returned.
49+
* @typedef {GlobalDataFunction<T>} GlobalDataFunction
5150
*/
5251

5352
/**
54-
* @template {Record<string, any>} Vars - The type of variables for the async post vars function
55-
* @template [PageReturn=any] PageReturn - The return type of the page function (defaults to any)
56-
* @template [LayoutReturn=string] LayoutReturn - The return type of the layout function (defaults to string)
57-
* @typedef {AsyncPostVarsFunction<Vars, PageReturn, LayoutReturn>} AsyncPostVarsFunction
53+
* @template {Record<string, any>} [T=Record<string, any>] - The shape of the derived vars object returned.
54+
* @typedef {AsyncGlobalDataFunction<T>} AsyncGlobalDataFunction
5855
*/
5956

6057
/**
@@ -225,7 +222,7 @@ export class DomStack {
225222
// Combine your existing 'anymatch' function with the new extension check
226223
return (
227224
anymatch(filePath) ||
228-
Boolean((stats?.isFile() && !/\.(js|css|html|md)$/.test(filePath)))
225+
Boolean((stats?.isFile() && !/\.(js|mjs|cjs|ts|mts|cts|css|html|md)$/.test(filePath)))
229226
)
230227
},
231228
persistent: true,

lib/build-pages/index.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { join } from 'path'
99
import pMap from 'p-map'
1010
import { cpus } from 'os'
1111
import { keyBy } from '../helpers/key-by.js'
12-
import { resolveVars } from './resolve-vars.js'
12+
import { resolveVars, resolveGlobalData } from './resolve-vars.js'
1313
import { pageBuilders, templateBuilder } from './page-builders/index.js'
1414
import { PageData, resolveLayout } from './page-data.js'
1515
import { pageWriter } from './page-builders/page-writer.js'
@@ -25,6 +25,32 @@ const __dirname = import.meta.dirname
2525
* }} PageBuilderReport
2626
*/
2727

28+
/**
29+
* Parameters passed to a global.data.js default export function.
30+
* @typedef {object} GlobalDataFunctionParams
31+
* @property {PageData<any, any, any>[]} pages - Fully initialized PageData instances for all pages.
32+
*/
33+
34+
/**
35+
* Synchronous global.data function. Receives initialized PageData[] (with .vars, .pageInfo, etc.)
36+
* and returns an object stamped onto every page's vars before rendering begins.
37+
*
38+
* @template {Record<string, any>} [T=Record<string, any>] - The shape of the derived vars object returned.
39+
* @callback GlobalDataFunction
40+
* @param {GlobalDataFunctionParams} params
41+
* @returns {T | Promise<T>}
42+
*/
43+
44+
/**
45+
* Asynchronous global.data function. Receives initialized PageData[] (with .vars, .pageInfo, etc.)
46+
* and returns an object stamped onto every page's vars before rendering begins.
47+
*
48+
* @template {Record<string, any>} [T=Record<string, any>] - The shape of the derived vars object returned.
49+
* @callback AsyncGlobalDataFunction
50+
* @param {GlobalDataFunctionParams} params
51+
* @returns {Promise<T>}
52+
*/
53+
2854
/**
2955
* @typedef {BuildStep<
3056
* 'page',
@@ -178,6 +204,20 @@ export async function buildPagesDirect (src, dest, siteData, _opts) {
178204
return pageData
179205
}, { concurrency: MAX_CONCURRENCY })
180206

207+
// Run global.data.js after all pages are initialized — receives fully resolved PageData[]
208+
// so it can filter/sort by page.vars.layout, page.vars.publishDate, etc.
209+
const globalDataVars = await resolveGlobalData({
210+
globalDataPath: siteData.globalData?.filepath,
211+
pages,
212+
})
213+
214+
// Stamp globalDataVars onto each page so they appear in page.vars at render time.
215+
if (Object.keys(globalDataVars).length > 0) {
216+
for (const page of pages) {
217+
page.globalDataVars = globalDataVars
218+
}
219+
}
220+
181221
if (result.errors.length > 0) return result
182222

183223
/** @type {[number, number]} Divided concurrency valus */

lib/build-pages/page-data.js

Lines changed: 7 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -76,52 +76,6 @@ export async function resolveLayout (layoutPath) {
7676
* @typedef {LayoutFunction<T, U, V> | AsyncLayoutFunction<T, U, V>} InternalLayoutFunction
7777
*/
7878

79-
/**
80-
* Common parameters for postVars functions.
81-
*
82-
* @template {Record<string, any>} T - The type of variables for the page
83-
* @template [U=any] U - The return type of the page function (defaults to any)
84-
* @template [V=string] V - The return type of the layout function (defaults to string)
85-
* @typedef {object} PostVarsFunctionParams
86-
* @property {T} vars - All default, global, layout, page, and builder vars shallow merged.
87-
* @property {string[]} [scripts] - Array of script URLs to include.
88-
* @property {string[]} [styles] - Array of stylesheet URLs to include.
89-
* @property {PageInfo} page - Info about the current page
90-
* @property {PageData<T, U, V>[]} pages - An array of info about every page
91-
* @property {Object<string, string>} [workers] - Map of worker names to their output paths
92-
*/
93-
94-
/**
95-
* Synchronous postVars function to generate page vars with access to all page data.
96-
*
97-
* @template {Record<string, any>} T - The type of variables for the page
98-
* @template [U=any] U - The return type of the page function (defaults to any)
99-
* @template [V=string] V - The return type of the layout function (defaults to string)
100-
* @callback PostVarsFunction
101-
* @param {PostVarsFunctionParams<T, U, V>} params - The parameters for the postVars function.
102-
* @returns {T | Promise<T>} The rendered postVars
103-
*/
104-
105-
/**
106-
* Asynchronous postVars function to generate page vars with access to all page data.
107-
*
108-
* @template {Record<string, any>} T - The type of variables for the page
109-
* @template [U=any] U - The return type of the page function (defaults to any)
110-
* @template [V=string] V - The return type of the layout function (defaults to string)
111-
* @callback AsyncPostVarsFunction
112-
* @param {PostVarsFunctionParams<T, U, V>} params - The parameters for the postVars function.
113-
* @returns {Promise<T>} The rendered postVars
114-
*/
115-
116-
/**
117-
* postVars functions can be used to generate page vars but access all page data (can be sync or async).
118-
*
119-
* @template {Record<string, any>} T - The type of variables for the page
120-
* @template [U=any] U - The return type of the page function (defaults to any)
121-
* @template [V=string] V - The return type of the layout function (defaults to string)
122-
* @typedef {PostVarsFunction<T, U, V> | AsyncPostVarsFunction<T, U, V>} InternalPostVarsFunction
123-
*/
124-
12579
/**
12680
* Represents the data for a page.
12781
* @template {Record<string, any>} T - The type of variables for the page data
@@ -132,14 +86,13 @@ export class PageData {
13286
/** @type {PageInfo} */ pageInfo
13387
/** @type {ResolvedLayout<T, U, V> | null | undefined} */ layout
13488
/** @type {object} */ globalVars
89+
/** @type {object} */ globalDataVars = {}
13590
/** @type {object?} */ pageVars = null
136-
/** @type {function?} */ postVars = null
13791
/** @type {object?} */ builderVars = null
13892
/** @type {string[]} */ styles = []
13993
/** @type {string[]} */ scripts = []
14094
/** @type {WorkerFiles} */ workerFiles = {}
14195
/** @type {boolean} */ #initialized = false
142-
/** @type {T?} */ #renderedPostVars = null
14396
/** @type {string?} */ #defaultStyle = null
14497
/** @type {string?} */ #defaultClient = null
14598
/** @type {BuilderOptions} */ builderOptions
@@ -185,10 +138,11 @@ export class PageData {
185138
*/
186139
get vars () {
187140
if (!this.#initialized) throw new Error('Initialize PageData before accessing vars')
188-
const { globalVars, pageVars, builderVars } = this
141+
const { globalVars, globalDataVars, pageVars, builderVars } = this
189142
// @ts-ignore
190143
return {
191144
...globalVars,
145+
...globalDataVars,
192146
...pageVars,
193147
...builderVars,
194148
}
@@ -202,28 +156,6 @@ export class PageData {
202156
return this.workerFiles
203157
}
204158

205-
/**
206-
* @type {AsyncPostVarsFunction<T, U, V>}
207-
*/
208-
async #renderPostVars ({ vars, styles, scripts, pages, page, workers }) {
209-
if (!this.#initialized) throw new Error('Initialize PageData before accessing renderPostVars')
210-
if (!this.postVars) return this.vars
211-
if (this.#renderedPostVars) return this.#renderedPostVars
212-
213-
const { globalVars, pageVars, builderVars } = this
214-
215-
const renderedPostVars = {
216-
...globalVars,
217-
...pageVars,
218-
...(await this.postVars({ vars, styles, scripts, pages, page, workers })),
219-
...builderVars,
220-
}
221-
222-
this.#renderedPostVars = renderedPostVars
223-
224-
return renderedPostVars
225-
}
226-
227159
/**
228160
* [init description]
229161
* @param {object} params - Parameters required to initialize
@@ -238,9 +170,7 @@ export class PageData {
238170
varsPath: pageVars?.filepath,
239171
resolveVars: globalVars,
240172
})
241-
this.postVars = await resolvePostVars({
242-
varsPath: pageVars?.filepath,
243-
})
173+
await resolvePostVars({ varsPath: pageVars?.filepath }) // throws if postVars export is detected
244174

245175
const builder = pageBuilders[type]
246176
const { vars: builderVars } = await builder({ pageInfo, options: this.builderOptions })
@@ -303,9 +233,8 @@ export class PageData {
303233
if (!pageInfo) throw new Error('A page is required to render')
304234
const builder = pageBuilders[pageInfo.type]
305235
const { pageLayout } = await builder({ pageInfo, options: builderOptions })
306-
const renderedPostVars = await this.#renderPostVars({ vars, styles, scripts, pages, page: pageInfo, workers })
307236
// @ts-expect-error - Builder types vary by page type, but the runtime type is correct
308-
const results = await pageLayout({ vars: renderedPostVars, styles, scripts, pages, page: pageInfo, workers })
237+
const results = await pageLayout({ vars, styles, scripts, pages, page: pageInfo, workers })
309238
return results
310239
}
311240

@@ -316,15 +245,14 @@ export class PageData {
316245
*/
317246
async renderFullPage ({ pages }) {
318247
if (!this.#initialized) throw new Error('Must be initialized before rendering full pages')
319-
const { pageInfo, layout, vars, styles, scripts, workers } = this
248+
const { pageInfo, layout, vars, styles, scripts } = this
320249
if (!pageInfo) throw new Error('A page is required to render')
321250
if (!layout) throw new Error('A layout is required to render')
322-
const renderedPostVars = await this.#renderPostVars({ vars, styles, scripts, pages, page: pageInfo, workers })
323251
const innerPage = await this.renderInnerPage({ pages })
324252

325253
return pretty(
326254
await layout.render({
327-
vars: renderedPostVars,
255+
vars,
328256
styles,
329257
scripts,
330258
page: pageInfo,

lib/build-pages/resolve-vars.js

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* @param {string} [params.varsPath] - Path to the file containing the variables.
66
* @param {object} [params.resolveVars] - Any variables you want passed to the reolveFunction.
77
* @param {string} [params.key='default'] - The key to extract from the imported module. Default: 'default'
8-
* @returns {Promise<object|function>} - Returns the resolved variables. If the imported variable is a function, it executes and returns its result. Otherwise, it returns the variable directly.
8+
* @returns {Promise<object>} - Returns the resolved variables. If the imported variable is a function, it executes and returns its result. Otherwise, it returns the variable directly.
99
*/
1010
export async function resolveVars ({
1111
varsPath,
@@ -31,12 +31,44 @@ export async function resolveVars ({
3131
}
3232
}
3333

34+
/**
35+
* @import { PageData } from './page-data.js'
36+
*/
37+
38+
/**
39+
* Resolve and call a global.data.js file with the initialized PageData array.
40+
* Receives fully resolved PageData instances (with .vars, .pageInfo, etc.) so
41+
* that global.data.js can filter and aggregate by layout, publishDate, title, etc.
42+
* Returns an empty object if no file is provided or the file exports nothing useful.
43+
*
44+
* @param {object} params
45+
* @param {string} [params.globalDataPath] - Path to the global.data file.
46+
* @param {PageData<any, any, any>[]} params.pages - Initialized PageData array.
47+
* @returns {Promise<object>}
48+
*/
49+
export async function resolveGlobalData ({ globalDataPath, pages }) {
50+
if (!globalDataPath) return {}
51+
52+
const imported = await import(globalDataPath)
53+
const maybeGlobalData = imported.default
54+
55+
if (isFunction(maybeGlobalData)) {
56+
const result = await maybeGlobalData({ pages })
57+
if (isObject(result)) return result
58+
throw new Error('global.data default export function must return an object')
59+
} else if (isObject(maybeGlobalData)) {
60+
return maybeGlobalData
61+
} else {
62+
return {}
63+
}
64+
}
65+
3466
/**
3567
* Resolve variables by importing them from a specified path.
3668
*
3769
* @param {object} params
3870
* @param {string} [params.varsPath] - Path to the file containing the variables.
39-
* @returns {Promise<function|null>} - Returns the resolved variables. If the imported variable is a function, it executes and returns its result. Otherwise, it returns the variable directly.
71+
* @returns {Promise<null>}
4072
*/
4173
export async function resolvePostVars ({
4274
varsPath,
@@ -47,14 +79,14 @@ export async function resolvePostVars ({
4779
const maybePostVars = imported.postVars
4880

4981
if (maybePostVars) {
50-
if (isFunction(maybePostVars)) {
51-
return maybePostVars
52-
} else {
53-
throw new Error('postVars must export a function')
54-
}
55-
} else {
56-
return null
82+
throw new Error(
83+
`postVars is no longer supported (found in ${varsPath}). ` +
84+
'Move data aggregation to a global.data.js file instead. ' +
85+
'See the domstack docs for details.'
86+
)
5787
}
88+
89+
return null
5890
}
5991

6092
/**

lib/helpers/dom-stack-warning.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* 'DOM_STACK_WARNING_DUPLICATE_ESBUILD_SETTINGS' |
1212
* 'DOM_STACK_WARNING_DUPLICATE_MARKDOWN_IT_SETTINGS' |
1313
* 'DOM_STACK_WARNING_DUPLICATE_GLOBAL_VARS' |
14+
* 'DOM_STACK_WARNING_DUPLICATE_GLOBAL_DATA' |
1415
* 'DOM_STACK_WARNING_PAGE_MD_SHADOWS_README'
1516
* } DomStackWarningCode
1617
*/

0 commit comments

Comments
 (0)