safe-mdx 0.0.5 → 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Holocron
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -10,9 +10,9 @@
10
10
 
11
11
  ## Features
12
12
 
13
- - Render MDX without `eval` on the server, so you can render MDX in Cloudflare Workers and Vercel Edge
14
- - Works with React Server Components
15
- - Supports custom MDX components
13
+ - Render MDX without `eval` on the server, so you can render MDX in Cloudflare Workers and Vercel Edge
14
+ - Works with React Server Components
15
+ - Supports custom MDX components
16
16
 
17
17
  ## Why
18
18
 
@@ -22,9 +22,9 @@ For example in an hypothetical platform similar to Notion, where users can write
22
22
 
23
23
  Some use cases for this package are:
24
24
 
25
- - Render MDX in Cloudflare Workers and Vercel Edge
26
- - Safely render dynamically generated MDX code, like inside a ChatGPT like interface
27
- - Render user generated MDX, like in a multi-tenant SaaS app
25
+ - Render MDX in Cloudflare Workers and Vercel Edge
26
+ - Safely render dynamically generated MDX code, like inside a ChatGPT like interface
27
+ - Render user generated MDX, like in a multi-tenant SaaS app
28
28
 
29
29
  <br>
30
30
 
@@ -78,12 +78,12 @@ If you want to use custom MDX plugins, you can pass your own MDX processed ast.
78
78
 
79
79
  By default `safe-mdx` already has support for
80
80
 
81
- - frontmatter
82
- - gfm
81
+ - frontmatter
82
+ - gfm
83
83
 
84
84
  ```tsx
85
85
  import { SafeMdxRenderer } from 'safe-mdx'
86
- import { remark } from 'remark'
86
+ import { remark, Root } from 'remark'
87
87
  import remarkMdx from 'remark-mdx'
88
88
 
89
89
  const code = `
@@ -94,9 +94,16 @@ This is a paragraph
94
94
  <Heading>Custom component</Heading>
95
95
  `
96
96
 
97
- const parser = remark().use(remarkMdx)
97
+ const parser = remark()
98
+ .use(remarkMdx)
99
+ .use(() => {
100
+ return (tree, file) => {
101
+ file.data.ast = tree
102
+ }
103
+ })
98
104
 
99
- const mdast = parser.parse(code)
105
+ const file = parser.processSync(code)
106
+ const mdast = file.data.ast as Root
100
107
 
101
108
  export function Page() {
102
109
  return <SafeMdxRenderer code={code} mdast={mdast} />
@@ -139,6 +146,31 @@ export function Page() {
139
146
  }
140
147
  ```
141
148
 
149
+ ## Override code block component
150
+
151
+ It's not pratical to override the code block component using `code` as a component override, because it will also be used for inline code blocks. It also does not have access to meta string and language.
152
+
153
+ Instead you can use `customTransformer` to return some jsx for a specific mdast node:
154
+
155
+ ```tsx
156
+ <SafeMdxRenderer
157
+ customTransformer={(node, transform) => {
158
+ if (node.type === 'code') {
159
+ const language = node.lang || ''
160
+ const meta = parseMetaString(node.meta)
161
+
162
+ return (
163
+ <CodeBlock {...meta} lang={language}>
164
+ <Pre>
165
+ <ShikiRenderer code={node.value} language={language} />
166
+ </Pre>
167
+ </CodeBlock>
168
+ )
169
+ }
170
+ }}
171
+ />
172
+ ```
173
+
142
174
  ## Handling errors
143
175
 
144
176
  `safe-mdx` ignores missing components or expressions, to show a message to the user in case of these errors you can use `MdastToJsx` directly
@@ -170,7 +202,7 @@ This is ok if you render your MDX in isolation from each tenant, for example on
170
202
 
171
203
  These features are not supported yet:
172
204
 
173
- - expressions with dynamic values or values defined with `export`
174
- - importing components or data from other files
205
+ - expressions or values defined with `export`
206
+ - importing components or data from other files
175
207
 
176
- To overcome these limitations you can define custom logic in your components and pass them to `SafeMdxRenderer`. This will also make your MDX files cleaner and easier to read.
208
+ To overcome these limitations you can define custom logic in your components and pass them to `SafeMdxRenderer` `components` prop. This will also make your MDX files cleaner and easier to read.
@@ -3,15 +3,21 @@ import { Root } from 'mdast';
3
3
  import { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx';
4
4
  import { ReactNode } from 'react';
5
5
  type MyRootContent = RootContent | Root;
6
- export declare function SafeMdxRenderer({ components, code, mdast, }: {
7
- components: any;
6
+ export declare function mdxParse(code: string): Root;
7
+ declare module 'mdast' {
8
+ interface Data {
9
+ hProperties?: {
10
+ id?: string;
11
+ };
12
+ }
13
+ }
14
+ export type CustomTransformer = (node: MyRootContent, transform: (node: MyRootContent) => ReactNode) => ReactNode | undefined;
15
+ export declare function SafeMdxRenderer({ components, code, mdast, customTransformer, }: {
16
+ components?: ComponentsMap;
8
17
  code?: string;
9
- mdast?: any;
18
+ mdast?: MyRootContent;
19
+ customTransformer?: CustomTransformer;
10
20
  }): any;
11
- declare const nativeTags: readonly ["blockquote", "strong", "em", "del", "hr", "a", "b", "br", "button", "div", "form", "h1", "h2", "h3", "h4", "head", "iframe", "img", "input", "label", "li", "link", "ol", "p", "path", "picture", "script", "section", "source", "span", "sub", "sup", "svg", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", "video", "code", "pre"];
12
- type ComponentsMap = {
13
- [k in (typeof nativeTags)[number]]?: any;
14
- };
15
21
  export declare class MdastToJsx {
16
22
  mdast: MyRootContent;
17
23
  str: string;
@@ -20,10 +26,12 @@ export declare class MdastToJsx {
20
26
  errors: {
21
27
  message: string;
22
28
  }[];
23
- constructor({ code, mdast, components, }: {
29
+ customTransformer?: CustomTransformer;
30
+ constructor({ code, mdast, components, customTransformer, }: {
24
31
  code?: string;
25
- mdast?: any;
32
+ mdast?: MyRootContent;
26
33
  components?: ComponentsMap;
34
+ customTransformer?: (node: MyRootContent, transform: (node: MyRootContent) => ReactNode) => ReactNode | undefined;
27
35
  });
28
36
  mapMdastChildren(node: any): any;
29
37
  mapJsxChildren(node: any): any;
@@ -35,5 +43,21 @@ export declare function getJsxAttrs(node: MdxJsxFlowElement | MdxJsxTextElement,
35
43
  message: string;
36
44
  }) => void): [string, any][];
37
45
  export declare function mdastBfs(node: Parent | Node, cb?: (node: Node | Parent) => any): any[];
46
+ declare const nativeTags: readonly ["blockquote", "strong", "em", "del", "hr", "a", "b", "br", "button", "div", "form", "h1", "h2", "h3", "h4", "head", "iframe", "img", "input", "label", "li", "link", "ol", "p", "path", "picture", "script", "section", "source", "span", "sub", "sup", "svg", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", "video", "code", "pre", "figure", "canvas", "details", "dl", "dt", "dd", "fieldset", "footer", "header", "legend", "main", "mark", "nav", "progress", "summary", "time"];
47
+ type ComponentsMap = {
48
+ [k in (typeof nativeTags)[number]]?: any;
49
+ } & {
50
+ [key: string]: any;
51
+ };
52
+ /**
53
+ * https://github.com/mdx-js/mdx/blob/b3351fadcb6f78833a72757b7135dcfb8ab646fe/packages/mdx/lib/plugin/remark-mark-and-unravel.js
54
+ * A tiny plugin that unravels `<p><h1>x</h1></p>` but also
55
+ * `<p><Component /></p>` (so it has no knowledge of "HTML").
56
+ *
57
+ * It also marks JSX as being explicitly JSX, so when a user passes a `h1`
58
+ * component, it is used for `# heading` but not for `<h1>heading</h1>`.
59
+ *
60
+ */
61
+ export declare function remarkMarkAndUnravel(): (tree: Root) => void;
38
62
  export {};
39
63
  //# sourceMappingURL=safe-mdx.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"safe-mdx.d.ts","sourceRoot":"","sources":["../src/safe-mdx.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAGjD,OAAO,EAAE,IAAI,EAAQ,MAAM,OAAO,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAKzE,OAAO,EAAY,SAAS,EAAE,MAAM,OAAO,CAAA;AAE3C,KAAK,aAAa,GAAG,WAAW,GAAG,IAAI,CAAA;AASvC,wBAAgB,eAAe,CAAC,EAC5B,UAAU,EACV,IAAS,EACT,KAAmB,GACtB;;;;CAAA,OAIA;AAED,QAAA,MAAM,UAAU,8VA6CN,CAAA;AAEV,KAAK,aAAa,GAAG;KAAG,CAAC,IAAI,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG;CAAE,CAAA;AAEjE,qBAAa,UAAU;IACnB,KAAK,EAAE,aAAa,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAK;IACnB,CAAC,EAAE,aAAa,CAAA;IAChB,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAK;gBACtB,EACR,IAAS,EACT,KAAwB,EACxB,UAAgC,GACnC;;;;KAAA;IAaD,gBAAgB,CAAC,IAAI,EAAE,GAAG;IAiB1B,cAAc,CAAC,IAAI,EAAE,GAAG;IAiBxB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS;IAsC9C,GAAG;IAQH,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS;CAwOnD;AAED,wBAAgB,WAAW,CACvB,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,EAC3C,OAAO,GAAE,CAAC,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAoB,mBAoE9D;AAcD,wBAAgB,QAAQ,CACpB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,KAAK,GAAG,SAiBpC"}
1
+ {"version":3,"file":"safe-mdx.d.ts","sourceRoot":"","sources":["../src/safe-mdx.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,OAAO,CAAA;AAMjD,OAAO,EAAE,IAAI,EAAQ,MAAM,OAAO,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAKzE,OAAO,EAAY,SAAS,EAAE,MAAM,OAAO,CAAA;AAE3C,KAAK,aAAa,GAAG,WAAW,GAAG,IAAI,CAAA;AAEvC,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAET,IAAI,CAC/B;AAED,OAAO,QAAQ,OAAO,CAAC;IACnB,UAAiB,IAAI;QACjB,WAAW,CAAC,EAAE;YACV,EAAE,CAAC,EAAE,MAAM,CAAA;SACd,CAAA;KACJ;CACJ;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC5B,IAAI,EAAE,aAAa,EACnB,SAAS,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,KAC5C,SAAS,GAAG,SAAS,CAAA;AAE1B,wBAAgB,eAAe,CAAC,EAC5B,UAAU,EACV,IAAS,EACT,KAAmB,EACnB,iBAAiB,GACpB,EAAE;IACC,UAAU,CAAC,EAAE,aAAa,CAAA;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;CACxC,OASA;AAED,qBAAa,UAAU;IACnB,KAAK,EAAE,aAAa,CAAA;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAK;IACnB,CAAC,EAAE,aAAa,CAAA;IAChB,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAK;IAClC,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;gBAEzB,EACR,IAAS,EACT,KAAwB,EACxB,UAAgC,EAChC,iBAAiB,GACpB,EAAE;QACC,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,KAAK,CAAC,EAAE,aAAa,CAAA;QACrB,UAAU,CAAC,EAAE,aAAa,CAAA;QAC1B,iBAAiB,CAAC,EAAE,CAChB,IAAI,EAAE,aAAa,EACnB,SAAS,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,SAAS,KAC5C,SAAS,GAAG,SAAS,CAAA;KAC7B;IAcD,gBAAgB,CAAC,IAAI,EAAE,GAAG;IAiB1B,cAAc,CAAC,IAAI,EAAE,GAAG;IAiBxB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS;IAsC9C,GAAG;IAQH,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS;CAiTnD;AAED,wBAAgB,WAAW,CACvB,IAAI,EAAE,iBAAiB,GAAG,iBAAiB,EAC3C,OAAO,GAAE,CAAC,GAAG,EAAE;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,KAAK,IAAoB,mBAmE9D;AAcD,wBAAgB,QAAQ,CACpB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,EAAE,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,KAAK,GAAG,SAiBpC;AAUD,QAAA,MAAM,UAAU,+eA6DN,CAAA;AA2RV,KAAK,aAAa,GAAG;KAAG,CAAC,IAAI,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG;CAAE,GAAG;IAChE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CACrB,CAAA;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,KACf,MAAM,IAAI,UAqE9B"}