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

Vue CLI も Rails+Webpacker も使わずに、Vue.js（単一ファイルコンポーネント）を試す方法です。

スクラッチから作るのが好きな人、WebpackとBabelが吐いたコードを眺めたい人はどうぞ。

ソースコード: https://github.com/kazubon/webpack-babel-vuejs

## 準備

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

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

webpack-dev-server は入れずに手動でコンパイルします。

（2020-09-28更新: dependencies と devDependencies を分けるのやめ。）

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

```
- 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アプリケーションを書きます。特に説明は要らないかと思います。

```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 Vue from 'vue';
import Hello from './hello.vue';

document.addEventListener('DOMContentLoaded', () => {
  new Vue(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でコンパイル

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

```webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
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'),
  },
  mode: env,
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  },
  plugins: [
    new VueLoaderPlugin()
  ]
}
```

2020-12-11 追記: 単一ファイルコンポーネントではなく、HTMLに埋め込まれたテンプレートを使うには、コンパイラ入りのVue.jsが必要です。コンパイラ入りを使うには、resolve.aliasを追加して、vue.esm.js を指定します（参照: [さまざまなビルドについて](https://jp.vuejs.org/v2/guide/installation.html#%E3%81%95%E3%81%BE%E3%81%96%E3%81%BE%E3%81%AA%E3%83%93%E3%83%AB%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)）。

```webpack.config.js
module.exports = {
// 略
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }
}
```

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

```package.json
{
  "scripts": {
    "build": "webpack --config=webpack.config.js"
  },
  "dependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "babel-loader": "^8.1.0",
    "core-js": "^3.6.5",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.3",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12"
  }
}
```

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

```zsh
% yarn build
```

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

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

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

## devtool オプション

さて、出来上がった dist/app.js を覗いてみますと、次のような感じで eval が埋め込まれていて、どう変換されたのかよく分からない。

```dist/app.js
eval("__webpack_require__.r(__webpack_exports__);\n//\n//\n//\n//\n//\n//\n//\n/* harmony ...
```

webpack.config.js で devtool オプションを指定すると、出力の形式を変えられるらしい。どういう値を指定できるかは、[Devtool | webpack](https://webpack.js.org/configuration/devtool/) に一覧があります。とりあえず source-map を指定します。

```webpack.config.js
module.exports = {
  // 略
  devtool: 'source-map',
  // 略
}
```

コンパイルすると dist/app.js が読めるようになります。

```dist/app.js
/* harmony default export */ __webpack_exports__["default"] = ({
  data() {
    return {
      message: ''
    };
  },
```

## @babel/preset-envの設定

dist/app.js をよく見ると、アロー関数などES2015のシンタックスがそのまま埋め込まれています。Babelを使ってIE 11対応の形に変換したい。

```dist/app.js
document.addEventListener('DOMContentLoaded', () => {
  new vue__WEBPACK_IMPORTED_MODULE_0__["default"](_hello_vue__WEBPACK_IMPORTED_MODULE_1__["default"]).$mount('#app');
});
```

webpack.config.js で .js の loader の設定を次のように変え、@babel/preset-env を指定します。

```webpack.config.js
module.exports = {
  // 略
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env']
          }
        }
      }
    ]
  },
  // 略
}
```

コンパイルしたものを見ると、アロー関数がfunctionに変換されていました。

```dist/app.js
document.addEventListener('DOMContentLoaded', function () {
  new vue__WEBPACK_IMPORTED_MODULE_0__["default"](_hello_vue__WEBPACK_IMPORTED_MODULE_1__["default"]).$mount('#app');
});
```

Babel の設定ファイル babel.config.js を使うこともできます。これをアプリケーションのルートに置いておくと設定が反映されます。

api.cacheの設定をしないとエラーが出るので、`api.cache(true)` としてあります。この設定の意味は分かりません。

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

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

webpack.config.js のほうは元に戻します。

```webpack.config.js
module.exports = {
  // 略
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.js$/,
        loader: 'babel-loader'
      }
    ]
  },
  // 略
}
```

[preset_envのマニュアル](https://babeljs.io/docs/en/babel-preset-env) に従い、IE 11がサポートしていない新しいメソッドを使うときは、2つのモジュール core-js/stable と regenerator-runtime/runtime をインポートしておきます。

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

import Vue from 'vue';
import Hello from './hello.vue';

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

2020-03-19追記: Vue.jsはデフォルトでIE 11に対応しているので、この程度のサンプルなら core-js/stable と regenerator-runtime/runtime は要りません。IEが対応していないJavaScriptのメソッドや組み込みクラスを使うときは core-js、asyncとawaitを使うときは regenerator-runtime が必要です。

以上です。

Vue 3版はこちら: [スクラッチからWebpack+Babel+Vue 3を使う](https://qiita.com/kazutosato/items/25680130c07904577563)

参考にしたページ:

- [How to configure Webpack and Vue from the ground up - DEV Community 👩‍💻👨‍💻](https://dev.to/macmacky/how-to-configure-webpack-and-vue-from-the-ground-up-4c19)
- [Getting Started | Vue Loader](https://vue-loader.vuejs.org/guide/)
- [Getting Started | webpack](https://webpack.js.org/guides/getting-started/)
- [Devtool | webpack](https://webpack.js.org/configuration/devtool/)
- [babel-loader | webpack](https://webpack.js.org/loaders/babel-loader/)
- [@babel/preset-env · Babel](https://babeljs.io/docs/en/babel-preset-env)