{"_id":"mdast-jsx","_rev":"3-36530276d918844cc983bfee6f08bf0a","name":"mdast-jsx","dist-tags":{"latest":"0.1.1"},"versions":{"0.0.0":{"name":"mdast-jsx","version":"0.0.0","keywords":["mdast","jsx","jsx-runtime","markdown","unified","remark","ast"],"license":"MIT","_id":"mdast-jsx@0.0.0","maintainers":[{"name":"gregnr","email":"greg.nmr@gmail.com"}],"homepage":"https://github.com/supabase-community/mdast-jsx#readme","bugs":{"url":"https://github.com/supabase-community/mdast-jsx/issues"},"dist":{"shasum":"1fd25851ed8df5de7f1612b3c9dfe5b588843b97","tarball":"https://registry.npmjs.org/mdast-jsx/-/mdast-jsx-0.0.0.tgz","fileCount":9,"integrity":"sha512-iw4KP87QxVBiyWn2wXF1Zwo9r0e6FhJupnUKZD6d+AoUQXuRtK2q2IB3jg9zsGeKVgbyCysKAk3EZwnqs7+9BA==","signatures":[{"sig":"MEQCIETXVc2LAUtLMUTs1zQ3sW1Bc9XtjccQeU/UPXNHRzeVAiBLspZLqvaqs5SwXv5NfaCegMCLw1WZ8GeVXf2CTdmJGQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":14764},"main":"./dist/index.js","type":"module","_from":"file:mdast-jsx-0.0.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./jsx-runtime":{"types":"./dist/jsx-runtime.d.ts","import":"./dist/jsx-runtime.js"},"./jsx-dev-runtime":{"types":"./dist/jsx-dev-runtime.d.ts","import":"./dist/jsx-dev-runtime.js"}},"scripts":{"test":"vitest run","build":"tsdown","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"gregnr","email":"greg.nmr@gmail.com"},"_resolved":"/private/var/folders/ct/6sqtvhf15pz6n55zryf4jzcw0000gn/T/e3c8985770acab53548273211fdc26fc/mdast-jsx-0.0.0.tgz","_integrity":"sha512-iw4KP87QxVBiyWn2wXF1Zwo9r0e6FhJupnUKZD6d+AoUQXuRtK2q2IB3jg9zsGeKVgbyCysKAk3EZwnqs7+9BA==","repository":{"url":"git+https://github.com/supabase-community/mdast-jsx.git","type":"git"},"_npmVersion":"10.9.2","description":"Generate markdown using JSX","directories":{},"_nodeVersion":"22.17.0","dependencies":{"@types/mdast":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","tsdown":"^0.21.1","vitest":"^4.0.18","typescript":"^5.0.0","mdast-util-gfm":"^2.0.2","mdast-util-to-markdown":"^1.5.0"},"_npmOperationalInternal":{"tmp":"tmp/mdast-jsx_0.0.0_1782362775519_0.2629527041555333","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"mdast-jsx","version":"0.1.0","keywords":["mdast","jsx","jsx-runtime","markdown","unified","remark","ast"],"license":"MIT","_id":"mdast-jsx@0.1.0","maintainers":[{"name":"gregnr","email":"greg.nmr@gmail.com"}],"homepage":"https://github.com/supabase-community/mdast-jsx#readme","bugs":{"url":"https://github.com/supabase-community/mdast-jsx/issues"},"dist":{"shasum":"de237f75c470cb69be791472896a32724e448393","tarball":"https://registry.npmjs.org/mdast-jsx/-/mdast-jsx-0.1.0.tgz","fileCount":9,"integrity":"sha512-j7cNQvwXE4vB2sJUrVOfdMKt3umkdoYm9ZIl49ycArR1i5r6ArX2mn88rLDoDcCTgQoqgpPSEzBCM5VB6RzYBQ==","signatures":[{"sig":"MEYCIQDBg4FUn0FIt9tvxPV/TOevih52p8bQiRDGW5Jx6XaKjQIhANs3nsK+Cq6U6xysp05eZwrOMOUSCUFJU4JoC9VytTin","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mdast-jsx@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":14764},"main":"./dist/index.js","type":"module","_from":"file:mdast-jsx-0.1.0.tgz","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./jsx-runtime":{"types":"./dist/jsx-runtime.d.ts","import":"./dist/jsx-runtime.js"},"./jsx-dev-runtime":{"types":"./dist/jsx-dev-runtime.d.ts","import":"./dist/jsx-dev-runtime.js"}},"scripts":{"test":"vitest run","build":"tsdown","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:280f8da3-abde-4e6f-bfb0-6bba61ba8b9c"}},"_resolved":"/tmp/dc8470ef9dde706d1faafe23829869ac/mdast-jsx-0.1.0.tgz","_integrity":"sha512-j7cNQvwXE4vB2sJUrVOfdMKt3umkdoYm9ZIl49ycArR1i5r6ArX2mn88rLDoDcCTgQoqgpPSEzBCM5VB6RzYBQ==","repository":{"url":"git+https://github.com/supabase-community/mdast-jsx.git","type":"git"},"_npmVersion":"11.13.0","description":"Generate markdown using JSX","directories":{},"_nodeVersion":"24.16.0","dependencies":{"@types/mdast":"^3.0.0"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","tsdown":"^0.21.1","vitest":"^4.0.18","typescript":"^5.0.0","mdast-util-gfm":"^2.0.2","mdast-util-to-markdown":"^1.5.0"},"_npmOperationalInternal":{"tmp":"tmp/mdast-jsx_0.1.0_1782363171734_0.11764416269605449","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"mdast-jsx","version":"0.1.1","description":"Generate markdown using JSX","type":"module","license":"MIT","repository":{"type":"git","url":"git+https://github.com/supabase-community/mdast-jsx.git"},"homepage":"https://github.com/supabase-community/mdast-jsx#readme","bugs":{"url":"https://github.com/supabase-community/mdast-jsx/issues"},"keywords":["mdast","jsx","jsx-runtime","markdown","unified","remark","ast"],"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./jsx-runtime":{"import":{"types":"./dist/jsx-runtime.d.ts","default":"./dist/jsx-runtime.js"},"require":{"types":"./dist/jsx-runtime.d.cts","default":"./dist/jsx-runtime.cjs"}},"./jsx-dev-runtime":{"import":{"types":"./dist/jsx-dev-runtime.d.ts","default":"./dist/jsx-dev-runtime.js"},"require":{"types":"./dist/jsx-dev-runtime.d.cts","default":"./dist/jsx-dev-runtime.cjs"}}},"dependencies":{"@types/mdast":"^3.0.0"},"devDependencies":{"mdast-util-gfm":"^2.0.2","mdast-util-to-markdown":"^1.5.0","tsdown":"^0.21.1","tsx":"^4.0.0","typescript":"^5.0.0","vitest":"^4.0.18"},"scripts":{"build":"tsdown","typecheck":"tsc --noEmit","test":"vitest run","test:watch":"vitest"},"_id":"mdast-jsx@0.1.1","_integrity":"sha512-7EVQNn9tWVIOaSHh0hj0mqPiAwuZ30SSsIazRUqKpU6Nj6Xud4LEjaO+k+8eptwCNF8jKT52lPiNhugaged0WA==","_resolved":"/tmp/6edac030941e3f2add489018dd1deaeb/mdast-jsx-0.1.1.tgz","_from":"file:mdast-jsx-0.1.1.tgz","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-7EVQNn9tWVIOaSHh0hj0mqPiAwuZ30SSsIazRUqKpU6Nj6Xud4LEjaO+k+8eptwCNF8jKT52lPiNhugaged0WA==","shasum":"2a8c8017bc0a7c2890f52208441922c629518ada","tarball":"https://registry.npmjs.org/mdast-jsx/-/mdast-jsx-0.1.1.tgz","fileCount":15,"unpackedSize":17922,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/mdast-jsx@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDSYOqcK6KYft1UDBfjBDgrYdQQMWy9ChzKADFz6Q4WWAiBfJFbV0MiU8oX48Cx5H20J8bvkCWd4LtmCVvX0c0pnow=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:280f8da3-abde-4e6f-bfb0-6bba61ba8b9c"}},"directories":{},"maintainers":[{"name":"gregnr","email":"greg.nmr@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mdast-jsx_0.1.1_1782364672847_0.9973187728661268"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-25T04:46:15.229Z","modified":"2026-06-25T05:17:53.258Z","0.0.0":"2026-06-25T04:46:15.649Z","0.1.0":"2026-06-25T04:52:51.893Z","0.1.1":"2026-06-25T05:17:53.005Z"},"bugs":{"url":"https://github.com/supabase-community/mdast-jsx/issues"},"license":"MIT","homepage":"https://github.com/supabase-community/mdast-jsx#readme","keywords":["mdast","jsx","jsx-runtime","markdown","unified","remark","ast"],"repository":{"type":"git","url":"git+https://github.com/supabase-community/mdast-jsx.git"},"description":"Generate markdown using JSX","maintainers":[{"name":"gregnr","email":"greg.nmr@gmail.com"}],"readme":"# mdast-jsx\n\nA tiny JSX runtime that builds [mdast](https://github.com/syntax-tree/mdast) (markdown AST) nodes, so that you can author markdown using JSX.\n\n```tsx\n/** @jsxRuntime automatic */\n/** @jsxImportSource mdast-jsx */\n\nimport { toMarkdown } from 'mdast-util-to-markdown';\n\ntoMarkdown(\n  <>\n    <heading depth={2}>Release notes</heading>\n    <paragraph>\n      Bumped to <inlineCode value=\"v2.0.0\" />.\n    </paragraph>\n  </>,\n);\n```\n\nproduces:\n\n```md\n## Release notes\n\nBumped to `v2.0.0`.\n```\n\n## Why?\n\nUp until now markdown has been a source format - something you write, not something you generate. But AI is changing that - agents often prefer markdown because it's terse and token efficient, so we increasingly want to generate it as the final output for LLMs to consume.\n\nNone of this matters when the markdown is static - if you're hand-writing prose, just write a `.md` file. JSX becomes attractive when the markdown is dynamic, built from data with loops and conditions. A common scenario is an existing data-driven React page that you want to render as markdown (e.g. a dynamic list of clients pulled from a DB) - the same component logic, just producing markdown instead of DOM.\n\n### Why not string concatenation?\n\nYou could dynamically build markdown using string concatenation, but you end up hand-managing markdown's whitespace and escaping rules, which is easy to get subtly wrong. More importantly, you lose type safety - a malformed structure shows up as broken output, not as an error while you're writing it.\n\n### Why not object notation?\n\nYou could instead build the markdown AST nodes by hand with object notation, but the DX is pretty poor (less readable, harder to maintain):\n\n```ts\n{ type: 'heading', depth: 2, children: [{ type: 'text', value: 'Setup' }] }\n```\n\nJSX is the perfect tool for the job - markdown is a tree, and JSX is just a syntax for building trees. It's the same AST node but written the way you'd write any markup:\n\n```tsx\n<heading depth={2}>Setup</heading>\n```\n\nYou keep the serializer's correctness and get JSX's ergonomics on top:\n\n```tsx\n<>\n  <heading depth={4}>{group.heading}</heading>\n  {group.keys.map((key) => (\n    <Client client={clientsByKey.get(key)} />\n  ))}\n  {isHosted && (\n    <paragraph>Authenticate in your browser on first use.</paragraph>\n  )}\n</>\n```\n\nBecause it's JSX, you get the usual tooling for free - loop with `.map`, branch with `&&` or ternaries, factor out reusable components - all type-checked against the markdown schema (i.e. `<heading>` requires `depth`, `<code>` can't take children, an unknown tag won't compile).\n\n### Why not just HTML?\n\nA reasonable alternative is to render the components to HTML (they're JSX after all) and convert that to markdown with rehype-remark or turndown. The problem is that HTML can express more than markdown can, so converting back is lossy - the converter has to guess how to represent things that have no markdown equivalent, and code blocks in particular come back as messy `<span>` markup. It's also more steps (JSX -> HTML -> parse -> markdown -> stringify) and you lose type safety on the markdown you actually emit. Going straight to mdast avoids all of that: you stay in markdown the whole time and can only produce valid markdown.\n\n## Install\n\n```shell\nnpm i mdast-jsx\n```\n\n```shell\npnpm add mdast-jsx\n```\n\n## Usage\n\nAdd the following pragmas to the top of your `.tsx` file, then write components (functions) that return mdast nodes:\n\n```tsx\n/** @jsxRuntime automatic */\n/** @jsxImportSource mdast-jsx */\n\nexport const MyComponent = () => (\n  <>\n    <heading depth={2}>Title</heading>\n    <paragraph>Body text.</paragraph>\n  </>\n);\n```\n\nThe pragmas are per-file comments the compiler reads (or set `jsxImportSource: \"mdast-jsx\"` in your tsconfig to apply it everywhere):\n\n- `@jsxRuntime automatic` compiles JSX into `jsx(...)` calls (the modern transform) instead of `React.createElement`.\n- `@jsxImportSource mdast-jsx` points those calls at this runtime, so `<heading />` builds an mdast node instead of a React element.\n\n### Rendering to markdown\n\nThe JSX just returns mdast nodes, so serialize them with [`mdast-util-to-markdown`](https://github.com/syntax-tree/mdast-util-to-markdown):\n\n```tsx\nimport { toMarkdown } from 'mdast-util-to-markdown';\n\nconst md = toMarkdown(MyComponent());\n// \"## Title\\n\\nBody text.\\n\"\n```\n\nFor GitHub-flavored markdown (tables, strikethrough, task lists), add the gfm extension:\n\n```tsx\nimport { gfmToMarkdown } from 'mdast-util-gfm';\n\nconst md = toMarkdown(\n  <table align={['left', 'right']}>\n    <tableRow>\n      <tableCell>Name</tableCell>\n      <tableCell>Count</tableCell>\n    </tableRow>\n    <tableRow>\n      <tableCell>a</tableCell>\n      <tableCell>1</tableCell>\n    </tableRow>\n  </table>,\n  { extensions: [gfmToMarkdown()] },\n);\n```\n\nproduces:\n\n```md\n| Name | Count |\n| :--- | ----: |\n| a    |     1 |\n```\n\n### Compiling\n\nThis is a standard automatic JSX runtime: if a tool can compile React's automatic runtime, it can compile this - the only difference is `jsxImportSource`. You opt in either with the per-file pragmas shown above, or once for the whole project with `jsxImportSource: \"mdast-jsx\"` (and `jsx: \"react-jsx\"`) in your tsconfig.\n\n> Note this doesn't use React - the JSX is just syntax the compiler turns into plain mdast objects. `react-jsx` is a legacy name for the `automatic` transform, not a React dependency.\n\nIn practice you will:\n\n- **Just run it** - `tsx file.tsx` or `bun file.tsx` execute `.tsx` directly with the transform included; the fastest way to try it.\n- **Compile in a project** - tsc (set `jsx: \"react-jsx\"` to emit runnable `jsx()` calls), esbuild, and Babel all support the automatic runtime and `jsxImportSource`, via their config or the pragmas. (With tsc's `jsx: \"preserve\"` the JSX is emitted untransformed for a downstream tool, so pair it with a runner or bundler.)\n- **Use Vite / Vitest** - needs no JSX-specific config: both read `jsx` and `jsxImportSource` from your tsconfig and honor the per-file pragmas, regardless of the underlying transformer (esbuild on Vite ≤7 / Vitest ≤3, Oxc on Vite 8 / Vitest 4).\n\n## How it maps to mdast\n\nThe JSX is almost a direct reflection of mdast. A lowercase tag becomes a node of that `type`, props become the node's fields, and children become its `children`:\n\n```tsx\n<heading depth={2}>Setup</heading>\n// { type: 'heading', depth: 2, children: [{ type: 'text', value: 'Setup' }] }\n```\n\nBare strings become `text` nodes, and inline elements nest the way you'd expect:\n\n```tsx\n<paragraph>\n  Run <inlineCode value=\"npm i\" /> to install.\n</paragraph>\n// {\n//   type: 'paragraph',\n//   children: [\n//     { type: 'text', value: 'Run ' },\n//     { type: 'inlineCode', value: 'npm i' },\n//     { type: 'text', value: ' to install.' },\n//   ],\n// }\n```\n\nIf you're ever unsure which tags are available or how to use them, check the [mdast schema](https://github.com/syntax-tree/mdast#nodes).\n\nA few rules cover the spots where mdast isn't perfectly uniform:\n\n- **Literal nodes** (`code`, `inlineCode`, `html`, `yaml`) hold their content in a `value` field, not children, so they're self-closing and take a `value` prop:\n\n  ```tsx\n  <code lang=\"bash\" value=\"npm run build\" />\n  // { type: 'code', lang: 'bash', value: 'npm run build' }\n  ```\n\n- **Capitalized tags are components** - called as functions, exactly like React:\n\n  ```tsx\n  const Item = ({ label }) => (\n    <listItem>\n      <paragraph>{label}</paragraph>\n    </listItem>\n  );\n  ```\n\n- **Fragments (`<>`) become a `root`** and flatten when nested, so `.map` drops in cleanly:\n\n  ```tsx\n  <root>\n    {items.map((i) => (\n      <paragraph>{i}</paragraph>\n    ))}\n  </root>\n  ```\n\n- `null` / `false` / whitespace-only children are dropped, so `{cond && <x />}` does the obvious thing.\n\n### HTML\n\nMarkdown allows raw HTML for the things it can't express on its own - collapsible `<details>`, sized or aligned images, and so on. mdast represents that HTML as a single opaque `html` node (a literal string it never parses into a tree), so you do the same here: pass the markup as a `value` and it's emitted verbatim.\n\n```tsx\n<html value=\"<details><summary>Show more</summary>...</details>\" />\n```\n\nThere are no native `<span>`, `<div>`, etc. tags, because those aren't mdast nodes - anything markdown can't express goes through `<html>` as a string.\n\nIf you want to _nest markdown_ inside an HTML block (e.g. a collapsible `<details>`), split the open and close tags into their own `html` nodes and place mdast nodes between them:\n\n```tsx\n<>\n  <html value=\"<details><summary>Details</summary>\" />\n  <paragraph>\n    Real <strong>markdown</strong> inside.\n  </paragraph>\n  <html value=\"</details>\" />\n</>\n```\n\n```md\n<details><summary>Details</summary>\n\nReal **markdown** inside.\n\n</details>\n```\n\nThe blank lines are what matter here - the serializer adds them between block siblings, and a blank line inside an HTML block is what tells the final renderer (GitHub, etc) to parse the content between the tags as markdown rather than literal text. (Note: `\\n` in a plain attribute string is literal; use `value={'...\\n...'}` if you need actual newlines.)\n\n## Maintenance\n\nmdast-jsx is just a thin wrapper around mdast. It has almost no knowledge of markdown itself - the core is essentially:\n\n```ts\nfunction jsx(type, props) {\n  if (typeof type === 'function') return type(props); // a component\n  const { children, ...fields } = props;\n  return { type, ...fields, children: normalize(children) }; // an mdast node\n}\n```\n\nIt simply builds an object with the right node shape, and the type system ensures that the shape is correct.\n\n`normalize` is the only other moving part - it flattens arrays/fragments, drops falsy children, and wraps bare strings as `text` nodes. Notably there's no reconciliation, no state, no lifecycle. Those exist in React to keep a live UI in sync over time; here we build a tree once and serialize it, so none of it applies.\n\nThe element types aren't hand-maintained either - they're derived from `@types/mdast` with a mapped type, so the set of usable tags _is_ the mdast node set and stays in sync on its own.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}