riotjs で svg タグの中を動的にマウントする時の tips

本記事は [Riot.js Advent Calendar 2019]() の第15日目の記事です.

Riot.js（以下、riot4[^1]）は非常にシンプルかつ軽量で入門の敷居も低く, とても書きやすいコンポーネント指向のUIライブラリです。

[^1]: 最新の Riot.js のメジャーバージョンが 4 ですので, 明示的に riot4 と呼ぶことにします.

今回は, 以下の2つのアプリを riot4 で作ってみましたが, svg のレンダリングでちょっと躓いたのでその知見を書いていきたいと思います。

※riot4 の基本機能の紹介になりますのでご了承ください :bow: 

# 作ったもの - バンドラ有り

こちらは [ICSメディア](https://ics.media/) さんの [サンプル](https://github.com/ics-creative/160916_performance_js_framework) を riot4 で作り直したもの(`riot-svg`)になります. 今回は [parcel-bundler](https://parceljs.org/) を利用しました.

* リポジトリ →  https://github.com/kkeeth/160916_performance_js_framework/tree/master/riot-svg
* デモ → https://kkeeth.github.io/160916_performance_js_framework/riot-svg/dist/index.html


もう一つ `riot-dom` の方はちょっと改造していますので, 見る文にはこちらのほうが楽しいかと思いますw

* リポジトリ →  https://github.com/kkeeth/160916_performance_js_framework/tree/master/riot-dom
* デモ
→ https://kkeeth.github.io/160916_performance_js_framework/riot-dom/dist/index.html

# 作ったもの - バンドラ無し

`vuejs` ユーザーなら一度は見たことがあるかもしれませんが, vuejs の公式サンプルを riot4 で作り直したものになります.

* リポジトリ → https://github.com/kkeeth/riot-examples/tree/master/bounds-header
* デモ → https://codesandbox.io/embed/riot4-bounds-header-jstlz

# アプリの構成

どちらも同じような躓き方をしていますので, 解説は1つ目のみにします. `parcel` を利用しており, こちらの方がより開発者向けかとも思いますので. 説明には関係ないコードは省略しておりますのでご注意ください.


まずはディレクトリ構成です.

```:ディレクトリ構成
./
　├ src/
　│　├ main.js
　│　├ particle-data.js
　│　└ components/
　│　 　├ app-components.riot ←親コンポーネント
　│　 　└ particle-components.riot ←子コンポーネント
　│
　├ public/
　│　├ index.html
　│　└ favicon.ico
　│　
　├ node_modules/
　├ dist/　←ビルド結果出力ディレクトリ
　├ package.json
　└ yarn.lock
```

`main.js` にて親である `app-component` を読み込み, グローバルに登録[^2] せずそのままコンポーネントを作成しマウントしています. 

さらに, `app-component` から子コンポーネント `particle-component` を読み込み, グローバルに登録し, マウントしますが, ここでグローバルへの登録の仕方が2通りあります.

[^2]: riot 固有の処理です. 詳しくは [こちら](https://riot.js.org/api/#riotregister) をご参照ください.

### ▼1つ目: `register` メソッドの利用

一番シンプルな登録方法だと思います. riot 本体の `register` メソッドを利用します.

```html:登録の仕方1
<particle-component /* attributes */ />
<script>
  // register メソッドの読み込み
  import { register } from 'riot'
  // 対象のコンポーネントの読み込み
  import ParticleComponent from './particle-component.riot'

  // 登録
  register('particle-component', ParticleComponent)
</script>
</app-component>
```

### ▼2つ目: `components` オブジェクトとしてセット

`export default` オブジェクトの１要素として登録する方法です. 個人的にはこちらの方が好きです.

```html:登録の仕方2
<particle-component /* attributes */ />
<script>
  // 対象のコンポーネントの読み込み
  import ParticleComponent from './particle-component.riot'

  export default {
    // ここでセット
    components: {
      ParticleComponent
    }
  }
</script>
```

上記の通り２つ目の方法では, 読み込み時の名前は変更可能です. 読み込んだ `ParticleComponent` の中身をコンソールに出力してみますと, 

<img width="582" alt="スクリーンショット 2019-12-10 14.29.07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/48034/bb58f745-4d67-4003-c1e4-ad0f389c2d4f.png">

このように, `name` キーでタグの名前が保持されており, riot4 がこちらを見てよしなにやってくれるからです.

〜閑話休題〜

改めて, 今回のアプリケーションの親・子コンポーネントの構成をご紹介します.

```html:app-component.riot
<app-component>
  <particle-component
    width={ window.innerWidth }
    height={ window.innerHeight }
    particles={ state.particles } />
  <script>
    import ParticleComponent from './particle-component.riot'

    export default {
      components: {
        ParticleComponent
      }
    }
  </script>
</app-component>
```

```html:particle-component.riot
<particle-component>
  <g each={ particle in props.particles }>
    <rect
      x={ particle.displayX }
      y={ particle.displayY }
      width="3"
      height="3">
    </rect>
  </g>
</particle-component>
```

じつはこの書き方だと動作しません.
なぜかと言うと, この状態でマウント・レンダリングすると, 以下のように `svg` タグが差し込まれまず, 画面としては画像が表示される部分が真っ白になります.

```html:レンダリング後のhtml
<div id="root" is="app-component">
  <div>
    <particle-component width="669" height="613">
      <g><rect width="3" height="3" x="118" y="576"></rect></g>
      (...以下 g タグが続く)
    </particle-component>
  </div>
</div> 
```

# ちょっと修正

直接的な修正の仕方としては, 以下のように `svg` タグを明示的に挿入するやり方です.

```diff:変更点1
 <particle-component>
+  <svg>
     <g each={ particle in props.particles }>
       <rect
         x={ particle.displayX }
         y={ particle.displayY }
         width="3"
         height="3">
       </rect>
     </g>
+  </svg>
+  <style>
+    svg {
+      100vw;
+      100vh;
+    }
+  </style>
 </particle-component>
```

これでも動くことは動きます. しかし riot4 の場合コンポーネントを作成すると, カスタムタグ(今回は `particle-component`)は消えずにそのままレンダリングされるため, そのカスタムタグで挟むことになってしまうのがイケてない…

これを回避するために, 以下のように修正します.

```diff:変更点
<app-component>
-  <particle-component
+  <svg 
+    is="particle-component"
     width={ window.innerWidth }
     height={ window.innerHeight}
     particles={ state.particles } />
  <script>
    import ParticleComponent from './particle-component.riot'

    export default {
      components: {
        ParticleComponent
      }
    }
  </script>
</app-component>
```

要は, HTML 標準のタグを riot4 のコンポーネントをして扱うということです. こちらは riot4 の基本機能[^3]であり公式ドキュメントにも記載がありますので, そちらをご参照いただければと思いますー. (過去の俺よ…なんで自分で翻訳しておきながら, この存在を忘れてしまった…)

[^3]: [HTML elements as components](https://riot.js.org/documentation/#html-elements-as-components)

# 終わりに

riot4 はとても良い子なので, JavaScript の UI ライブラリで軽量かつ簡単に導入できるものを探しているなら, ぜひ一度検討していただけたら嬉しいです！

ではでは(=ﾟωﾟ)ﾉ