スクラッチからWebpack+Babel+Vue 3を使う

[スクラッチからWebpack+Babel+Vue.jsを使う](https://qiita.com/kazutosato/items/ab785414be70e536c267) をVue 3に対応させてみました。

Vue CLI も Rails+Webpacker も使わずに、Vue.js（Single File Component）のバージョン3を試す方法です。スクラッチから作るのが好きな人、WebpackとBabelが吐いたコードを眺めたい人はどうぞ。

## 準備

てきとうなディレクトリを作り、yarnで必要なモジュールをインストールします。

```zsh
% mkdir scratch && cd scratch
% yarn add @babel/core @babel/preset-env babel-loader core-js vue@next vue-loader@next @vue/compiler-sfc vue-template-compiler webpack webpack-cli
```

vue と vue-loader をVue 3のものにするために `vue@next`、`vue-loader@next` とします。また、vue-loader を動かすのに `@vue/compiler-sfc` が必要です。

最終的なディレクトリとファイルの配置は次のようになります。

```
- dist
  - app.js
  - app.js.map
- node_modules
- src
  - app.js
  - hello.vue
- babel.config.js
- index.html
- package.json
- wbpack.config.js
- yarn.lock
```

## Vueアプリケーション

HTMLと簡単なVueアプリケーションを書きます。Vue 2との違いは、アプリケーションを作成してマウントする部分 `createApp(Hello).mount('#app');` だけです。

```index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>てすと</title>
    <script src="dist/app.js"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>
```

```src/app.js
import "core-js/stable";
import "regenerator-runtime/runtime";

import { createApp } from 'vue';
import Hello from './hello.vue';

document.addEventListener('DOMContentLoaded', () => {
  createApp(Hello).mount('#app');
});
```

```src/hello.vue
<template>
  <div>
    <h1>{{message}}</h1>
    <p><input v-model="message"></p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: ''
    };
  },
  created() {
    this.message = 'hello';
  }
}
</script>
```

## webpack.config.js

webpack.config.js を書きます。src/app.js が dist/app.js に出力されるようにします。

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

const env = process.env.NODE_ENV || 'development';

module.exports = {
  entry: './src/app.js',
  output: {
    filename: 'app.js',
    path: path.resolve(__dirname, 'dist'),
  },
  devtool: 'source-map',
  mode: env,
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin(),
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    })
  ]
}
```

1行目は、Vue 2では、

```javascript
const VueLoaderPlugin = require('vue-loader/lib/plugin');
```

としていたところをVue 3ではちょっと変えます。

```javascript
const { VueLoaderPlugin } = require('vue-loader');
```

plugins: のところで、2つのオプションを指定していますが、これがないと実行時にWarningが出ます（[Bundler Build Feature Flags](https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags) を参照）。

```javascript
    new webpack.DefinePlugin({
      __VUE_OPTIONS_API__: true,
      __VUE_PROD_DEVTOOLS__: false
    })
```

## babel.config.js

IE 11対応にするために、babel.config.js に [preset-env](https://babeljs.io/docs/en/babel-preset-env) の設定を書きます。ここは Vue 2のものから変えていません。

```babel.config.js
module.exports = api => {
  api.cache(true);

  return {
    presets: [
      [
        "@babel/preset-env",
        {
          targets: { ie: "11" },
          useBuiltIns: "entry",
          corejs: 3
        }
      ]
    ]
  }
}
```

## コンパイル

package.json に scripts.build を追加すると、yarnコマンドでwebpackを呼び出せます。

```package.json
{
  "scripts": {
    "build": "webpack --config=webpack.config.js"
  },
  "dependencies": {
    "@babel/core": "^7.12.3",
    "@babel/preset-env": "^7.12.1",
    "@vue/compiler-sfc": "^3.0.2",
    "babel-loader": "^8.1.0",
    "core-js": "^3.6.5",
    "vue": "^3.0.2",
    "vue-loader": "^16.0.0-beta.8",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^5.2.0",
    "webpack-cli": "^4.1.0"
  }
}
```

yarn build とすると、webpackが dist/app.js を生成します。

```zsh
% yarn build
```

環境変数 NODE_ENV を production にすると、本番環境用に圧縮された dist/app.js ができます。

```zsh
% NODE_ENV=production yarn build
```

index.html を ブラウザーで開いてVueアプリケーションが動けば成功です。

2020-12-15 追記: app.js に以下の2行を入れても、Vue 3はIE 11で動きません。「catch ステートメントでは適用されますが、throw ステートメントでは適用されません。」のエラーが出ます（`throw TypeError` が引っかかる）。core-jsで発生するエラーのようです（ https://github.com/zloirock/core-js/issues/514 ）。core-jsを最新版に上げてもダメでした。

```src/app.js
import "core-js/stable";
import "regenerator-runtime/runtime";
```