Webpack + Riot + Materializeで、Webサイト環境を作った話

RiotとWebPackを使う目的

うーちゃん家のWebサイトは、現状、サーバー上のRailsで動いてますが、そんだけのために、Webサーバーを使ってるのがもったいなく感じてきたので、webpackを使いstaticな静的ファイルを生成してWebサーバーを使うのをやめようということで、モダンなjsで代表的なのはReactですが、(Angularはどうだろう)、今回はちゃちゃっと済ましたいので、Riotを使ってみることにしました。

環境作り

  • webpackでプレコンパイルして、staticな形で出力する
  • riot は.tagファイルで記述し読む
  • materializeを全体的に使いたい
  • 自作スタイルシートはsassで記述したいけど、js中でmoduleで読むのは面倒なので、cssファイルを出力し、タグ中で普通に使いたい
  • 画像はHTTPリクエストを最大限に減らすため、base64でコンパイルする

バージョンについて

  • webpack 3.8.0
  • riot 3.7.3


必要なパッケージを入れる

node 初期化

$ npm init

node が入ってない場合はbrewか何かで入れて下さい。

Webpack

$ npm install --save-dev webpack

Babel を入れる

$ npm install --save-dev babel-core babel-loader

ec2015

$ npm install --save-dev babel-preset-es2015 babel-preset-es2015-riot

webpack のためのローダー

$ npm install --save-dev riotjs-loader

Riot.js を入れる

$ npm install --save riot

Materialize とsassに必要なもの

$ npm install --save materialize-css
$ npm install --save-dev sass-loader css-loader file-loader url-loader materialize-loader

jquery materializeは、jqueryを使ってるため

$ npm install --save jquery


Webpackの設定ファイル

webpack.config.babel.js

import path from 'path';
import webpack from 'webpack';
import ExtractTextPlugin from 'extract-text-webpack-plugin';

export default [
    {
        context: path.join(__dirname, './source/javascripts'),
        entry: {
            app: './app.js'
        },
        output: {
            path: path.join(__dirname, './public'),
            filename: '[name].js'
        },
        module: {
            rules: [
                {
                    test: /\.tag$/,
                    exclude: /node_modules/,
                    enforce: "pre",
                    loader: 'riotjs-loader'
                },
                {
                    test: /\.js[x]?$|\.tag$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                },
                {
                    test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/,
                    loader: 'url-loader',
                },
            ],
        },
        resolve: {
            extensions: ['*', '.js', '.tag']
        },
        plugins: [
            new webpack.ProvidePlugin({
                riot: 'riot'
            })
        ]
    },
    {
        context: path.join(__dirname, './source/stylesheets'),
        entry: {
            app: './style.sass'
        },
        output: {
            path: path.join(__dirname, './public'),
            filename: '[name].css'
        },
        module: {
            rules: [
                {
                    test: /\.sass$/,
                    exclude: /node_modules/,
                    use: ExtractTextPlugin.extract({
                        fallback: "style-loader",
                        use: [
                            {
                                loader: "css-loader",
                                options: {
                                    url: false,
                                    minimize: true
                                }
                            },
                            "sass-loader"
                        ]
                    })
                },
                {
                    test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf|otf)(\?.*)?$/,
                    loader: 'url-loader',
                },
            ]
        },
        plugins: [
            new ExtractTextPlugin({
                filename: '[name].css'
            })
        ]
    }
]

※ entry や output部分のファイル名やパスは、必要に応じて変更して下さい。

  • jsの方は、riot-loaderと、babelのloaderを指定してます。
  • cssは、sassをコンパイルして、cssを履きその内容をタグの中でつかえるようにしました。moduleで読む方法もあるけど、毎回js中でimportするのが面倒なので、この方法をとりました。
  • 画像はjsからもcssからも、base64で読むようにurl-loaderをそれぞれで指定してます。そのためpublic以下にわざわざ画像を出力しません。

.babelrc

{
    "presets": [ "es2015", "es2015-riot", "stage-3" ]
}

これで環境ができました。

あとは、riotとmaterializeを使って書いていきます。


Materialize

設定

webpack.config.babel.js に下記を追加します。

context: path.join(__dirname, './source/javascripts'),
entry: {
    app: './app.js'
},
.
.
.
ここから
  {
      test: /\.scss$/,
      loader: ExtractTextPlugin.extract('style-loader', 'css-loader?-url&minimize&sourceMap!sass-loader')
  },
  {
      test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 
      loader: 'url-loader?limit=10000&mimetype=application/font-woff' 
  },
  {
      test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
      loader: 'file-loader'
  }
ここまで
  • scssをプレコンパイル対象に追加したのは、materializeの色をカスタマイズするために、下記で設定するscssファイルが必要だったからです。
  • woffとttfなどは、materializeで仕様しているフォントファイル等が必要なためです。
  • file-loaderにより、public以下に出力されます。
  • url-loaderはmaterializeのcssファイル中から、fontを読めるようにするためです。
設定ファイルを用意

source/config ディレクトリを作成し

materialize.config.jsを作成

module.exports = {
    styles: {
        'materialize': true,
    }
};

materialize.config.scssを作成

$primary-color: color("blue-grey", "lighten-2");
$primary-color-light: lighten($primary-color, 15%);
$primary-color-dark: darken($primary-color, 15%);

※好きな色を指定する(色をカスタマイズしたいときのみ)

materialize.config.cssを作成

読み込み

app.jsでrequireする

require('materialize-loader!../config/materialize.config.js');
require('materialize-css');

これでmaterializeが適応されました

  • materialize-loaderはcssしかロードしてくれないような感じでしたので、jsも適応したいので、materialize-css を直接記述してます。

Material icon

npm install --save-dev material-design-icons


Riot

試しにカウントアップ・ダウンのサンプルをやってみます
source/tags ディレクトリを作詞し、
count.tagを作成

<count>
    <p>カウント: { count }</p>
    <button onclick={ add }>+</button>
    <button onclick={ minus }>-</button>
    <script>
        this.count = 0;
        add() {
            this.count += 1;
        }
        minus() {
            this.count -= 1;
        }
    </script>
</count>
  • scriptタグ中のthis.xxxと定義すると、htmlタグ中で、{xxx}の形で変数を扱えます。
  • methodについても、script中で定義したmethodは{xxxx}で扱えます。
    わかりやすいですね。

source/javascripts/app.jsを編集

import count from '../tags/count'

riot.mount('count')

mountし、描画される

複数の.tagを読み込む際、何行もimportするのを回避する

source/tags/index.jsを用意

import count from './count'
import navigation from './navigation'

※もっといい方法あれば教えてください。

source/tags/navigation.tagsを適当に用意する

そうすると、app.jsでは

import {count, navigation} from '../tags'

こう書くことができ、一気にたくさんimportできます

これで整いました

まとめ

webpackでの、画像の取扱や、cssのloaderがたくさんあるので、少し戸惑いましたが、理解できてスッキリしました。
riotは視覚的に非常にわかりやすく、モダンにjsを書きたいけど、reactまでやるのはちょっとって方にはいいかもしれません。