54
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-04-21

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

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

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で変換され、確認することができます

Screen Shot 2019-04-20 at 20.20.42.png

Next.jsで動作していますので、pages/hello.mdx とファイルを作成すると、http://localhost:3000/hello とアクセスすると変換されたhtmlをみれます。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>
Screen Shot 2019-04-21 at 0.32.45.png

webpackで動かす

Next.jsではなくても、webpackparcelなどを使うこともできます。

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
webpack.config.js
module.exports = {
    mode: "development",
    module: {
        rules: [
            {
                test: /\.js$/,
                use: "babel-loader"
            },
            {
                test: /\.mdx?$/,
                use: ["babel-loader", "@mdx-js/loader"]
            },
        ]
    }
}
.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"))
src/hello.mdx
# 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で実行できたのが確認できました。

Screen Shot 2019-04-21 at 1.05.28.png

Imports

  • 別のマークダウンをインポート
doc.mdx
import License from "./license.md"

### Hello, world!

<License />
src/license.md
ライセンス

---

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

**<small>本ソフトウェアの複製、出版、頒布、再使用許諾、および/または販売を行うこと、および本ソフトウェアの提供を受けた人に、以下の条件に従うことを許可すること。</small>**
Screen Shot 2019-04-21 at 15.57.41.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>
Btn.js
import React from "react"

export default ({ children, color }) => {
    return (
        <button style={{backgroundColor: color}}>
            {children}
        </button>
    );
}
Screen Shot 2019-04-21 at 16.06.55.png

export

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

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

# Post about MDX

MDX is a JSX in Markdown loader, parser, and renderer for ambitious projects.
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")
)
Screen Shot 2019-04-21 at 16.29.21.png

MDXProvider

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

doc.mdx
# Document

## Document

1. item
1. item
1. item
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")
)
Screen Shot 2019-04-21 at 2.34.21.png

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!")
}
```
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>
  )
}
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")
)
Screen Shot 2019-04-21 at 3.55.13.png

Live code

  • doc.mdx

    ```js live=true ←ここでtrueを指定する


# Document

```js live=true
class Example extends React.Component {
  render() {
    return <strong>Hello World</strong>
  }
}
```
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>
  )
}
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


今まで↑で自分で試したコードはこちらにも用意

Plugin

remark-emojiを使ってみました

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]
                        }
                    },
                ]
            },
        ]
    }
}
src/index.js
import React from "react"
import {render} from "react-dom"
import Doc from "./doc.mdx"

render(
    <Doc />,
    document.getElementById("root")
)
Screen Shot 2019-04-21 at 5.19.54.png

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

pluginを自作する

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

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]
                        }
                    },
                ]
            },
        ]
    }
}
src/doc.mdx
# MDX Sample

## abcdefg

1. hoge
1. fuga
1. piyo

*foo* **bar**
Screen Shot 2019-04-21 at 5.45.57.png

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

自分で試したコード

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)
}
webpack.config.js
  module.exports = {
      mode: "development",
      module: {
          rules: [
              {
                  test: /\.js$/,
                  use: "babel-loader"
              },
              {
                  test: /\.mdx?$/,
                  use: [
                      "babel-loader", 
                      "@mdx-js/loader",
+                     `${__dirname}/myLoader`,
                  ]
              },
          ]
      }
  }
src/doc.mdx
# Gallery

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

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

Screen Shot 2019-04-21 at 13.42.30.png

自分で試したコード

Wrapper customization

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

src/doc.mdx
### Kicker

# Hello, world!

Working with React children is fun!
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")
)
Screen Shot 2019-04-21 at 14.00.11.png

子要素を操作する

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")
)

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

Screen Shot 2019-04-21 at 14.04.46.png

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

54
38
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
54
38

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?