Riot.js + RiotControl で Flux を実装してみる - スタイルガイド編


## はじめに

以下の記事の **スタイルガイド編** です。

[Riot.js と RiotControl で Flux を実装してみる - Qiita](http://qiita.com/kotarella1110/items/f9958195c929da6508b6 "Riot.js と RiotControl で Flux を実装してみる - Qiita")

[De Voorhoede](https://www.voorhoede.nl/ "De Voorhoede") 社チームのための Riot.js スタイルガイドに載っとり開発しました。スタイルガイドに載っとることでソースコードの一貫性や品質を保つことが出来たかなと思います。

実際に作成したプロジェクト ( 開発環境 ) は以下にリポジトリを置いています。

[kotarella1110/riot-starter at v2.0.0](https://github.com/kotarella1110/riot-starter/tree/v2.0.0 "kotarella1110/riot-starter at v2.0.0")

## スタイルガイドに載っとる

[![RiotJS Style Guide badge](https://cdn.rawgit.com/voorhoede/riotjs-style-guide/master/riotjs-style-guide.svg)](https://github.com/voorhoede/riotjs-style-guide)

今回は [開発環境構築編](http://qiita.com/kotarella1110/items/e4d0120dd70057ffdfdc "Riot.js + RiotControl で Flux を実装してみる - 開発環境構築編 - Qiita") で作成した[プロジェクト](https://github.com/kotarella1110/riot-starter/tree/v1.0.0 "kotarella1110/riot-starter at v1.0.0")に修正を加えてスタイルガイドを反映させていきたいと思います。既にスタイルガイドが反映されている箇所もありますので、そこに関しての説明は省きます。

### [1モジュール = 1ディレクトリ](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#1%E3%83%A2%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%AB--1%E3%83%87%E3%82%A3%E3%83%AC%E3%82%AF%E3%83%88%E3%83%AA)

[差分](https://github.com/kotarella1110/riot-starter/commit/cdd4f2aba7dfd1983ebd82ff4f2c6d95869b18de)

1モジュール = 1ディレクトリにするために以下の構成から

```tree:src
.
├── .gitkeep 
├── index.html
├── index.js
├── todo.scss
└── todo.tag
```

以下の構成へ修正します。

```tree:src
.
├── index.html
├── index.js
└── todo
    ├── todo.scss
    └── todo.tag
```

_src/index.js_ を以下のように修正します。


```diff:src/index.js
-import './todo.tag';
+import './todo/todo.tag';
```

### [*.tag.html を使う](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#taghtml-%E3%82%92%E4%BD%BF%E3%81%86)

[差分](https://github.com/kotarella1110/riot-starter/commit/1c71bf0f73c250773bd03c209033a4ee2eca3a4c)

_src/todo/todo.tag_ から _src/todo/todo.tag.html_ のように変更し、タグの拡張子を修正します。

以下のように _*.tag.html_ ファイルを `babel-loader` と `riot-tag-loader` が読み込めるように Webpack を修正します。

```diff:webpack/common.config.js
     module: {
       rules: [
         { // js
-          test: /\.(js|tag)$/,
+          test: /\.(js|tag|tag.html)$/,
           include: /src/,
           exclude: /node_modules/,
           enforce: 'post',
```

```diff:webpack/dev.config.js&prod.config.js
     module: {
       rules: [
         { // riot
-          test: /\.tag$/,
+          test: /\.(tag|tag.html)$/,
           include: /src/,
           exclude: /node_modules/,
           enforce: 'pre',
```

_src/todo/todo.tag.html_ を `import './todo/todo.tag'` とすれば import できるように修正します。

```diff:webpack/common.config.js
       ]
     },
     resolve: {
-      extensions: ['.js'],
+      extensions: ['.js', '.html'],
     },
     plugins: [
       new webpack.ProvidePlugin({
```

### [タグオプションを設定する](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#%E3%82%BF%E3%82%B0%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)

[差分](https://github.com/kotarella1110/riot-starter/commit/2bb1ee420a3ab51b95f97eb6e4ed476cb37728f5)

```diff:src/todo/todo.tag.html
 <todo>
 
-  <h3>{ opts.title }</h3>
+  <h3>{ title }</h3>
 
   <ul>
     <li each={ items.filter(whatShow) }>
...
   <!-- this script this is optional -->
   <script>
-    this.items = opts.items;
+    this.title = opts.title || '';
+    this.items = opts.items || [];
+    this.text = '';
 
     this.edit = (e) => {
       this.text = e.target.value;
```

### [thisをtagにアサインする](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#this%E3%82%92tag%E3%81%AB%E3%82%A2%E3%82%B5%E3%82%A4%E3%83%B3%E3%81%99%E3%82%8B)


[差分](https://github.com/kotarella1110/riot-starter/commit/c46f3aa84c0941893721c7923f0e68adc59bbf69)

```diff:src/todo/todo.tag.html
  <!-- this script this is optional -->
   <script>
-    this.title = opts.title || '';
-    this.items = opts.items || [];
-    this.text = '';
+    const tag = this;
 
-    this.edit = (e) => {
-      this.text = e.target.value;
+    tag.title = opts.title || '';
+    tag.items = opts.items || [];
+    tag.text = '';
+
+    tag.edit = (e) => {
+      tag.text = e.target.value;
     };
 
-    this.add = (e) => {
-      if (this.text) {
-        this.items.push({ title: this.text })
-        this.text = this.refs.input.value = '';
+    tag.add = (e) => {
+      if (tag.text) {
+        tag.items.push({ title: tag.text })
+        tag.text = tag.refs.input.value = '';
       }
       e.preventDefault();
     };
 
-    this.removeAllDone = (e) => {
-      this.items = this.items.filter((item) => {
+    tag.removeAllDone = (e) => {
+      tag.items = tag.items.filter((item) => {
         return !item.done;
       });
     };
 
     // an two example how to filter items on the list
-    this.whatShow = (item) => {
+    tag.whatShow = (item) => {
       return !item.hidden;
     };
 
-    this.onlyDone = (item) => {
+    tag.onlyDone = (item) => {
       return item.done;
     };
 
-    this.toggle = (e) => {
+    tag.toggle = (e) => {
       const item = e.item;
       item.done = !item.done;
       return true;
```

### [each ... inシンタックスを使う](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#each--in%E3%82%B7%E3%83%B3%E3%82%BF%E3%83%83%E3%82%AF%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86)

[差分](https://github.com/kotarella1110/riot-starter/commit/9307a852fe06cd9d66a146d774c9c306136c381c)

```diff:src/todo/todo.tag.html
  <h3>{ title }</h3>
 
   <ul>
-    <li each={ items.filter(whatShow) }>
-      <label class={ completed: done }>
-        <input type="checkbox" checked={ done } onclick={ parent.toggle }> { title }
+    <li each={ item in items.filter(whatShow) }>
+      <label class={ completed: item.done }>
+        <input type="checkbox" checked={ item.done } onclick={ parent.toggle }> { item.title }
       </label>
     </li>
   </ul>
...
     };
 
     tag.toggle = (e) => {
-      const item = e.item;
+      const item = e.item.item;
       item.done = !item.done;
       return true;
     };
```



### [タグ名をスタイルスコープとして使う](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#%E3%82%BF%E3%82%B0%E5%90%8D%E3%82%92%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AB%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E3%81%A8%E3%81%97%E3%81%A6%E4%BD%BF%E3%81%86)

[差分](https://github.com/kotarella1110/riot-starter/commit/641cf7f0074c1f6b2f04c984dbd21d755e623ce3)

以下の _src/todo/todo.scss_ ファイルの `body` と `todo` を分離します。

```scss:src/todo/todo.scss
body {
  font-family: 'myriad pro', sans-serif;
  font-size: 20px;
  border: 0;

  todo {
    display: block;
    max-width: 400px;
    margin: 5% auto;
...
```

以下のように _src/main.scss_ という新規ファイルを作成し、`body` のスタイルを記述します。

```scss:src/main.scss
body {
  font-family: 'myriad pro', sans-serif;
  font-size: 20px;
  border: 0;
}
```

_src/todo/todo.scss_ は以下のように修正します。

**`data-is` 属性を使ってカスタムタグを作成しているケースも考えられるため、以下のように指定するのが良いでしょう。**

```scss:src/todo/todo.scss
todo, [data-is="todo"] {
...
}
```

_src/main.scss_ ファイルを _src/index.js_ で import することを忘れないでください。


```diff:src/index.js
+import './main.scss';
 import './todo/todo.tag';
  
 riot.mount('todo', {
...
```

### [自分のタグAPIをドキュメント化する](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#%E8%87%AA%E5%88%86%E3%81%AE%E3%82%BF%E3%82%B0api%E3%82%92%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E5%8C%96%E3%81%99%E3%82%8B)

[差分](https://github.com/kotarella1110/riot-starter/commit/1a93c5c44b2ca2b3c75d0575c537033f13b3e9b1)

`todo` タグのドキュメントを以下のように作成します。

```file:README.md
# Todo

## Functionality

Quickly add a todo to a list, and strike them off when you're done.

## Usage

`<todo>` supports the following custom tag attributes:

| attribute | type | description |
| --- | --- | --- |
| `title` | String | Name of the todo list. |
| `items` | Array _optional_ | List of todo items with `title` ( String ) and `done` state ( Boolean ) and `hidden` state ( Boolean ). E.g. `[{ title:'Alpha' }, { title:'Beta', done:true }, { title:'Omega', hidden:true }]`. |
```

### [タグのデモを追加する](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#%E3%82%BF%E3%82%B0%E3%81%AE%E3%83%87%E3%83%A2%E3%82%92%E8%BF%BD%E5%8A%A0%E3%81%99%E3%82%8B)

[差分](https://github.com/kotarella1110/riot-starter/commit/4925839089453e28019390cce1df01d32bdbe9b7)

本稿で最もためになる部分かなと筆者は思っています。

タグについては Sass と ES6 で記述し Webpack でビルドしているため、De Voorhoede 社のスタイルガイドの内容とは異なる方法でデモを追加し、そのデモをブラウザで確認できるようにする必要がありました。アプリ用の HTML とデモ用の HTML を分けてビルドするようにすることでデモをブラウザで確認できるようできるようにしています。

少し詳しくお話ししますと、以下のような構成から

```tree:src
.
├── demos
│   ├── demo
│   │   ├── demo.scss
│   │   └── demo.tag.html
│   ├── index.html          // デモ用の HTML
│   └── index.js            // デモ用の HTML から読み込まれる JavaScript
├── todo
│   ├── README.md
│   ├── todo-demos.tag.html // todo タグのデモ用のタグ
│   ├── todo.scss
│   └── todo.tag.html
├── index.html              // アプリ用の HTML
├── index.js                // アプリ用の HTML から読み込まれる JavaScript
└──  main.scss
```

`yarn build` でビルドした際に、以下のように出力されるように Webpack の config を設定しています。

```tree:dist
.
├── demos.bundle.js     // デモ用の HTML から読み込まれる JavaScript
├── demos.html          // デモ用の HTML
├── index.html          // アプリ用の HTML
├── main.bundle.js      // アプリ用の HTML から読み込まれる JavaScript
├── main.css
├── polyfills.bundle.js
└── vendor.bundle.js
```


まずは `demo` タグを作成します。

`demo` タグの `<yield/>` を使うことでデモをしたいタグを埋め込めるようにします。

```html:src/demos/demo/demo.tag.html
import './demo.scss';

<demo>
  <p>{ label }</p>
  <yield />
  <script>
    const tag = this;
    tag.label = opts.label || '';
  </script>
</demo>
```

```sass:src/demos/demo/demo.scss
demo, [data-is="demo"] {
  p {
    display: block;
    background: #F3F5F5;
    padding: .5em;
    clear: both;
    margin: .5em 0;
  }
}
```

`todo-demos` タグという `todo` タグのデモ用のタグを作成します。

`demo` タグに `todo` タグを埋め込むことでデモを追加することができます。

```html:src/todo/todo-demos.tag.html
import '../demos/demo/demo.tag';
import './todo.tag';

<todo-demos>

  <h2>todo demos</h2>

  <demo label="todo with title and empty list">
    <todo title="Groceries" />
  </demo>

  <demo label="todo with title and initial items">
    <todo title="Groceries" items="{ [{ title:'Apples' }, { title:'Oranges', done:true }, { title:'Grapes', hidden:true }] }" />
  </demo>

</todo-demos>
```

_src/demos/index.js_ ファイルを作成します。

先ほど作成した `todo-demos` タグを import し、マウントします。


```js:src/demos/index.js
import '../todo/todo-demos.tag';

riot.mount('*');
```

最後に HTML ファイルを作成します。

```html:demos/demo.html
<!doctype html>

<html>
  <head>
    <title>Riot Demos</title>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <!--[if lt IE 9]>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/es5-shim/4.0.5/es5-shim.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.2/html5shiv.min.js"></script>
    <script>html5.addElements('todo')</script>
    <![endif]-->

  </head>

  <body>

    <todo-demos></todo-demos>

  </body>

</html>
```

後は Webpack の config を修正して、 デモ用の HTML として出力するようにするだけです。

まずは、 _webpack/paths.js_ に先ほど作成したデモ用の HTML と JavaScript のパスを追加します。

```diff:webpack/paths.js
...
  export default {
   srcHtml: path.join(__dirname, '../src/index.html'),
+  srcDemosHtml: path.join(__dirname, '../src/demos/index.html'),
   srcJs: path.join(__dirname, '../src/index.js'),
+  srcDemosJs: path.join(__dirname, '../src/demos/index.js'),
   distDir: path.join(__dirname, '../dist')
 } 
```

`html-webpack-plugin` を使って HTML の出力を分けるようにしてあげましょう。`chunks` ではどの `entry` を HTML ファイルに含めるかを定義しています。

デモ用の HTML は開発環境の時だけ出力されるように `webpack/dev.config.js` ファイルを修正します。

```diff:webpack/dev.config.js
 import webpack from 'webpack';
+import HtmlWebpackPlugin from 'html-webpack-plugin';
 import env from '../env.js';
 import paths from './paths.js';
 
...
     entry: {
       vendor: [
         'riot-hot-reload'
+      ],
+      demo: [
+        paths.srcDemosJs
       ]
     },
     module: {
...
           'NODE_ENV': JSON.stringify('development'),
         }
       }),
-      new webpack.HotModuleReplacementPlugin()
+      new webpack.HotModuleReplacementPlugin(),
+      new HtmlWebpackPlugin({
+        template: paths.srcDemosHtml,
+        filename: 'demos.html',
+        chunks: ['demo', 'polyfills', 'vendor']
+      }),
     ],
     devtool: 'inline-source-map',
     devServer: {
```

アプリ用の HTML も今まで通り出力されように `webpack/dev.config.js` ファイルを修正します。

```diff:webpack/common.config.js
       }),
       new HtmlWebpackPlugin({
         template: paths.srcHtml,
+        chunks: ['main', 'polyfills', 'vendor']
       }),
       new CleanWebpackPlugin(['*'], {
         root: paths.distDir,
```

これでデモページを表示できるようになりました。

`yarn start` を実行すると、ブラウザ上でアプリ用の HTML が表示されると思います。 `/demos.html` とパスを追加してあげるとデモ用の HTML が表示できます。


### [タグファイルをlintする](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#%E3%82%BF%E3%82%B0%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92lint%E3%81%99%E3%82%8B)

省略

### [プロジェクトにバッジをつける](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%81%AB%E3%83%90%E3%83%83%E3%82%B8%E3%82%92%E3%81%A4%E3%81%91%E3%82%8B)

[差分](https://github.com/kotarella1110/riot-starter/commit/29a4c3fd0547cfbc74b71cc1fac28f0891208ebc)

_README.md_ にバッジをつけます。

## 最後に

筆者が作成した[プロジェクト](https://github.com/kotarella1110/riot-starter/tree/v2.0.0 "kotarella1110/riot-starter at v2.0.0")に誤りがある場合はプルリクエストやコメント等でご指摘お願いします。:thumbsup:


## 参考文献

* [voorhoede/riotjs-style-guide: Opinionated RiotJS Style Guide for teams.](https://github.com/voorhoede/riotjs-style-guide "voorhoede/riotjs-style-guide: Opinionated RiotJS Style Guide for teams.")
* [voorhoede/riotjs-demos: Companion to the RiotJS Style Guide.](https://github.com/voorhoede/riotjs-demos "voorhoede/riotjs-demos: Companion to the RiotJS Style Guide.")
* [[翻訳] Riotjs Style Guide - Qiita](http://qiita.com/risacan/items/d782fa24ad4d4f8b73f8 "[翻訳] Riotjs Style Guide - Qiita")