Markdown as the source of truth for a JS-data-driven static site
When to use
- A static site (plain HTML/CSS/JS, no build framework) renders content from a data object in a JS file, e.g.
window.PORTFOLIO_DATA = {...} consumed by a render script (main.js) that builds the DOM.
- The user wants to edit content in Markdown / Obsidian and have the site update, WITHOUT touching the HTML/template/CSS.
- The render must stay EXACTLY the same (pixel/semantics identical to today).
Core idea
Find the "data seam": the single JS object the render engine reads. Make a Markdown file with YAML frontmatter the SOURCE OF TRUTH. Write a tiny generator that emits that same JS object. The render engine never changes → output is identical by construction. Verify the emitted object is semantically equal to the original, NOT byte-equal (formatting differs, meaning must not).
Steps
- Locate the data object. Grep for
window.<X> = / const PORTFOLIO_DATA. Confirm what consumes it (e.g. main.js reads window.X and builds DOM).
- Snapshot the original data file to
*.orig — this is your verification baseline.
- (Optional, one-time)
seed.js: load the original JS (shim window / module.exports), dump the parsed object to a YAML-frontmatter MD. Freezes today's content as the source.
build.py: read the MD, emit valid JS reconstructing the SAME object:
- Serialize with a real JS-literal emitter (objects/arrays as
{}/[], strings double-quoted with \" and \n escapes). Do NOT emit YAML — the browser must evaluate it.
- Preserve structure exactly: same keys, same nesting, same field order where the consumer relies on it.
- Resolve constants: if the original referenced variables inside the data (e.g.
"https://orcid.org/" + ORCID), inline the values so the generated object equals the original at runtime.
- For i18n, mirror the EXACT nesting of the original (e.g. bolsas may nest
i18n.en = {title,kind,desc} per language). Match it; don't transpose unless you also change the consumer.
verify.js (Node): load BOTH the generated file and *.orig via new Function('window', src + 'return window.X'), then deep-equal the objects. Exit 1 on diff. This is the real test — a byte diff will always show false positives.
- Pre-commit hook: run
build.py so git commit always recompiles; git add the generated file.
- User workflow: edit
src/<site>.md in Obsidian → python3 tools/build.py → git add -A && git commit && git push.
Pitfalls
- Emitting YAML instead of JS: the browser can't consume it. Use a JS-literal emitter.
- Comparing generated vs original by
diff/bytes: formatting always differs → false alarms. Compare the PARSED OBJECT.
- Transposed i18n: if you restructure how translations are stored in the MD, the generator must re-nest to the consumer's expected shape, or the render breaks. Keep the consumer untouched.
- Round-trip drift: after
seed, immediately build + verify and confirm deep-equal BEFORE editing. If not equal, fix the serializer first.
- Never hand-edit the generated data file — it's a build artifact the hook overwrites.
- Version the stable display first:
git tag stable-display HEAD before introducing the pipeline, so the user can revert if the round-trip isn't faithful.
Verification (ad-hoc, run after changes)
node tools/verify.js must print " gerado == original". Also: edit one field in the MD, rebuild, confirm it appears in the generated file, then revert and re-verify.
References
references/pipeline-pattern.md — condensed walkthrough of the seed→build→verify sequence.
scripts/generate_js_data.py — generalized MD(frontmatter)→JS generator skeleton (adapt normalize() to the site's schema; emits valid JS).
scripts/verify_deep_equal.js — generalized Node deep-equal verifier for two JS data files.
1---2name: markdown-static-site-source3description: Make a data-driven static site (content lives in a JS/JSON object consumed by a render script) editable from Markdown/Obsidian. Generate the data file from a YAML-frontmatter MD source and verify fidelity with SEMANTIC deep-equal (not byte comparison), keeping the HTML/CSS/render engine untouched so the site renders identically.4---56# Markdown as the source of truth for a JS-data-driven static site78## When to use9- A static site (plain HTML/CSS/JS, no build framework) renders content from a data object in a JS file, e.g. `window.PORTFOLIO_DATA = {...}` consumed by a render script (`main.js`) that builds the DOM.10- The user wants to edit content in Markdown / Obsidian and have the site update, WITHOUT touching the HTML/template/CSS.11- The render must stay EXACTLY the same (pixel/semantics identical to today).1213## Core idea14Find the "data seam": the single JS object the render engine reads. Make a Markdown file with YAML frontmatter the SOURCE OF TRUTH. Write a tiny generator that emits that same JS object. The render engine never changes → output is identical by construction. Verify the emitted object is semantically equal to the original, NOT byte-equal (formatting differs, meaning must not).1516## Steps171. Locate the data object. Grep for `window.<X> =` / `const PORTFOLIO_DATA`. Confirm what consumes it (e.g. `main.js` reads `window.X` and builds DOM).182. Snapshot the original data file to `*.orig` — this is your verification baseline.193. (Optional, one-time) `seed.js`: load the original JS (shim `window` / `module.exports`), dump the parsed object to a YAML-frontmatter MD. Freezes today's content as the source.204. `build.py`: read the MD, emit valid JS reconstructing the SAME object:21 - Serialize with a real JS-literal emitter (objects/arrays as `{}/[]`, strings double-quoted with `\"` and `\n` escapes). Do NOT emit YAML — the browser must evaluate it.22 - Preserve structure exactly: same keys, same nesting, same field order where the consumer relies on it.23 - Resolve constants: if the original referenced variables inside the data (e.g. `"https://orcid.org/" + ORCID`), inline the values so the generated object equals the original at runtime.24 - For i18n, mirror the EXACT nesting of the original (e.g. bolsas may nest `i18n.en = {title,kind,desc}` per language). Match it; don't transpose unless you also change the consumer.255. `verify.js` (Node): load BOTH the generated file and `*.orig` via `new Function('window', src + 'return window.X')`, then deep-equal the objects. Exit 1 on diff. This is the real test — a byte `diff` will always show false positives.266. Pre-commit hook: run `build.py` so `git commit` always recompiles; `git add` the generated file.277. User workflow: edit `src/<site>.md` in Obsidian → `python3 tools/build.py` → `git add -A && git commit && git push`.2829## Pitfalls30- Emitting YAML instead of JS: the browser can't consume it. Use a JS-literal emitter.31- Comparing generated vs original by `diff`/bytes: formatting always differs → false alarms. Compare the PARSED OBJECT.32- Transposed i18n: if you restructure how translations are stored in the MD, the generator must re-nest to the consumer's expected shape, or the render breaks. Keep the consumer untouched.33- Round-trip drift: after `seed`, immediately `build` + `verify` and confirm deep-equal BEFORE editing. If not equal, fix the serializer first.34- Never hand-edit the generated data file — it's a build artifact the hook overwrites.35- Version the stable display first: `git tag stable-display HEAD` before introducing the pipeline, so the user can revert if the round-trip isn't faithful.3637## Verification (ad-hoc, run after changes)38`node tools/verify.js` must print "<VAR> gerado == original". Also: edit one field in the MD, rebuild, confirm it appears in the generated file, then revert and re-verify.3940## References41- `references/pipeline-pattern.md` — condensed walkthrough of the seed→build→verify sequence.42- `scripts/generate_js_data.py` — generalized MD(frontmatter)→JS generator skeleton (adapt `normalize()` to the site's schema; emits valid JS).43- `scripts/verify_deep_equal.js` — generalized Node deep-equal verifier for two JS data files.