Architecture
For contributors: how Leadlens is built. To use Leadlens, start with Getting started.
Leadlens is a VS Code extension written in TypeScript, bundled with esbuild into a single out/extension.js. The email-client knowledge comes from the open-source @emailens/engine; Leadlens adds Marketo understanding, source mapping, the editor integration and the preview interface.
Design principles
Section titled “Design principles”- Source text in, ranges out. Email HTML depends on conditional comments, VML and unusual table markup that a browser parser is allowed to rewrite. Leadlens works on source text and offsets, never on a reserialised DOM, so it can report exact positions and never alters markup it doesn’t own.
- The source is never changed except by an explicit quick fix. Previews, Marketo preparation and composition all produce copies.
- Editor-free logic. Everything that makes a decision lives in modules that don’t import
vscode, so it’s tested directly against the real engine.extension.tsandpreview/panel.tstranslate results into VS Code’s types. - Unknown stays unknown. Marketo output that can’t be produced locally is labelled, never invented.
The analysis pipeline
Section titled “The analysis pipeline”When a document changes, extension.ts runs it through these stages:
- Format and compile (
compile.ts). HTML passes straight through. MJML is compiled with the workspace’s ownmjmlpackage, only in trusted workspaces, with a timeout. A finding in the compiled output is mapped back to the MJML source where that mapping is unambiguous. - Linked stylesheets and fragments (
sharded.ts). Local<link rel="stylesheet">files are folded into the analysed copy, with segments recording which file each range came from. Fragments are recognised so whole-email rules are skipped for them. - Marketo preparation (
marketo.ts). The source is classified astemplate,email,fragmentorgeneric. Declarations, references, tokens, containers, modules and editable regions are scanned from the source text. Variables, preview values and token fallbacks are substituted into a copy, and structural issues are collected. Segments map every substitution back to the source characters that produced it. - Component composition (
marketo-compose.ts,component-preview.ts). When a component is paired with a template, its source replaces the chosen editable region in a copy of the template, and segments record which file each range belongs to. - Audit (
@emailens/engine). The prepared copy goes throughauditEmailwith positions enabled.audit-context.tsremoves conclusions that don’t hold for Marketo, such as treating the HTML<title>as the message subject. - Findings (
report.ts). The engine’s report sections become a flat list of findings, with client attribution, skip and severity settings, and occurrence limits applied. - Source mapping and suppression (
sharded.ts,suppress.ts). Each finding’s offset in the prepared copy is mapped through the segments to the file and characters that own it.leadlens-disable…comments in that file are applied. - Diagnostics (
extension.ts). Findings become VS Code diagnostics on the right documents, including linked stylesheets and composed components.
The preview
Section titled “The preview”preview/panel.ts owns one webview panel that follows the active email.
preview/render.tsasks the engine to transform the prepared HTML for each requested client, then applies the client’s dark-mode simulation to that transformed result, so light and dark always share the same layout.preview/local-assets.tsfinds relative image paths in the source without reparsing it, andpreview/local-asset-resolver.tsresolves them inside the workspace (following symbolic links and refusing paths that leave it). They’re swapped for placeholders before the client transform and for webview URIs afterwards.preview/content.tsbuilds the webview markup. The email renders in asrcdociframe sandboxed without scripts or same-origin access, under a content security policy that allows only inline styles, data and local images, and remote images when the user enables them.marketo-preview.tsturns declarations and tokens into the Preview values controls, and records where each value came from.
Editor features
Section titled “Editor features”| Module | Feature |
|---|---|
hover.ts |
Per-client CSS support for the property under the cursor. |
marketo-hover.ts |
Marketo variable, token, module and region details, and Go to Definition. |
marketo-outline.ts |
The Outline and breadcrumb hierarchy. |
marketo-completion.ts |
${variable} completions and declaration snippets. |
fixes.ts |
Mechanical quick fixes, computed as offset, length and replacement text. |
prepared-document.ts, prepared-html.ts |
The read-only prepared-HTML virtual document. |
workspace-config.ts, configuration.ts |
Project configuration files, settings and their precedence. |
The engine
Section titled “The engine”@emailens/engine is a dependency, pinned to an exact version and bundled into the extension. It supplies the client profiles, compatibility data (derived from caniemail.com), client transforms, dark-mode simulation, the audit checks and some fixes.
New engine versions arrive as Dependabot pull requests. CI runs the full unit and integration suites against them, so a green PR means Leadlens works with the new version. A failing one means the engine changed something Leadlens depends on, and needs a code change before it’s merged.
build.mjs bundles src/extension.ts with esbuild. Two details matter:
- Some engine dependencies ship ESM builds that load data with
createRequire(import.meta.url), which breaks once bundled into CommonJS. The build resolves those packages to their CommonJS entry points. - Every non-watch build writes
THIRD_PARTY_NOTICES.mdfrom esbuild’s record of exactly what it bundled, including each package’s licence text, so the notices always match the shipped code.
- Unit tests (
src/__tests__, run withbun test) cover the editor-free modules against the real engine, including fixtures for an inherited-style Marketo template and the sample newsletter. - Integration tests (
src/integration, run withbun run test:integration) launch a real VS Code with the built extension and check activation, diagnostics, quick fixes, hovers, configuration and the preview. - CI runs both, plus type checking and packaging, on every pull request and on
main.