MDX v1 リリース 🎉   ということでマークダウンにJSXを書く勉強をしてみた！


2019/04/15にMDXの安定版のv1がリリースされていたようです。

- [Release v1.0.0 · mdx-js/mdx](https://github.com/mdx-js/mdx/releases/tag/v1.0.0)
- [MDX goes stable](https://mdxjs.com/blog/v1/)


前から興味があったので、この機会にドキュメントを読んで試してみました。
**間違いなどありましたらご指摘お願いします**:bow:

# MDXとは

- [mdx-js/mdx - github](https://github.com/mdx-js/mdx)
- https://github.com/mdx-js/specification


readmeの一部引用です ※google翻訳

> MDXは、マークダウン文書でJSXをシームレスに使用できるようにする許可可能な形式です。インタラクティブなチャートや通知などのコンポーネントをインポートしたり、メタデータをエクスポートしたりできます。これにより、コンポーネントを使用して長い形式のコンテンツを作成することが容易になります。 🚀

マークダウンの機能はそのままに、`<Chart />`などと書くことでJSXを埋め込むことができたり、インポート、エクスポートなどもできます。


```md
import { Chart } from "../components/chart"

# Here’s a chart

The chart is rendered inside our MDX document.

<Chart />
```

こちらのデモなどを見ると、結構複雑なこともできるようですごいです。

- https://frontarm.com/demoboard/
- [What is MDX -youtube](https://www.youtube.com/watch?v=d2sQiI5NFAM&list=PLV5CVI1eNcJgCrPH_e6d57KRUTiDZgs0u)


# Getting started

```bash
#npm >= 6.1.0 の場合
npm init mdx

#npm >= 5.2.0 の場合はこちらでインストール
npm i create-mdx
npx create-mdx
```

```bash
cd next-mdx/
npm i
npx next
```

この状態で、 http://localhost:3000 にアクセスすると、mdxで書かれた、`index.md` がhtmlで変換され、確認することができます

<img width="512" alt="Screen Shot 2019-04-20 at 20.20.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/a3d2786c-5bd4-c8d9-f6a5-29f47b133cc4.png">

Next.jsで動作していますので、`pages/hello.mdx` とファイルを作成すると、`http://localhost:3000/hello` とアクセスすると変換されたhtmlをみれます。JSXをマークダウン中に書くことができました。簡単ですね、すごい！

```jsx:pages/hello.mdx
# Below is a JSX block

<div style={{ padding: "10px 30px", backgroundColor: "tomato" }}>
    <h2>Try making this heading have the color tomato</h2>
</div>
```

<img width="549" alt="Screen Shot 2019-04-21 at 0.32.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/3bdab377-e162-5b85-9284-1493b7256e37.png">

## webpackで動かす

Next.jsではなくても、[webpack](https://mdxjs.com/getting-started/webpack)や[parcel](https://mdxjs.com/getting-started/parcel)などを使うこともできます。

webpackでやってみました。

[自分で試したコード](https://github.com/okumurakengo/00018_mdx/tree/master/webpack-sample)

```bash
npm i @mdx-js/mdx @mdx-js/loader webpack webpack-cli @babel/core @babel/preset-env @babel/preset-react babel-loader react react-dom
```

```webpack.config.js
module.exports = {
    mode: "development",
    module: {
        rules: [
            {
                test: /\.js$/,
                use: "babel-loader"
            },
            {
                test: /\.mdx?$/,
                use: ["babel-loader", "@mdx-js/loader"]
            },
        ]
    }
}
```

```json:.babelrc
{"presets": ["@babel/env", "@babel/react"]}
```

```index.html
<!DOCTYPE html>
<meta charset="utf-8">
<title>MDX Test</title>
<div id=root></div>
<script src=dist/main.js></script>
```

```src/index.js
import React from "react"
import {render} from "react-dom"
import Hello from "./hello.mdx"

render(<Hello />, document.getElementById("root"))
```

```md:src/hello.mdx
# Hello, MDX!

This is an example using webpack

<ul>
    {[...Array(5)].map((_, i) => <li key={i}>item {i+1}</li>)}
</ul>
```

```bash
npx webpack #./dist/main.jsが出力
```

index.htmlを開くとwebpackで実行できたのが確認できました。

<img width="549" alt="Screen Shot 2019-04-21 at 1.05.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/3bf3fa84-4de1-2653-25b1-4b39a7edff96.png">

# Imports

- 別のマークダウンをインポート

```md:doc.mdx
import License from "./license.md"

### Hello, world!

<License />
```

```src/license.md
ライセンス

---

**<small>本ソフトウェアおよび関連するドキュメンテーションファイル（以下「本ソフトウェア」）のコピーを入手したすべての人に、使用、コピー、変更、マージの権利を含むがこれに限定されずに本ソフトウェアを扱う許可が無償で与えられます。</small>**

**<small>本ソフトウェアの複製、出版、頒布、再使用許諾、および/または販売を行うこと、および本ソフトウェアの提供を受けた人に、以下の条件に従うことを許可すること。</small>**
```

<img width="581" alt="Screen Shot 2019-04-21 at 15.57.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/0cb83778-e843-edf9-8992-40ffe38c0751.png">

---

- React componentをインポート

```doc.mdx
import Btn from "./Btn"

### Hello, world!

<Btn color="tomato">button1</Btn>
<Btn color="limegreen">button2</Btn>
<Btn color="skyblue">button3</Btn>
```

```jsx:Btn.js
import React from "react"

export default ({ children, color }) => {
    return (
        <button style={{backgroundColor: color}}>
            {children}
        </button>
    );
}
```

<img width="550" alt="Screen Shot 2019-04-21 at 16.06.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/cf75db2f-9a84-11c0-803c-6b1593d56c26.png">

# export

mdxファイルから`export`することができます

```md:post.mdx
export const metadata = {
  authors: ["Jhon", "Bob"]
}

# Post about MDX

MDX is a JSX in Markdown loader, parser, and renderer for ambitious projects.
```

```jsx:index.js
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")
)
```

<img width="615" alt="Screen Shot 2019-04-21 at 16.29.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/abe5fe1b-1936-441c-5bf3-38d81c570ece.png">

# MDXProvider

タグそれぞれのデフォルトのスタイルを設定できます

```md:doc.mdx
# Document

## Document

1. item
1. item
1. item
```

```jsx:index.js
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")
)
```

<img width="549" alt="Screen Shot 2019-04-21 at 2.34.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/6176e3a0-1f30-0f17-7bd5-cc343ec2f156.png">

### prism-react-renderer

[prism-react-renderer](https://github.com/FormidableLabs/prism-react-renderer)と組み合わせてコードブロックでシンタックスハイライトを使えるようにできます。

- `doc.mdx`

<pre>
&#035; Document

```html
&lt;!DOCTYPE html&gt;
&lt;html lang="ja"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;
    &lt;title>Document&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1&gt;hello&lt;/h1&gt;
&lt;/body&gt;
&lt;/html&gt;
```

```go
package main

import "fmt"

func main() {
    fmt.Println("Great!")
}
```
</pre>

```jsx:CodeBlock.js
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>
  )
}
```

```jsx:index.js
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")
)
```

<img width="593" alt="Screen Shot 2019-04-21 at 3.55.13.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/ae2adc9e-f29f-5d0b-de93-8cb0b23bf507.png">

# Live code

- `doc.mdx`
     
    <code>```js live=true</code> ←ここでtrueを指定する

<pre>
&#035; Document

```js live=true
class Example extends React.Component {
  render() {
    return &lt;strong&gt;Hello World&lt;/strong&gt;
  }
}
```
</pre>

```jsx:CodeBlock.js
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>
  )
}
```

```jsx:index.js
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")
)
```

![0bAo7oVepJ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/d75b7edc-a113-afa7-7ae0-9dc9d22bde8b.gif)

---

[今まで↑で自分で試したコードはこちらにも用意](https://github.com/okumurakengo/00018_mdx/tree/master/sample)

# Plugin

https://mdxjs.com/advanced/plugins#using-remark-and-rehype-plugins

`remark-emoji`を使ってみました

```md:src/doc.mdx
# :dog: Document :cat:
```

```webpack.config.js
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]
                        }
                    },
                ]
            },
        ]
    }
}
```

```js:src/index.js
import React from "react"
import {render} from "react-dom"
import Doc from "./doc.mdx"

render(
    <Doc />,
    document.getElementById("root")
)
```

<img width="550" alt="Screen Shot 2019-04-21 at 5.19.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/76a935ff-e45f-b329-b906-c53c11362a78.png">

pluginを使うことができました

### pluginを自作する

こちらを参考に、見出しタグ(h1〜h6)の文字を逆にさせるpluginを作成してみました

- https://mdxjs.com/guides/writing-a-plugin

```js:myPlugin.js
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("")
        })
    })
}
```

```webpack.config.js
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]
                        }
                    },
                ]
            },
        ]
    }
}
```

```md:src/doc.mdx
# MDX Sample

## abcdefg

1. hoge
1. fuga
1. piyo

*foo* **bar**
```

<img width="550" alt="Screen Shot 2019-04-21 at 5.45.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/1b8f4203-c3d5-a909-9d04-b5428cd63946.png">

pluginを自作して、見出しタグの文字だけを逆にさせることができました

[自分で試したコード](https://github.com/okumurakengo/00018_mdx/tree/master/plugin)

# Custom loader

https://mdxjs.com/guides/custom-loader

webpackの機能でloaderを自作することもできます

マークダウン中の画像をインラインでhtmlに埋め込んでくれるloaderを作ってみました(※pngのみ対応)

```./myLoader.js
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)
}
```

```diff:webpack.config.js
  module.exports = {
      mode: "development",
      module: {
          rules: [
              {
                  test: /\.js$/,
                  use: "babel-loader"
              },
              {
                  test: /\.mdx?$/,
                  use: [
                      "babel-loader", 
                      "@mdx-js/loader",
+                     `${__dirname}/myLoader`,
                  ]
              },
          ]
      }
  }
```

```md:src/doc.mdx
# Gallery

![アマガエル](./img/amagaeru.png)
```

data URLスキームで画像を埋め込んで表示することができました

<img width="657" alt="Screen Shot 2019-04-21 at 13.42.30.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/ca91bd29-2ad5-4050-15e0-35bec611e5e1.png">

[自分で試したコード](https://github.com/okumurakengo/00018_mdx/tree/master/loader)

# Wrapper customization

https://mdxjs.com/guides/wrapper-customization

レイアウトをラップできます

```md:src/doc.mdx
### Kicker

# Hello, world!

Working with React children is fun!
```

```jsx:src/index.js
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")
)
```

<img width="657" alt="Screen Shot 2019-04-21 at 14.00.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/0c2dbd81-ce31-935b-2f63-7202b7a8136c.png">

### 子要素を操作する

```jsx:src/index.js
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")
)
```

子要素の順番を逆にできました

<img width="550" alt="Screen Shot 2019-04-21 at 14.04.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/142910/28d908fd-88a5-98af-b3bb-653313b6e21c.png">

# Vue

※ALPHAと記載があります

- https://mdxjs.com/guides/vue
- https://github.com/mdx-js/mdx/tree/master/examples/vue

# TypeScript

https://mdxjs.com/advanced/typescript

google翻訳

> `*.mdx`拡張子を持つインポートに関連してTypeScriptからエラーが発生する場合は、typesディレクトリに`mdx.d.ts`ファイルを作成し、それを`tsconfig.json`内に含めます。

```ts
// types/mdx.d.ts
declare module '*.mdx' {
  let MDXComponent: (props) => JSX.Element;
  export default MDXComponent;
}
```

---

最後まで読んでいただいてありがとうございましたm(_ _)m