Skip to content

The generative-dom Umbrella Package

generative-dom is a single-install distribution: the core engine, every first-party plugin, and every framework adapter behind one npm package and a namespaced subpath export map. It exists so a project carries one dependency line, one version, and one import root — and trusts the bundle's tree-shaking instead of curating a per-plugin install list.

This page documents the umbrella's contents, the test for picking it over the granular @generative-dom/* packages, and the four built-in presets (minimal, commonmark, github, full).

When to Choose the Umbrella

The crisp test: does anything in your project need to disagree with the umbrella's release cadence? If the answer is no, use generative-dom. The umbrella is the right default for an application — a chat UI, a docs site, a playground, a CLI consumer. The granular packages exist for the cases where lockstep versioning is the wrong answer:

  • You publish a downstream library that has its own consumers. Re-exporting generative-dom would force them to inherit your version pin.
  • You need to pin one plugin to a specific patch — for a regression hold, a security fix, or a fork — without dragging the rest along.
  • You run on a bundler that mishandles subpath exports (older webpack 4, some legacy Rollup configs) and you would rather depend on flat package names.
  • You require a plugin the umbrella does not re-export — currently @generative-dom/plugin-companion and @generative-dom/plugin-rich-table. Mix granular for those; see Mixing umbrella and granular below.

Runtime behavior is identical either way. The umbrella inlines workspace @generative-dom/* packages at build time via Vite library mode; after a competent bundler's tree-shaking, the wire sizes are within minifier noise of the granular install.

Install

bash
pnpm add generative-dom

The umbrella declares no required runtime dependencies. React, Vue, Svelte, Angular, Lit, and Astro are listed as optional peers -- they are pulled only when you import the matching subpath, and your package manager will not fail if you do not install them.

Import Surface

The umbrella exposes 27 subpath exports across five groups:

SubpathWhat it gives you
generative-domGenerativeDom, createGenerativeDom, all public types
generative-dom/pluginsBarrel re-export of every plugin factory
generative-dom/plugins/<name>One plugin per subpath — 15 in total: markdown-base, markdown-inline, markdown-heading, markdown-code, markdown-quote, markdown-list, markdown-table, markdown-link, highlight, custom-elements, events, interactive, cursor, code-actions, external-renderer
generative-dom/presets/<name>The four presets covered below: minimal, commonmark, github, full
generative-dom/<framework>Framework adapter — react, vue, svelte, angular, lit, astro (6 in total)

Prefer subpath imports over the barrel. import { highlight } from 'generative-dom/plugins' resolves the barrel module first, which means tree-shaking has to prove every other plugin export is unused. Modern bundlers usually get this right with "sideEffects": false (which the umbrella sets), but import { highlight } from 'generative-dom/plugins/highlight' skips the question entirely. For a ~14 KB gzipped plugin like highlight, the deterministic path is worth the longer import string.

The Default Factory

createGenerativeDom is a thin convenience that pre-configures the CommonMark preset and appends any caller-supplied plugins after it:

ts
import { createGenerativeDom } from 'generative-dom';

const md = createGenerativeDom({
  container: document.getElementById('out')!,
});

md.push('# Hello **world**');
md.flush();

The factory exists so a "just give me a working markdown renderer" snippet fits in three lines. If you need anything other than CommonMark output (e.g. syntax highlighting, an LLM cursor, tool-call custom elements) skip the factory and instantiate GenerativeDom directly with an explicit plugin list.

The Four Built-in Presets

Each preset is exported from its own subpath so importing one preset does not pull the others into your bundle. Every preset is a zero-argument function returning GenerativeDomPlugin[] -- you can spread it, filter it, or pass it straight to the constructor.

presets/minimal — Just Paragraphs

ts
import { GenerativeDom } from 'generative-dom';
import { minimal } from 'generative-dom/presets/minimal';

const md = new GenerativeDom({ plugins: minimal() });

Contains: markdownBase only.

The minimal preset registers nothing but the catch-all paragraph plugin. Use it when you want a guaranteed-stable baseline plus a custom plugin set you are still designing -- for example, an experimental DSL where you control every block matcher and only need the engine's paragraph fallback for unmatched lines.

In practice most teams reach for commonmark instead. minimal is the one to pick when even ATX headings would be too much.

presets/commonmark — The Eight CommonMark Plugins

ts
import { GenerativeDom } from 'generative-dom';
import { commonmark } from 'generative-dom/presets/commonmark';

const md = new GenerativeDom({ plugins: commonmark() });

Contains: markdownBase, markdownInline, markdownHeading, markdownCode, markdownQuote, markdownList, markdownLink, markdownTable.

Full CommonMark surface plus GFM-style pipe tables. This is what createGenerativeDom uses by default and what most static-content sites should start with: blog posts, docs pages, READMEs, and anything else where the input is hand-authored Markdown rather than streamed model output.

commonmark() is also the preset to spread when you need CommonMark plus something specific:

ts
import { commonmark } from 'generative-dom/presets/commonmark';
import { highlight }  from 'generative-dom/plugins/highlight';

const md = new GenerativeDom({
  plugins: [...commonmark(), highlight()],
});

That snippet is exactly what the next preset gives you in one import.

presets/github — CommonMark + Syntax Highlighting

ts
import { GenerativeDom } from 'generative-dom';
import { github } from 'generative-dom/presets/github';

const md = new GenerativeDom({ plugins: github() });

Contains: everything from commonmark() plus highlight().

Named for the rendering style, not for any GitHub-specific syntax: CommonMark plus syntax-highlighted fenced code blocks. The bundled highlighter ships 10 built-in languages — JavaScript, TypeScript, HTML, CSS, JSON, C, C++, Bash, Julia, Zig — and exposes a LangDef API for declaring more without rebuilding the package.

This is the smallest preset that includes highlight. If you are spreading commonmark() and appending highlight() by hand, switch to github() — it is the same plugin list with one fewer line of setup.

Extended GFM features beyond pipe tables and code highlighting are not included today: task lists, footnotes, strikethrough autolinks, and the GFM www. shorthand all wait on their own plugins. (Standard CommonMark autolinks — <https://example.com> — work; they are part of markdownLink.) If you depend on a specific GFM extension, watch the plugin index or vendor your own.

presets/full — Every Zero-Config Plugin

ts
import { GenerativeDom } from 'generative-dom';
import { full } from 'generative-dom/presets/full';

const md = new GenerativeDom({ plugins: full() });

Contains: everything from github() plus customElements, events, interactive, cursor, codeActions.

full is the kitchen-sink preset for streamed model output — chat UIs, copilots, anything where the source is an LLM. It adds:

  • customElements — render md-clock, md-plot, md-counter, and other Web Components emitted in the stream.
  • events — invisible event-emitter elements that fire DOM events without rendering anything visible (handy for tool-call hooks).
  • interactive — stateful interactive elements that survive re-renders.
  • cursor — the blinking caret on the in-flight token.
  • codeActions — a language label and a copy button on every fenced code block.

full deliberately excludes two plugins:

  • external-renderer — requires caller-supplied adapters (e.g. mermaid, KaTeX), so it cannot be zero-config. Add it explicitly when you need it.
  • companion — a niche plugin for projects using the custom-elements-collection ecosystem (ce-* and lesson-* tags). Add it explicitly if you need that specific tag namespace.

To extend full with one of those:

ts
import { full } from 'generative-dom/presets/full';
import { externalRenderer } from 'generative-dom/plugins/external-renderer';

const md = new GenerativeDom({
  plugins: [...full(), externalRenderer({ mermaid: renderMermaid })],
});

Choosing a Preset

If you are rendering...Start with
Hand-authored Markdown (blog posts, docs)commonmark
Hand-authored Markdown with code samplesgithub
Streamed LLM output (chat, copilots)full
Your own DSL with custom matchersminimal

You can always start narrow and add plugins as needs surface. The presets are plain GenerativeDomPlugin[], so dropping or replacing one is a one-line change:

ts
import { full }      from 'generative-dom/presets/full';
import { highlight } from 'generative-dom/plugins/highlight';

// Replace the bundled highlighter with a configured instance
const plugins = [
  ...full().filter((p) => p.name !== 'highlight'),
  highlight({ include: ['typescript', 'json', 'bash'] }),
];

Custom Plugin Sets Without a Preset

Sometimes you know exactly which plugins you want and do not want a preset to make the decision. Import them individually from generative-dom/plugins/*:

ts
import { GenerativeDom }    from 'generative-dom';
import { markdownHeading }  from 'generative-dom/plugins/markdown-heading';
import { markdownTable }    from 'generative-dom/plugins/markdown-table';
import { highlight }        from 'generative-dom/plugins/highlight';

const md = new GenerativeDom({
  plugins: [markdownHeading(), markdownTable(), highlight()],
});

Order is plugin priority, not import order — the engine sorts plugins by their internal priority field at registration time. The list above renders identically to the same plugins listed in any other order. The Plugin Overview explains the priority ranges if you need to reason about precedence (e.g. why highlight at priority 95 wins over markdown-code at 100 for fenced blocks).

Framework Adapters

Each adapter lives behind its own subpath and depends on the matching framework as an optional peer. Install only the peer you actually use.

tsx
// React
import { GenerativeDomRenderer } from 'generative-dom/react';
ts
// Vue
import { GenerativeDomRenderer } from 'generative-dom/vue';
ts
// Svelte
import { createGenerativeDom, generativeDomAction } from 'generative-dom/svelte';
ts
// Angular (standalone component)
import { GenerativeDomRendererComponent } from 'generative-dom/angular';
ts
// Lit — side-effect import registers <generative-dom-renderer>
import 'generative-dom/lit';
ts
// Astro
import { defineGenerativeDomIntegration } from 'generative-dom/astro';

The adapters wrap the same GenerativeDom class shipped from generative-dom, so anything you can pass to the constructor (presets, custom plugin sets, options like debounceMs) also works through the adapter's props or arguments. Framework-specific docs live under each adapter package's README.

Bundle Footprint

Order-of-magnitude gzip sizes for the published builds, ES2022 target, no application minifier applied:

PresetPluginsApprox. gzip
minimal1~28 KB (engine + base)
commonmark8~55 KB (engine + 8 markdown plugins)
github9~70 KB (commonmark + highlight)
full14~80 KB (github + 5 LLM-oriented plugins)

@generative-dom/core (~25 KB gz) is the dominant line item across every preset; plugin-highlight (~14 KB gz, with all 10 languages) is the second. Past those two, individual plugins are 1–7 KB gzipped each. The delta from commonmark to github is essentially the highlighter; the delta from github to full is the cursor, interactive, custom-elements, events, and code-actions layer summed.

These figures are upper bounds. A production bundler (Vite, Rollup, esbuild with --minify) will run terser-equivalent minification on top of the published code and typically claw back 20–35%. Measure your own bundle before optimising — the absolute number depends on what else lives in your application chunk.

If highlight is the dominant line item and you do not need every language, scope it explicitly:

ts
import { commonmark } from 'generative-dom/presets/commonmark';
import { highlight }  from 'generative-dom/plugins/highlight';

const md = new GenerativeDom({
  plugins: [
    ...commonmark(),
    highlight({ include: ['typescript', 'json', 'bash'] }),
  ],
});

The include option scopes the matchBlock allowlist; it does not currently strip the unused LangDef modules from the bundle (that requires custom build wiring against the granular @generative-dom/plugin-highlight source). What you save is parser-loop work on streamed input, not bytes on the wire.

Mixing Umbrella and Granular

The granular @generative-dom/* packages stay published independently. You can install them alongside the umbrella, but you should know what happens when you do.

The umbrella is built with Vite library mode and inlines every workspace @generative-dom/* source it depends on into its own dist/. From npm's perspective the bundled copies are just bytes inside the generative-dom package — they are not deduplicated against a separately installed @generative-dom/plugin-highlight, because they are not the same package. The same module identity rule applies to the @generative-dom/core exports: the GenerativeDom class re-exported from generative-dom is not === identical to the one re-exported from a separately installed @generative-dom/core if both end up in the graph.

The practical consequences:

  1. Double bytes. A separately installed plugin lives next to its umbrella-bundled twin. For plugin-highlight that is ~14 KB gz of duplicate code on the wire. Your bundler will not warn about it.
  2. Split state. Plugin singletons, registries, or module-level caches live in whichever copy you imported. Two cursor() instances from two different module identities do not share a singleton; mixing them inside one GenerativeDom instance is undefined territory.
  3. Type drift. Two copies of GenerativeDomPlugin from different module identities are structurally compatible but not nominally equal — TypeScript will accept assignments, but a instanceof check will not.

The legitimate reasons to mix anyway:

  • Plugin not exposed by the umbrella. @generative-dom/plugin-companion (the ce-* / lesson-* namespace for the custom-elements-collection ecosystem) and @generative-dom/plugin-rich-table (DOM-driven extended tables) are not in the umbrella's subpath map. Install them granularly when you need them.
  • Patch-pinning one plugin. You need @generative-dom/plugin-highlight@1.4.2 while the umbrella has shipped 1.5.x. Install the granular package, import it from @generative-dom/plugin-highlight, and ensure no other code path imports generative-dom/plugins/highlight (which would resolve to the umbrella-bundled version and reintroduce duplication).

Rule of thumb: import any given plugin from exactly one of generative-dom/plugins/<name> or @generative-dom/plugin-<name> across your whole codebase. Grep your imports during review.

Next