2019/04/15にMDXの安定版のv1がリリースされていたようです。
前から興味があったので、この機会にドキュメントを読んで試してみました。
間違いなどありましたらご指摘お願いします
MDXとは
readmeの一部引用です ※google翻訳
MDXは、マークダウン文書でJSXをシームレスに使用できるようにする許可可能な形式です。インタラクティブなチャートや通知などのコンポーネントをインポートしたり、メタデータをエクスポートしたりできます。これにより、コンポーネントを使用して長い形式のコンテンツを作成することが容易になります。 🚀
マークダウンの機能はそのままに、<Chart />
などと書くことでJSXを埋め込むことができたり、インポート、エクスポートなどもできます。
import { Chart } from "../components/chart"
# Here’s a chart
The chart is rendered inside our MDX document.
<Chart />
こちらのデモなどを見ると、結構複雑なこともできるようですごいです。
Getting started
#npm >= 6.1.0 の場合
npm init mdx
#npm >= 5.2.0 の場合はこちらでインストール
npm i create-mdx
npx create-mdx
cd next-mdx/
npm i
npx next
この状態で、 http://localhost:3000 にアクセスすると、mdxで書かれた、index.md
がhtmlで変換され、確認することができます
Next.jsで動作していますので、pages/hello.mdx
とファイルを作成すると、http://localhost:3000/hello
とアクセスすると変換されたhtmlをみれます。JSXをマークダウン中に書くことができました。簡単ですね、すごい!
# Below is a JSX block
<div style={{ padding: "10px 30px", backgroundColor: "tomato" }}>
<h2>Try making this heading have the color tomato</h2>
</div>
webpackで動かす
Next.jsではなくても、webpackやparcelなどを使うこともできます。
webpackでやってみました。
npm i @mdx-js/mdx @mdx-js/loader webpack webpack-cli @babel/core @babel/preset-env @babel/preset-react babel-loader react react-dom
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader"
},
{
test: /\.mdx?$/,
use: ["babel-loader", "@mdx-js/loader"]
},
]
}
}
{"presets": ["@babel/env", "@babel/react"]}
<!DOCTYPE html>
<meta charset="utf-8">
<title>MDX Test</title>
<div id=root></div>
<script src=dist/main.js></script>
import React from "react"
import {render} from "react-dom"
import Hello from "./hello.mdx"
render(<Hello />, document.getElementById("root"))
# Hello, MDX!
This is an example using webpack
<ul>
{[...Array(5)].map((_, i) => <li key={i}>item {i+1}</li>)}
</ul>
npx webpack #./dist/main.jsが出力
index.htmlを開くとwebpackで実行できたのが確認できました。
Imports
- 別のマークダウンをインポート
import License from "./license.md"
### Hello, world!
<License />
ライセンス
---
**<small>本ソフトウェアおよび関連するドキュメンテーションファイル(以下「本ソフトウェア」)のコピーを入手したすべての人に、使用、コピー、変更、マージの権利を含むがこれに限定されずに本ソフトウェアを扱う許可が無償で与えられます。</small>**
**<small>本ソフトウェアの複製、出版、頒布、再使用許諾、および/または販売を行うこと、および本ソフトウェアの提供を受けた人に、以下の条件に従うことを許可すること。</small>**
- React componentをインポート
import Btn from "./Btn"
### Hello, world!
<Btn color="tomato">button1</Btn>
<Btn color="limegreen">button2</Btn>
<Btn color="skyblue">button3</Btn>
import React from "react"
export default ({ children, color }) => {
return (
<button style={{backgroundColor: color}}>
{children}
</button>
);
}
export
mdxファイルからexport
することができます
export const metadata = {
authors: ["Jhon", "Bob"]
}
# Post about MDX
MDX is a JSX in Markdown loader, parser, and renderer for ambitious projects.
import React from "react"
import {render} from "react-dom"
import MDXDocument, { metadata } from "./post.mdx"
render(
<>
<MDXDocument />
<footer>
<p>By: {metadata.authors.join(", ")}.</p>
</footer>
</>,
document.getElementById("root")
)
MDXProvider
タグそれぞれのデフォルトのスタイルを設定できます
# Document
## Document
1. item
1. item
1. item
import React from "react"
import {render} from "react-dom"
import {MDXProvider} from "@mdx-js/react"
import styled from "styled-components"
import Doc from "./doc.mdx"
const List = styled.li`
background: skyblue;
color: #fff;
padding: 5px;
`
const components = {
h1: props => <h1 style={{color: "tomato"}} {...props} />,
h2: props => <h2 style={{color: "limegreen"}} {...props} />,
li: props => <List {...props} />,
}
render(
<MDXProvider components={components}>
<Doc />
</MDXProvider>,
document.getElementById("root")
)
prism-react-renderer
prism-react-rendererと組み合わせてコードブロックでシンタックスハイライトを使えるようにできます。
doc.mdx
# Document
```html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<h1>hello</h1>
</body>
</html>
```
```go
package main
import "fmt"
func main() {
fmt.Println("Great!")
}
```
import React from "react"
import Highlight, {defaultProps} from "prism-react-renderer"
export default ({children, className}) => {
const language = className.replace(/language-/, "")
return (
<Highlight {...defaultProps} code={children} language={language}>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<pre className={className} style={{...style, padding: "20px"}}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({line, key: i})}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
))}
</div>
))}
</pre>
)}
</Highlight>
)
}
import React from "react"
import {render} from "react-dom"
import {MDXProvider} from "@mdx-js/react"
import Doc from "./doc.mdx"
import CodeBlock from "./CodeBlock"
const components = {
h1: props => <h1 style={{color: "tomato"}} {...props} />,
code: CodeBlock,
}
render(
<MDXProvider components={components}>
<Doc />
</MDXProvider>,
document.getElementById("root")
)
Live code
-
doc.mdx
```js live=true
←ここでtrueを指定する
# Document
```js live=true
class Example extends React.Component {
render() {
return <strong>Hello World</strong>
}
}
```
import React from "react"
import Highlight, {defaultProps} from "prism-react-renderer"
import {LiveProvider, LiveEditor, LiveError, LivePreview} from "react-live"
export default ({children, className, live}) => {
const language = className.replace(/language-/, "")
if (live) {
return (
<div style={{marginTop: "40px"}}>
<LiveProvider code={children}>
<LivePreview />
<LiveEditor />
<LiveError />
</LiveProvider>
</div>
)
}
return (
<Highlight {...defaultProps} code={children} language={language}>
{({className, style, tokens, getLineProps, getTokenProps}) => (
<pre className={className} style={{...style, padding: "20px"}}>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({line, key: i})}>
{line.map((token, key) => (
<span key={key} {...getTokenProps({token, key})} />
))}
</div>
))}
</pre>
)}
</Highlight>
)
}
import React from "react"
import {render} from "react-dom"
import {MDXProvider} from "@mdx-js/react"
import Doc from "./doc.mdx"
import CodeBlock from "./CodeBlock"
const components = {
code: CodeBlock
}
render(
<MDXProvider components={components}>
<Doc />
</MDXProvider>,
document.getElementById("root")
)
Plugin
remark-emoji
を使ってみました
# :dog: Document :cat:
const emoji = require("remark-emoji")
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader"
},
{
test: /\.mdx?$/,
use: [
"babel-loader",
{
loader: "@mdx-js/loader",
options: {
remarkPlugins: [emoji]
}
},
]
},
]
}
}
import React from "react"
import {render} from "react-dom"
import Doc from "./doc.mdx"
render(
<Doc />,
document.getElementById("root")
)
pluginを使うことができました
pluginを自作する
こちらを参考に、見出しタグ(h1〜h6)の文字を逆にさせるpluginを作成してみました
const visit = require("unist-util-visit")
module.exports = () => (tree, file) => {
visit(tree, "heading", node => {
visit(node, "text", textNode => {
textNode.value = textNode.value.split("").reverse().join("")
})
})
}
const reverse = require("./myPlugin")
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader"
},
{
test: /\.mdx?$/,
use: [
"babel-loader",
{
loader: "@mdx-js/loader",
options: {
remarkPlugins: [reverse]
}
},
]
},
]
}
}
# MDX Sample
## abcdefg
1. hoge
1. fuga
1. piyo
*foo* **bar**
pluginを自作して、見出しタグの文字だけを逆にさせることができました
Custom loader
webpackの機能でloaderを自作することもできます
マークダウン中の画像をインラインでhtmlに埋め込んでくれるloaderを作ってみました(※pngのみ対応)
const fs = require("fs");
module.exports = async function(src) {
const callback = this.async()
const newSrc = src.replace(/\!\[(?<alt>.*)\]\((?<imgPath>.*\.png)\)/g, (match, alt, imgPath) => {
const buff = new Buffer(fs.readFileSync(imgPath))
return `![${alt}](data:image/png;base64,${buff.toString("base64")})`
})
return callback(null, newSrc)
}
module.exports = {
mode: "development",
module: {
rules: [
{
test: /\.js$/,
use: "babel-loader"
},
{
test: /\.mdx?$/,
use: [
"babel-loader",
"@mdx-js/loader",
+ `${__dirname}/myLoader`,
]
},
]
}
}
# Gallery
![アマガエル](./img/amagaeru.png)
data URLスキームで画像を埋め込んで表示することができました
Wrapper customization
レイアウトをラップできます
### Kicker
# Hello, world!
Working with React children is fun!
import React from "react"
import {render} from "react-dom"
import {MDXProvider} from "@mdx-js/react"
import Doc from "./doc.mdx"
const components = {
wrapper: props => (
<div style={{ padding: "20px", backgroundColor: "tomato" }}>
<main {...props} />
</div>
)
}
render(
<MDXProvider components={components}>
<Doc />
</MDXProvider>,
document.getElementById("root")
)
子要素を操作する
import React from "react"
import {render} from "react-dom"
import {MDXProvider} from "@mdx-js/react"
import Doc from "./doc.mdx"
const components = {
wrapper: ({ children, ...props }) => {
const reversedChildren = React.Children.toArray(children).reverse()
return <>{reversedChildren}</>
}
}
render(
<MDXProvider components={components}>
<Doc />
</MDXProvider>,
document.getElementById("root")
)
子要素の順番を逆にできました
Vue
※ALPHAと記載があります
TypeScript
google翻訳
*.mdx
拡張子を持つインポートに関連してTypeScriptからエラーが発生する場合は、typesディレクトリにmdx.d.ts
ファイルを作成し、それをtsconfig.json
内に含めます。
// types/mdx.d.ts
declare module '*.mdx' {
let MDXComponent: (props) => JSX.Element;
export default MDXComponent;
}
最後まで読んでいただいてありがとうございましたm(_ _)m