Hanami + FuseBox で快適フロント開発 （Sass, TypeScript）

## About

1. [このサイト](http://nantonaku-shiawase.hatenablog.com/entry/2018/03/25/233925)ではHanamiとassets-webpack-pluginを使ってバックエンドとフロントエンドを綺麗に分離してる、かっこいい
2. でもwebpack.config.jsの書き方（ネスト）が気に入らない...
3. それに他のassets（CSS）とかはどうすればいいんだろう...?
4. webpack以外のタスクランナーでいいものってあるのかな？
5. [FuseBox](https://fuse-box.org)を見つける

というわけでその他にもやってみたかったことを付け加えて実践します。（HamlとPostgreSQL）
環境構築ばっかでアプリが作れない...

### FuseBoxとは

[公式サイト](https://fuse-box.org)より
> FuseBox is a blazing fast bundler/module loader, where you measure you build time in millseconds.
> Blazing fast, TypeScript First, Minimalist configs

とあるように、TypeScriptベースの高速なモジュールバンドラーです（適当）。
比較できるほど他のバンドラーを使っていないのですが、webpackより格段に早いです。`webpack`実行後と同じくらい待つつもりでいたら一瞬で終わり驚きました。
加えて、設定ファイルがシンプルです。Gulpに似てるかな？
`const fuse = FuseBox.init({ /* ..settings.. */ })`のようにタスク系の指定をして、`fuse.run()`で動作させます。「やるべきこと（タスク）」と「タスクのための設定」のように分かれてるので見やすく、かつ簡単に書けるという利点があります。


## Environment

- ruby 2.5.1
- bundler 1.16.1
- node 9.7.1
- yarn 1.7.0
- ブラウザ : Google Chrome

## Setup

[前の記事](https://qiita.com/azzly/items/4e32f170130d94d25e44)をもとにグローバルを汚さないようにhanamiのアプリを作ります。

```sh:shell
mkdir hanami-fusebox-test && cd hanami-fusebox-test
bundle init
# ...
# Gemfileの編集をする
# ...
bundle install --path=vendor/bundle --binstubs=vendor/bin
hanami new hanami-fusebox-test --database=postgres --application-name=default --template=haml
mv hanami-fusebox-test/* ./ && rm -rf hanami-fusebox-test
bundle update
hanami s # localhost:2300で確認
```

```ruby:Gemfile
source 'https://rubygems.org'
gem 'hanami'
```

PostgreSQLは初利用だったので[この記事](https://qiita.com/yh2020/items/8be3087004d100fe752b)を参考に初期設定をしました。手順については必要とあらば後述します。
あと忘れがちなのでこの時点で`.gitignore`に追記をします。

```text:.gitignore
# ...(中略)
/vendor
```

確認用ページとしてトップページを作ります。

```sh:shell
hanami g action default static#home
```

```ruby:apps/default/config/routes.rb
root to: 'static#home'
```

```haml:apps/default/templates/static/home.html.haml
%h1#home HOME IS HERE
```

`hanami s`でサーバを起動してこんな感じになればOK。
![トップページ変更](https://qiita-image-store.s3.amazonaws.com/0/150487/9b13f078-f223-480b-528c-3323caf69c39.png)

## Main

### Assets不使用

[こちらの記事](https://qiita.com/isuke/items/ce2652cd7d9f39367dc5)を参考にhanamiのassetsを利用しない設定にします。

```sh:shell
rm -rf apps/default/assets
```

次に以下の部分をコメントアウト（or 削除）します。

```diff:apps/default/application.rb
- require 'hanami/assets'
# ...
configure do
  # ...
-  assets do
-   # ...
-  end
end

configure :production do
-  assets do
-    # ...
-  end
end
```

### FuseBox インストール・設定

`apps/default`ディレクトリ内にFuseBoxをインストールします。
以降は追記しない限りこのディレクトリで作業するものとします。

```sh:shell
# cd apps/default
yarn init -y
yarn add fuse-box typescript -D
```

同じディレクトリに設定ファイル`fuse.js`を作成します。

```js:fuse.js
const path = require('path');
const publicPath = path.resolve(__dirname, '..', '..', 'public');

const { FuseBox } = require('fuse-box');
const fuse = FuseBox.init({
  homeDir : 'frontend',
  target  : 'browser@es6',
  output  : `${publicPath}/$name.js`
});
fuse.bundle('app').instructions('> application.ts');
fuse.run();
```

エントリーポイントの`application.ts`を`frontend/`へ作成します。

```typescript:frontend/application.ts
console.log('Hello, FuseBox');
```

`package.json`へ`scripts`を追記します。

```diff:package.json
{
  "name": "default",
  "version": "1.0.0",
  "devDependencies": {
    "fuse-box": "^3.2.2",
    "typescript": "^2.9.1"
  },
+ "scripts": {
+   "build": "node fuse.js"
+ }
}
```

こうすると`yarn build`でバンドラーが起動できます。これを書かずに`node fuse.js`でもできます。
次にメインテンプレートへJSが読み込まれるように追記します。

```diff:templates/application.html.haml
!!!
%html
  %head
    %title Default
    = favicon
  %body
    = yield
+   %script{type: 'text/javascript', src: 'app.js'}
```

最後に、`.gitignore`へ追記をします。
FuseBoxは`.fusebox`へcacheを作成するためそれも必要に応じて`.gitignore`へ追記します。
バンドルされる`public/app.js`も除外するようにします。

```diff:hanami-fusebox-test/.gitignore
+ /apps/*/node_modules
+ /apps/*/.fusebox
+ /public/*.js
```

### 確認

上述したように`yarn build`を実行するとFuseBoxが起動し、成功すれば`hanami-fusebox-test/public`へ`app.js`として吐き出されます。
`hanami s`で実際にJSが動作しているか確認しましょう（アプリケーションのルートディレクトリで起動してください）。

![Hello, FuseBox](https://qiita-image-store.s3.amazonaws.com/0/150487/47e95b1c-6252-a775-a4de-ce83a4614451.png)

Got it!

## Addition

FuseBoxの追加設定として有用なものの一部を紹介します。

### Shimming（jQuery）

FuseBoxには[Shimming](https://fuse-box.org/page/configuration#shimming)という設定があります。
バンドルされるファイルに対して読み込んでおきたい変数などを「継ぎ足す（shimming）」ものです。
今回はjQueryを導入します。

```sh:shell
yarn add jquery
```

```diff:fuse.js
const path = require('path');
const publicPath = path.resolve(__dirname, '..', '..', 'public');

const { FuseBox } = require('fuse-box');
const fuse = FuseBox.init({
  homeDir : 'frontend',
  target  : 'browser@es6',
  output  : `${publicPath}/$name.js`,
+ shim : {
+   jquery : {
+     source  : 'node_modules/jquery/dist/jquery.min.js',
+     exports : '$'
+  }
+ }
});
fuse.bundle('app').instructions('> application.ts');
fuse.run();
```

```diff:templates/static/home.html.haml
%h1#home HOME IS HERE

+ %button.btn ClickME!
```

```typescript:frontend/application.ts
function add() {
  $('#home').after('<p>Hello FuseBox with jQuery!</p>');
}

$('.btn').on('click', add);
```

```sh:shell
yarn build

# in hanami-fusebox-test/
hanami s
```

クリック前
![Before click](https://qiita-image-store.s3.amazonaws.com/0/150487/3c530cb9-732b-2749-f8b3-199640c227ea.png)

クリック後
![After click](https://qiita-image-store.s3.amazonaws.com/0/150487/1703ccf2-7a96-be7e-91ba-2a15818102a2.png)

うまくいくと多分こうなります。


### Plugin（Sass）

FuseBoxの主要な[Plugins](https://fuse-box.org/page/about-plugins)というものがあります。
チェーン（複数のプラグインをパイプでつなぐ）が利用できるため、組み合わせによっては色々な機能が実装できるかも...?
色々種類はありますが今回はSassでスタイルを書けるようにします。

```sh:shell
yarn add node-sass -D
```

```diff:fuse.js
const path = require('path');
const publicPath = path.resolve(__dirname, '..', '..', 'public');

+ const { FuseBox, CSSPlugin, SassPlugin } = require('fuse-box');
const fuse = FuseBox.init({
  homeDir : 'frontend',
  target  : 'browser@es6',
  output  : `${publicPath}/$name.js`,
+ plugins : [
+   [SassPlugin(), CSSPlugin()]
+ ],
  shim : {
    jquery : {
      source  : 'node_modules/jquery/dist/jquery.min.js',
      exports : '$'
    }
  }
});
fuse.bundle('app').instructions('> application.ts');
fuse.run();
```

```scss:frontend/application.scss
#home {
  &:hover {
    color: red;
  }
}
```

```diff:frontend/application.ts
+ import './application.scss';

function add() {
  $('#home').after('<p>Hello FuseBox with jQuery!</p>');
}

$('.btn').on('click', add);
```

ビルドをお忘れなく...

```sh:shell
yarn build

# in hanami-fusebox-test/
hanami s
```

カーソル乗せる前
![Not hover](https://qiita-image-store.s3.amazonaws.com/0/150487/949c3e77-b52d-92c1-4cc1-b59d31b16111.png)

カーソル乗せた後
![Hover](https://qiita-image-store.s3.amazonaws.com/0/150487/90b2e3a2-7144-e5b1-dbde-f9a93a7f05a7.png)


### Watching

最後に[Watching](https://fuse-box.org/page/watching)について説明します。
変更のたびに`yarn build`するのは手間なので、他のモジュールバンドラーによくある`watch`をFuseBoxでも活用させましょう、あるので。

```diff:fuse.js
- fuse.bundle('app').instructions('> application.ts');
+ fuse.bundle('app').instructions('> application.ts').watch('frontend/**');
```

`fuse.watch(dir)`とすると`dir`内のファイル変更を監視し、変更時に`fuse.run()`が行われます。
`yarn build`を一度実行しておけばあとは触らなくてよくなります、便利。

## おまけ

実際のソースが確認できるように[こちら](https://github.com/assly/hanami-fusebox-test)に上げました、よろしければご参考に。

[React.jsも導入してみました。](https://qiita.com/azzly/items/89d5878607254344d1cc)
## 参考

- [webpackよりもシンプルで高速！最新モジュールバンドラー「FuseBox」超入門](https://www.webprofessional.jp/fusebox-faster-webpack-alternative/)
- [FuseBox](https://fuse-box.org)
- [Hanami + Vue.jsでTODOアプリを作った](http://nantonaku-shiawase.hatenablog.com/entry/2018/03/25/233925)
- [Hanamiでwebpackを使う方法](https://qiita.com/isuke/items/ce2652cd7d9f39367dc5)
- [Haml Documentation: reference](http://haml.info/docs/yardoc/file.REFERENCE.html)
- [MacのRailsアプリでPostgreSQLを使う方法](https://qiita.com/yh2020/items/8be3087004d100fe752b)