Dockerを利用してDXOpalアプリケーションを手軽に事前コンパイルする

使いたくなったときにサッと使えるようにメモ。Docker イメージを用意しておいて docker run すればコンパイルできる、という形にしたい。

プログラムが小さいうちはわざわざ事前コンパイルしなくても問題ないのですが、大きくなってくると事前コンパイルすることによる時間短縮が効いてきますし、source map ありでデバッガが使えて捗ります :thumbsup:

---


:::note

（2025-06-08 追記）
以下のリポジトリで管理するようにしました。記事公開後の変更についてはこちらを参照してください。

https://github.com/sonota88/dxopal-builder

:::


まずは Dockerfile。Opal と DXOpal を含んだイメージを作ります。素の DXOpal ではなく、フォークして修正を加えたブランチを使っているのがポイント（後述）。

```dockerfile
# Dockerfile

FROM ubuntu:22.04

RUN apt-get update \
  && apt-get install -y --no-install-recommends \
    git \
    ruby \
  && apt-get clean \
  && rm -rf /var/lib/apt/lists/*

RUN echo 'gem: --no-document' >> ~/.gemrc
RUN gem instal opal -v 1.2.0

WORKDIR /opt
RUN git clone \
  --branch v1.5.2-no-opal-parser \
  https://github.com/sonota88/dxopal.git

RUN mkdir /tmp/work
WORKDIR /tmp/work
```

適当なシェルスクリプト `run.sh` 。

```sh
#!/bin/bash

IMG_NAME=dxopal-builder:1

cmd_build_image() {
  docker build -t $IMG_NAME .
}

cmd_compile() {
  local rbfile="$1"; shift

  local cmd=""
  cmd="${cmd}docker run --rm -it "
  cmd="${cmd}  -v$(pwd):/tmp/work "

  # コンテナ外の DXOpal を使う場合
  # cmd="${cmd}  -v/path/to/dxopal:/opt/dxopal "

  cmd="${cmd}  ${IMG_NAME} "
  cmd="${cmd}    opal --compile --no-opal --no-exit "
  cmd="${cmd}      --include /opt/dxopal/lib "
  cmd="${cmd}      --include . "
  cmd="${cmd}      $rbfile "

  $cmd # 組み立てたコマンドを実行
}

cmd="$1"; shift
case $cmd in
  build-image )
    cmd_build_image
;; compile )
    cmd_compile "$@"
;; * )
    echo "unknown command (${cmd})" >&2
    exit 1
    ;;
esac
```


イメージをビルド。

```
./run.sh build-image
```

イメージができたら、コンパイルしたいファイルを渡してコンパイルします。以下は DXOpal のサイトにあるサンプルコードを使った例。

```terminal
$ cat my_app.rb
require 'dxopal'; include DXOpal
Window.width = 300
Window.height = 300
Window.bgcolor = C_WHITE
Window.load_resources do
  x = rand(Window.width)
  y = rand(Window.height)
  dx = dy = 2
  Window.loop do
    Window.draw_circle_fill(x, y, 10, C_RED)
    dx = -dx if x < 0 || x > Window.width
    dy = -dy if y < 0 || y > Window.height
    x += dx
    y += dy
  end
end

$ ./run.sh compile my_app.rb > my_app.js
```

JavaScript に変換できたら、 `my_app.js` を HTML ファイルから読み込むようにして、ブラウザで開けばOK。

```html
<!-- 最低限だとこんな感じ -->
<html>
  <body>
    <canvas id="dxopal-canvas"></canvas>
    <div id="dxopal-errors"></div>
    <script src="my_app.js"></script>
  </body>
</html>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/234055/5e75d2be-5b87-5a40-c415-b04c565e6fc5.png)

source map 付きでコンパイルされるので、ブラウザの開発者ツールで Ruby のソースをそのまま見ることができ、デバッガも利用できます。

参考: [Opal::Builder を使ってコンパイルする際に source map も生成する](https://qiita.com/sonota88/items/22183b135084a8875494)


<!--
# しくみ

自分の理解の整理のためのメモ。

- Opal コンパイラ
    - TODO command
    - Ruby で書かれたプログラムを JavaScript にコンパイルする。
- このとき、プログラム内で require していると、require 先のコードも含めてコンパイルされる
    - gem でも同様
    - 探索パスを通しておく必要はある

以上は DXOpal とは関係ない Opal 自体の話。

DXOpal も普通に require して利用しているわけで、上記のしくみに乗っかっている（DXOpal だけのために特殊なことをしているわけではなく、普通にコンパイルしているだけ）。
つまり、DXOpal を require しているプログラムは、DXOpal のコードも含めてコンパイルされる。

参考: [OpalでRubyをJavaScriptにコンパイルする - Qiita](https://qiita.com/takahashim/items/901612df671a096de4e7)


---

普通に（コンパイルせずに） DXOpal アプリを実行した場合
- ブラウザ上でコンパイル rb → js
    - ブラウザで動かすOpalコンパイラが必要
- 事前コンパイルした場合、ブラウザで動かすOpalコンパイラは不要なので、その部分は外してしまえる

結果として、事前コンパイルで生成された JS の内容は以下のようになる。

- DXOpal アプリケーション（自分が書いたプログラム） ... 含む
- DXOpal ライブラリ（自分が書いたプログラムから利用する） ... 含む
- Opalコンパイラ（利用しない） ... 含まない
-->

# メモ



## opal-parser の除外

事前コンパイルする場合は opal-parser を含めないようにするとよいので、除外するようにしたブランチを git clone しています（上記 Dockerfile 参照）。

参考: [DxOpalのゲームをコンパクトにして公開する方法 - Qiita](https://qiita.com/crane_house/items/2d0059c7d35542509397)
<!-- DxOpalのゲームをコンパクトにして公開する方法 - Qiita -->



## DXOpal 本体に手を加えてコンパイルしたい

DXOpal 本体に手を加えないと実現できない機能がある場合や、DXOpal をいじってあれこれ実験したい場合があります。

その場合は、docker run でコンパイルする際に `-v` オプションを使って DXOpal のディレクトリをマウントします。こうすることで、あらかじめイメージ内に入れておいた DXOpal の代わりに自分で修正した DXOpal が参照されるようになります。

```sh
docker run -v "{コンテナ外にあるDXOpalのディレクトリ}:/opt/dxopal" ...
```