@draftbase/renderer 0.5.2 → 0.5.4

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.
@@ -1,6 +1,6 @@
1
1
  import type { MDXComponents } from "mdx/types";
2
2
  import type { ComponentType, ElementType, ReactNode } from "react";
3
- import { type FailedMDX } from "./core.js";
3
+ import { type FailedMDX, type LinkedEntryData } from "./core.js";
4
4
  export interface CompiledMDX {
5
5
  ok: true;
6
6
  Content: ComponentType<{
@@ -33,9 +33,14 @@ export interface MDXContentProps {
33
33
  * throws while rendering. Either way the fallback plain-text output still renders instead of
34
34
  * crashing. */
35
35
  onError?: (error: unknown) => void;
36
+ /** The containing entry's own `entryLinks` (from `getEntry`/`getEntries` called with `include`
37
+ * set) — id-keyed resolved data for every `<EntryLink id="...">` found in `source`. When given,
38
+ * each `EntryLink` instance receives its target's data as extra props, so `components.EntryLink`
39
+ * doesn't need to re-fetch it. */
40
+ entryLinks?: Record<string, LinkedEntryData>;
36
41
  }
37
42
  /**
38
43
  * Next.js Server Component wrapper around {@link compileMDX}. For non-RSC React, call `compileMDX` directly instead —
39
44
  * `await` inside a component body only works as an RSC.
40
45
  */
41
- export declare function MDXContent({ source, components, unstyled, className, wrapperTag, errorTag, onError, }: MDXContentProps): Promise<ReactNode>;
46
+ export declare function MDXContent({ source, components, unstyled, className, wrapperTag, errorTag, onError, entryLinks, }: MDXContentProps): Promise<ReactNode>;
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as runtime from "react/jsx-runtime";
3
- import { compileMDXCore, makeDefaultEntryLink, makeDefaultImage } from "./core.js";
3
+ import { compileMDXCore, makeDefaultEntryLink, makeDefaultImage, withEntryLinkData, } from "./core.js";
4
4
  import { wrapperClassName } from "./wrapperClassName.js";
5
5
  import { MDXErrorBoundary } from "./MDXErrorBoundary.js";
6
6
  import { CSS_TEXT } from "./cssText.js";
@@ -19,10 +19,16 @@ export async function compileMDX(source) {
19
19
  * Next.js Server Component wrapper around {@link compileMDX}. For non-RSC React, call `compileMDX` directly instead —
20
20
  * `await` inside a component body only works as an RSC.
21
21
  */
22
- export async function MDXContent({ source, components, unstyled, className, wrapperTag = "div", errorTag = "p", onError, }) {
22
+ export async function MDXContent({ source, components, unstyled, className, wrapperTag = "div", errorTag = "p", onError, entryLinks, }) {
23
23
  const Wrapper = wrapperTag;
24
24
  const ErrorTag = errorTag;
25
25
  const wrapperClass = wrapperClassName(unstyled, className);
26
+ const effectiveComponents = entryLinks
27
+ ? {
28
+ ...components,
29
+ EntryLink: withEntryLinkData(components?.EntryLink ?? defaultComponents.EntryLink, entryLinks, runtime),
30
+ }
31
+ : components;
26
32
  // `href` + `precedence` make React DOM treat this as a de-duplicated, hoisted stylesheet
27
33
  // resource (React 19+) instead of a plain inline tag repeated per render.
28
34
  const styles = unstyled ? null : (_jsx("style", { href: "db-content-styles", precedence: "default", children: CSS_TEXT }));
@@ -35,5 +41,5 @@ export async function MDXContent({ source, components, unstyled, className, wrap
35
41
  return (_jsxs(_Fragment, { children: [styles, _jsx(Wrapper, { className: wrapperClass, children: _jsx(ErrorTag, { children: source }) })] }));
36
42
  }
37
43
  const { Content } = compiled;
38
- return (_jsxs(_Fragment, { children: [styles, _jsx(Wrapper, { className: wrapperClass, children: _jsx(MDXErrorBoundary, { fallback: _jsx(ErrorTag, { children: source }), onError: onError, children: _jsx(Content, { components: components }) }) })] }));
44
+ return (_jsxs(_Fragment, { children: [styles, _jsx(Wrapper, { className: wrapperClass, children: _jsx(MDXErrorBoundary, { fallback: _jsx(ErrorTag, { children: source }), onError: onError, children: _jsx(Content, { components: effectiveComponents }) }) })] }));
39
45
  }
package/dist/core.d.ts CHANGED
@@ -13,12 +13,31 @@ export interface FailedMDX {
13
13
  * the remark/rehype pipeline. `defaultComponents`, when given, are merged underneath the caller's own `components` (caller always wins per-tag).
14
14
  */
15
15
  export declare function compileMDXCore<TComponent>(source: string, jsxRuntime: JsxRuntime, defaultComponents?: Record<string, unknown>): Promise<CompiledMDX<TComponent> | FailedMDX>;
16
- /** Default `EntryLink` for a given JSX runtime renders `<a href="/entries/{id}">`; apps that
17
- * route entries differently override it by passing `components.EntryLink`. */
18
- export declare function makeDefaultEntryLink(jsxRuntime: JsxRuntime): ({ id, children }: {
16
+ /** The linked entry's own resolved datathe same shape the delivery API's `getEntry`/`getEntries`
17
+ * attach at `entry.entryLinks[id]` when called with `include` set (optionally widened with
18
+ * `entryLinkFields`). Mirrors the SDK's `EntryLinkView` minus `id`, kept local so the
19
+ * framework-agnostic renderer doesn't depend on `@draftbase/sdk`. */
20
+ export interface LinkedEntryData {
21
+ templateId: string;
22
+ title: string;
23
+ status: string;
24
+ fields?: Record<string, unknown>;
25
+ }
26
+ /** Props passed to `EntryLink` — the id of the linked entry, authored via the entry picker in
27
+ * the MDX editor, plus that entry's own resolved data when the caller passed `entryLinks` to
28
+ * `MDXContent`/`compileMDX` (avoids an extra per-link fetch at render time). Apps overriding
29
+ * `components.EntryLink` should type their component against this. */
30
+ export interface EntryLinkProps extends Partial<LinkedEntryData> {
19
31
  id?: string;
20
32
  children?: unknown;
21
- }) => JSX.Element;
33
+ }
34
+ /** Default `EntryLink` for a given JSX runtime — renders `<a href="/entries/{id}">`; apps that
35
+ * route entries differently override it by passing `components.EntryLink`. */
36
+ export declare function makeDefaultEntryLink(jsxRuntime: JsxRuntime): ({ id, children }: EntryLinkProps) => JSX.Element;
37
+ /** Wraps `EntryLink` (custom or default) so every instance also receives its resolved
38
+ * `LinkedEntryData` from `entryLinks[id]` — the caller's already-fetched `getEntry`/`getEntries`
39
+ * result — instead of each link having to re-fetch its own target. */
40
+ export declare function withEntryLinkData(EntryLink: unknown, entryLinks: Record<string, LinkedEntryData> | undefined, jsxRuntime: JsxRuntime): unknown;
22
41
  /** Default `img` for a given JSX runtime — defers offscreen image loads instead of the browser's
23
42
  * eager default. Apps that want `next/image` (or another optimizer) override it via `components.img`;
24
43
  * an explicit `loading`/`decoding` on the element itself (rare, but author-settable) still wins. */
package/dist/core.js CHANGED
@@ -38,6 +38,17 @@ export function makeDefaultEntryLink(jsxRuntime) {
38
38
  return jsxRuntime.jsx("a", { href: id ? `/entries/${id}` : undefined, children });
39
39
  };
40
40
  }
41
+ /** Wraps `EntryLink` (custom or default) so every instance also receives its resolved
42
+ * `LinkedEntryData` from `entryLinks[id]` — the caller's already-fetched `getEntry`/`getEntries`
43
+ * result — instead of each link having to re-fetch its own target. */
44
+ export function withEntryLinkData(EntryLink, entryLinks, jsxRuntime) {
45
+ if (!entryLinks)
46
+ return EntryLink;
47
+ return function EntryLinkWithData(props) {
48
+ const linked = props.id ? entryLinks[props.id] : undefined;
49
+ return jsxRuntime.jsx(EntryLink, { ...props, ...linked });
50
+ };
51
+ }
41
52
  /** Default `img` for a given JSX runtime — defers offscreen image loads instead of the browser's
42
53
  * eager default. Apps that want `next/image` (or another optimizer) override it via `components.img`;
43
54
  * an explicit `loading`/`decoding` on the element itself (rare, but author-settable) still wins. */
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export { MDXContent, compileMDX } from "./MDXContent.js";
2
2
  export type { MDXContentProps, CompiledMDX, FailedMDX } from "./MDXContent.js";
3
+ export type { EntryLinkProps, LinkedEntryData } from "./core.js";
3
4
  export { toHtml } from "./toHtml.js";
4
5
  export { MDXErrorBoundary } from "./MDXErrorBoundary.js";
@@ -1,6 +1,6 @@
1
1
  import type { MDXComponents } from "mdx/types";
2
2
  import type { ComponentType } from "react";
3
- import { type FailedMDX } from "./core.js";
3
+ import type { FailedMDX } from "./core.js";
4
4
  import { type ReactNativePrimitives, type ReactNativeStyleOptions } from "./reactNativeComponents.js";
5
5
  export interface CompiledMDX {
6
6
  ok: true;
@@ -12,6 +12,12 @@ export type { FailedMDX };
12
12
  /**
13
13
  * Wires up a React Native `compileMDX` with default Text/View/Image mappings for every standard markdown element
14
14
  * (and `EntryLink`), so a project sets up its RN primitives once instead of mapping every tag on every call.
15
+ *
16
+ * Parses MDX to an AST and walks it directly instead of compiling to JS and `evaluate()`-ing it — Hermes (React
17
+ * Native's JS engine) has no `Function`/`eval` support, which `@mdx-js/mdx`'s `evaluate()` requires. This also
18
+ * means only literal string JSX attributes are supported (e.g. `<EntryLink id="x">`) — `{expression}` attributes
19
+ * and `{expression}` content would need the same eval this exists to avoid, and aren't used by lesson content.
20
+ *
15
21
  * A tag/component the source references but nothing maps (a typo'd custom component, a raw HTML tag RN has no
16
22
  * native view for) is caught at render — logged via `console.error` and replaced with the raw source as
17
23
  * plain text, instead of crashing the screen.
@@ -1,10 +1,20 @@
1
+ import { unified } from "unified";
2
+ import remarkParse from "remark-parse";
3
+ import remarkGfm from "remark-gfm";
4
+ import remarkMdx from "remark-mdx";
1
5
  import * as runtime from "react/jsx-runtime";
2
- import { compileMDXCore } from "./core.js";
3
6
  import { MDXErrorBoundary } from "./MDXErrorBoundary.js";
4
7
  import { buildReactNativeComponents, } from "./reactNativeComponents.js";
8
+ const processor = unified().use(remarkParse).use(remarkGfm).use(remarkMdx);
5
9
  /**
6
10
  * Wires up a React Native `compileMDX` with default Text/View/Image mappings for every standard markdown element
7
11
  * (and `EntryLink`), so a project sets up its RN primitives once instead of mapping every tag on every call.
12
+ *
13
+ * Parses MDX to an AST and walks it directly instead of compiling to JS and `evaluate()`-ing it — Hermes (React
14
+ * Native's JS engine) has no `Function`/`eval` support, which `@mdx-js/mdx`'s `evaluate()` requires. This also
15
+ * means only literal string JSX attributes are supported (e.g. `<EntryLink id="x">`) — `{expression}` attributes
16
+ * and `{expression}` content would need the same eval this exists to avoid, and aren't used by lesson content.
17
+ *
8
18
  * A tag/component the source references but nothing maps (a typo'd custom component, a raw HTML tag RN has no
9
19
  * native view for) is caught at render — logged via `console.error` and replaced with the raw source as
10
20
  * plain text, instead of crashing the screen.
@@ -12,32 +22,29 @@ import { buildReactNativeComponents, } from "./reactNativeComponents.js";
12
22
  export function createReactNativeRenderer(primitives, styleOptions) {
13
23
  const defaultComponents = buildReactNativeComponents(primitives, styleOptions);
14
24
  async function compileMDX(source) {
15
- const result = await compileMDXCore(source, runtime, defaultComponents);
16
- if (!result.ok)
17
- return result;
18
- // MDX bakes the source's literal inter-block whitespace into the compiled output as bare
19
- // "\n" string children of the root Fragment (e.g. between a heading and the paragraph after
20
- // it). React Native throws on a bare string child that isn't inside a <Text>, so those need
21
- // stripping here — element-mapped tags already handle it themselves (see reactNativeComponents.ts).
22
- const Content = result.Content;
23
- // withDefaultComponents (core.ts) wraps the compiled MDX component in one props-forwarding
24
- // layer, so unwrap exactly that to reach the actual root element (a Fragment for a multi-block
25
- // document, or a single mapped element like Text/View otherwise) before stripping whitespace.
26
- // Calling any further would start invoking the RN primitives (Text/View/Image) themselves.
27
- const RootWithoutWhitespace = (props) => {
28
- const wrapped = Content(props);
29
- const rootElement = typeof wrapped.type === "function"
30
- ? wrapped.type(wrapped.props)
31
- : wrapped;
32
- return stripWhitespaceRootChildren(rootElement);
33
- };
34
- // A JSX component referenced in `source` but missing from `components`, or a raw HTML tag RN has
35
- // no native view for, throws mid-render — RN has no DOM to silently fall back on the way web
36
- // does, so catch it here instead of taking down the whole screen, and log it loudly so the
37
- // content author (or whoever wired up `components`) notices and fixes it.
25
+ let tree;
26
+ try {
27
+ tree = processor.parse(source);
28
+ }
29
+ catch (error) {
30
+ return { ok: false, error };
31
+ }
32
+ // Reference-style links/images (`[text][ref]` + a `[ref]: url` definition anywhere in the
33
+ // doc) resolve against whichever `definition` node shares their identifier — collect them
34
+ // once per parse instead of re-scanning the tree for every reference.
35
+ const definitions = {};
36
+ for (const node of tree.children ?? []) {
37
+ if (node.type === "definition" && node.identifier && node.url) {
38
+ definitions[node.identifier] = node.url;
39
+ }
40
+ }
41
+ function Root(props) {
42
+ const components = { ...defaultComponents, ...props.components };
43
+ return renderChildren(tree.children ?? [], { components, definitions, source });
44
+ }
38
45
  const SafeContent = (props) => runtime.jsx(MDXErrorBoundary, {
39
46
  fallback: runtime.jsx(primitives.Text, { children: source }),
40
- children: runtime.jsx(RootWithoutWhitespace, props),
47
+ children: runtime.jsx(Root, props),
41
48
  });
42
49
  return {
43
50
  ok: true,
@@ -46,12 +53,118 @@ export function createReactNativeRenderer(primitives, styleOptions) {
46
53
  }
47
54
  return { compileMDX };
48
55
  }
49
- function stripWhitespaceRootChildren(element) {
50
- const children = element.props?.children;
51
- if (!Array.isArray(children))
52
- return element;
53
- const filtered = children.filter((child) => typeof child !== "string" || child.trim() !== "");
54
- if (filtered.length === children.length)
55
- return element;
56
- return runtime.jsxs(element.type, { ...element.props, children: filtered }, element.key ?? undefined);
56
+ function renderChildren(nodes, ctx) {
57
+ const rendered = nodes.map((node, index) => renderNode(node, ctx, index)).filter(isRenderable);
58
+ // Mirrors @mdx-js's compiled output: a single top-level node renders directly, only 2+
59
+ // siblings need a Fragment wrapper.
60
+ if (rendered.length === 1)
61
+ return rendered[0];
62
+ return runtime.jsx(runtime.Fragment, { children: rendered });
63
+ }
64
+ function textChild(nodes, ctx) {
65
+ const rendered = nodes.map((node, index) => renderNode(node, ctx, index)).filter(isRenderable);
66
+ return rendered.length === 1 ? rendered[0] : rendered;
67
+ }
68
+ function isRenderable(node) {
69
+ return node !== null && node !== undefined;
70
+ }
71
+ // A single bad node (a typo'd/missing custom component, an mdast node type this walker doesn't
72
+ // know) shouldn't blank out an otherwise-good document — catch here, at the smallest node that
73
+ // failed, and swap in its literal source text instead of losing every sibling around it.
74
+ function renderNode(node, ctx, key) {
75
+ try {
76
+ return renderNodeUnsafe(node, ctx, key);
77
+ }
78
+ catch (error) {
79
+ console.error("MDX content failed to render a section, showing its raw source instead", error);
80
+ const raw = node.position
81
+ ? ctx.source.slice(node.position.start.offset, node.position.end.offset)
82
+ : `<${node.name ?? node.type}>`;
83
+ return jsx(ctx.components.p, { children: raw }, key);
84
+ }
85
+ }
86
+ function renderNodeUnsafe(node, ctx, key) {
87
+ const { components } = ctx;
88
+ switch (node.type) {
89
+ case "paragraph":
90
+ return jsx(components.p, { children: textChild(node.children ?? [], ctx) }, key);
91
+ case "heading":
92
+ return jsx(components[`h${node.depth}`], { children: textChild(node.children ?? [], ctx) }, key);
93
+ case "strong":
94
+ return jsx(components.strong, { children: textChild(node.children ?? [], ctx) }, key);
95
+ case "emphasis":
96
+ return jsx(components.em, { children: textChild(node.children ?? [], ctx) }, key);
97
+ case "delete":
98
+ return jsx(components.del, { children: textChild(node.children ?? [], ctx) }, key);
99
+ case "inlineCode":
100
+ return jsx(components.code, { children: node.value }, key);
101
+ case "code":
102
+ return jsx(components.pre, { children: jsx(components.code, { children: node.value }, 0) }, key);
103
+ case "list":
104
+ return jsx(node.ordered ? components.ol : components.ul, { children: (node.children ?? []).map((item, i) => renderNode(item, ctx, i)) }, key);
105
+ case "listItem":
106
+ return jsx(components.li, { children: textChild(node.children ?? [], ctx) }, key);
107
+ case "blockquote":
108
+ return jsx(components.blockquote, { children: (node.children ?? []).map((child, i) => renderNode(child, ctx, i)) }, key);
109
+ case "thematicBreak":
110
+ return jsx(components.hr, {}, key);
111
+ case "table":
112
+ return renderTable(node, ctx, key);
113
+ case "link":
114
+ return jsx(components.a, { href: node.url, children: textChild(node.children ?? [], ctx) }, key);
115
+ case "image":
116
+ return jsx(components.img, { src: node.url, alt: node.alt ?? undefined }, key);
117
+ case "linkReference":
118
+ return jsx(components.a, {
119
+ href: node.identifier ? ctx.definitions[node.identifier] : undefined,
120
+ children: textChild(node.children ?? [], ctx),
121
+ }, key);
122
+ case "imageReference":
123
+ return jsx(components.img, {
124
+ src: node.identifier ? ctx.definitions[node.identifier] : undefined,
125
+ alt: node.alt ?? undefined,
126
+ }, key);
127
+ case "definition":
128
+ // Consumed up front into `ctx.definitions`; renders nothing on its own.
129
+ return null;
130
+ case "text":
131
+ return node.value;
132
+ case "break":
133
+ return "\n";
134
+ case "mdxJsxFlowElement":
135
+ case "mdxJsxTextElement":
136
+ return renderJsx(node, ctx, key);
137
+ default:
138
+ throw new Error(`Unsupported MDX content: "${node.type}" node`);
139
+ }
140
+ }
141
+ function renderJsx(node, ctx, key) {
142
+ const Component = node.name ? ctx.components[node.name] : undefined;
143
+ if (!Component)
144
+ throw new Error(`Unknown MDX component: <${node.name ?? "?"}>`);
145
+ const props = {};
146
+ for (const attr of node.attributes ?? []) {
147
+ if (attr.type === "mdxJsxAttribute" &&
148
+ attr.name &&
149
+ (attr.value === null || typeof attr.value === "string")) {
150
+ props[attr.name] = attr.value ?? true;
151
+ }
152
+ }
153
+ return jsx(Component, { ...props, children: textChild(node.children ?? [], ctx) }, key);
154
+ }
155
+ function renderTable(node, ctx, key) {
156
+ const { components } = ctx;
157
+ const [headerRow, ...bodyRows] = node.children ?? [];
158
+ const cells = (row, CellTag) => (row?.children ?? []).map((cell, i) => jsx(CellTag, { children: textChild(cell.children ?? [], ctx) }, i));
159
+ return jsx(components.table, {
160
+ children: [
161
+ jsx(components.thead, { children: jsx(components.tr, { children: cells(headerRow, components.th) }, 0) }, 0),
162
+ jsx(components.tbody, {
163
+ children: bodyRows.map((row, i) => jsx(components.tr, { children: cells(row, components.td) }, i)),
164
+ }, 1),
165
+ ],
166
+ }, key);
167
+ }
168
+ function jsx(Component, props, key) {
169
+ return runtime.jsx(Component, props, key);
57
170
  }
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
3
  import { createReactNativeRenderer } from "./reactNative.js";
4
- import { MDXErrorBoundary } from "./MDXErrorBoundary.js";
5
4
  // Fakes standing in for react-native's Text/View/Image — see reactNativeComponents.test.ts.
6
5
  const Text = () => null;
7
6
  const View = () => null;
@@ -30,9 +29,9 @@ test("React Native smoke test: renders standard markdown through Text/View with
30
29
  assert.equal(heading.type, Text);
31
30
  assert.equal(heading.props.style.fontSize, 28);
32
31
  });
33
- test("React Native falls back to the raw source and logs to console instead of crashing on an unknown tag", async () => {
32
+ test("React Native swaps only the unknown tag for its raw source, logs to console, and keeps rendering its siblings", async () => {
34
33
  const { compileMDX } = createReactNativeRenderer({ Text, View, Image });
35
- const result = await compileMDX("<Callout>hi</Callout>");
34
+ const result = await compileMDX("# Heading\n\n<Callout>hi</Callout>\n\nMore text");
36
35
  assert.equal(result.ok, true);
37
36
  if (!result.ok)
38
37
  return;
@@ -40,22 +39,20 @@ test("React Native falls back to the raw source and logs to console instead of c
40
39
  const logged = [];
41
40
  console.error = (...args) => logged.push(args);
42
41
  try {
43
- // No react-dom/test-renderer in this package drive the real MDXErrorBoundary class by
44
- // hand the way React would: render children, and on throw, catch + re-render the fallback.
45
- const boundaryElement = result.Content({});
46
- assert.equal(boundaryElement.type, MDXErrorBoundary);
47
- const boundary = new MDXErrorBoundary(boundaryElement.props);
48
- try {
49
- resolve(boundaryElement.props.children);
50
- assert.fail("expected the missing <Callout> component to throw");
51
- }
52
- catch (error) {
53
- boundary.componentDidCatch(error);
54
- Object.assign(boundary.state, MDXErrorBoundary.getDerivedStateFromError());
55
- }
56
- const fallback = resolve(boundary.render());
42
+ // MDXErrorBoundary never triggers here — the failure is caught per-node inside compileMDX,
43
+ // not left to bubble up and blank the whole document.
44
+ const element = resolve(result.Content({}));
45
+ const children = element.props.children;
46
+ assert.equal(children.length, 3);
47
+ assert.equal(resolve(children[0]).type, Text); // heading
48
+ // <Callout>hi</Callout> parses as an inline JSX child of its own paragraph, so the fallback
49
+ // (also Text-wrapped) sits one level inside that paragraph rather than replacing it outright.
50
+ const paragraph = resolve(children[1]);
51
+ assert.equal(paragraph.type, Text);
52
+ const fallback = resolve(paragraph.props.children);
57
53
  assert.equal(fallback.type, Text);
58
54
  assert.equal(fallback.props.children, "<Callout>hi</Callout>");
55
+ assert.equal(resolve(children[2]).type, Text); // "More text" paragraph
59
56
  }
60
57
  finally {
61
58
  console.error = originalConsoleError;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@draftbase/renderer",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
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",
@@ -59,6 +59,7 @@
59
59
  "dependencies": {
60
60
  "@mdx-js/mdx": "^3.1.1",
61
61
  "remark-gfm": "^4.0.0",
62
+ "remark-mdx": "^3.1.1",
62
63
  "remark-parse": "^11.0.0",
63
64
  "remark-rehype": "^11.1.2",
64
65
  "rehype-raw": "^7.0.0",