Skip to content

Commit 70cb04f

Browse files
muningiskentcdodds
andauthored
feat(jsx): base for supporting more runtimes (#236)
Co-authored-by: Kent C. Dodds <[email protected]>
1 parent 9e5c958 commit 70cb04f

File tree

15 files changed

+496
-24
lines changed

15 files changed

+496
-24
lines changed

README.md

Lines changed: 89 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ everything for you.
4444
</summary>
4545

4646
[MDX](https://mdxjs.com/) enables you to combine terse markdown syntax for your
47-
content with the power of React components. For content-heavy sites, writing the
47+
content with the power of JSX components. For content-heavy sites, writing the
4848
content with straight-up HTML can be annoyingly verbose. Often people solve this
4949
using a WSYWIG editor, but too often those fall short in mapping the writer's
5050
intent to HTML. Many people prefer using markdown to express their content
@@ -56,8 +56,8 @@ to insert an element that JavaScript targets (which is annoyingly indirect), or
5656
you can use an `iframe` or something.
5757

5858
As previously stated, [MDX](https://mdxjs.com/) enables you to combine terse
59-
markdown syntax for your content with the power of React components. So you can
60-
import a React component and render it within the markdown itself. It's the best
59+
markdown syntax for your content with the power of JSX components. So you can
60+
import a JSX component and render it within the markdown itself. It's the best
6161
of both worlds.
6262

6363
</details>
@@ -141,6 +141,16 @@ bundling. So it's best suited for SSR frameworks like Remix/Next.
141141

142142
</details>
143143

144+
<details>
145+
<summary>
146+
<strong>
147+
"Can I use this other JSX libraries other than React?"
148+
</strong>
149+
</summary>
150+
151+
Yes! If JSX runtime you want to use is mentioned here - https://mdxjs.com/docs/getting-started/#jsx, it's guaranteed to work. Libraries, such as `hono` which has `react` compatible API also works. Check to [Other JSX runtimes](#other-jsx-runtimes) to get started.
152+
</details>
153+
144154
<details>
145155
<summary>
146156
<strong>
@@ -770,9 +780,84 @@ export const MDXComponent: React.FC<{
770780
771781
### Known Issues
772782
783+
### Other JSX runtimes
784+
JSX runtimes mentioned [here](https://mdxjs.com/docs/getting-started/#jsx) are guaranteed to be supported, however any JSX runtime should work without problem, as long as they export their own jsx runtime. For example, `hono` is not mentioned here, but as it has `react` compatible API, it can be used without any issues.
785+
786+
To do so, you will have to pass a configuration object and use JSX Component factory.
787+
```tsx
788+
const getMDX = (source) => {
789+
return bundleMDX({
790+
source,
791+
jsxConfig: {
792+
jsxLib: {
793+
varName: 'HonoJSX',
794+
package: 'hono/jsx',
795+
},
796+
jsxDom: {
797+
varName: 'HonoDOM',
798+
package: 'hono/jsx/dom',
799+
},
800+
jsxRuntime: {
801+
varName: '_jsx_runtime',
802+
package: 'hono/jsx/jsx-runtime',
803+
},
804+
}
805+
});
806+
}
807+
808+
// ...
809+
810+
import { getMDXComponent } from "mdx-bundler/client/jsx";
811+
812+
import * as HonoJSX from "hono/jsx";
813+
import * as HonoDOM from "hono/jsx/dom";
814+
import * as _jsx_runtime from "hono/jsx/jsx-runtime";
815+
const jsxConfig = {
816+
HonoJSX,
817+
HonoDOM,
818+
_jsx_runtime
819+
};
820+
821+
export const MDXComponent: React.FC<{
822+
code: string;
823+
}> = ({ code }) => {
824+
const Component = useMemo(
825+
() => getMDXComponent(code, jsxConfig),
826+
[code],
827+
);
828+
829+
return (
830+
<Component components={{ Text: ({ children }) => <p>{children}</p> }} />
831+
);
832+
};
833+
```
834+
835+
To use it with others, adjust `jsxConfig` passed to bundler.
836+
```ts
837+
const jsxConfig = {
838+
jsxLib: {
839+
varName: 'HonoJSX',
840+
package: 'hono/jsx',
841+
},
842+
jsxDom: {
843+
varName: 'HonoDOM',
844+
package: 'hono/jsx/dom',
845+
},
846+
jsxRuntime: {
847+
varName: '_jsx_runtime',
848+
package: 'hono/jsx/jsx-runtime',
849+
},
850+
}
851+
```
852+
and to `getMDXComponent`
853+
```ts
854+
const jsxConfig = { HonoJSX, HonoDOM, _jsx_runtime };
855+
```
856+
857+
773858
#### Cloudflare Workers
774859
775-
We'd _love_ for this to work in cloudflare workers. Unfortunately cloudflares
860+
We'd _love_ for this to work in cloudflare workers. Unfortunately cloudflare workers
776861
have two limitations that prevent `mdx-bundler` from working in that
777862
environment:
778863

client/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
module.exports = require('../dist/client')
1+
module.exports = require('../dist/client/index.js')

client/jsx.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../dist/client/jsx')

client/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
22
"type": "commonjs",
33
"main": "./index.js",
4-
"types": "./index.d.ts"
4+
"types": "./index.d.ts",
5+
"exports": {
6+
"react": "./react.js",
7+
"jsx": "./jsx.js"
8+
}
59
}

client/react.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('../dist/client/index.js')

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@
5353
"esbuild": "0.*"
5454
},
5555
"devDependencies": {
56+
"@testing-library/preact": "3.2.4",
5657
"@testing-library/react": "^14.1.0",
58+
"@testing-library/vue": "8.1.0",
5759
"@types/jsdom": "^21.1.5",
5860
"@types/mdx": "^2.0.10",
5961
"@types/react": "^18.2.37",
@@ -63,15 +65,18 @@
6365
"c8": "^8.0.1",
6466
"cross-env": "^7.0.3",
6567
"esbuild": "^0.19.5",
68+
"hono": "4.6.14",
6669
"jsdom": "^22.1.0",
6770
"kcd-scripts": "^14.0.1",
6871
"left-pad": "^1.3.0",
6972
"mdx-test-data": "^1.0.1",
73+
"preact": "10.25.3",
7074
"react": "^18.2.0",
7175
"react-dom": "^18.2.0",
7276
"remark-mdx-images": "^3.0.0",
7377
"typescript": "^5.2.2",
74-
"uvu": "^0.5.6"
78+
"uvu": "^0.5.6",
79+
"vue": "3.5.13"
7580
},
7681
"eslintConfig": {
7782
"extends": "./node_modules/kcd-scripts/eslint.js",

src/__tests__/hono.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import './setup-tests.js'
2+
import { Hono } from "hono";
3+
/* eslint-disable import/no-unresolved --
4+
* imports paths are there in node_modules/hono/package.json
5+
* but it doesn't get resolved
6+
*/
7+
import * as HonoJSX from "hono/jsx";
8+
import * as HonoDOM from "hono/jsx/dom";
9+
import * as _jsx_runtime from "hono/jsx/jsx-runtime";
10+
/* eslint-enable import/no-unresolved */
11+
import {suite} from 'uvu'
12+
import * as assert from 'uvu/assert'
13+
import {bundleMDX} from '../index.js'
14+
import {getMDXComponent} from '../client/jsx.js'
15+
16+
const test = suite("hono");
17+
18+
const jsxBundlerConfig = {
19+
jsxLib: {
20+
varName: 'HonoJSX',
21+
package: 'hono/jsx',
22+
},
23+
jsxDom: {
24+
varName: 'HonoDOM',
25+
package: 'hono/jsx/dom',
26+
},
27+
jsxRuntime: {
28+
varName: '_jsx_runtime',
29+
package: 'hono/jsx/jsx-runtime',
30+
},
31+
}
32+
const jsxComponentConfig = { HonoJSX, HonoDOM, _jsx_runtime }
33+
34+
const mdxSource = `
35+
---
36+
title: Example Post
37+
published: 2021-02-13
38+
description: This is some meta-data
39+
---
40+
import Demo from './demo'
41+
42+
# This is the title
43+
44+
Here's a **neat** demo:
45+
<Demo />
46+
`.trim();
47+
48+
const demoTsx = `
49+
export default function Demo() {
50+
return <div>mdx-bundler with hono's runtime!</div>
51+
}
52+
`.trim();
53+
54+
55+
test('smoke test for hono', async () => {
56+
57+
const result = await bundleMDX({
58+
source: mdxSource,
59+
jsxConfig: jsxBundlerConfig,
60+
files: {
61+
'./demo.tsx': demoTsx
62+
}
63+
});
64+
65+
/** @param {HonoJSX.DOMAttributes} props */
66+
const SpanBold = ({ children }) => {
67+
return HonoJSX.createElement('span', { className: "strong" }, children)
68+
}
69+
70+
const app = new Hono()
71+
.get("/", (c) => {
72+
const Component = getMDXComponent(result.code, jsxComponentConfig);
73+
return c.html(HonoJSX.jsx(Component, { components: { strong: SpanBold } }).toString());
74+
});
75+
76+
const req = new Request("http://localhost/");
77+
const res = await app.fetch(req);
78+
assert.equal(await res.text(), `<h1>This is the title</h1>
79+
<p>Here&#39;s a <span class="strong">neat</span> demo:</p>
80+
<div>mdx-bundler with hono&#39;s runtime!</div>`);
81+
})
82+
83+
test.run()

src/__tests__/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import './setup-tests.js'
22
import path from 'path'
3-
import {test} from 'uvu'
3+
import {suite} from 'uvu'
44
import * as assert from 'uvu/assert'
55
import React from 'react'
66
import rtl from '@testing-library/react'
77
import leftPad from 'left-pad'
88
import remarkMdxImages from 'remark-mdx-images'
99
import {VFile} from 'vfile'
1010
import {bundleMDX} from '../index.js'
11-
import {getMDXComponent, getMDXExport} from '../client.js'
11+
import {getMDXComponent, getMDXExport} from '../client/react.js'
1212

13+
const test = suite("react");
1314
const {render} = rtl
1415

1516
test('smoke test', async () => {

src/__tests__/preact.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import './setup-tests.js'
2+
import * as Preact from "preact";
3+
import * as PreactDOM from "preact/compat";
4+
import * as _jsx_runtime from 'preact/jsx-runtime';
5+
import {suite} from 'uvu'
6+
import * as assert from 'uvu/assert'
7+
import { render } from '@testing-library/preact'
8+
import {bundleMDX} from '../index.js'
9+
import {getMDXComponent} from '../client/jsx.js'
10+
11+
const test = suite("preact");
12+
13+
const jsxBundlerConfig = {
14+
jsxLib: {
15+
varName: 'Preact',
16+
package: 'preact',
17+
},
18+
jsxDom: {
19+
varName: 'PreactDom',
20+
package: 'preact/compat',
21+
},
22+
jsxRuntime: {
23+
varName: '_jsx_runtime',
24+
package: 'preact/jsx-runtime',
25+
},
26+
}
27+
const jsxComponentConfig = { Preact, PreactDOM, _jsx_runtime }
28+
29+
const mdxSource = `
30+
---
31+
title: Example Post
32+
published: 2021-02-13
33+
description: This is some meta-data
34+
---
35+
import Demo from './demo'
36+
37+
# This is the title
38+
39+
Here's a **neat** demo:
40+
<Demo />
41+
`.trim();
42+
43+
const demoTsx = `
44+
export default function Demo() {
45+
return <div>mdx-bundler with Preact's runtime!</div>
46+
}
47+
`.trim();
48+
49+
50+
test('smoke test for preact', async () => {
51+
52+
const result = await bundleMDX({
53+
source: mdxSource,
54+
jsxConfig: jsxBundlerConfig,
55+
files: {
56+
'./demo.tsx': demoTsx
57+
}
58+
});
59+
60+
/**
61+
* @type {import('preact').FunctionComponent<{ components?: Record<string, any> }>}
62+
*/
63+
const Component = getMDXComponent(result.code, jsxComponentConfig)
64+
65+
/** @type {Preact.FunctionComponent<{ children:Preact.ComponentChildren }>} props */
66+
const SpanBold = ({children}) => {
67+
return Preact.createElement('span', { className: "strong" }, children)
68+
}
69+
70+
assert.equal(result.frontmatter, {
71+
title: 'Example Post',
72+
published: new Date('2021-02-13'),
73+
description: 'This is some meta-data',
74+
})
75+
76+
const {container} = render(
77+
Preact.h(Component, {components: {strong: SpanBold}})
78+
)
79+
80+
assert.equal(
81+
container.innerHTML,
82+
`<h1>This is the title</h1>
83+
<p>Here's a <span class="strong">neat</span> demo:</p>
84+
<div>mdx-bundler with Preact's runtime!</div>`,
85+
)
86+
})
87+
88+
test.run()

0 commit comments

Comments
 (0)