@draftbase/renderer 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -8
- package/dist/MDXContent.js +2 -1
- package/dist/MDXErrorBoundary.d.ts +17 -0
- package/dist/MDXErrorBoundary.js +18 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/styles.css +6 -0
- package/package.json +8 -9
package/README.md
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# @draftbase/renderer
|
|
2
4
|
|
|
3
|
-
Framework-agnostic MDX renderer for [Draftbase](https://draftbase.co)
|
|
5
|
+
**Framework-agnostic MDX renderer for [Draftbase](https://draftbase.co)** — the MDX-based headless CMS for React developers.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@draftbase/renderer)
|
|
8
|
+
[](https://github.com/draftbase-co/renderer)
|
|
9
|
+
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
Takes an entry's MDX/markdown field and renders it into a real component tree (React or Vue) or a plain HTML string — no vendor lock-in to one frontend framework. `compileMDX` is a plain async function with no dependency on Next.js, a bundler, or a router.
|
|
13
|
+
|
|
14
|
+
## 📦 Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @draftbase/renderer
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 🧭 Pick your entry point
|
|
4
21
|
|
|
5
22
|
Every framework entry point exposes the **same API shape** — `compileMDX(source)` resolving to `{ ok: true, Content }` or `{ ok: false, error }` — so switching frameworks (or supporting several in one monorepo) means changing the import path, not the calling code:
|
|
6
23
|
|
|
@@ -12,13 +29,7 @@ Every framework entry point exposes the **same API shape** — `compileMDX(sourc
|
|
|
12
29
|
|
|
13
30
|
Only import the entry point for the framework you use — each pulls in just that framework's peer dependency (React or Vue), never both, so an app using one never bundles code for the other.
|
|
14
31
|
|
|
15
|
-
##
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
pnpm add @draftbase/renderer
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Next.js App Router
|
|
32
|
+
## ⚛️ Next.js App Router
|
|
22
33
|
|
|
23
34
|
```tsx
|
|
24
35
|
import { MDXContent } from "@draftbase/renderer";
|
|
@@ -61,6 +72,16 @@ function Entry({ source }: { source: string }) {
|
|
|
61
72
|
}
|
|
62
73
|
```
|
|
63
74
|
|
|
75
|
+
`compiled.ok` only catches MDX _syntax_ errors. If the source references a JSX component you didn't pass in `components` (e.g. `<Callout>` without a `Callout` implementation), React throws while rendering `<Content>` — wrap it in the exported `MDXErrorBoundary` (React only, not React Native's non-DOM tree unless you supply an `errorTag`-equivalent fallback) to log it to the console and fail soft instead of crashing the page. `MDXContent` (the Next.js RSC helper above) already does this for you automatically.
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { MDXErrorBoundary } from "@draftbase/renderer";
|
|
79
|
+
|
|
80
|
+
<MDXErrorBoundary fallback={<Text>{source}</Text>}>
|
|
81
|
+
<Content components={{ p: Text, h1: Text /* ... */ }} />
|
|
82
|
+
</MDXErrorBoundary>;
|
|
83
|
+
```
|
|
84
|
+
|
|
64
85
|
Extended markdown (tables, strikethrough, task lists, autolinks) is supported out of the box via `remark-gfm`.
|
|
65
86
|
|
|
66
87
|
## Astro
|
|
@@ -181,6 +202,27 @@ import { Callout, ImageBlock } from "@/components/content";
|
|
|
181
202
|
|
|
182
203
|
Any standard markdown element (`h1`, `table`, `a`, ...) can also be overridden the same way, by key — **required** on non-DOM renderers like React Native, which have no intrinsic `div`/`p`/`a`/`img` tags.
|
|
183
204
|
|
|
205
|
+
### Entry links
|
|
206
|
+
|
|
207
|
+
The Draftbase editor can insert `<EntryLink id="...">Link text</EntryLink>` into rich text to link to another entry. It's a plain JSX component like any other — no special renderer support for the tag itself — so you must supply an `EntryLink` implementation the same way as `Callout`/`ImageBlock`. If `EntryLink` isn't supplied and the source contains one, rendering throws (same as any missing custom component).
|
|
208
|
+
|
|
209
|
+
To route `id` correctly per content type, fetch the entry with `include=1` (via `@draftbase/sdk`) — the response includes an `entryLinks` map keyed by every `EntryLink` id found in that entry's richText fields, each with its `templateId`:
|
|
210
|
+
|
|
211
|
+
```tsx
|
|
212
|
+
const entry = await client.entries.get(entryId, undefined, 1);
|
|
213
|
+
// entry.entryLinks = { "64f1a2b3c4d5e6f7a8b9c0d1": { id, templateId, title, status } }
|
|
214
|
+
|
|
215
|
+
const ROUTE_BY_TEMPLATE: Record<string, string> = { blogPost: "/blog", product: "/products" };
|
|
216
|
+
|
|
217
|
+
function EntryLink({ id, children }: { id: string; children: React.ReactNode }) {
|
|
218
|
+
const link = entry.entryLinks?.[id];
|
|
219
|
+
const base = ROUTE_BY_TEMPLATE[link?.templateId ?? ""] ?? "/entries";
|
|
220
|
+
return <a href={`${base}/${id}`}>{children}</a>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
<MDXContent source={entry.fields.body} components={{ EntryLink }} />;
|
|
224
|
+
```
|
|
225
|
+
|
|
184
226
|
## Styling
|
|
185
227
|
|
|
186
228
|
`@draftbase/renderer/styles.css` wraps output in a `.db-content` class with slim, sensible defaults (typography, tables, code blocks). Web-only, opt-in:
|
|
@@ -219,3 +261,15 @@ Yes — `compileMDX`/`toHtml` produce standard semantic HTML (headings, lists, t
|
|
|
219
261
|
|
|
220
262
|
**Which frontend frameworks are supported?**
|
|
221
263
|
React (Next.js App Router/RSC, plain client React, React Native, Remix, Astro islands, Vite) and Vue (including Nuxt) get first-class component output. Any other framework (Svelte, Angular, Solid, plain HTML/email/RSS) can use `toHtml` to get a plain HTML string instead.
|
|
264
|
+
|
|
265
|
+
## Links
|
|
266
|
+
|
|
267
|
+
- [npm](https://www.npmjs.com/package/@draftbase/renderer)
|
|
268
|
+
- [Source](https://github.com/draftbase-co/renderer)
|
|
269
|
+
- [Issues](https://github.com/draftbase-co/renderer/issues)
|
|
270
|
+
- [`@draftbase/sdk`](https://www.npmjs.com/package/@draftbase/sdk) — fetches the content this package renders
|
|
271
|
+
- [draftbase.co](https://draftbase.co) — product site
|
|
272
|
+
- [Framework support](https://draftbase.co/frameworks) — per-framework rendering guide this README is based on
|
|
273
|
+
- [API reference](https://draftbase.co/docs/api-reference)
|
|
274
|
+
- [Docs](https://draftbase.co/docs)
|
|
275
|
+
- [Pricing](https://draftbase.co/pricing)
|
package/dist/MDXContent.js
CHANGED
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import * as runtime from "react/jsx-runtime";
|
|
3
3
|
import { compileMDXCore } from "./core.js";
|
|
4
4
|
import { wrapperClassName } from "./wrapperClassName.js";
|
|
5
|
+
import { MDXErrorBoundary } from "./MDXErrorBoundary.js";
|
|
5
6
|
/**
|
|
6
7
|
* Compiles raw MDX/markdown into a renderable React component — no DOM assumptions,
|
|
7
8
|
* safe to call from a React Native loader, a client-side effect, or a Next.js Server
|
|
@@ -27,5 +28,5 @@ export async function MDXContent({ source, components, unstyled, className, wrap
|
|
|
27
28
|
return (_jsx(Wrapper, { className: wrapperClass, children: _jsx(ErrorTag, { children: source }) }));
|
|
28
29
|
}
|
|
29
30
|
const { Content } = compiled;
|
|
30
|
-
return (_jsx(Wrapper, { className: wrapperClass, children: _jsx(Content, { components: components }) }));
|
|
31
|
+
return (_jsx(Wrapper, { className: wrapperClass, children: _jsx(MDXErrorBoundary, { fallback: _jsx(ErrorTag, { children: source }), children: _jsx(Content, { components: components }) }) }));
|
|
31
32
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Component, type ReactNode } from "react";
|
|
2
|
+
interface MDXErrorBoundaryProps {
|
|
3
|
+
children: ReactNode;
|
|
4
|
+
fallback: ReactNode;
|
|
5
|
+
}
|
|
6
|
+
interface MDXErrorBoundaryState {
|
|
7
|
+
hasError: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare class MDXErrorBoundary extends Component<MDXErrorBoundaryProps, MDXErrorBoundaryState> {
|
|
10
|
+
state: MDXErrorBoundaryState;
|
|
11
|
+
static getDerivedStateFromError(): {
|
|
12
|
+
hasError: boolean;
|
|
13
|
+
};
|
|
14
|
+
componentDidCatch(error: unknown): void;
|
|
15
|
+
render(): ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Component } from "react";
|
|
3
|
+
// Catches render-time errors from compiled MDX content — most commonly a JSX component
|
|
4
|
+
// referenced in the source (e.g. `<Callout>`, `<EntryLink>`) that wasn't supplied in
|
|
5
|
+
// `components`. Without this, that throws "Element type is invalid" and crashes the whole
|
|
6
|
+
// page instead of just the content block.
|
|
7
|
+
export class MDXErrorBoundary extends Component {
|
|
8
|
+
state = { hasError: false };
|
|
9
|
+
static getDerivedStateFromError() {
|
|
10
|
+
return { hasError: true };
|
|
11
|
+
}
|
|
12
|
+
componentDidCatch(error) {
|
|
13
|
+
console.error("MDX content failed to render", error);
|
|
14
|
+
}
|
|
15
|
+
render() {
|
|
16
|
+
return this.state.hasError ? this.props.fallback : this.props.children;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/styles.css
CHANGED
|
@@ -25,6 +25,12 @@
|
|
|
25
25
|
line-height: 1.25;
|
|
26
26
|
margin: 1.5em 0 0.5em;
|
|
27
27
|
color: inherit;
|
|
28
|
+
scroll-margin-top: 96px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/* Highlights a footnote/citation anchor when the URL hash lands on it, e.g. #fn-1. */
|
|
32
|
+
.db-content :target {
|
|
33
|
+
background: var(--accent-subtle, rgba(127, 127, 127, 0.15));
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
.db-content h1 {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@draftbase/renderer",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Framework-agnostic MDX renderer for Draftbase content (React, Next.js RSC, React Native, Astro, Vite, Vue)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"draftbase",
|
|
@@ -17,8 +17,7 @@
|
|
|
17
17
|
"homepage": "https://draftbase.co",
|
|
18
18
|
"repository": {
|
|
19
19
|
"type": "git",
|
|
20
|
-
"url": "git+https://github.com/draftbase-co/
|
|
21
|
-
"directory": "packages/renderer"
|
|
20
|
+
"url": "git+https://github.com/draftbase-co/renderer.git"
|
|
22
21
|
},
|
|
23
22
|
"publishConfig": {
|
|
24
23
|
"access": "public"
|
|
@@ -39,6 +38,11 @@
|
|
|
39
38
|
"LICENSE",
|
|
40
39
|
"README.md"
|
|
41
40
|
],
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "tsc -p tsconfig.json && cp src/styles.css dist/styles.css",
|
|
43
|
+
"lint": "tsc --noEmit",
|
|
44
|
+
"test": "node --test dist/wrapperClassName.test.js dist/toHtml.test.js dist/compileMDX.test.js"
|
|
45
|
+
},
|
|
42
46
|
"peerDependencies": {
|
|
43
47
|
"react": ">=18",
|
|
44
48
|
"vue": ">=3"
|
|
@@ -67,10 +71,5 @@
|
|
|
67
71
|
"react": "^19.0.0",
|
|
68
72
|
"vue": "^3.5.0",
|
|
69
73
|
"typescript": "^5.6.0"
|
|
70
|
-
},
|
|
71
|
-
"scripts": {
|
|
72
|
-
"build": "tsc -p tsconfig.json && cp src/styles.css dist/styles.css",
|
|
73
|
-
"lint": "tsc --noEmit",
|
|
74
|
-
"test": "node --test dist/wrapperClassName.test.js dist/toHtml.test.js dist/compileMDX.test.js"
|
|
75
74
|
}
|
|
76
|
-
}
|
|
75
|
+
}
|