webpackでMaterializeを使うにあたりハマったこと


モジュールバンドラwebpackでマテリアルデザインベースのCSSフレームワークMaterializeを使おうとしてだいぶハマったので，誰かの役に立つかもしてないと思いメモ

## 完成したテンプレート

最終的に完成したコードはGitHubに公開してあるので，役に立つことがあれば使って下さい
- [最小構成（スクリプトのバンドルのみ）](https://github.com/usk83/materialize-webpack/tree/minimum)
- [フル（スクリプト、スタイルシート、フォントのコンパイル&バンドル）](https://github.com/usk83/materialize-webpack)

## ゴール

Materializeの公式サイトにて公開されているStarter Template([DEMO](http://materializecss.com/templates/starter-template/preview.html))をwebpackで使う
スタイルシートやフォントのバンドルはハマりどころではないのでここではスクリプトファイルのバンドルのみ
また，MaterializeはjQueryに依存しているが現在，標準でjQuery2.1.1を使用するようになっているので，これを編集時最新版のjQuery3.2.1を使用するようにする（すでにGitHub上ではjQueryのバージョンが更新されているので，近いうちに更新されると思われる）
それぞれ編集時点で最新のものを使用

- [Getting Started - Materialize](http://materializecss.com/getting-started.html)
- [Starter Template (zip)](http://materializecss.com/templates/starter-template.zip)

## 直面した問題

1. ページ表示時に`$(...).sideNav`が見つからないエラー
2. サイドナビを開こうとすると`e.velocity`が見つからないエラー

## webpackとMaterialize

#### webpack

モジュールバンドラ
スクリプト，スタイルシート，イメージ，フォント，その他リソースを依存関係を解決しながらまとめるもの
webpack2を使用

- [webpack](https://webpack.js.org/)
- [webpack on GitHub](https://github.com/webpack/webpack)

#### Materialize

Googleが提唱するマテリアルデザインをベースにしたCSSフレームワーク
Googleから公式に[Material Design Lite](https://getmdl.io/)というフレームワークも提供されているが，機能があまり多くないのと個人的に使いにくかったため，今回はドキュメントも多いMaterializeを選択

- [Documentation - Materialize](http://materializecss.com/)
- [materialize on GitHub](https://github.com/Dogfalo/materialize)


## 動作確認済み環境（バージョン）

- webpack 2.6.1
- Materialize 0.98.2
- jquery 3.2.1

## 環境構築

### テンプレートの入手

http://materializecss.com/templates/starter-template.zip よりMaterializeのStarter Templateを入手する

### materialize, jqueryのインストール

```bash:command
npm install --save materialize-css jquery
```

※ Materializeのnpmモジュール名は`materialize-css`なので注意．`materialize`は別物

### webpackのインストール

```bash:command
npm install --save-dev webpack
```

### webpackの設定と起点となるスクリプトの追加

`js`ディレクトリ以下に`main.js`を作成し，これを基点に`bundle.js`にバンドルする
ここでは，MaterializeがjQueryに依存しているため，webpackのProvidePluginを使用して`$`及び`jQuery`が参照された時にjQueryモジュールを返すようにする

```js:webpack.config.js
const path = require("path");
var webpack = require("webpack");

module.exports = {
  entry: path.join(__dirname, "js", "main.js"),
  output: {
    path: path.join(__dirname, "js"),
    filename: "bundle.js"
  }
  plugins: [
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery"
    })
  ]
};
```

```js:main.js
require('materialize-css');
require("./init");
```

### index.htmlの修正

```html:index.html
...
  <!--  Scripts-->
  <script src="js/bundle.js"></script>

  </body>
</html>

```

### buildスクリプトの追加

`package.json`にbuildスクリプトを追加する
必ずしも追加する必要はないが，`npm install`でプロジェクトのローカルにモジュールをインストールした場合，通常コマンドへのパスが通っていないので，例えば`webpack`を実行する時，以下のようにする必要がある

```bash:command
$(npm bin)/webpack
```

毎回これをするのは面倒なので，`package.json`に以下のように追加する
npm scriptにおいては自動的にプロジェクトのローカルのモジュールへのパスは解決してくれる

```json:package.json
  ...
  "scripts": {
    "build": "webpack"
    ...
  },
```

### Mateliarizeで追加されているはずの関数が参照エラーになる

ビルドしてページを表示してみるもエラーが発生

```bash:command
npm run build
```

```
Uncaught TypeError: $(...).sideNav is not a function
```

`$.fn.sideNav`はMaterializeのスクリプト内で宣言されているはずであるが，これが参照できていない
ここでwebpackの出力を見てみると以下のようになっていることが分かる

```:log
    Asset    Size  Chunks                    Chunk Names
bundle.js  754 kB       0  [emitted]  [big]  main
   [0] ./~/materialize-css/~/jquery/dist/jquery.js 258 kB {0} [built]
   [1] ./js/init.js 139 bytes {0} [built]
   [2] ./~/materialize-css/bin/materialize.js 149 kB {0} [built]
   [3] ./js/main.js 187 bytes {0} [built]
   [4] ./~/hammerjs/hammer.js 73.8 kB {0} [built]
   [5] ./~/jquery/dist/jquery.js 268 kB {0} [built]
   [6] (webpack)/buildin/amd-options.js 82 bytes {0} [built]
```

これをよく見てみると，`[0]`でMaterializeが内包しているjQueryが読み込まれており，`[5]`でインストールしたjQueryが読み込まれていることが分かる．実際に，例えば`materialize.js`内及び`init.js`内で`console.log($.fn.jquery);`などとして参照されているjQueryのバージョンを出力させてみると異なるバージョンとなっていることが確認できた
これはMaliterializeのスクリプトの先頭で変数`jQuery`の存在確認をして存在しない場合にjQueryをrequireしているためで，この時Materizlizeが内包しているjQueryが参照されてしまっている

#### 解決策

これを解決するためには，webpackにより参照されるjQueryモジュールを一意に定める必要がある
そこでwebpackの設定ファイルに以下を追加

```js:webpack.config.js
  ...
  resolve: {
    alias: {
      jquery: path.join(__dirname, 'node_modules', 'jquery')
    }
  },
  ...
```

これにより，`jquery`モジュールが参照された時は常にnpmでインストールしたjQueryを参照しにいくようになる

### `sideNav`を開こうとするとエラー

ここまででページを表示してもエラーは出なくなったが，ウィンドウ幅を小さくして，ページ左上のハンバーガーアイコンをクリックしてサイドナビを開こうとすると以下のエラーが発生する

```
Uncaught TypeError: e.velocity is not a function
```

今度は`velocity`という関数が参照エラーになっている
細かいことは省略するが，これはMaterializeが内部で使用している`VelocityJS`で宣言されているはずである

#### 解決策

結論から言うと，`VelocityJS`では`$`でも`jQuery`でもなく`window.jQuery`を参照しているようなので，webpackの設定ファイルに以下のように追加する

```js:webpack.config.js
  ...
    new webpack.ProvidePlugin({
      $: "jquery",
      jQuery: "jquery",
      "window.jQuery": "jquery"
    })
  ...
```

## ゴールとその他

以上でMaterializeのStarter Templateがwebpackを使用して正常に動作するようになった
あとは，せっかくwebpackを使用しているので，スタイルシートのバンドルをしたり，フォントファイル(`roboto`，`material icons`)のロードをwebpackで行うようにすれば依存ファイルをきれいに管理できる