Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft: feat(jsx): base for supporting more runtimes #236

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 89 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ everything for you.
</summary>

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

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

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

</details>

<details>
<summary>
<strong>
"Can I use this other JSX libraries other than React?"
</strong>
</summary>

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.
</details>

<details>
<summary>
<strong>
Expand Down Expand Up @@ -770,9 +780,84 @@ export const MDXComponent: React.FC<{

### Known Issues

### Other JSX runtimes
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.

To do so, you will have to pass a configuration object and use JSX Component factory.
```tsx
const getMDX = (source) => {
return bundleMDX({
source,
jsxConfig: {
jsxLib: {
varName: 'HonoJSX',
package: 'hono/jsx',
},
jsxDom: {
varName: 'HonoDOM',
package: 'hono/jsx/dom',
},
jsxRuntime: {
varName: '_jsx_runtime',
package: 'hono/jsx/jsx-runtime',
},
}
});
}

// ...

import { getMDXComponent } from "mdx-bundler/client/jsx";

import * as HonoJSX from "hono/jsx";
import * as HonoDOM from "hono/jsx/dom";
import * as _jsx_runtime from "hono/jsx/jsx-runtime";
const jsxConfig = {
HonoJSX,
HonoDOM,
_jsx_runtime
};

export const MDXComponent: React.FC<{
code: string;
}> = ({ code }) => {
const Component = useMemo(
() => getMDXComponent(code, jsxConfig),
[code],
);

return (
<Component components={{ Text: ({ children }) => <p>{children}</p> }} />
);
};
```

To use it with others, adjust `jsxConfig` passed to bundler.
```ts
const jsxConfig = {
jsxLib: {
varName: 'HonoJSX',
package: 'hono/jsx',
},
jsxDom: {
varName: 'HonoDOM',
package: 'hono/jsx/dom',
},
jsxRuntime: {
varName: '_jsx_runtime',
package: 'hono/jsx/jsx-runtime',
},
}
```
and to `getMDXComponent`
```ts
const jsxConfig = { HonoJSX, HonoDOM, _jsx_runtime };
```


#### Cloudflare Workers

We'd _love_ for this to work in cloudflare workers. Unfortunately cloudflares
We'd _love_ for this to work in cloudflare workers. Unfortunately cloudflare workers
have two limitations that prevent `mdx-bundler` from working in that
environment:

Expand Down
2 changes: 1 addition & 1 deletion client/index.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = require('../dist/client')
module.exports = require('../dist/client/index.js')
1 change: 1 addition & 0 deletions client/jsx.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../dist/client/jsx')
2 changes: 2 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
{
"type": "commonjs",
"main": "./index.js",
"react": "./react.js",
"jsx": "./jsx.js",
"types": "./index.d.ts"
}
1 change: 1 addition & 0 deletions client/react.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('../dist/client/index.js')
7 changes: 6 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
"esbuild": "0.*"
},
"devDependencies": {
"@testing-library/preact": "3.2.4",
"@testing-library/react": "^14.1.0",
"@testing-library/vue": "8.1.0",
"@types/jsdom": "^21.1.5",
"@types/mdx": "^2.0.10",
"@types/react": "^18.2.37",
Expand All @@ -63,15 +65,18 @@
"c8": "^8.0.1",
"cross-env": "^7.0.3",
"esbuild": "^0.19.5",
"hono": "4.6.14",
"jsdom": "^22.1.0",
"kcd-scripts": "^14.0.1",
"left-pad": "^1.3.0",
"mdx-test-data": "^1.0.1",
"preact": "10.25.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"remark-mdx-images": "^3.0.0",
"typescript": "^5.2.2",
"uvu": "^0.5.6"
"uvu": "^0.5.6",
"vue": "3.5.13"
},
"eslintConfig": {
"extends": "./node_modules/kcd-scripts/eslint.js",
Expand Down
83 changes: 83 additions & 0 deletions src/__tests__/hono.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import './setup-tests.js'
import { Hono } from "hono";
/* eslint-disable import/no-unresolved --
* imports paths are there in node_modules/hono/package.json
* but it doesn't get resolved
*/
import * as HonoJSX from "hono/jsx";
import * as HonoDOM from "hono/jsx/dom";
import * as _jsx_runtime from "hono/jsx/jsx-runtime";
/* eslint-enable import/no-unresolved */
import {suite} from 'uvu'
import * as assert from 'uvu/assert'
import {bundleMDX} from '../index.js'
import {getMDXComponent} from '../client/jsx.js'

const test = suite("hono");

const jsxBundlerConfig = {
jsxLib: {
varName: 'HonoJSX',
package: 'hono/jsx',
},
jsxDom: {
varName: 'HonoDOM',
package: 'hono/jsx/dom',
},
jsxRuntime: {
varName: '_jsx_runtime',
package: 'hono/jsx/jsx-runtime',
},
}
const jsxComponentConfig = { HonoJSX, HonoDOM, _jsx_runtime }

const mdxSource = `
---
title: Example Post
published: 2021-02-13
description: This is some meta-data
---
import Demo from './demo'

# This is the title

Here's a **neat** demo:
<Demo />
`.trim();

const demoTsx = `
export default function Demo() {
return <div>mdx-bundler with hono's runtime!</div>
}
`.trim();


test('smoke test for hono', async () => {

const result = await bundleMDX({
source: mdxSource,
jsxConfig: jsxBundlerConfig,
files: {
'./demo.tsx': demoTsx
}
});

/** @param {HonoJSX.DOMAttributes} props */
const SpanBold = ({ children }) => {
return HonoJSX.createElement('span', { className: "strong" }, children)
}

const app = new Hono()
.get("/", (c) => {
const Component = getMDXComponent(result.code, jsxComponentConfig);
return c.html(HonoJSX.jsx(Component, { components: { strong: SpanBold } }).toString());
});

const req = new Request("http://localhost/");
const res = await app.fetch(req);
assert.equal(await res.text(), `<h1>This is the title</h1>
<p>Here&#39;s a <span class="strong">neat</span> demo:</p>
<div>mdx-bundler with hono&#39;s runtime!</div>`);
})

test.run()
5 changes: 3 additions & 2 deletions src/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import './setup-tests.js'
import path from 'path'
import {test} from 'uvu'
import {suite} from 'uvu'
import * as assert from 'uvu/assert'
import React from 'react'
import rtl from '@testing-library/react'
import leftPad from 'left-pad'
import remarkMdxImages from 'remark-mdx-images'
import {VFile} from 'vfile'
import {bundleMDX} from '../index.js'
import {getMDXComponent, getMDXExport} from '../client.js'
import {getMDXComponent, getMDXExport} from '../client/react.js'

const test = suite("react");
const {render} = rtl

test('smoke test', async () => {
Expand Down
85 changes: 85 additions & 0 deletions src/__tests__/preact.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import './setup-tests.js'
import * as Preact from "preact";
import * as PreactDOM from "preact/compat";
import * as _jsx_runtime from 'preact/jsx-runtime';
import {suite} from 'uvu'
import * as assert from 'uvu/assert'
import { render } from '@testing-library/preact'
import {bundleMDX} from '../index.js'
import {getMDXComponent} from '../client/jsx.js'

const test = suite("preact");

const jsxBundlerConfig = {
jsxLib: {
varName: 'Preact',
package: 'preact',
},
jsxDom: {
varName: 'PreactDom',
package: 'preact/compat',
},
jsxRuntime: {
varName: '_jsx_runtime',
package: 'preact/jsx-runtime',
},
}
const jsxComponentConfig = { Preact, PreactDOM, _jsx_runtime }

const mdxSource = `
---
title: Example Post
published: 2021-02-13
description: This is some meta-data
---
import Demo from './demo'

# This is the title

Here's a **neat** demo:
<Demo />
`.trim();

const demoTsx = `
export default function Demo() {
return <div>mdx-bundler with Preact's runtime!</div>
}
`.trim();


test('smoke test for preact', async () => {

const result = await bundleMDX({
source: mdxSource,
jsxConfig: jsxBundlerConfig,
files: {
'./demo.tsx': demoTsx
}
});

const Component = getMDXComponent(result.code, jsxComponentConfig)

/** @param {Preact.JSX.HTMLAttributes<HTMLSpanElement>} props */
const SpanBold = ({children}) => {
return Preact.createElement('span', { className: "strong" }, children)
}

assert.equal(result.frontmatter, {
title: 'Example Post',
published: new Date('2021-02-13'),
description: 'This is some meta-data',
})

const {container} = render(
Preact.h(Component, {components: {strong: SpanBold}})
)

assert.equal(
container.innerHTML,
`<h1>This is the title</h1>
<p>Here's a <span class="strong">neat</span> demo:</p>
<div>mdx-bundler with Preact's runtime!</div>`,
)
})

test.run()
Loading
Loading