safe-mdx 0.0.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/README.md ADDED
@@ -0,0 +1,86 @@
1
+ <div align='center'>
2
+ <br/>
3
+ <br/>
4
+ <br/>
5
+ <h3>safe-mdx</h3>
6
+ <p>Render MDX in React without eval</p>
7
+ <br/>
8
+ <br/>
9
+ </div>
10
+
11
+ ## Why
12
+
13
+ The default MDX renderer uses `eval` to render MDX components. This is a security risk and it's not allowed in some environments like Cloudflare Workers.
14
+
15
+ Some use cases for this package are:
16
+
17
+ - Render MDX in Cloudflare Workers and Vercel Edge
18
+ - Safely render dynamically generated MDX code, like inside a ChatGPT like interface
19
+ - Render user generated MDX, like in a multi-tenant SaaS app
20
+
21
+ <br>
22
+
23
+ ## Install
24
+
25
+ ```
26
+ npm i safe-mdx
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```tsx
32
+ import { SafeMdxRenderer } from 'safe-mdx'
33
+
34
+ const code = `
35
+ # Hello world
36
+
37
+ This is a paragraph
38
+
39
+ <Heading>Custom component</Heading>
40
+ `
41
+
42
+ export function Page() {
43
+ return (
44
+ <MdxRenderer
45
+ code={code}
46
+ components={{
47
+ // You can pass your own components here
48
+ Heading({ children }) {
49
+ return <h1>{children}</h1>
50
+ },
51
+ }}
52
+ />
53
+ )
54
+ }
55
+ ```
56
+
57
+ ## Change default MDX parser
58
+
59
+ If you want to use custom MDX plugins, you can pass your own MDX processed ast.
60
+
61
+ By default `safe-mdx` already has support for
62
+
63
+ - frontmatter
64
+ - gfm
65
+
66
+ ```tsx
67
+ import { SafeMdxRenderer } from 'safe-mdx'
68
+ import { remark } from 'remark'
69
+ import remarkMdx from 'remark-mdx'
70
+
71
+ const code = `
72
+ # Hello world
73
+
74
+ This is a paragraph
75
+
76
+ <Heading>Custom component</Heading>
77
+ `
78
+
79
+ const parser = remark().use(remarkMdx)
80
+
81
+ const mdast = parser.parse(code)
82
+
83
+ export function Page() {
84
+ return <MdxRenderer code={code} mdast={mdast} />
85
+ }
86
+ ```
@@ -0,0 +1,6 @@
1
+ export declare function SafeMdxRenderer({ components, code, mdast, }: {
2
+ components: any;
3
+ code?: string | undefined;
4
+ mdast?: any;
5
+ }): any;
6
+ //# sourceMappingURL=safe-mdx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-mdx.d.ts","sourceRoot":"","sources":["../src/safe-mdx.tsx"],"names":[],"mappings":"AAmBA,wBAAgB,eAAe,CAAC,EAC5B,UAAU,EACV,IAAS,EACT,KAAmB,GACtB;;;;CAAA,OAIA"}
@@ -0,0 +1,395 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SafeMdxRenderer = void 0;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const html_to_jsx_transform_1 = require("html-to-jsx-transform");
9
+ const remark_frontmatter_1 = __importDefault(require("remark-frontmatter"));
10
+ const remark_1 = require("remark");
11
+ const remark_gfm_1 = __importDefault(require("remark-gfm"));
12
+ const remark_mdx_1 = __importDefault(require("remark-mdx"));
13
+ const react_1 = require("react");
14
+ const mdxParser = (0, remark_1.remark)()
15
+ .use(remark_frontmatter_1.default, ['yaml', 'toml'])
16
+ .use(remark_gfm_1.default)
17
+ .use(remark_mdx_1.default);
18
+ function SafeMdxRenderer({ components, code = '', mdast = null, }) {
19
+ const visitor = new MdastToJsx({ code, mdast, components });
20
+ const result = visitor.run();
21
+ return result;
22
+ }
23
+ exports.SafeMdxRenderer = SafeMdxRenderer;
24
+ const nativeTags = [
25
+ 'blockquote',
26
+ 'strong',
27
+ 'em',
28
+ 'del',
29
+ 'br',
30
+ 'hr',
31
+ 'a',
32
+ 'b',
33
+ 'br',
34
+ 'button',
35
+ 'div',
36
+ 'form',
37
+ 'h1',
38
+ 'h2',
39
+ 'h3',
40
+ 'h4',
41
+ 'head',
42
+ 'iframe',
43
+ 'img',
44
+ 'input',
45
+ 'label',
46
+ 'li',
47
+ 'link',
48
+ 'ol',
49
+ 'p',
50
+ 'path',
51
+ 'picture',
52
+ 'script',
53
+ 'section',
54
+ 'source',
55
+ 'span',
56
+ 'sub',
57
+ 'sup',
58
+ 'svg',
59
+ 'table',
60
+ 'tbody',
61
+ 'td',
62
+ 'th',
63
+ 'thead',
64
+ 'tr',
65
+ 'ul',
66
+ 'video',
67
+ 'code',
68
+ 'pre',
69
+ ];
70
+ class MdastToJsx {
71
+ constructor({ code = '', mdast, components = {} }) {
72
+ this.jsxStr = '';
73
+ this.errors = [];
74
+ this.str = code;
75
+ this.mdast = mdast || mdxParser.parse(code);
76
+ // TODO add tags and their allowed import sources
77
+ this.c = Object.assign(Object.assign({}, Object.fromEntries(nativeTags.map((tag) => {
78
+ return [tag, tag];
79
+ }))), components);
80
+ }
81
+ mapMdastChildren(node) {
82
+ var _a;
83
+ const res = (_a = node.children) === null || _a === void 0 ? void 0 : _a.flatMap((child) => this.mdastTransformer(child));
84
+ if (Array.isArray(res)) {
85
+ if (res.length === 1) {
86
+ return res[0];
87
+ }
88
+ else {
89
+ return res.map((x, i) => (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: x }, i));
90
+ }
91
+ }
92
+ return res || null;
93
+ }
94
+ mapJsxChildren(node) {
95
+ var _a;
96
+ const res = (_a = node.children) === null || _a === void 0 ? void 0 : _a.flatMap((child, i) => this.jsxTransformer(child));
97
+ if (Array.isArray(res)) {
98
+ if (res.length === 1) {
99
+ return res[0];
100
+ }
101
+ else {
102
+ return res.map((x, i) => (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: x }, i));
103
+ }
104
+ }
105
+ return res || null;
106
+ }
107
+ jsxTransformer(node) {
108
+ if (!node) {
109
+ return [];
110
+ }
111
+ switch (node.type) {
112
+ case 'mdxJsxTextElement':
113
+ case 'mdxJsxFlowElement': {
114
+ if (!node.name) {
115
+ return [];
116
+ }
117
+ const Component = accessWithDot(this.c, node.name);
118
+ if (!Component) {
119
+ this.errors.push(`Unsupported jsx tag ${node.name}`);
120
+ return null;
121
+ }
122
+ let attrsList = this.getJsxAttrs(node);
123
+ let attrs = Object.fromEntries(attrsList);
124
+ return ((0, jsx_runtime_1.jsx)(Component, Object.assign({}, attrs, { children: this.mapJsxChildren(node) })));
125
+ }
126
+ default: {
127
+ return this.mdastTransformer(node);
128
+ }
129
+ }
130
+ }
131
+ run() {
132
+ const res = this.mdastTransformer(this.mdast);
133
+ if (Array.isArray(res) && res.length === 1) {
134
+ return res[0];
135
+ }
136
+ return res;
137
+ }
138
+ mdastTransformer(node) {
139
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
140
+ if (!node) {
141
+ return [];
142
+ }
143
+ switch (node.type) {
144
+ case 'mdxjsEsm': {
145
+ const start = (_b = (_a = node.position) === null || _a === void 0 ? void 0 : _a.start) === null || _b === void 0 ? void 0 : _b.offset;
146
+ const end = (_d = (_c = node.position) === null || _c === void 0 ? void 0 : _c.end) === null || _d === void 0 ? void 0 : _d.offset;
147
+ let text = this.str.slice(start, end);
148
+ // console.log('esm', node)
149
+ const tree = (_e = node.data) === null || _e === void 0 ? void 0 : _e.estree;
150
+ return [];
151
+ }
152
+ case 'mdxJsxTextElement':
153
+ case 'mdxJsxFlowElement': {
154
+ const start = (_g = (_f = node.position) === null || _f === void 0 ? void 0 : _f.start) === null || _g === void 0 ? void 0 : _g.offset;
155
+ const end = (_j = (_h = node.position) === null || _h === void 0 ? void 0 : _h.end) === null || _j === void 0 ? void 0 : _j.offset;
156
+ const text = this.str.slice(start, end);
157
+ try {
158
+ this.jsxStr = text;
159
+ const result = this.jsxTransformer(node);
160
+ if (Array.isArray(result)) {
161
+ console.log(`Unexpected array result`);
162
+ }
163
+ else if (result) {
164
+ return result;
165
+ }
166
+ }
167
+ finally {
168
+ this.jsxStr = '';
169
+ }
170
+ return [];
171
+ }
172
+ case 'mdxFlowExpression':
173
+ case 'mdxTextExpression': {
174
+ if (!node.value) {
175
+ return [];
176
+ }
177
+ return [];
178
+ }
179
+ case 'yaml': {
180
+ if (!node.value) {
181
+ return [];
182
+ }
183
+ return [];
184
+ }
185
+ case 'heading': {
186
+ const level = node.depth;
187
+ const Tag = `h${level}`;
188
+ return (0, jsx_runtime_1.jsx)(Tag, { children: this.mapMdastChildren(node) });
189
+ }
190
+ case 'paragraph': {
191
+ return (0, jsx_runtime_1.jsx)(this.c.p, { children: this.mapMdastChildren(node) });
192
+ }
193
+ case 'blockquote': {
194
+ return ((0, jsx_runtime_1.jsx)(this.c.blockquote, { children: this.mapMdastChildren(node) }));
195
+ }
196
+ case 'thematicBreak': {
197
+ return (0, jsx_runtime_1.jsx)(this.c.hr, {});
198
+ }
199
+ case 'code': {
200
+ if (!node.value) {
201
+ return [];
202
+ }
203
+ const language = node.lang || '';
204
+ const code = node.value;
205
+ return ((0, jsx_runtime_1.jsx)(this.c.pre, { children: (0, jsx_runtime_1.jsx)(this.c.code, { className: `language-${language}`, children: code }) }));
206
+ }
207
+ case 'list': {
208
+ if (node.ordered) {
209
+ return ((0, jsx_runtime_1.jsx)(this.c.ol, { start: node.start, children: this.mapMdastChildren(node) }));
210
+ }
211
+ return (0, jsx_runtime_1.jsx)(this.c.ul, { children: this.mapMdastChildren(node) });
212
+ }
213
+ case 'listItem': {
214
+ // https://github.com/syntax-tree/mdast-util-gfm-task-list-item#syntax-tree
215
+ if ((node === null || node === void 0 ? void 0 : node.checked) != null) {
216
+ return ((0, jsx_runtime_1.jsx)(this.c.li, { "data-checked": node.checked, children: this.mapMdastChildren(node) }));
217
+ }
218
+ return (0, jsx_runtime_1.jsx)(this.c.li, { children: this.mapMdastChildren(node) });
219
+ }
220
+ case 'text': {
221
+ if (!node.value) {
222
+ return [];
223
+ }
224
+ return node.value;
225
+ }
226
+ case 'image': {
227
+ const src = node.url || '';
228
+ const alt = node.alt || '';
229
+ const title = node.title || '';
230
+ return (0, jsx_runtime_1.jsx)(this.c.img, { src: src, alt: alt, title: title });
231
+ }
232
+ case 'link': {
233
+ const href = node.url || '';
234
+ const title = node.title || '';
235
+ return ((0, jsx_runtime_1.jsx)(this.c.a, { href, title, children: this.mapMdastChildren(node) }));
236
+ }
237
+ case 'strong': {
238
+ return ((0, jsx_runtime_1.jsx)(this.c.strong, { children: this.mapMdastChildren(node) }));
239
+ }
240
+ case 'emphasis': {
241
+ return (0, jsx_runtime_1.jsx)(this.c.em, { children: this.mapMdastChildren(node) });
242
+ }
243
+ case 'delete': {
244
+ return (0, jsx_runtime_1.jsx)(this.c.del, { children: this.mapMdastChildren(node) });
245
+ }
246
+ case 'inlineCode': {
247
+ if (!node.value) {
248
+ return [];
249
+ }
250
+ return (0, jsx_runtime_1.jsx)(this.c.code, { children: node.value });
251
+ }
252
+ case 'break': {
253
+ return (0, jsx_runtime_1.jsx)(this.c.br, {});
254
+ }
255
+ case 'root': {
256
+ return ((0, jsx_runtime_1.jsx)(this.c.div, { className: '', children: this.mapMdastChildren(node) }));
257
+ }
258
+ case 'table': {
259
+ const align = node.align;
260
+ return ((0, jsx_runtime_1.jsx)(this.c.table, { children: this.mapMdastChildren(node) }));
261
+ }
262
+ case 'tableRow': {
263
+ return ((0, jsx_runtime_1.jsx)(this.c.tr, { className: '', children: this.mapMdastChildren(node) }));
264
+ }
265
+ case 'tableCell': {
266
+ let content = this.mapMdastChildren(node);
267
+ return (0, jsx_runtime_1.jsx)(this.c.td, { className: '', children: content });
268
+ }
269
+ case 'definition': {
270
+ return [];
271
+ }
272
+ case 'footnoteReference': {
273
+ return [];
274
+ }
275
+ case 'footnoteDefinition': {
276
+ return [];
277
+ }
278
+ case 'html': {
279
+ // console.log('html', node)
280
+ const start = (_l = (_k = node.position) === null || _k === void 0 ? void 0 : _k.start) === null || _l === void 0 ? void 0 : _l.offset;
281
+ const end = (_o = (_m = node.position) === null || _m === void 0 ? void 0 : _m.end) === null || _o === void 0 ? void 0 : _o.offset;
282
+ const text = this.str.slice(start, end);
283
+ if (!text) {
284
+ return [];
285
+ }
286
+ const jsx = (0, html_to_jsx_transform_1.htmlToJsx)(text);
287
+ try {
288
+ this.jsxStr = jsx;
289
+ const result = this.jsxTransformer(node);
290
+ if (Array.isArray(result)) {
291
+ console.log(`Unexpected array result`);
292
+ }
293
+ else if (result) {
294
+ return result;
295
+ }
296
+ }
297
+ finally {
298
+ this.jsxStr = '';
299
+ }
300
+ return [];
301
+ }
302
+ case 'imageReference': {
303
+ return [];
304
+ }
305
+ default: {
306
+ mdastBfs(node, (node) => {
307
+ delete node.position;
308
+ });
309
+ throw new Error(`cannot convert node` + JSON.stringify(node, null, 2));
310
+ return [];
311
+ }
312
+ }
313
+ }
314
+ getJsxAttrs(node) {
315
+ let attrsList = node.attributes
316
+ .map((attr) => {
317
+ // TODO what is mdxJsxExpressionAttribute
318
+ if (attr.type === 'mdxJsxExpressionAttribute') {
319
+ throw new Error(`mdxJsxExpressionAttribute is not supported: ${attr.value}`);
320
+ }
321
+ if (attr.type !== 'mdxJsxAttribute') {
322
+ throw new Error(`non mdxJsxAttribute is not supported: ${attr}`);
323
+ }
324
+ const v = attr.value;
325
+ if (typeof v === 'string' || typeof v === 'number') {
326
+ return [attr.name, v];
327
+ }
328
+ if (v === null) {
329
+ return [attr.name, true];
330
+ }
331
+ if ((v === null || v === void 0 ? void 0 : v.type) === 'mdxJsxAttributeValueExpression') {
332
+ // logger.json({value})
333
+ // if it's a number, just return it
334
+ if (v.value === 'true') {
335
+ return [attr.name, true];
336
+ }
337
+ if (v.value === 'false') {
338
+ return [attr.name, false];
339
+ }
340
+ if (v.value === 'null') {
341
+ return [attr.name, null];
342
+ }
343
+ if (v.value === 'undefined') {
344
+ return [attr.name, undefined];
345
+ }
346
+ if (
347
+ // TODO add a way to parse ' and `
348
+ ['"'].some((q) => v.value.startsWith(q) && v.value.endsWith(q))) {
349
+ return [attr.name, JSON.parse(v.value)];
350
+ }
351
+ const number = Number(v.value);
352
+ if (!isNaN(number)) {
353
+ return [attr.name, number];
354
+ }
355
+ this.errors.push(`Expressions in jsx props are not supported (${attr.name}={${v.value}})`);
356
+ // return [attr.name, `{${v.value}}`]
357
+ }
358
+ else {
359
+ console.log('unhandled attr', { attr }, attr.type);
360
+ }
361
+ return;
362
+ })
363
+ .filter(isTruthy);
364
+ return attrsList;
365
+ }
366
+ }
367
+ function isTruthy(val) {
368
+ return Boolean(val);
369
+ }
370
+ function accessWithDot(obj, path) {
371
+ return path
372
+ .split('.')
373
+ .map((x) => x.trim())
374
+ .filter(Boolean)
375
+ .reduce((o, i) => o[i], obj);
376
+ }
377
+ function mdastBfs(node, cb) {
378
+ const queue = [node];
379
+ const result = [];
380
+ while (queue.length) {
381
+ const node = queue.shift();
382
+ let r = cb && node ? cb(node) : node;
383
+ if (Array.isArray(r)) {
384
+ queue.push(...r);
385
+ }
386
+ else if (r) {
387
+ result.push(r);
388
+ }
389
+ if (node && 'children' in node && node.children) {
390
+ queue.push(...node.children);
391
+ }
392
+ }
393
+ return result;
394
+ }
395
+ //# sourceMappingURL=safe-mdx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safe-mdx.js","sourceRoot":"","sources":["../src/safe-mdx.tsx"],"names":[],"mappings":";;;;;;;AAAA,iEAAiD;AAEjD,4EAAkD;AAIlD,mCAA+B;AAC/B,4DAAkC;AAClC,4DAAkC;AAElC,iCAA2C;AAI3C,MAAM,SAAS,GAAG,IAAA,eAAM,GAAE;KACrB,GAAG,CAAC,4BAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACxC,GAAG,CAAC,oBAAS,CAAC;KACd,GAAG,CAAC,oBAAS,CAAQ,CAAA;AAE1B,SAAgB,eAAe,CAAC,EAC5B,UAAU,EACV,IAAI,GAAG,EAAE,EACT,KAAK,GAAG,IAAW,GACtB;IACG,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAA;IAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAC5B,OAAO,MAAM,CAAA;AACjB,CAAC;AARD,0CAQC;AAED,MAAM,UAAU,GAAG;IACf,YAAY;IACZ,QAAQ;IACR,IAAI;IACJ,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,GAAG;IACH,GAAG;IACH,IAAI;IACJ,QAAQ;IACR,KAAK;IACL,MAAM;IACN,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,MAAM;IACN,QAAQ;IACR,KAAK;IACL,OAAO;IACP,OAAO;IACP,IAAI;IACJ,MAAM;IACN,IAAI;IACJ,GAAG;IACH,MAAM;IACN,SAAS;IACT,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,MAAM;IACN,KAAK;IACL,KAAK;IACL,KAAK;IACL,OAAO;IACP,OAAO;IACP,IAAI;IACJ,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,IAAI;IACJ,OAAO;IACP,MAAM;IACN,KAAK;CACC,CAAA;AAIV,MAAM,UAAU;IAMZ,YAAY,EAAE,IAAI,GAAG,EAAE,EAAE,KAAK,EAAE,UAAU,GAAG,EAAmB,EAAE;QAHlE,WAAM,GAAW,EAAE,CAAA;QAEnB,WAAM,GAAa,EAAE,CAAA;QAEjB,IAAI,CAAC,GAAG,GAAG,IAAI,CAAA;QACf,IAAI,CAAC,KAAK,GAAG,KAAK,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC3C,iDAAiD;QACjD,IAAI,CAAC,CAAC,mCACC,MAAM,CAAC,WAAW,CACjB,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;YACnB,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;QACrB,CAAC,CAAC,CACL,GACE,UAAU,CAChB,CAAA;IACL,CAAC;IACD,gBAAgB,CAAC,IAAS;;QACtB,MAAM,GAAG,GAAG,MAAA,IAAI,CAAC,QAAQ,0CAAE,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CACzC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAC/B,CAAA;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YACpB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE;gBAClB,OAAO,GAAG,CAAC,CAAC,CAAC,CAAA;aAChB;iBAAM;gBACH,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,uBAAC,gBAAQ,cAAU,CAAC,IAAL,CAAC,CAAgB,CAAC,CAAA;aAC7D;SACJ;QACD,OAAO,GAAG,IAAI,IAAI,CAAA;IACtB,CAAC;IACD,cAAc,CAAC,IAAS;;QACpB,MAAM,GAAG,GAAG,MAAA,IAAI,CAAC,QAAQ,0CAAE,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAC5C,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAC7B,CAAA;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;YACpB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE;gBAClB,OAAO,GAAG,CAAC,CAAC,CAAC,CAAA;aAChB;iBAAM;gBACH,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,uBAAC,gBAAQ,cAAU,CAAC,IAAL,CAAC,CAAgB,CAAC,CAAA;aAC7D;SACJ;QACD,OAAO,GAAG,IAAI,IAAI,CAAA;IACtB,CAAC;IACD,cAAc,CAAC,IAAmB;QAC9B,IAAI,CAAC,IAAI,EAAE;YACP,OAAO,EAAE,CAAA;SACZ;QAED,QAAQ,IAAI,CAAC,IAAI,EAAE;YACf,KAAK,mBAAmB,CAAC;YACzB,KAAK,mBAAmB,CAAC,CAAC;gBACtB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;oBACZ,OAAO,EAAE,CAAA;iBACZ;gBAED,MAAM,SAAS,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;gBAElD,IAAI,CAAC,SAAS,EAAE;oBACZ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uBAAuB,IAAI,CAAC,IAAI,EAAE,CAAC,CAAA;oBACpD,OAAO,IAAI,CAAA;iBACd;gBAED,IAAI,SAAS,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;gBAEtC,IAAI,KAAK,GAAG,MAAM,CAAC,WAAW,CAAC,SAAS,CAAC,CAAA;gBACzC,OAAO,CACH,uBAAC,SAAS,oBAAK,KAAK,cACf,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAClB,CACf,CAAA;aACJ;YACD,OAAO,CAAC,CAAC;gBACL,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAA;aACrC;SACJ;IACL,CAAC;IAED,GAAG;QACC,MAAM,GAAG,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAc,CAAA;QAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,EAAE;YACxC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAA;SAChB;QACD,OAAO,GAAG,CAAA;IACd,CAAC;IAED,gBAAgB,CAAC,IAAmB;;QAChC,IAAI,CAAC,IAAI,EAAE;YACP,OAAO,EAAE,CAAA;SACZ;QAED,QAAQ,IAAI,CAAC,IAAI,EAAE;YACf,KAAK,UAAU,CAAC,CAAC;gBACb,MAAM,KAAK,GAAG,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,KAAK,0CAAE,MAAM,CAAA;gBAC1C,MAAM,GAAG,GAAG,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,GAAG,0CAAE,MAAM,CAAA;gBACtC,IAAI,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;gBACrC,2BAA2B;gBAC3B,MAAM,IAAI,GAAG,MAAA,IAAI,CAAC,IAAI,0CAAE,MAAM,CAAA;gBAE9B,OAAO,EAAE,CAAA;aACZ;YACD,KAAK,mBAAmB,CAAC;YACzB,KAAK,mBAAmB,CAAC,CAAC;gBACtB,MAAM,KAAK,GAAG,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,KAAK,0CAAE,MAAM,CAAA;gBAC1C,MAAM,GAAG,GAAG,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,GAAG,0CAAE,MAAM,CAAA;gBACtC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;gBACvC,IAAI;oBACA,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;oBAClB,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;oBACxC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;wBACvB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAA;qBACzC;yBAAM,IAAI,MAAM,EAAE;wBACf,OAAO,MAAM,CAAA;qBAChB;iBACJ;wBAAS;oBACN,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;iBACnB;gBACD,OAAO,EAAE,CAAA;aACZ;YAED,KAAK,mBAAmB,CAAC;YACzB,KAAK,mBAAmB,CAAC,CAAC;gBACtB,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;oBACb,OAAO,EAAE,CAAA;iBACZ;gBACD,OAAO,EAAE,CAAA;aACZ;YACD,KAAK,MAAM,CAAC,CAAC;gBACT,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;oBACb,OAAO,EAAE,CAAA;iBACZ;gBACD,OAAO,EAAE,CAAA;aACZ;YACD,KAAK,SAAS,CAAC,CAAC;gBACZ,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;gBAExB,MAAM,GAAG,GAAQ,IAAI,KAAK,EAAE,CAAA;gBAC5B,OAAO,uBAAC,GAAG,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAO,CAAA;aAClD;YACD,KAAK,WAAW,CAAC,CAAC;gBACd,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAY,CAAA;aAC5D;YACD,KAAK,YAAY,CAAC,CAAC;gBACf,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,UAAU,cACb,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GACZ,CACvB,CAAA;aACJ;YACD,KAAK,eAAe,CAAC,CAAC;gBAClB,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAG,CAAA;aACvB;YACD,KAAK,MAAM,CAAC,CAAC;gBACT,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;oBACb,OAAO,EAAE,CAAA;iBACZ;gBACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,IAAI,EAAE,CAAA;gBAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAA;gBACvB,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,GAAG,cACP,uBAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAC,SAAS,EAAE,YAAY,QAAQ,EAAE,YACzC,IAAI,GACK,GACL,CAChB,CAAA;aACJ;YAED,KAAK,MAAM,CAAC,CAAC;gBACT,IAAI,IAAI,CAAC,OAAO,EAAE;oBACd,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAC,KAAK,EAAE,IAAI,CAAC,KAAM,YACxB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GACpB,CACf,CAAA;iBACJ;gBACD,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAa,CAAA;aAC9D;YACD,KAAK,UAAU,CAAC,CAAC;gBACb,2EAA2E;gBAC3E,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO,KAAI,IAAI,EAAE;oBACvB,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,oBAAe,IAAI,CAAC,OAAO,YAChC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GACpB,CACf,CAAA;iBACJ;gBACD,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAa,CAAA;aAC9D;YACD,KAAK,MAAM,CAAC,CAAC;gBACT,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;oBACb,OAAO,EAAE,CAAA;iBACZ;gBACD,OAAO,IAAI,CAAC,KAAK,CAAA;aACpB;YACD,KAAK,OAAO,CAAC,CAAC;gBACV,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,CAAA;gBAC1B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,CAAA;gBAC1B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAA;gBAC9B,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAI,CAAA;aAC1D;YACD,KAAK,MAAM,CAAC,CAAC;gBACT,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,IAAI,EAAE,CAAA;gBAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,CAAA;gBAC9B,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAO,IAAI,EAAE,KAAK,YACtB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GACrB,CACd,CAAA;aACJ;YACD,KAAK,QAAQ,CAAC,CAAC;gBACX,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,MAAM,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAiB,CAC/D,CAAA;aACJ;YACD,KAAK,UAAU,CAAC,CAAC;gBACb,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAa,CAAA;aAC9D;YACD,KAAK,QAAQ,CAAC,CAAC;gBACX,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,GAAG,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAc,CAAA;aAChE;YACD,KAAK,YAAY,CAAC,CAAC;gBACf,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;oBACb,OAAO,EAAE,CAAA;iBACZ;gBACD,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,IAAI,cAAE,IAAI,CAAC,KAAK,GAAe,CAAA;aACjD;YACD,KAAK,OAAO,CAAC,CAAC;gBACV,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAG,CAAA;aACvB;YACD,KAAK,MAAM,CAAC,CAAC;gBACT,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAC,SAAS,EAAC,EAAE,YACnB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GACnB,CAChB,CAAA;aACJ;YACD,KAAK,OAAO,CAAC,CAAC;gBACV,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAA;gBACxB,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,KAAK,cAAE,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GAAgB,CAC7D,CAAA;aACJ;YACD,KAAK,UAAU,CAAC,CAAC;gBACb,OAAO,CACH,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAC,SAAS,EAAC,EAAE,YAClB,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,GACpB,CACf,CAAA;aACJ;YACD,KAAK,WAAW,CAAC,CAAC;gBACd,IAAI,OAAO,GAAG,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAA;gBAEzC,OAAO,uBAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAC,SAAS,EAAC,EAAE,YAAE,OAAO,GAAa,CAAA;aACvD;YACD,KAAK,YAAY,CAAC,CAAC;gBACf,OAAO,EAAE,CAAA;aACZ;YACD,KAAK,mBAAmB,CAAC,CAAC;gBACtB,OAAO,EAAE,CAAA;aACZ;YAED,KAAK,oBAAoB,CAAC,CAAC;gBACvB,OAAO,EAAE,CAAA;aACZ;YACD,KAAK,MAAM,CAAC,CAAC;gBACT,4BAA4B;gBAE5B,MAAM,KAAK,GAAG,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,KAAK,0CAAE,MAAM,CAAA;gBAC1C,MAAM,GAAG,GAAG,MAAA,MAAA,IAAI,CAAC,QAAQ,0CAAE,GAAG,0CAAE,MAAM,CAAA;gBACtC,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAA;gBACvC,IAAI,CAAC,IAAI,EAAE;oBACP,OAAO,EAAE,CAAA;iBACZ;gBAED,MAAM,GAAG,GAAG,IAAA,iCAAS,EAAC,IAAI,CAAC,CAAA;gBAC3B,IAAI;oBACA,IAAI,CAAC,MAAM,GAAG,GAAG,CAAA;oBACjB,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,CAAA;oBACxC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;wBACvB,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC,CAAA;qBACzC;yBAAM,IAAI,MAAM,EAAE;wBACf,OAAO,MAAM,CAAA;qBAChB;iBACJ;wBAAS;oBACN,IAAI,CAAC,MAAM,GAAG,EAAE,CAAA;iBACnB;gBAED,OAAO,EAAE,CAAA;aACZ;YACD,KAAK,gBAAgB,CAAC,CAAC;gBACnB,OAAO,EAAE,CAAA;aACZ;YAED,OAAO,CAAC,CAAC;gBACL,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;oBACpB,OAAO,IAAI,CAAC,QAAQ,CAAA;gBACxB,CAAC,CAAC,CAAA;gBAEF,MAAM,IAAI,KAAK,CACX,qBAAqB,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CACxD,CAAA;gBAED,OAAO,EAAE,CAAA;aACZ;SACJ;IACL,CAAC;IACD,WAAW,CAAC,IAA2C;QACnD,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU;aAC1B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;YACV,yCAAyC;YACzC,IAAI,IAAI,CAAC,IAAI,KAAK,2BAA2B,EAAE;gBAC3C,MAAM,IAAI,KAAK,CACX,+CAA+C,IAAI,CAAC,KAAK,EAAE,CAC9D,CAAA;aACJ;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,iBAAiB,EAAE;gBACjC,MAAM,IAAI,KAAK,CACX,yCAAyC,IAAI,EAAE,CAClD,CAAA;aACJ;YAED,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAA;YACpB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE;gBAChD,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAA;aACxB;YACD,IAAI,CAAC,KAAK,IAAI,EAAE;gBACZ,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;aAC3B;YACD,IAAI,CAAA,CAAC,aAAD,CAAC,uBAAD,CAAC,CAAE,IAAI,MAAK,gCAAgC,EAAE;gBAC9C,uBAAuB;gBACvB,mCAAmC;gBACnC,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE;oBACpB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;iBAC3B;gBACD,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO,EAAE;oBACrB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;iBAC5B;gBACD,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,EAAE;oBACpB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;iBAC3B;gBACD,IAAI,CAAC,CAAC,KAAK,KAAK,WAAW,EAAE;oBACzB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;iBAChC;gBACD;gBACI,kCAAkC;gBAClC,CAAC,GAAG,CAAC,CAAC,IAAI,CACN,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CACtD,EACH;oBACE,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;iBAC1C;gBAED,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;gBAC9B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE;oBAChB,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;iBAC7B;gBAED,IAAI,CAAC,MAAM,CAAC,IAAI,CACZ,+CAA+C,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,KAAK,IAAI,CAC3E,CAAA;gBAED,qCAAqC;aACxC;iBAAM;gBACH,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;aACrD;YAED,OAAM;QACV,CAAC,CAAC;aACD,MAAM,CAAC,QAAQ,CAAoB,CAAA;QACxC,OAAO,SAAS,CAAA;IACpB,CAAC;CACJ;AAED,SAAS,QAAQ,CAAI,GAAiC;IAClD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAA;AACvB,CAAC;AAED,SAAS,aAAa,CAAC,GAAG,EAAE,IAAY;IACpC,OAAO,IAAI;SACN,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;AACpC,CAAC;AAED,SAAS,QAAQ,CAAC,IAAmB,EAAE,EAAiC;IACpE,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,CAAA;IACpB,MAAM,MAAM,GAAU,EAAE,CAAA;IACxB,OAAO,KAAK,CAAC,MAAM,EAAE;QACjB,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,EAAE,CAAA;QAC1B,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACpC,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YAClB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;SACnB;aAAM,IAAI,CAAC,EAAE;YACV,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;SACjB;QACD,IAAI,IAAI,IAAI,UAAU,IAAI,IAAI,IAAI,IAAI,CAAC,QAAQ,EAAE;YAC7C,KAAK,CAAC,IAAI,CAAC,GAAI,IAAI,CAAC,QAAgB,CAAC,CAAA;SACxC;KACJ;IACD,OAAO,MAAM,CAAA;AACjB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "safe-mdx",
3
+ "version": "0.0.0",
4
+ "private": false,
5
+ "description": "Render MDX in React without eval",
6
+ "main": "dist/safe-mdx.js",
7
+ "types": "dist/safe-mdx.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "prepublishOnly": "pnpm build"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "src"
15
+ ],
16
+ "keywords": [
17
+ "mdx"
18
+ ],
19
+ "author": "remorses <beats.by.morse@gmail.com>",
20
+ "license": "MIT",
21
+ "peerDependencies": {
22
+ "react": "*"
23
+ },
24
+ "dependencies": {
25
+ "html-to-jsx-transform": "^1.1.0",
26
+ "mdast": "^3.0.0",
27
+ "remark": "^15.0.1",
28
+ "remark-frontmatter": "^5.0.0",
29
+ "remark-gfm": "^4.0.0",
30
+ "remark-mdx": "^3.0.0"
31
+ },
32
+ "devDependencies": {
33
+ "@types/mdast": "^4.0.3",
34
+ "@types/node": "^20.10.0",
35
+ "@types/react": "18.2.15",
36
+ "mdast-util-mdx-jsx": "^3.0.0",
37
+ "react": "^18.2.0"
38
+ }
39
+ }
@@ -0,0 +1,481 @@
1
+ import { htmlToJsx } from 'html-to-jsx-transform'
2
+ import { Node, Parent } from 'mdast'
3
+ import remarkFrontmatter from 'remark-frontmatter'
4
+
5
+ import { Root, RootContent } from 'mdast'
6
+ import { MdxJsxFlowElement, MdxJsxTextElement } from 'mdast-util-mdx-jsx'
7
+ import { remark } from 'remark'
8
+ import remarkGfm from 'remark-gfm'
9
+ import remarkMdx from 'remark-mdx'
10
+
11
+ import { Fragment, ReactNode } from 'react'
12
+
13
+ type MyRootContent = RootContent | Root
14
+
15
+ const mdxParser = remark()
16
+ .use(remarkFrontmatter, ['yaml', 'toml'])
17
+ .use(remarkGfm)
18
+ .use(remarkMdx) as any
19
+
20
+ export function SafeMdxRenderer({
21
+ components,
22
+ code = '',
23
+ mdast = null as any,
24
+ }) {
25
+ const visitor = new MdastToJsx({ code, mdast, components })
26
+ const result = visitor.run()
27
+ return result
28
+ }
29
+
30
+ const nativeTags = [
31
+ 'blockquote',
32
+ 'strong',
33
+ 'em',
34
+ 'del',
35
+ 'br',
36
+ 'hr',
37
+ 'a',
38
+ 'b',
39
+ 'br',
40
+ 'button',
41
+ 'div',
42
+ 'form',
43
+ 'h1',
44
+ 'h2',
45
+ 'h3',
46
+ 'h4',
47
+ 'head',
48
+ 'iframe',
49
+ 'img',
50
+ 'input',
51
+ 'label',
52
+ 'li',
53
+ 'link',
54
+ 'ol',
55
+ 'p',
56
+ 'path',
57
+ 'picture',
58
+ 'script',
59
+ 'section',
60
+ 'source',
61
+ 'span',
62
+ 'sub',
63
+ 'sup',
64
+ 'svg',
65
+ 'table',
66
+ 'tbody',
67
+ 'td',
68
+ 'th',
69
+ 'thead',
70
+ 'tr',
71
+ 'ul',
72
+ 'video',
73
+ 'code',
74
+ 'pre',
75
+ ] as const
76
+
77
+ type ComponentsMap = { [k in (typeof nativeTags)[number]]: any }
78
+
79
+ class MdastToJsx {
80
+ mdast: MyRootContent
81
+ str: string
82
+ jsxStr: string = ''
83
+ c: ComponentsMap
84
+ errors: string[] = []
85
+ constructor({ code = '', mdast, components = {} as ComponentsMap }) {
86
+ this.str = code
87
+ this.mdast = mdast || mdxParser.parse(code)
88
+ // TODO add tags and their allowed import sources
89
+ this.c = {
90
+ ...Object.fromEntries(
91
+ nativeTags.map((tag) => {
92
+ return [tag, tag]
93
+ }),
94
+ ),
95
+ ...components,
96
+ }
97
+ }
98
+ mapMdastChildren(node: any) {
99
+ const res = node.children?.flatMap((child) =>
100
+ this.mdastTransformer(child),
101
+ )
102
+ if (Array.isArray(res)) {
103
+ if (res.length === 1) {
104
+ return res[0]
105
+ } else {
106
+ return res.map((x, i) => <Fragment key={i}>{x}</Fragment>)
107
+ }
108
+ }
109
+ return res || null
110
+ }
111
+ mapJsxChildren(node: any) {
112
+ const res = node.children?.flatMap((child, i) =>
113
+ this.jsxTransformer(child),
114
+ )
115
+ if (Array.isArray(res)) {
116
+ if (res.length === 1) {
117
+ return res[0]
118
+ } else {
119
+ return res.map((x, i) => <Fragment key={i}>{x}</Fragment>)
120
+ }
121
+ }
122
+ return res || null
123
+ }
124
+ jsxTransformer(node: MyRootContent): ReactNode {
125
+ if (!node) {
126
+ return []
127
+ }
128
+
129
+ switch (node.type) {
130
+ case 'mdxJsxTextElement':
131
+ case 'mdxJsxFlowElement': {
132
+ if (!node.name) {
133
+ return []
134
+ }
135
+
136
+ const Component = accessWithDot(this.c, node.name)
137
+
138
+ if (!Component) {
139
+ this.errors.push(`Unsupported jsx tag ${node.name}`)
140
+ return null
141
+ }
142
+
143
+ let attrsList = this.getJsxAttrs(node)
144
+
145
+ let attrs = Object.fromEntries(attrsList)
146
+ return (
147
+ <Component {...attrs}>
148
+ {this.mapJsxChildren(node)}
149
+ </Component>
150
+ )
151
+ }
152
+ default: {
153
+ return this.mdastTransformer(node)
154
+ }
155
+ }
156
+ }
157
+
158
+ run() {
159
+ const res = this.mdastTransformer(this.mdast) as ReactNode
160
+ if (Array.isArray(res) && res.length === 1) {
161
+ return res[0]
162
+ }
163
+ return res
164
+ }
165
+
166
+ mdastTransformer(node: MyRootContent): ReactNode {
167
+ if (!node) {
168
+ return []
169
+ }
170
+
171
+ switch (node.type) {
172
+ case 'mdxjsEsm': {
173
+ const start = node.position?.start?.offset
174
+ const end = node.position?.end?.offset
175
+ let text = this.str.slice(start, end)
176
+ // console.log('esm', node)
177
+ const tree = node.data?.estree
178
+
179
+ return []
180
+ }
181
+ case 'mdxJsxTextElement':
182
+ case 'mdxJsxFlowElement': {
183
+ const start = node.position?.start?.offset
184
+ const end = node.position?.end?.offset
185
+ const text = this.str.slice(start, end)
186
+ try {
187
+ this.jsxStr = text
188
+ const result = this.jsxTransformer(node)
189
+ if (Array.isArray(result)) {
190
+ console.log(`Unexpected array result`)
191
+ } else if (result) {
192
+ return result
193
+ }
194
+ } finally {
195
+ this.jsxStr = ''
196
+ }
197
+ return []
198
+ }
199
+
200
+ case 'mdxFlowExpression':
201
+ case 'mdxTextExpression': {
202
+ if (!node.value) {
203
+ return []
204
+ }
205
+ return []
206
+ }
207
+ case 'yaml': {
208
+ if (!node.value) {
209
+ return []
210
+ }
211
+ return []
212
+ }
213
+ case 'heading': {
214
+ const level = node.depth
215
+
216
+ const Tag: any = `h${level}`
217
+ return <Tag>{this.mapMdastChildren(node)}</Tag>
218
+ }
219
+ case 'paragraph': {
220
+ return <this.c.p>{this.mapMdastChildren(node)}</this.c.p>
221
+ }
222
+ case 'blockquote': {
223
+ return (
224
+ <this.c.blockquote>
225
+ {this.mapMdastChildren(node)}
226
+ </this.c.blockquote>
227
+ )
228
+ }
229
+ case 'thematicBreak': {
230
+ return <this.c.hr />
231
+ }
232
+ case 'code': {
233
+ if (!node.value) {
234
+ return []
235
+ }
236
+ const language = node.lang || ''
237
+ const code = node.value
238
+ return (
239
+ <this.c.pre>
240
+ <this.c.code className={`language-${language}`}>
241
+ {code}
242
+ </this.c.code>
243
+ </this.c.pre>
244
+ )
245
+ }
246
+
247
+ case 'list': {
248
+ if (node.ordered) {
249
+ return (
250
+ <this.c.ol start={node.start!}>
251
+ {this.mapMdastChildren(node)}
252
+ </this.c.ol>
253
+ )
254
+ }
255
+ return <this.c.ul>{this.mapMdastChildren(node)}</this.c.ul>
256
+ }
257
+ case 'listItem': {
258
+ // https://github.com/syntax-tree/mdast-util-gfm-task-list-item#syntax-tree
259
+ if (node?.checked != null) {
260
+ return (
261
+ <this.c.li data-checked={node.checked}>
262
+ {this.mapMdastChildren(node)}
263
+ </this.c.li>
264
+ )
265
+ }
266
+ return <this.c.li>{this.mapMdastChildren(node)}</this.c.li>
267
+ }
268
+ case 'text': {
269
+ if (!node.value) {
270
+ return []
271
+ }
272
+ return node.value
273
+ }
274
+ case 'image': {
275
+ const src = node.url || ''
276
+ const alt = node.alt || ''
277
+ const title = node.title || ''
278
+ return <this.c.img src={src} alt={alt} title={title} />
279
+ }
280
+ case 'link': {
281
+ const href = node.url || ''
282
+ const title = node.title || ''
283
+ return (
284
+ <this.c.a {...{ href, title }}>
285
+ {this.mapMdastChildren(node)}
286
+ </this.c.a>
287
+ )
288
+ }
289
+ case 'strong': {
290
+ return (
291
+ <this.c.strong>{this.mapMdastChildren(node)}</this.c.strong>
292
+ )
293
+ }
294
+ case 'emphasis': {
295
+ return <this.c.em>{this.mapMdastChildren(node)}</this.c.em>
296
+ }
297
+ case 'delete': {
298
+ return <this.c.del>{this.mapMdastChildren(node)}</this.c.del>
299
+ }
300
+ case 'inlineCode': {
301
+ if (!node.value) {
302
+ return []
303
+ }
304
+ return <this.c.code>{node.value}</this.c.code>
305
+ }
306
+ case 'break': {
307
+ return <this.c.br />
308
+ }
309
+ case 'root': {
310
+ return (
311
+ <this.c.div className=''>
312
+ {this.mapMdastChildren(node)}
313
+ </this.c.div>
314
+ )
315
+ }
316
+ case 'table': {
317
+ const align = node.align
318
+ return (
319
+ <this.c.table>{this.mapMdastChildren(node)}</this.c.table>
320
+ )
321
+ }
322
+ case 'tableRow': {
323
+ return (
324
+ <this.c.tr className=''>
325
+ {this.mapMdastChildren(node)}
326
+ </this.c.tr>
327
+ )
328
+ }
329
+ case 'tableCell': {
330
+ let content = this.mapMdastChildren(node)
331
+
332
+ return <this.c.td className=''>{content}</this.c.td>
333
+ }
334
+ case 'definition': {
335
+ return []
336
+ }
337
+ case 'footnoteReference': {
338
+ return []
339
+ }
340
+
341
+ case 'footnoteDefinition': {
342
+ return []
343
+ }
344
+ case 'html': {
345
+ // console.log('html', node)
346
+
347
+ const start = node.position?.start?.offset
348
+ const end = node.position?.end?.offset
349
+ const text = this.str.slice(start, end)
350
+ if (!text) {
351
+ return []
352
+ }
353
+
354
+ const jsx = htmlToJsx(text)
355
+ try {
356
+ this.jsxStr = jsx
357
+ const result = this.jsxTransformer(node)
358
+ if (Array.isArray(result)) {
359
+ console.log(`Unexpected array result`)
360
+ } else if (result) {
361
+ return result
362
+ }
363
+ } finally {
364
+ this.jsxStr = ''
365
+ }
366
+
367
+ return []
368
+ }
369
+ case 'imageReference': {
370
+ return []
371
+ }
372
+
373
+ default: {
374
+ mdastBfs(node, (node) => {
375
+ delete node.position
376
+ })
377
+
378
+ throw new Error(
379
+ `cannot convert node` + JSON.stringify(node, null, 2),
380
+ )
381
+
382
+ return []
383
+ }
384
+ }
385
+ }
386
+ getJsxAttrs(node: MdxJsxFlowElement | MdxJsxTextElement) {
387
+ let attrsList = node.attributes
388
+ .map((attr) => {
389
+ // TODO what is mdxJsxExpressionAttribute
390
+ if (attr.type === 'mdxJsxExpressionAttribute') {
391
+ throw new Error(
392
+ `mdxJsxExpressionAttribute is not supported: ${attr.value}`,
393
+ )
394
+ }
395
+ if (attr.type !== 'mdxJsxAttribute') {
396
+ throw new Error(
397
+ `non mdxJsxAttribute is not supported: ${attr}`,
398
+ )
399
+ }
400
+
401
+ const v = attr.value
402
+ if (typeof v === 'string' || typeof v === 'number') {
403
+ return [attr.name, v]
404
+ }
405
+ if (v === null) {
406
+ return [attr.name, true]
407
+ }
408
+ if (v?.type === 'mdxJsxAttributeValueExpression') {
409
+ // logger.json({value})
410
+ // if it's a number, just return it
411
+ if (v.value === 'true') {
412
+ return [attr.name, true]
413
+ }
414
+ if (v.value === 'false') {
415
+ return [attr.name, false]
416
+ }
417
+ if (v.value === 'null') {
418
+ return [attr.name, null]
419
+ }
420
+ if (v.value === 'undefined') {
421
+ return [attr.name, undefined]
422
+ }
423
+ if (
424
+ // TODO add a way to parse ' and `
425
+ ['"'].some(
426
+ (q) => v.value.startsWith(q) && v.value.endsWith(q),
427
+ )
428
+ ) {
429
+ return [attr.name, JSON.parse(v.value)]
430
+ }
431
+
432
+ const number = Number(v.value)
433
+ if (!isNaN(number)) {
434
+ return [attr.name, number]
435
+ }
436
+
437
+ this.errors.push(
438
+ `Expressions in jsx props are not supported (${attr.name}={${v.value}})`,
439
+ )
440
+
441
+ // return [attr.name, `{${v.value}}`]
442
+ } else {
443
+ console.log('unhandled attr', { attr }, attr.type)
444
+ }
445
+
446
+ return
447
+ })
448
+ .filter(isTruthy) as [string, any][]
449
+ return attrsList
450
+ }
451
+ }
452
+
453
+ function isTruthy<T>(val: T | undefined | null | false): val is T {
454
+ return Boolean(val)
455
+ }
456
+
457
+ function accessWithDot(obj, path: string) {
458
+ return path
459
+ .split('.')
460
+ .map((x) => x.trim())
461
+ .filter(Boolean)
462
+ .reduce((o, i) => o[i], obj)
463
+ }
464
+
465
+ function mdastBfs(node: Parent | Node, cb?: (node: Node | Parent) => any) {
466
+ const queue = [node]
467
+ const result: any[] = []
468
+ while (queue.length) {
469
+ const node = queue.shift()
470
+ let r = cb && node ? cb(node) : node
471
+ if (Array.isArray(r)) {
472
+ queue.push(...r)
473
+ } else if (r) {
474
+ result.push(r)
475
+ }
476
+ if (node && 'children' in node && node.children) {
477
+ queue.push(...(node.children as any))
478
+ }
479
+ }
480
+ return result
481
+ }