Description
setPayloadFrontMatter calls jsonCodec.Marshal(fm) + ParseJSON() on every page for every hook call, even though most post-render hooks never read page.frontMatter. On the RHDS benchmark (820 pages), this costs 3.2 seconds of JSON work on ~1KB maps that are never accessed.
Same pattern applies to TOC in callHookTransformPayload — jsonCodec.Marshal(v.TOC) + ParseJSON() per page, rarely read by plugins.
Fix
Use Object.defineProperty with a getter backed by a Go callback. The JSON marshal + ParseJSON only runs if the plugin actually accesses the property.
frontMatter
Store the Go map in r.pendingFM. Install a lazy getter via a pre-compiled __installLazyFM function:
function __installLazyFM(target) {
Object.defineProperty(target, 'frontMatter', {
get: function() {
var v = JSON.parse(__resolveFM()); // Go callback, marshals on demand
Object.defineProperty(this, 'frontMatter', {value: v, writable: true, configurable: true});
return v;
},
set: function(v) {
Object.defineProperty(this, 'frontMatter', {value: v, writable: true, configurable: true});
},
configurable: true
});
}
The setter is required — plugins that write page.frontMatter = {...} before reading must not throw TypeError. The setter replaces the accessor with a writable data property.
__resolveFM is a Go callback registered via ctx.SetFunc during Init(). It reads r.pendingFM, marshals to JSON, and returns the string. Same pattern for __resolveTOC / r.pendingTOC.
TOC
Identical pattern with __installLazyTOC and __resolveTOC. Only installed when len(v.TOC) > 0.
Measured impact
RHDS benchmark: frontMatter serialization dropped from 3.2s to 36ms (89x). Total build dropped from 20.2s to 15.9s. Combined with other optimizations, total reached 14.8s (faster than v0.5.0's 15.8s).
Spec test needs
- Plugin reads
page.frontMatter.title → receives correct value (getter fires, JSON resolves)
- Plugin writes
page.frontMatter = {custom: true} before reading → no TypeError (setter works)
- Plugin never reads frontMatter → no error, no perf penalty (getter never fires)
- Plugin reads frontMatter twice → second access returns cached value (getter self-replaces with data property)
- Nil frontMatter →
page.frontMatter is empty object (not undefined, not null)
- Empty frontMatter map → same as nil
- Same patterns for TOC: read, write-before-read, never-read, nil, empty
Refs #1185, #1180
Description
setPayloadFrontMattercallsjsonCodec.Marshal(fm)+ParseJSON()on every page for every hook call, even though most post-render hooks never readpage.frontMatter. On the RHDS benchmark (820 pages), this costs 3.2 seconds of JSON work on ~1KB maps that are never accessed.Same pattern applies to TOC in
callHookTransformPayload—jsonCodec.Marshal(v.TOC)+ParseJSON()per page, rarely read by plugins.Fix
Use
Object.definePropertywith a getter backed by a Go callback. The JSON marshal + ParseJSON only runs if the plugin actually accesses the property.frontMatter
Store the Go map in
r.pendingFM. Install a lazy getter via a pre-compiled__installLazyFMfunction:The setter is required — plugins that write
page.frontMatter = {...}before reading must not throw TypeError. The setter replaces the accessor with a writable data property.__resolveFMis a Go callback registered viactx.SetFuncduringInit(). It readsr.pendingFM, marshals to JSON, and returns the string. Same pattern for__resolveTOC/r.pendingTOC.TOC
Identical pattern with
__installLazyTOCand__resolveTOC. Only installed whenlen(v.TOC) > 0.Measured impact
RHDS benchmark: frontMatter serialization dropped from 3.2s to 36ms (89x). Total build dropped from 20.2s to 15.9s. Combined with other optimizations, total reached 14.8s (faster than v0.5.0's 15.8s).
Spec test needs
page.frontMatter.title→ receives correct value (getter fires, JSON resolves)page.frontMatter = {custom: true}before reading → no TypeError (setter works)page.frontMatteris empty object (not undefined, not null)Refs #1185, #1180