カスタムテンプレートを作ってvue-cliでinstallする

# 始めに
Vue.jsを始めるに当たって、簡単に始めるためにvue-cliを作って環境を取ってくることがあると思います。

```:vue-cliで環境を取ってくる
$ vue init webpack <project-name>
```

実はこのテンプレートは自作することができて、そのテンプレートを使うことができます。

こちらにテンプレートを作ってみたので、これを取ってくる場合は以下のようにします。
https://github.com/TakanoriOnuma/vue-template

```:カスタムテンプレートを取ってくる
$ vue init TakanoriOnuma/vue-template <project-name>
```

このカスタムテンプレートですが、結構作り方が見つからなかったのでまとめてみました。

# テンプレートの基本構成
カスタムテンプレートを作る場合はざっくり言うと以下のような構成になります。
`vue init`すると、templateディレクトリの中身がごっそりコピーされるので、設定がいらない場合は環境をそのままtemplateディレクトリに入れるだけでテンプレートができてしまいます。
ただ他のテンプレートを使っている人はわかると思いますが、lintを入れてみたり、vuexを入れてみたり、色々オプションを聞かれて必要なものだけ取り込まれていると思います。その辺の設定はmeta.jsで書き、ここで得られたフラグを元にコードの出し分けをします。

```:ディレクトリ構成
- template/  # template以下のものが全てコピーされます（設定で取り除いたりできます）
- meta.js    # テンプレートを取り込む際に色々設定するところです
- README.md  # なくても大丈夫ですが、一応
```

# meta.jsの設定
meta.jsは以下のような設定をします。jsonでも設定は出来ますが、JSの方が細かい設定もできるのでこっちにしています（コメントもかけますし）。

```js:meta.js
module.exports = {
  prompts: {
    name: {
      type: 'string',
      message: 'Project Name'
    },
    description: {
      type: 'string',
      message: 'Description',
      default: 'A Vue.js project'
    },
    author: {
      type: 'string',
      message: 'Author'
    },
    licence: {
      type: 'string',
      message: 'licence',
      default: 'MIT'
    },
    devServerPort: {
      type: 'string',
      message: 'dev server port',
      default: '4000'
    },
    // confirmがフラグになる
    isUseStore: {
      type: 'confirm',
      message: 'is use Vuex Store?'
    }
  },
  // 条件にマッチする時だけコピーする
  filters: {
    'src/javascripts/store/**/*': 'isUseStore'
  },
  // vue initで展開させないファイルを指定する
  skipInterpolation: ['node_modules/**'],
  complete: (data) => {
    console.log('\nTo get started:');
    // 別ディレクトリに生成する場合は出力先のディレクトリに移動するメッセージを出す
    if (!data.inPlace) {
      console.log(`  cd ${data.destDirName}`);
    }
    console.log('  yarn install');
    console.log('  yarn start');
  }
};
```

promptsのところで各項目の質問を書きます。
文字列を受け取りたいときはtypeを`string`にして、true/falseのフラグは`confirm`にします。
ちなみにnameやauthorは勝手にデフォルトの値が設定されます。おそらくnameはディレクトリ名、authorはgitのauthorから取ってきていると思います。
filtersやskipInterpolationは後ほど説明します。
completeではテンプレートの読みこみが終了した時の処理を書きます。vue initでは第２引数にディレクトリ名を指定するのが通例ですが、ない場合はカレントディレクトリに読み込まれるのでその条件分岐も書かれています。

実際に実行するとこんな感じになります。
<img width="549" alt="スクリーンショット 2019-01-08 17.07.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/187768/fb03b512-2a4e-30cb-5eab-75fa6e5ca675.png">

# meta.jsで設定したパラメータを展開する
## 単純な展開
meta.jsのpromptに書かれたkey名が変数として使えるので、これを使ってテンプレートをカスタマイズしていきます。
変数の展開はmustash記法が使え、例えばREADME.mdだと以下のようになります。
`name`と`description`と`devServerPort`がプロンプトで設定した値が入る感じです。

```:README.md
# {{ name }}
{{ description }}

## 環境
+ node: 10.15.0
+ npm: 6.2.0

## インストール
`$ npm install`

## タスク一覧
### 開発サーバーの立ち上げ
`$ npm start`  
[http://localhost:{{ devServerPort }}](http://localhost:{{ devServerPort }})にアクセスしてフロントエンドの開発をする。  

### フロントエンドのコードのビルド
`$ npm run build`  
dist以下に生成物を配置する。
```

## フラグに応じた出し分け
今回は`isUseStore`と言う変数を使ってVuexを使うかのフラグを持っています。これに応じてvuexをpackage.jsonに書くか否かを設定するので、package.jsonは以下のようになります。
以下のように`{{#フラグ変数}}  コンテンツ  {{/フラグ変数}}`でフラグ変数がtrueの時だけコンテンツを出力します。

```:package.json
{
  // dependenciesだけ記載
  "dependencies": {
    "reset-css": "^4.0.1",
    "vue": "^2.5.21",
    "vue-types": "^1.3.4"{{#isUseStore}},
    "vuex": "^3.0.1"{{/isUseStore}}
  }
}
```

storeの設定はstoreディレクトリに配置しており、ファイルごと取り除きたい場合はmeta.jsの方に記述します。

```js:meta.js（一部のみ）
module.exports = {
  // isUseStoreがtrueの時だけstore以下のファイルを出力する
  filters: {
    'src/javascripts/store/**/*': 'isUseStore'
  }
};
```

## Vue.jsにもmustash記法で展開したい場合
同じ要領でVue.jsにも書いていきたいですが、Vue.jsでもmustash記法が使われており、vue initで展開すると困る場合は`\`でエスケープして設定します。

```vue
<template lang="pug">
div
  img(src='../images/home.svg')
  p Hello World!
  TestComponent
  {{#isUseStore}}
  p counter
  button(@click="onDecrementButtonClick") -
  //- \{{ $store.state.count }}と書くことで展開後は普通の{{ $store.state.count }}になる
  span(style="padding: 0 10px;") \{{ $store.state.count }}
  button(@click="onIncrementButtonClick") +
  {{/isUseStore}}
</template>
```

これで対応は出来ますが、テンプレートに書く場合は一々`\`でエスケープしないといけません。もしVueファイルは展開しないのでしたらmata.jsの`skipInterpolation`に書くといいです。（interpolationは「内挿」と言う意味らしいです）
ちなみにnode_modulesも書いているのは、ローカルで動作確認するときにnode_modulesも展開の対象になってしまったためです。（普通は必要ないです）

```js:meta.js（一部のみ）
module.exports = {
  // vueファイルやnode_modules以下のものは展開しない
  skipInterpolation: ['src/**/*.vue', 'node_modules/**']
};
```

### 余談
mustash記法は別な表記方法で認識させることもできるらしいので、そっちでやると言う方法もありそうです。

[mustache記法について簡単にまとめてみた](https://qiita.com/sengok/items/1d958348215647a5eaf0)

# ローカルで動作確認する
一々githubにあげて試していくのは面倒なのでローカルでインストールする方法を紹介します。
やり方は単純で、テンプレートの場所は相対パスで書くだけでいいです。

```:ローカルのテンプレートを使用する
$ vue init ./vue-template vue-template-test
```

# 作ってみての所感
テンプレートを作ると以降の環境構築は非常に楽になりますが、**テンプレートの開発はかなりめんどくさいです。**
一々テンプレートを取り込んでみて動くかを確認しないといけないので、結構時間がかかります。
**更に条件分岐が入るとエディタもエラーになってかなりコードが書きにくいです。**
<img width="470" alt="スクリーンショット 2019-01-08 16.25.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/187768/a26e242f-71fc-4468-3de8-a95285f81bf0.png">

vue initではブランチ名を指定できるので、それぞれ分けて使っていく運用が割と楽なんじゃないかなぁって思っています。

```
$ vue init TakanoriOnuma/vue-template#vuex vue-template-test
```

まぁこの場合vuex+vue-routerとか複数のオプションがある場合は対応は厳しいですけど。基本的にカスタムテンプレートではそんなにパターンを用意しないほうがいいんじゃないかなと思っています。

# 参考資料
既にあるテンプレートを見ると結構参考になると思います。僕もよく分からないことが多かったので参考にしました。（completeの処理とか）

+ [vue init webpackで参照するテンプレート](https://github.com/vuejs-templates/webpack)
+ [vue init webpack-simpleで参照するテンプレート](https://github.com/vuejs-templates/webpack-simple)

あとは公式ドキュメントですね。

+ [https://www.npmjs.com/package/vue-cli](https://www.npmjs.com/package/vue-cli)

# 終わりに
`vue init`と言うコマンドですが、別にVue.jsじゃなくてもReactとかpugをビルドするだけの静的サイトとか、そう言うのでもテンプレートとして作れます。一度作ると使い回すのが凄く楽になるので、似たような環境をよく作る人は試してみてはいかがでしょうか。