webpackをtsで構成してみる

## はじめに

使用バージョン 

```
node: v10.15.3
npm: 6.4.1
```

```json:package.json
"typescript": "3.7.2"
"webpack": "4.41.2"
```

## 前提条件

- node.jsがインストール済み
- webpackを使用してTypescriptのバンドルができる状態

## なぜwebpackをTypescriptで構成するか

typescriptを使用している時にどうせなら`webpack`もtsにしてみたいなぁと、まあなんとなくです

## 構成する前に

ちなみにフォルダ構成はこうなっています

```
.
├── build
│   └── src
│       └── (bundleされたものが入る)
├── node_modules
│   └── ...(多いので省略)
├── src
│   └── app.ts
├── index.html
├── package.json
├── package-lock.json
├── tsconfig.json
└── webpack.config.js
```

`app.ts`と`webpack.config.js`のソースコード

```ts:app.ts
class Baz {
  name: string;
  constructor() {
    this.name = Baz.name;
  }
}

const baz = new Baz();
console.log(baz.name);
```

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

module.exports = {
  mode: 'development',
  // エントリーポイントの設定
  entry: './src/app.ts',
  watchOptions: {
    // ディレクトリを監視対象から除外する
    ignored: /node_modules/
  },
  // 出力先の設定
  output: {
    // 出力するファイル名
    filename: 'app.js',
    // 出力先のパス(絶対パス)
    path: path.join(__dirname, './build/src'),
  },
  module: {
    rules: [
      {
        // 利用するローダー
        loader: 'ts-loader',
        // ローダーの処理対象ファイル
        test: /\.ts$/,
      }
    ]
  }
};
```

webpackを実行すると`build/src/`配下に`app.js`がバンドルされるようになっています。
そして、バンドルされたjsファイルをhtml側で読み込めば、コンソールに`Baz`が表示されるようになっています。

とまあこんな感じになっています。

## 本題

ここからwebpackの設定ファイルをtsで構成していこうと思います。

## 型定義ファイル
まず型定義ファイルが必要になってくるので`@types/webpack`をnpmインストールします。

```shell
npm install --save-dev @types/webpack
```

## webpackの設定
終わったらwebpackの設定ファイルを変更していきます。

まず、ファイル名を`webpack.config.ts`に変更します。

ソースコードの大まかな変更点としては3つあり、

1. ライブラリを`require`ではなく`import`で読み込む
(importとrequireの違いは[こちらの記事等](https://qiita.com/zuzuwen/items/2577680a5432f5b15247)で確認してください)
2. 型定義を行う
3. exportする

です。

全てを行った状態がこちらになります。

```ts:webpack.config.ts
import * as path from 'path';
import * as webpack from 'webpack';

const config: webpack.Configuration = {
  mode: 'development',
  // エントリーポイントの設定
  entry: './src/app.ts',
  watchOptions: {
    // ディレクトリを監視対象から除外する
    ignored: /node_modules/
  },
  // 出力先の設定
  output: {
    // 出力するファイル名
    filename: 'app.js',
    // 出力先のパス(絶対パス)
    path: path.join(__dirname, './build/src'),
  },
  module: {
    rules: [
      {
        // 利用するローダー
        loader: 'ts-loader',
        // ローダーの処理対象ファイル
        test: /\.ts$/,
      }
    ]
  }
};

export default config;
```

これで設定完了！とはいかず、このまま`webpack`を実行してもエラーになります。

```
(function (exports, require, module, __filename, __dirname) { import * as path from 'path';
                                                                     ^
SyntaxError: Unexpected token *
```

原因としては、`tsconfig.json`の`"module"`に`CommonJS`以外のもの指定されていた為でした。
これは、ts-nodeがcommonjs以外のモジュール構文をサポートしていないために発生するそうです。[(webpack公式より)](https://webpack.js.org/configuration/configuration-languages/#typescript)

なので、`tsconfig.json`の`"module"`を`CommonJS`にすれば、解決し`webpack`を実行することができるようになります。

## moduleにCommonJS以外を指定したい場合

タイトル通り、`CommonJS`ではなく`ES2015`を指定したいとなった場合、別のアプローチをする必要があります。
別のアプローチの詳細としては、webpack用のtsconfigファイルを作るというアプローチになります。

最後にこちらだけ書いてこの記事はおしまいにしたいと思います。

#### 必要なものをインストール

```shell
npm install --save-dev cross-env tsconfig-paths
```

次に、webpack用のtsconfigファイルを作成します

```shell
touch tsconfig.webpack.json (ファイル名は任意)
```

中のソースについては公式のものをそのままコピペしてます

```json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5",
    "esModuleInterop": true
  }
}
```

そして最後に実行のためのnpmスクリプトを`package.json`に追加します。

```json
 "build": "cross-env TS_NODE_PROJECT=\"tsconfig.webpack.json\" webpack"
```

`TS_NODE_PROJECT`というのはtsconfig-pathsによって提供される環境変数で、こちらを利用することでtsconfig.jsonファイルの問題を解決することができます。

webpackを実行したいときは`npm run build`を行えばビルドができます。

## 最後に
最後の方ちょっと雑かもしれないですがこれで終わります。

## 参考資料

https://webpack.js.org/configuration/configuration-languages/