webpack4を使ってTypeScriptとSassを使ったVue.tsのアプリ作成

# 前書き

自分は今年2018年に某IT企業にエンジニアとして新卒入社した1年目のペーペーエンジニアだ。
入社して８ヶ月（実際に現場に配属されてからは4ヶ月）、JavaやJavaScript[TypeScript]などサーバーもクライアントも触ってきたが、会社では既存のサービスの拡張しかして来なかったため、
学んできたことが本当に身についているのか確認(自戒)の意を込めて、自分で1から軽いVueアプリを作ろうと思った。

しかし、**思いの外詰まった。**

会社で触っているVueを使った大規模Webアプリを軽く真似てみようと試みたが、そう簡単なものではなかった。（サービスの立ち上げに携わる人たちの凄さを少しだけ感じた）
webpackを扱うのにこんなに設定ファイルをいじる必要あるのかよ、
Webpackを使って複数のtsファイルをまとめてポイっとしたら完成だろ　なんて甘く見てた。
そんなこんなで、自分みたいに最初につまづいたり戸惑ったりするかもしれない人のために、また自分のちゃんとした理解と忘備録も兼ねて書くことにする。

## 環境

- macOS Mojava v10.14.1
- VisualStudio Code v1.29.1
- node v11.3.0
- npm v6.4.1

## 前提

- nodeがインストールされていること

##### nodeのインストールについては以下記事など参照
[MacにNode.jsをインストール](https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09)
[Node.js / npmをインストールする（for Windows）](https://qiita.com/taiponrock/items/9001ae194571feb63a5e)

# 構築手順

## npmをはじめるよ〜

当然だが、まず最初にアプリ用のディレクトリを作成する。作成したディレクトリに移動のうえ、魔法の言葉`npm init`を唱える。そして必要なライブラリ群、ts, scssをjs,cssにロードするのに際最低限必要なものをインストールする。

```terminal
$ mkdir vue-app
$ npm init -y
$ npm install --save-dev webpack webpack-cli typescript ts-loader node-sass sass-loader css-loader mini-css-extract-plugin
$ npm i -S vue vue-cli

```

**上記コマンドについて幾つか注記**

- `npm init`のオプションの`-y`は、通常`npm init`だけだと以下のように質問が繰り返されるが、その答えをyとしてスキップしてくれるもの

```terminal
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (tmp) 
version: (1.0.0) 
description: 
entry point: (index.js) 
test command: 
git repository: 
keywords: 
author: 
license: (ISC) 
About to write to /Users/riku/tmp/package.json:

{
  "name": "tmp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes) 
```

- `npm install`に関して
  - `npm install --save-dev`を他のサイトや記事では`npm i -D`のように省略して書いてある場合もあるが、意味は同じ
  - 逆にいえば、`npm i -S`は`npm install --save`と同じということ

##### npmのコマンドについて詳しくは以下の記事を参照
[npm よく使うコマンドまとめ](https://qiita.com/standard-software/items/2ac49a409688733c90e7)

---

- そして最後に、当然といえば当然のことかもしれないが `npm init`する前にnpmでライブラリをインストールしようとすると、以下のようなエラーが発生する

```terminal
npm WARN saveError ENOENT: no such file or directory, open '/Users/xxxx/vue-app/package.json'
```

`npm init`して初めて`package.json`が作成されるのに、その前に`package.json`を参照する`npm install`を実行してるのだから当たり前ですね。


さて、ここまでくればディレクトリ構成は以下の通りになっているはず。

```terminal
$ tree -L 1
.
├── node_modules
├── package-lock.json
└── package.json
```

そして、`package.json`の中身は以下の通り。

```terminal:package.json
{
  "name": "vue-app",   　　 // アプリ名（デフォルトだとディレクトリ名になる）
  "version": "1.0.0",  　　 // アプリのバージョン（始めたてなので当然v1.0.0）
  "description": "", 　　   // アプリの概要や説明（なくてよい）
  "main": "index.js", 　　  // モジュールの中で最初に呼ばれるスクリプトファイル
  "scripts": {         　　 // コマンドのエイリアスを登録する（下記の例だと、`npm test`コマンドで`echo ~`というコマンドを実行できる）
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",      　　   // その名の通り、コード実装者
  "license": "ISC",
  "devDependencies": {　　  // アプリの開発に必要だけどモジュールとして公開する際には含む必要のないライブラリたち
    "css-loader": "^1.0.1",
    "mini-css-extract-plugin": "^0.5.0",
    "node-sass": "^4.10.0",   // nodeでsassファイルを扱うのに必要。ないとsaccコンパイルでエラーが発生する
    "sass-loader": "^7.1.0",
    "ts-loader": "^5.3.1",
    "typescript": "^3.2.2",
    "webpack": "^4.27.1",
    "webpack-cli": "^3.1.2"
  },
  "dependencies": {    　　 // 当アプリが依存するライブラリとそのバージョン
    "vue": "^2.5.21",
    "vue-cli": "^2.9.6"
  }
}
```

依存ライブラリのバージョンはキャレット記法になっているが、これについては割愛。詳しくは以下の記事を参照。
[package.json のチルダ(~) とキャレット(^)](https://qiita.com/sotarok/items/4ebd4cfedab186355867)

ちなみに、webpack４系になってからは、webpack-cliがないとコマンドライン上でwebpackの操作ができないみたいなので注意。

## ディレクトリとファイル作成

ライブラリのインストールはいったん以上として、実際に必要なhtml, tsを用意する。が、その前にwebpackがコンパイルする前のものと、コンパイル後のものとを分けるためにディレクトリも大きく2つに分ける。

```terminal
$ mkdir dist src
$ tree -L 1
.
├── dist
├── node_modules
├── package-lock.json
├── package.json
└── src
```

一般的な命名に倣って、コンパイル前のファイルはsrcディレクトリに、コンパイル後の配布するモジュールはdistディレクトリに格納する。

tsファイルはコンパイルの必要がある一方で、htmlファイルは特にloaderなどを利用する必要がないので、srcディレクトリ配下にあらかじめ配置しておく。例えば以下の通りに。

```terminal
$
.
├── dist
│   ├── css
│   └── index.html
│   └── js
├── node_modules
├── package-lock.json
├── package.json
└── src
    ├── scss
    └── ts

```

### htmlファイル

htmlファイルは簡単のために以下のようにし、上述の通りdistディレクトリに配置する。

```html:index.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>webpack app</title>
    <link rel="stylesheet" href="./css/bundle.css">
</head>

<body>
    <div id="app">
        <h1>{{ message }}</h1>
        <button @click='onClick()' class='btn'>Click here!</button>
    </div>
    <script src="./js/bundle.js"></script>
</body>

</html>
```

head内にてlinkタグでバンドルされたcssファイルを、body内の最下部でscriptタグでバンドルされたjsファイルを読み込む。

### tsファイル

tsファイルはコンパイル・バンドルの必要があるので`src/ts`配下に置く。ファイルの中身は簡単に以下のような感じ。

```typescript:main.ts
import Vue from './../../node_modules/vue/dist/vue'
import './../scss/main.scss'

const app = new Vue({
    el: '#app',
    data: {
        message: 'hello world'
    },
    methods: {
        onClick(): void {
            alert('hello innovation');
        }
    }
});

```

### scssファイル

scssも同様に`src/scss`配下に置く。注意点としては、このscssファイルをエントリーポイントとなる上のtsファイルでimportしておくこと。

```scss:main.scss
#app {
    .btn {
        background: 808080;
    }
}
```

## 各種設定ファイル

次に理解が少し面倒な設定ファイルに移る。まず、webpackをインストールしたら必ず作る必要のある`webpack.config.js`を作成する。

```terminal
$ touch webpack.config.js
```

```js:webpack.config.js
const path = require("path");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
    // モードの設定 development/production/none の3択
    // development:souceMap有効でjsファイル出力、  production: 最適化された状態でjsファイル出力
    mode: 'development',
    // エントリーポイントの設定
    entry: './src/ts/main.ts',
    output: {
        path: __dirname + '/dist', // path.join(__dirname, 'dist')でも可だが絶対パスの指定が必要。というかwindows環境でも使うことを考えればpath使ったほうが無難？
        filename: 'js/bundle.js',
    },
    plugins: [
        new MiniCssExtractPlugin({
            filename: 'css/bundle.css'
        })
    ],
    module: {
        rules: [
            {   // 拡張子 .ts の場合
                test: /\.ts$/,
                // TypeScript をコンパイルする
                use: 'ts-loader',
                exclude: /node_modules/
            },
            {
                test: /\.(sc|sa|c)ss/,
                use: [
                    { loader: MiniCssExtractPlugin.loader }, {
                        loader: 'css-loader',
                        options: {
                            url: false,
                            sourceMap: true,
                            minimize: true,
                        }
                    }, {
                        loader: 'sass-loader',
                        options: {
                            sourceMap: true
                        }
                    }
                ]
            }
        ]
    },
    // import 文で .ts ファイルを解決するため
    resolve: {
        extensions: ['.ts', '.js'],
    }
}
```

tsファイルを使ったアプリのコンパイルするには以下の設定ファイルが必要で、この中にコンパイルする際のオプションを指定する。

```json:tsconfig.json
{
    "compilerOptions": {
        "module": "ES6", // TSのモジュールはES Modulesとして出力
        "target": "ES6", // トランスパイル後のesのバージョン指定
        "noImplicitAny": false,
        "sourceMap": true, // ソースマップ作成の有無
        "allowSyntheticDefaultImports": true // import Vue from 'vue' の書き方を許容する
    }
}
```

## webpack実行！

ここまでくればようやくwebpackを走らせることができる。
やっぱりこうやってちょっとしたアプリとか作ろうって思った時って最初の環境構築や準備がそれなりに大変。
webpackとかいうすごい大発明があるおかげでかなり楽に開発できるようにはなっているのだろうけど、それでもconfigファイルの理解にはまあ時間がかかる。まだまだ分かっていないオプションも多いし。。

さっそくwebpackコマンドを実行したいところだけど、まだパスの設定をしていなかったのでパスを通してあげる必要がある。

```terminal
$ export PATH=$PATH:./node_modules/.bin
```

これでwebpackコマンドを実行できるので、実行してみる。

```terminal
$ webpack
Hash: f189f68a757a47e4eb8f
Version: webpack 4.27.1
Time: 1673ms
Built at: 2018/12/15 14:18:31
         Asset      Size  Chunks             Chunk Names
css/bundle.css  37 bytes    main  [emitted]  main
  js/bundle.js   339 KiB    main  [emitted]  main
Entrypoint main = css/bundle.css js/bundle.js
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
[./src/scss/main.scss] 39 bytes {main} [built]
[./src/ts/main.ts] 260 bytes {main} [built]
    + 5 hidden modules
Child mini-css-extract-plugin node_modules/css-loader/dist/cjs.js??ref--5-1!node_modules/sass-loader/lib/loader.js??ref--5-2!src/scss/main.scss:
    Entrypoint mini-css-extract-plugin = *
    [./node_modules/css-loader/dist/cjs.js?!./node_modules/sass-loader/lib/loader.js?!./src/scss/main.scss] ./node_modules/css-loader/dist/cjs.js??ref--5-1!./node_modules/sass-loader/lib/loader.js??ref--5-2!./src/scss/main.scss 416 bytes {mini-css-extract-plugin} [built]
        + 1 hidden module
```

webpackコマンド実行後は、以下の通りになっているはず。

```terminal
$
.
├── dist
│   ├── css
│   │   └── bundle.css
│   ├── index.html
│   └── js
│       └── bundle.js
├── node_modules
├── package-lock.json
├── package.json
└── src
│   ├── scss
│   │   └── main.scss
│   └── ts
│       └── main.ts
├── tsconfig.json
└── webpack.config.js
```

## 実際にブラウザ表示

今回はサーバーを立てずにただhtmlファイルをブラウザ表示するだけとする。
実際の画面がこんな感じ

![スクリーンショット 2018-12-15 14.38.10.png](https://qiita-image-store.s3.amazonaws.com/0/271259/49e03d1c-1eb0-9013-ad76-1e8b31cb7ba1.png)


ボタンをクリックすると、

<img width="1440" alt="スクリーンショット 2018-12-15 14.45.34.png" src="https://qiita-image-store.s3.amazonaws.com/0/271259/42e35824-ce74-164e-d0c9-4885e8565f55.png">


ちゃんとアラート出てますね。めでたしめでたし。

## おわりに

これでようやくwebpackを使ったアプリの初歩の初歩は終えられたかな。新しい言語やフレームワークを勉強するにあたって一番つまづきやすいのがやっぱり最初だと思う。
何回もアプリ作って書いて作って書いてを繰り返して慣れていくしかないな。。

SFCなVueアプリについて（vue-loaderなど）やサーバを立ち上げたりする話も書きたかったけど記事が肥大化するしもう疲れたのでおわり。