こんにちは。
Markdown から MDX に移行するときハマりました。MDX をビルドする方法を備忘録として残します。
MDX とは
MDX は Markdown を拡張したものです。文章の中に JSX を入れられ、より細かいスタイルやインタラクティブなコンテンツを実現できます。拡張子は .mdx です。 詳しくはドキュメントをご覧ください。
MDX をビルドし JSX.Element を生成する
ここでは MDX をビルドし JSX.Element を生成してみます。ビルドには esbuild と @mdx-js/esbuild を用います。
ディレクトリ構造は次のとおりです。
. ├── package.json ├── build.ts └── src ├── index.ts └── sample.mdx
サンプルとして次のような MDX を用います (sample.mdx)
# About あああ <span style={{color: '#ff6633'}}>オレンジ色の文字</span> # Links - [example](https://example.com)
パッケージング
パッケージングのため index.ts で上記の MDX をimportします。
import Sample from './sample.mdx' export { Sample }
(後述しますが) ビルドするとき@mdx-js/esbuildを用いるため拡張子は .mdx のままimportできます。./sample.mdx の default export は JSX.Element です。MDXの中身をJSXにしたものになります。
これで他のパッケージからSampleをimport出来るようになりました。
ビルド
package.json は次のとおりです。
{ "name": "content", "version": "0.1.0", "license": "MIT", "type": "module", "exports": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { "build": "tsx build.ts && tsc" }, ... (省略) }
npm run build を叩くと build.ts が実行されます。
build.ts は次のとおりです。
import esbuild from 'esbuild' import mdx from '@mdx-js/esbuild' await esbuild.build({ bundle: true, entryPoints: [ 'src/index.ts', ], outdir: './dist', platform: 'node', format: 'esm', plugins: [mdx()] })
ポイントは plugins です。mdx() を入れてます。これが MDX を JSX.Element に変換してくれます。
npm run build を叩くと dist/index.js ができました。中身を見てみましょう。
function _createMdxContent(props) { const _components = Object.assign({ h1: "h1", p: "p", ul: "ul", li: "li", a: "a" }, props.components); return (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [(0, import_jsx_runtime.jsx)(_components.h1, { children: "About" }), // 省略 } function MDXContent(props = {}) { const { wrapper: MDXLayout } = props.components || {}; return MDXLayout ? (0, import_jsx_runtime.jsx)(MDXLayout, Object.assign({}, props, { children: (0, import_jsx_runtime.jsx)(_createMdxContent, props) })) : _createMdxContent(props); } var sample_default = MDXContent; export { sample_default as Sample };
JSXですね。Markdownの記法を解析してh1タグなどに置き換えてくれてます。
importするには
他のパッケージからimportしてみます。例えばNext.jsでは次のように書きます。
// pages/sample.tsx import { Sample } from 'content' export default function Page() { return ( <> <Sample /> </> ) }
Sampleの実体はJSX.Elementなので普通にimportできます。UIコンポーネントライブラリのような感じですね。
感想
「MarkdownにJSXを入れられるんだ〜便利だな〜」と手を出してみたのですが、思いのほか奥が深い技術でした。 JSXを入れられる反面、JavsScriptのランタイムが必要になり、他の言語でのParseは難しくなります。
コンテンツをGitで管理するときは便利なのですが、運用によっては Markdown の方が合うケースがありそうです。