【webpack5】ts-loader + babel-loader を併用する【TypeScript】

## ts-loaderとbabel-loaderを組み合わせる

`babel-loader`だけで、TypeScriptはトランスパイルが可能です。
具体的には

[@babel/preset-typescript](https://babeljs.io/docs/en/babel-preset-typescript)

を使います。
もしBabelが必要な状況なのであれば、`ts-loader`は必要ありません。
逆に、Babelが不要なのであれば`ts-loader`が必要です。

しかし、

- レガシーブラウザ向けにBabelを通したもの
- モダンブラウザ向けにTSをトランスパイルしただけのもの

の両方が必要な場合は、自分は`ts-loader`と`babel-loader`を組み合わせるようにしています。

私の環境ではtsconfig.jsonはいずれの場合も次のようなイメージでした。

```plaintext:ファイル構成
project
│  .babelrc
│  package-lock.json
│  package.json
│  tsconfig.json
│  webpack.config.js
│
├─dist（ここに吐き出す）
│
├─node_modules
│
└─src
　  │  hogehoge.ts （具体的な処理を書いていく）
　  │  polyfill.ts （polyfillのimportのみ。IE向けのバンドルで統合します。）
```

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "newLine": "lf",
    "outDir": "dist"
  },
  "exclude": [
    "node_modules",
    "**/*.test.ts"
  ]
}
```

## IE11のためにTypeScriptをBabelを使ってトランスパイルする構成

```json:package.json
{
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-env": "^7.13.10",
    "@babel/preset-typescript": "^7.13.0",
    "babel-loader": "^8.2.2",
    "core-js": "^3.9.1",
    "webpack": "^5.26.3",
    "webpack-cli": "^4.5.0"
  }
}
```

```json:.babelrc
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": 11
        },
        "corejs": 3,
        "useBuiltIns": "usage"
      }
    ],
    [
      "@babel/preset-typescript"
    ]
  ],
  "plugins": [
  ]
}
```

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

module.exports = [
  {
    entry: 'hogehoge.ts', // ['polyfill.ts', 'hogehoge.ts'] などのようにしてもよさそう
    output: {
      filename: 'hogehoge.min.legacy.js',
      path: path.join(__dirname, 'dist'),
    },
    resolve: {
      extensions: [
        '.ts',
        '.js',
      ],
    },
    module: {
      rules: {
        test: /\.(ts|js)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          },
        ],
      },
    },
  },
];
```

## モダンブラウザだけを対象にTypeScriptをts-loaderを使ってトランスパイルする構成

```json:package.json
{
  "devDependencies": {
    "core-js": "^3.9.1",
    "ts-loader": "^8.0.18",
    "tslint": "^6.1.3",
    "tslint-loader": "^3.5.4",
    "typescript": "^4.2.3",
    "webpack": "^5.26.3",
    "webpack-cli": "^4.5.0"
  }
}
```

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

module.exports = [
  {
    entry: 'hogehoge.ts'
    output: {
      filename: 'hogehoge.min.js',
      path: path.join(__dirname, 'dist'),
    },
    resolve: {
      extensions: [
        '.ts',
        '.js',
      ],
    },
    module: {
      rules: {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader',
          },
          // linterも通しておくと吉
          // {
          //   loader: 'tslint-loader',
          //   options: {
          //     typeCheck: true,
          //     fix: false,
          //     emitErrors: true,
          //   },
          // },
        ],
      },
    },
  },
];
```


## ts-loaderとbabel-loaderを組み合わせる

- レガシーブラウザ向けにBabelを通したもの
- モダンブラウザ向けにTSをトランスパイルしただけのもの

の２種類を出力したい時。

```json:package.json
{
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-env": "^7.13.10",
    "@babel/preset-typescript": "^7.13.0",
    "babel-loader": "^8.2.2",
    "core-js": "^3.9.1",
    "ts-loader": "^8.0.18",
    "tslint": "^6.1.3",
    "tslint-loader": "^3.5.4",
    "typescript": "^4.2.3",
    "webpack": "^5.26.3",
    "webpack-cli": "^4.5.0"
  }
}
```

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

module.exports = [
  // べた書きだと長いのでそれぞれをモジュール化したり、
  // 重複している部分を変数化したりしてまとめてもよさそう
  {
    // モダン向け
    target: ['web', 'es5'],
    output: {
      entry: './src/hogehoge.ts',
      path: path.join(__dirname, 'dist'),
    },
    filename: 'hogehoge.min.js',
    resolve: {
      extensions: [
        '.ts',
        '.js',
      ],
    },
    module: {
      rules: [
        {
          test: /\.ts$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'ts-loader',
            },
            {
              loader: 'tslint-loader',
              options: {
                typeCheck: true,
                fix: false,
                emitErrors: true,
              },
            },
          ],
        },
      ],
    },
  },
  {
    // IE向け
    entry: [
      './src/_polyfill.ts',
      './src/hogehoge.ts',
    ],
    target: ['web', 'es5'],
    output: {
      filename: 'hogehoge.min.legacy.js',
      path: path.join(__dirname, 'dist'),
    },
    resolve: {
      extensions: [
        '.ts',
        '.js',
      ],
    },
    module: {
      rules: [
        {
          test: /\.(ts|js)$/,
          exclude: /node_modules/,
          use: [
            {
              loader: 'babel-loader',
            },
          ],
        },
      ],
    },
  },
];
```

IE向けとモダンブラウザ向けをそれぞれ読み込む。

```html:JSを読み込ませたいhtmlファイル
<script src="./hogehoge.min.js" type="module"></script>
<script src="./hogehoge.min.legacy.js" nomodule></script>
```

### Module not found: Error: Can't resolve

Babelを使っているときにこういうエラーがでることがあります。

これが出てしまったときは、

- `.babelrc`ファイル内でcore-jsを`3`と明示しているか
- babel-loaderの適応条件（`test`）が、`ts`と`js`を対象としているか（`/\.(ts|js)$/`）
- `resolve.extensions`で`ts`と`js`を指定しているか

### Module parse failed: Unexpected token

ts-loader側ではなくbabel-loader側でこれが出てきたら、pluginsの出番です[1]。

[1]: どちら出ているエラーかを確かめるには、ts-loader、babel-loaderどちらかをwebpackから削除してビルドしてみましょう。

さまざまなプラグインが存在しているので、必要なものを`npm i -D`して`.babelrc`に追記しましょう。

```json:.babelrcの例
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "ie": 11
        },
        "corejs": 3,
        "useBuiltIns": "usage"
      }
    ],
    [
      "@babel/preset-typescript"
    ]
  ],
  "plugins": [
    "@babel/plugin-proposal-class-properties"
  ]
}
```

### IE11での「***は定義されていません。」「このオブジェクトではサポートされていない操作です。」など

IEでこれが出てくるときは、core-jsでは賄えないpolyfillの出番です。

- [mdn-polyfills](https://www.npmjs.com/package/mdn-polyfills)
- [whatwg-fetch](https://www.npmjs.com/package/whatwg-fetch)
- [picturefill](https://www.npmjs.com/package/picturefill)
- [smoothscroll-polyfill](https://www.npmjs.com/package/smoothscroll-polyfill)

この辺とかよく使います。
IE11用のバンドルでしか読み込まないpolyfill.tsでどんどんimportしちゃいましょう（IEで動くように書くよりpolyfill突っ込め派。）。

## まとめ

webpackの仕様の深さ、loaderの概念、TypeScript、Babel、polyfill、などなど引っかかるポイントがめちゃめちゃ多くて大変なので、Babelがいらない平和な世界になってほしいと切に願います( ˘ω˘ )