AsciiDoc+ブラウザ自動更新環境のDockerイメージを作った

# はじめに

Asciidocによるドキュメント作成時のライブプレビュー環境をDockerを使って構築しました。
動機は、Asciidoctorにはライブプレビュー機能(`mkdocs serve`、`gitbook serve`のような機能)が提供されていないので欲しかったからです。
[公式サイト](https://asciidoctor.org/docs/editing-asciidoc-with-live-preview/)にもlive-previewの方法は書いてあるんですが、ChromeかFirefox固定で、更に拡張機能を入れないといけない方法です。
流石にそれは嫌、ということでサーバ側のみで完結する方法を探しました。

ちなみにDockerを使った理由は以下です。

1. ruby+Node.jsの構成になってしまったので、環境構築を簡単にしたかった
      *  布教しようと考えた時に、非プログラマーの人にこの2つの環境を作れと言えないので。。。dockerならOK?
2. 遅ればせながら、wsl上でdockerを使えるようになっていることを知ったため。
    * [WSL上でDocker Engineが動くようになっていたっぽいという話](https://qiita.com/yanoshi/items/dcecbf117d9cbd14af87)
    * ただ、docker-composeは使えないので今回の記事は意味無いです 
3. 公開することを考えてDockerを使ったことがなかったので、使えるようになりたかった
    * [DockerhubのAutomated build を設定する](https://qiita.com/FoxBoxsnet/items/a31fc46681d9b67c8cc6) を参考にさせていただきました

# 結論

以下を作成して、asciidoctorのライブプレビュー環境を作成

1. `asciidoctor` + `filewatcher` によるasciidoctorの自動ビルドのdockerイメージ [dbgso/asciidoctor-watcher](https://hub.docker.com/r/dbgso/asciidoctor-watcher)
2. `simple-autoreload-server` によるブラウザオートリロードのdockerイメージ[dbgso/autoreloadserver](https://hub.docker.com/r/dbgso/autoreloadserver)

**使い方**

1. 以下のファイルを`docker-compose.yml`として保存
2. `mkdir data`でマウント用ディレクトリ作成
3. `sudo docker-compose up`で起動
4. `sudo chown -R $USERNAME:$USERNAME data`で権限変更。(マウント時にrootの持ち物になってしまうので)
4. `data/index.adoc`でasciidoctorのファイル作成・更新を行う
5. http://localhost:18080/index.html を開くと`data/index.adoc`の変更内容がオートリロードされる


```docker-compose.yml
version: '3'

services: 
  adoc:
    image: dbgso/asciidoctor-watcher:0.1

    volumes:
      - document_root:/data
    container_name: adoc_container
    working_dir: '/data'
  nodeapp:
    image: dbgso/autoreloadserver:0.1
    volumes: 
      - document_root:/data
    container_name: autoreloadserver
    working_dir: '/data'
    ports: 
      - 18080:8080

volumes:
  document_root:
    driver_opts:
      type: none
      device: $PWD/data
      o: bind
```


# Dockerfile作成

以下動きをするコンテナをそれぞれ作成した

1. Asciidoctorによる自動ビルド
2. htmlが生成されたタイミングでブラウザをリロード

## 自動ビルド環境作成

ソースとなるAsciidocファイルが更新された時に自動ビルドを行うDockerイメージを作成しました。  
Asciidoctorによるビルド環境自体は[dockerhub](https://hub.docker.com/r/asciidoctor/docker-asciidoctor/)に公開されているので、これをベースとしてファイル更新の監視機能とコードハイライト機能を追加で組み込みました。
公開されているイメージにはPlantUMLやblockdiag等の環境も用意されているので、テキストで作図もできます。ここの環境も作りたかったので、このDockerイメージをベースにしました。

* [Dockerイメージ](https://hub.docker.com/r/dbgso/asciidoctor-watcher)
* [Dockerfile](https://github.com/dbgso/docker-asciidoctor-watcher/blob/master/Dockerfile)

`追加ライブラリ`

* ファイル更新の監視
  * [filewatcher](https://github.com/filewatcher/filewatcher)
  * コマンドラインレベルで使えるてお手軽だったのと、alpine環境でも追加インストール等いらずで簡単に使えたので(Guardはビルドが中々通らなかった)
* コードハイライト
  * [coderay](https://github.com/rubychan/coderay)
  * Ruby製で相性が良さそうだったので(~~これしか使ったことがないので~~)

## オートリロード環境

HTMLファイルが変更された時にブラウザをリロードするために、オートリロード[simple-autoreload-server](https://www.npmjs.com/package/simple-autoreload-server)のライブラリを利用。これを組み込んだDockerイメージを作成しました。

まあやっていることは`Node.js`+`simple-autoreload-server`というだけの環境なので、このコンテナ単体で使うメリットはあまり無いですね。
別コンテナと連携したくて作った感じです。

* [Dockerイメージ](https://hub.docker.com/r/dbgso/autoreloadserver)
* [Dockerfile](https://github.com/dbgso/docker-simple-autoreload-server/blob/master/Dockerfile)

# docker-composeによる起動

dockerコマンドでもいいですが、docker-composeの方が簡単なのでその方法でいきます。

## 利用方法

↑でイメージができたので、この2つを連携させた状態で起動します。  
最初にも書きましたが、以下のファイルを用意して`docker-compose up`すれば動きます。


```docker-compose.yml
version: '3'

services: 
  adoc:
    image: dbgso/asciidoctor-watcher:0.1

    volumes:
      - document_root:/data
    container_name: adoc_container
    working_dir: '/data'
  nodeapp:
    image: dbgso/autoreloadserver:0.1
    volumes: 
      - document_root:/data
    container_name: autoreloadserver
    working_dir: '/data'
    ports: 
      - 18080:8080

volumes:
  document_root:
    driver_opts:
      type: none
      device: $PWD/data
      o: bind
```

↑のファイルを作成したら、以下の手順で起動します。



```bash
mkdir data # マウント対象のディレクトリ作成
touch data/index.adoc # ソースとなるファイル作成
docker-compose up # コンテナ起動
sudo chown -R $USERNAME:$USERNAME data # dataディレクトリの権限変更。(マウント時にrootの持ち物になってしまうので)
```

この状態で、 http://localhost:18080/index.html を開くと`index.adoc`の変更内容が自動的に反映されます。  
あとは、index.adocに追記していくなり、ファイルを分割して進めていくなりすれば良いです。


`※ dataディレクトリ配下の権限には注意してください。dockerを使っているとルート権限で操作することが多いので、そのまま行くと通常ユーザの時に書き込めくなるので`
マウント時にroot以外の権限にする方法無いですか？誰か教えてください


## 動作概要

、`./data`が2つのコンテナにマウントされます。  

* `adoc_container`コンテナが`index.adoc`の変更を監視して`asciidoctor`コマンドを実行
* `autoreloadserver`コンテナが出力された`index.html`の変更を監視して、ブラウザをリロードしてくれます

デフォルトでは`data/index.adoc`が開始のファイルと定義してあるので、このファイル名のファイルを作成して執筆すれば自動ビルドしてくれます。(読み込むファイルを変更する方法もありますが、今回は省略します)
最小構成の場合のフォルダ構成は以下のような感じです。

```
./data
./data/index.adoc
./docker-compose.yml
```


# 例

こんな感じのものができました。バッチリ作図、コードハイライトも効いています

![sample_adoc.png](https://qiita-image-store.s3.amazonaws.com/0/330514/d62fa81c-03c8-5a18-b506-2b6c3a5052ea.png)

```sample.adoc
:toc: left
:source-highlighter: coderay


= サンプル

== はじめに

サンプルファイル

== 作図

* PlantUML
* blockdiag
* 他にも色々いける

=== PlantUML
aiueo

[plantuml, sample, png]
--

include::sample.puml[]
--

=== blockdiag

[blockdiag, blockdiag, png]
--
blockdiag {
   // Set span metrix
   span_width = 240;
   span_height = 120;

   adoc -> html, PDF;
}
--


== ハイライト

[source, javascript]
--

class Hoge  {
    constructor() {
        this.num = 1;
    }
}  

--
```

ファイル分割もできるので、plantumlは別ファイルにしました

```sample.puml
@startuml hoge

actor user as user
participant filewatcher
collections "asciidoc files" as adoc
participant autoreloadserver
collections html as html

par 監視
    filewatcher -> adoc: 監視
    activate filewatcher
    else
    autoreloadserver -> html: 監視
    activate autoreloadserver
end

...

== オートビルド ==

user -> adoc: ファイル保存
activate adoc
adoc -> filewatcher: ファイル変更検知
deactivate adoc
activate filewatcher

filewatcher -> html : build
html --> filewatcher: ok
deactivate filewatcher

== ブラウザオートリロード ==

activate html
html --> autoreloadserver: ファイル変更検知
autoreloadserver -> user: WebSocket ブラウザリロード


@enduml
```

# おわりに

Asciidoctorの執筆環境をDockerを使って構築しました。
AsciidoctorはMarkdownと同程度の書きやすさで表現力が高いので、もっと流行ってほしいです。ただ、環境構築は多少ネックになるのかなと思うので今回作ったイメージで多少楽できて、布教できたら良いなと思います。
あと、asciidoctorはフロントマター等のオプションが多い+まとまってない感じがするので、それをまとめる記事を書いたりしようかなと思ってます