Riot.js v4対応 Router

[Riot.js Advent Calendar 2019](https://qiita.com/advent-calendar/2019/riotjs) の5日目が空いていたので埋めます。

## Riot Router
`Riot.js`が`v4`になったことで`Riot Router`も新しくなっていたので再入門します。
https://github.com/riot/route

インストールは`npm i -S @riotjs/route`

## 簡単なサンプル
まずは簡単なコードから試していきます。

```html:index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Riot Router</title>
</head>
<body>
  <div id="app"></div>
  <script src="./scripts/bundle.js"></script>
</body>
</html>
```
```javascript:index.js
import { component, register } from 'riot'
import { Router, Route } from '@riotjs/route'
import App from './app.riot'
import Hello from './hello.riot'
import Goodbye from './goodbye.riot'

// グローバルコンポーネントとして登録
register('my-router', Router);
register('my-route', Route);
register('my-hello', Hello);
register('my-goodbye', Goodbye);

// グローバルに登録せずに直接コンポーネントを生成＆マウント
component(App)(document.getElementById('app'));
```
```html:app.riot
<my-app>
  <my-router>
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="/hello">Hello</a></li>
        <li><a href="/goodbye">Goodbye</a></li>
      </ul>
    </nav>

    <my-route path="/">
      Welcome to home
    </my-route>
    <my-route path="/hello">
      <my-hello></my-hello>
    </my-route>
    <my-route path="/goodbye">
      <my-goodbye></my-goodbye>
    </my-route>
  </my-router>
</my-app>
```
```html:hello.riot
<my-hello>
  <p>Hello World!!</p>
</my-hello>
```
```html:goodbye.riot
<my-goodbye>
  <p>Goodbye World!!</p>
</my-goodbye>
```
私はWebpackを使っていますが、ここから下はお好みで。

```json:package.json
{
  "name": "riotv4-router-sample",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "webpack --mode production --devtool source-map",
    "start": "webpack-dev-server --inline --watch --hot --colors --content-base app/ --open-page index.html -d --port 4500"
  },
  "keywords": [],
  "author": "KAJIKEN <kentaro@kajiken.jp> (http://kajiken.jp)",
  "license": "MIT",
  "dependencies": {
    "@riotjs/route": "^4.1.0"
  },
  "devDependencies": {
    "@babel/core": "^7.7.2",
    "@babel/preset-env": "^7.7.1",
    "@riotjs/compiler": "^4.5.2",
    "@riotjs/hot-reload": "^4.0.0",
    "@riotjs/webpack-loader": "^4.0.1",
    "babel-loader": "^8.0.6",
    "riot": "^4.6.6",
    "webpack": "^4.41.2",
    "webpack-cli": "^3.3.10",
    "webpack-dev-server": "^3.9.0"
  }
}
```
```javascript:webpack.config.js
const path = require('path')

module.exports = {
  mode: 'development',
  //mode: 'production',

  entry: './src/scripts/index.js',
  output: {
    path: path.resolve(__dirname, 'app/scripts'),
    filename: 'bundle.js',
    publicPath: '/scripts/',
  },

  devtool: 'inline',
  //devtool: 'source-map',

  module: {
    rules: [
      {
        test: /\.riot$/,
        exclude: /node_modules/,
        use: [{
          loader: '@riotjs/webpack-loader',
          options: {
            hot: true, // set it to true if you are using hmr
            // add here all the other @riotjs/compiler options riot.js.org/compiler
            // template: 'pug' for example
          }
        }]
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  }
}
```
```json:.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "modules": false,
        "targets": [
          ">0.25%",
          "not ie 11",
          "not op_mini all"
        ]
      }
    ]
  ]
}
```
```:ディレクトリ構成
.
│  .babelrc
│  package.json
│  webpack.config.js
│  
├─app
│  │  index.html
│  │  
│  └─scripts
│          bundle.js
│          bundle.js.map
│          
├─node_modules
│              
└─src
    └─scripts
            app.riot
            goodbye.riot
            hello.riot
            index.js
```
URLに応じたタグの内容に切り替わることがわかります。
![rr1_2019_12_04_130833(slt)(raw).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27443/68a8156e-91c6-41a2-cc2e-26bf5996cbc4.gif)

## 注意点

このままではURLを直接入力した場合はエラーになります。
Web Server側の設定でトップに飛ばすなどすれば回避は可能です。
![rr2_2019_12_04_132509(slt)(raw).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27443/e2ac7a63-ccde-2e09-5d79-69e42cf24ef4.gif)

今回の例ですと、`webpack-dev-server`を使っているので、`historyApiFallback`オプションを利用します。

```json:package.json(抜粋)
  "scripts": {
    "build": "webpack --mode production --devtool source-map",
    "start": "webpack-dev-server --inline --watch --hot --colors --content-base app/ --open-page index.html --historyApiFallback true -d --port 4500"
  },
```
`historyApiFallback:true`とすることで`404 not found`の時に`index.html`に飛ばされます。

![rr3_2019_12_05_170830(slt)(raw).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27443/aa0fbe48-200e-8a39-d668-91de931414cc.gif)

Web Server側で対処できないような場合はハッシュを使いましょう。

```html:app.riot
<my-app>
  <my-router>
    <nav>
      <ul>
        <li><a href="#/">Home</a></li>
        <li><a href="#/hello">Hello</a></li>
        <li><a href="#/goodbye">Goodbye</a></li>
      </ul>
    </nav>

    <my-route path="(/|/#/)">
      Welcome to home
    </my-route>
    <my-route path="/#/hello">
      <my-hello></my-hello>
    </my-route>
    <my-route path="/#/goodbye">
      <my-goodbye></my-goodbye>
    </my-route>
  </my-router>
</my-app>
```
![rr7_2019_12_05_172759(slt)(raw)_2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27443/7f7e6ab5-7757-d049-3de1-932469371d74.gif)