TMPL Preview
A VS Code extension for previewing Booking.com BHC .inc / .tmpl templates locally — without a KVM, a Perl backend, or any deployment.
Intended audience: Booking.com engineers working on affiliate_data templates.
Install
Search TMPL Preview in the VS Code Extensions panel, or:
ext install thatsuperdev.tmpl-preview
Why
Iterating on affiliate_data templates means waiting for a KVM to boot and round-tripping through a server on every change. This extension parses templates locally, injects real CSS/JS bundles, evaluates the full BHC dialect, and renders a live preview side-by-side with the editor.
If your KVM is dead and you just want to see if a <TMPL_IF> branch renders correctly — open the preview.
What it renders
Full BHC dialect support:
TMPL_VAR / TMPL_V / TMPL_E / TMPL_TEXT / TMPL_DATE / TMPL_URI
TMPL_IF / TMPL_ELSIF / TMPL_ELSE / TMPL_UNLESS (with [% expr %] conditions)
TMPL_LOOP / TMPL_FOR var IN=collection
TMPL_CASE / TMPL_WHEN / TMPL_OTHERWISE
TMPL_WITH source (scope binding)
TMPL_SETVAR / TMPL_ASSIGN / TMPL_SET
TMPL_INLINE / TMPL_INCLUDE (recursive, namespace-aware, cross-affiliate)
TMPL_TRANS / TMPL_CLIENT_TRANS (45 locales, humanised fallbacks)
TMPL_HELPER (icon, font, modify_color, export)
TMPL_STATIC_URL (rewritten to configurable CDN base)
- Tilde-trim syntax
<~TMPL_IF~> / </TMPL_IF~>
[% ... %] expression engine: eq, ne, &&, ||, ?:, ->length, ->{key}, ->[i], regex match, function calls
Features
Live preview — side-by-side webview, re-renders on save of any transitively-included file.
Full-page CSS/JS — walks template_specific_stylesheets.inc → resolves .concat.css bundles → TMPL-renders CSS (evaluates TMPL_HELPER modify_color, TMPL_VAR bui_color_*) → injects BUI design tokens as :root { --bui_*: … }. What you see matches production styling.
Three-tab sidebar (Values / Translations / Assets) — edit variables, loops, function mocks, and translation overrides. Values persist per-file at .tmpl-preview/<rel-path>.json.
Role & context detection — auto-detects role (book, mesgen, extranet, app) from path and filename. For .inc fragments, shows which parent templates include it and lets you pick the render context (chip strip above the preview).
Scope-aware mock inference — loop-local variables are placed inside the correct loop, not at root. Scope chip on each row shows loop: b_xyz so you know where edits land.
AI-assisted mocks — ✨ Fill with AI sends the template + action file + test fixtures to Claude and generates realistic values. Requires an Anthropic API key (set via command palette: TMPL: Set API Key).
Import from test fixture — 📋 Import from test parses Perl .t test files and populates values with no API key needed. Often more accurate than AI for state-heavy pages.
45-locale translations — switch language from the Translations tab. Missing keys show as humanised text (ugcc_review_form_header__property_name → "Property name") instead of raw [[key_name]] noise.
Info banners — structured banners for parse errors, unresolved includes, missing variables, empty output, and script load failures. Nothing is silently dropped.
Settings
Open the settings panel via the gear icon in the TMPL Preview sidebar header.
| Setting |
Default |
Purpose |
tmplPreview.codeRoot |
— |
Parent folder containing your repos (e.g. ~/git_tree). Enables role detection and AI mocks. |
tmplPreview.includedRepos |
[] |
Repos under codeRoot to search for role detection and test fixtures. |
tmplPreview.roleSegmentDepth |
1 |
Path segments below each repo root used as the role label. |
tmplPreview.staticUrlPrefix |
https://t-cf.bstatic.com |
Base URL for TMPL_STATIC_URL paths. |
tmplPreview.translationLanguage |
en-gb |
Language for TMPL_TRANS lookups (45 locales supported). |
tmplPreview.injectSiblingAssets |
true |
Auto-inject sibling .css/.js next to the previewed file. |
tmplPreview.autoInjectByRole |
— |
Per-role CSS/JS inject override (e.g. mesgen: { css: true, js: false }). |
tmplPreview.useRenderedPartialCache |
true |
Reuse rendered partials when included from a parent preview. |
tmplPreview.useClaude |
false |
Use local Claude CLI as fallback when role/parent detection fails. |
Cache layout
<workspace>/.tmpl-preview/ (gitignored, auto-created)
├── _globals.json workspace-level page assets
├── _bui_tokens.json resolved BUI design token cache
├── _translations.json translation cache, keyed by language
├── _bundles/<key>.css built CSS bundle cache
├── _assets/<rel>.rendered.css/js pre-rendered TMPL asset files
├── _renders/<rel>.html partial render cache
├── _ai-cache/ AI mock generation cache
├── _role-overrides.json per-file manual role overrides
└── <rel-path>.json per-template mocks
Troubleshooting
| Symptom |
Fix |
| Preview looks unstyled |
Set Code root in Settings and select affiliate_data as a repo. Check View → Output → TMPL Preview for bundle discovery logs. |
| Variable edits don't change the preview |
Check the scope chip — if it says loop: b_xyz, the value lives inside a loop array. Expand the loop row and edit there. |
✨ Fill with AI does nothing |
Set your API key via TMPL: Set API Key in the command palette. |
Missing translations / [[key]] shown |
Extension can't reach TranslateAPI. Connect to VPN, or the key is missing from _translations.json — clear the translation cache in Settings. |
<!-- include not resolved --> in output |
Namespace isn't mapped. Check tmplPreview.namespaceMap or add a fallback root. |
| Role pill says "role unknown" |
Click the pill and pick the role manually — the override persists per-file. |
Limitations
- Best-effort renderer, not a real BHC engine. Coverage is ~95% of the dialect; exotic helpers and action-supplied vars may not evaluate exactly as production. A Perl bridge (bazel-backed) is scaffolded and coming soon.
- Interactive scripts (jQuery, Prototype) can't run inside the VS Code webview due to CSP. Use Open in browser for interactive testing.
- Conditional CSS loaded under specific
b_action branches (iPhone, RTL, cancellation_tpi…) is not auto-injected — toggle manually in the Assets tab.
- Role detection is heuristic; authoritative repo-grep backend is in progress.
Contributing
npm run build # esbuild bundle
npm run typecheck # TypeScript check
npm run dev:install # build + sync to installed extension (then reload VS Code window)
npm run release # build + package releases/tmpl-preview-latest.vsix