Docusaurus に Elm を組み込む

普通そのようなことはしないので需要はないと思います。

## やること

以下のデモのように Docusaurus のページに Elm のアプリケーションを埋め込みます。

> https://n4o847.github.io/docusaurus-elm-demo/elm-project

## Docusaurus プロジェクトを用意する

[Docusaurus v2](https://v2.docusaurus.io/) のデフォルトの初期化によって作られる [以下のようなフォルダ構造](https://v2.docusaurus.io/docs/installation#project-structure) を前提とします。

```
website
├── blog
│   ├── 2019-05-28-hola.md
│   ├── 2019-05-29-hello-world.md
│   └── 2020-05-30-welcome.md
├── docs
│   ├── doc1.md
│   ├── doc2.md
│   ├── doc3.md
│   └── mdx.md
├── src
│   ├── css
│   │   └── custom.css
│   └── pages
│       ├── styles.module.css
│       └── index.js
├── static
│   └── img
├── docusaurus.config.js
├── package.json
├── README.md
├── sidebars.js
└── yarn.lock
```

## Elm プロジェクトを作る

場所はどこでも良いですが、Elm プロジェクトを配置します。

```
├── elm-project
│   ├── src
│   │   └── Main.elm
│   └── elm.json
└── website
    └── ...
```

## elm, elm-webpack-loader, elm-hot-webpack-loader をインストール

```bash
$ cd website
$ yarn add elm elm-webpack-loader elm-hot-webpack-loader
```

```diff:website/package.json
    "dependencies": {
      "@docusaurus/core": "2.0.0-alpha.70",
      "@docusaurus/preset-classic": "2.0.0-alpha.70",
      "@mdx-js/react": "^1.6.21",
      "clsx": "^1.1.1",
+     "elm": "^0.19.1-5",
+     "elm-hot-webpack-loader": "^1.1.8",
+     "elm-webpack-loader": "^7.0.1",
      "react": "^16.8.4",
      "react-dom": "^16.8.4"
    },
```

elm はグローバルにあれば十分ですが CI 環境でもコンパイルするには必要です。

[elm-webpack-loader](https://github.com/elm-community/elm-webpack-loader) は Elm を JavaScript からモジュールとして読み込むのに必要です。

[elm-hot-webpack-loader](https://github.com/klazuka/elm-hot-webpack-loader) は必須ではありませんが、ローカルサーバでの実行中に Elm のソースファイルを変更しても、状態が保存されたままリロードされるようになります。

## プラグインを作る

Docusaurus の [プラグインの作り方](https://v2.docusaurus.io/docs/using-plugins#creating-plugins) に従ってプラグインを作ります。

[Webpack の設定を追加できる](https://v2.docusaurus.io/docs/lifecycle-apis#configurewebpackconfig-isserver-utils) ので、これを使います。

```js:website/plugins/docusaurus-plugin-elm.js
module.exports = function (context, options) {
  return {
    name: 'docusaurus-plugin-elm',
    configureWebpack(config, isServer, utils) {
      return {
        resolve: {
          extensions: ['.elm'],
        },
        module: {
          rules: [
            {
              test: /\.elm$/,
              exclude: [/elm-stuff/, /node_modules/],
              use: [
                utils.getCacheLoader(isServer),
                'elm-hot-webpack-loader',
                {
                  loader: 'elm-webpack-loader',
                  options: {
                    cwd: options.cwd,
                  },
                },
              ].filter(Boolean),
            },
          ],
        },
      };
    },
  };
};
```

（`getCacheLoader` が CI 環境では `null` を返すというのが引っかかりポイントでした……。）

これを読み込むために website/docusaurus.config.js に以下を追加します。

```js:website/docusaurus.config.js
  plugins: [
    [
      path.resolve(__dirname, 'plugins/docusaurus-plugin-elm'),
      {
        cwd: path.resolve(__dirname, '../elm-project'),
      },
    ],
  ],
```

## ページを作る

Elm が動作するページを website/src/pages/ 以下に作ります。

```jsx:website/src/pages/elm-project/index.js
import React, {useEffect, useRef} from 'react';
import Layout from '@theme/Layout';
import {Elm} from '../../../../elm-project/src/Main';

function ElmProject() {
  const elmNode = useRef(null);
  useEffect(() => {
    Elm.Main.init({
      node: elmNode.current,
    });
  }, []);
  return (
    <Layout>
      <div ref={elmNode}></div>
    </Layout>
  );
}

export default ElmProject;
```

## 完成

完成です。website/ 内で `yarn start` を実行して上で作ったページにアクセスすると、Elm のアプリケーションが動作します。

ソースコードはこちらで公開しています。

> https://github.com/n4o847/docusaurus-elm-demo

また出来上がったページはここからアクセスできます。

> https://n4o847.github.io/docusaurus-elm-demo/elm-project

## エイリアスを追加する

相対パスによるインポートがややこしいので、エイリアスを追加しても良いです。

```diff:website/plugins/docusaurus-plugin-elm.js
          resolve: {
+           alias: {
+             [options.alias]: options.cwd,
+           },
            extensions: ['.elm'],
          },
```

```diff:website/docusaurus.config.js
    plugins: [
      [
        path.resolve(__dirname, 'plugins/docusaurus-plugin-elm'),
        {
+         alias: '@elm-project',
          cwd: path.resolve(__dirname, '../elm-project'),
        },
      ],
    ],
```

```diff:website/src/pages/elm-project/index.js
- import {Elm} from '../../../../elm-project/src/Main';
+ import {Elm} from '@elm-project/src/Main';
```