Webpack 5 で Vue 3 × TypeScript の環境構築とバンドル

Vue 3 × TypeScript の開発環境の構築 & バンドルには Vue CLI や Vite を用いれば良いが、諸事情でそれらを使わずに素の Webpack で構築したい場合があるかもしれない・・・。2021/9/26 現在で Webpack × Vue × TypeScript の記事はググれば出てくるものの、バージョンの違いか、スムーズに進まないことが多々あった。そこで今回、快適に構築するため手順のメモを作成したので公開。

## 各ツール・パッケージのバージョン

筆者が動作確認した際には、下記バージョンのものを使用した。

```
yarn: 1.22.10
-
ts-loader: 9.2.6
typescript: 4.4.3
vue: 3.2.19
vue-loader: 16.8.1
webpack: 5.54.0
webpack-cli: 4.8.0
webpack-dev-server: 4.3.0
```

## プロジェクト構成

```
webpack_vue_ts
├── dist
│   └── main.js
├── node_modules
│   └── ...
├── package.json
├── public
│   └── index.html
├── src
│   ├── App.vue
│   ├── main.ts
│   └── shims-vue.d.ts
├── tsconfig.json
├── webpack.config.js
└── yarn.lock
```

## プロジェクトの初期化

`yarn init` 時には `-y` オプションでマニュアルの初期設定をスキップし、 `--private` オプションで `"private": true` を設定している。

```
$ mkdir webpack_vue_ts && cd $_
$ yarn init -y --private
```

## 必要なパッケージの追加

必要な場合は適宜バージョン指定する。

```
$ yarn add -D ts-loader typescript vue@next vue-loader@next \
   webpack webpack-cli webpack-dev-server
```

## html ファイルの作成

Vue インスタンスをマウントする開発環境用の `public/index.html` を作成する。 `<div id="app"></div>` にマウントする想定。

```html:public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>
  <script src="http://localhost:8080/main.js"></script>
</body>
</html>
```

## TypeScript 設定ファイルの作成

`tsconfig.json` を作成する。 [Vue.js 公式](https://v3.ja.vuejs.org/guide/typescript-support.html) などを参照。

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "noImplicitThis": true,
    "jsx": "preserve",
    "moduleResolution": "node",
  },
  "include": ["./src/"]
}
```

`src/shims-vue.d.ts` を作成する。 Vue CLI 使用時に作成されるものを参照。

```ts:shims-vue.d.ts
declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}
```

## エントリポイントと App コンポーネントの作成

メインのコンポーネントとなる `src/App.vue` ファイルを作成し、エントリポイントとなる `src/main.ts` でマウントするための記述をする。

とりあえず `defineComponent` を用いて `src/App.vue` のサンプルコードを記述してみる。

```vue:App.vue
<template>
  <h1>Hello</h1>
  <div>{{ message }}</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

type DataType = {
  message: string
}

export default defineComponent({
  data(): DataType {
    return {
      message: "",
    }
  },
  created() {
    this.initMessage()
  },
  methods: {
    initMessage(): void {
      this.message = "World"
    }
  }
})
</script>
```

例えばここで `this.message` に number 型の値を代入しようとすると、VSCode で Vetur などの拡張機能を入れている場合、エラーメッセージを表示して教えてくれるはず。

`src/main.ts` で `App` をマウントするための記述をする。

```ts:main.ts
import { createApp } from "vue"
import App from './App.vue'

createApp(App).mount('#app')
```

## Webpack 設定ファイルの作成

`webpack.config.js` を作成する。

```js:webpack.config.js
const { VueLoaderPlugin } = require('vue-loader')
const webpack = require('webpack')

module.exports = {
  mode: 'development',
  entry: './src/main.ts',
  output: {
    path: `${__dirname}/dist`,
    filename: "main.js",
  },
  devServer: {
    static: [
      {
        directory: `${__dirname}/public`,
      },
      {
        directory: `${__dirname}/dist`,
      }
    ],
    headers: {
      "Access-Control-Allow-Origin": "*"
    },
  },
  module: {
    rules: [
      {
        test: /\.(tsx|ts)?$/,
        loader: 'ts-loader',
        options: {
          appendTsSuffixTo: [/\.vue$/],
        },
        exclude: /node_modules/,
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      }
    ]
  },
  resolve: {
    modules: ['node_modules'],
    extensions: ['.ts', '.js', '.vue'],
  },
  plugins: [
    new webpack.DefinePlugin({
      "__VUE_OPTIONS_API__": true,
      "__VUE_PROD_DEVTOOLS__": false
    }),
    new VueLoaderPlugin()
  ],
  target: ["web", "es5"]
}
```

上記ファイルに関する参考記事は下記

- Webpack
  - [Webpack 公式](https://webpack.js.org/configuration/)
  - [webpack.config.js がわからない](https://qiita.com/one-kelvin/items/b810aafb6b5ef90789a3)
- Vue × TypeScript
  - [Vue.js 公式](https://v3.ja.vuejs.org/guide/typescript-support.html)
- Feature Flags
  - [Vue 3.0 の GitHub リボジトリ README](https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags)

## script の追加

`package.json` に、開発サーバー起動スクリプトとバンドルファイル出力スクリプトを追加する。

```json:package.json
{
  ...
  "scripts": {
    "dev": "webpack-dev-server --open",
    "build": "webpack"
  },
  ...
}
```

以上で開発環境の構築が完了。

## 開発サーバーの起動

スクリプトを追加したため、プロジェクトルートで下記コマンドにより開発サーバーが起動し、デフォルトのブラウザで `localhost:8080` が開かれる。

```
$ yarn dev
```

## バンドルファイルの出力

下記コマンドにより、バンドルファイル `dist/main.js` が出力される。

```
$ yarn build
```

以上です。閲覧ありがとうございました！