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 +21 -0
- package/README.md +46 -14
- package/dist/safe-mdx.d.ts +33 -9
- package/dist/safe-mdx.d.ts.map +1 -1
- package/dist/safe-mdx.js +474 -74
- package/dist/safe-mdx.js.map +1 -1
- package/dist/safe-mdx.test.js +657 -250
- package/dist/safe-mdx.test.js.map +1 -1
- package/package.json +16 -13
- package/src/safe-mdx.test.tsx +671 -251
- package/src/safe-mdx.tsx +583 -83
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
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
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
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
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
|
-
-
|
|
82
|
-
-
|
|
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()
|
|
97
|
+
const parser = remark()
|
|
98
|
+
.use(remarkMdx)
|
|
99
|
+
.use(() => {
|
|
100
|
+
return (tree, file) => {
|
|
101
|
+
file.data.ast = tree
|
|
102
|
+
}
|
|
103
|
+
})
|
|
98
104
|
|
|
99
|
-
const
|
|
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
|
-
-
|
|
174
|
-
-
|
|
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
|
|
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.
|
package/dist/safe-mdx.d.ts
CHANGED
|
@@ -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
|
|
7
|
-
|
|
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?:
|
|
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
|
-
|
|
29
|
+
customTransformer?: CustomTransformer;
|
|
30
|
+
constructor({ code, mdast, components, customTransformer, }: {
|
|
24
31
|
code?: string;
|
|
25
|
-
mdast?:
|
|
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
|
package/dist/safe-mdx.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|