SPAじゃない開発でVue+Webpackの構成

様々な事情でSPAじゃない開発やらないといけないorそうじゃないほうが都合が良いこともあるよね。
個人的にハマったので備忘録として投稿。

# やりたいこと
index.html
index2.html
・・・
があってそれぞれに
app.js→コンポーネントApp
app2.js→コンポーネントApp,App2
・・・
を紐づけたい。
index.htmlに集約させたくない。

# 構成
```
|- dist
|- |- js
|- |- |- app.js  //ビルド後のJS
|- |- |- app2.js //ビルド後のJS
|- src
|- |- js
|- |- |- app.js
|- |- |- app2.js
|- |- |- components
|- |- |- |- App.vue
|- |- |- |- App2.vue
|- index.html
|- index2.html
|- package.json
|- webpack.config.js
```

# Vue

```vue:App.vue
<template>
  <div>
    <span class="text">app</span>
  </div>
</template>
<script>

export default {
  name: 'app'
}
</script>
<style lang="scss" scoped>
.text {
    color: red;
  }
</style>
```

```vue:App2.vue
<template>
  <div>
    <span class="text">app2</span>
  </div>
</template>
<script>

export default {
  name: 'app'
}
</script>
<style lang="scss" scoped>
.text {
    color: blue;
  }
</style>
```

```js:app.js
import Vue from 'vue';
import App from './components/App.vue';

new Vue({
    el: "#app",
    render: h => h(App)
  })
```

```js:app2.js
import Vue from 'vue';
import App from './components/App.vue';
import App2 from './components/App2.vue';

new Vue({
    el: "#app",
    render: h => h(App)
  })
new Vue({
    el: "#app2",
    render: h => h(App2)
  })
```

# html
```html:index.html
<!DOCTYPE html>
<html lang="ja" dir="ltr">
<head>
  <meta charset="utf-8">
  <title>sample</title>
</head>
<body>
  <div id="app"></div>
  <script src="./dist/js/app.js"></script>
</body>
</html>
```

```html:index2.html
<!DOCTYPE html>
<html lang="ja" dir="ltr">
<head>
  <meta charset="utf-8">
  <title>sample</title>
</head>
<body>
  <div id="app"></div>
  <div id="app2"></div>
  <script src="./dist/js/app2.js"></script>
</body>
</html>
```

# パッケージとか
```json:package.json
{
  "name": "gin_test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "npx webpack --mode development",
    "watch": "npx webpack -w --mode development"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@vue/cli-init": "^4.5.11"
  },
  "devDependencies": {
    "@babel/core": "^7.12.10",
    "@babel/preset-env": "^7.12.11",
    "axios": "^0.21.1",
    "babel-loader": "^8.2.2",
    "css-loader": "^5.0.1",
    "html-webpack-plugin": "^4.5.1",
    "node-sass": "^5.0.0",
    "optimize-css-assets-webpack-plugin": "^5.0.4",
    "sass-loader": "^10.1.1",
    "style-loader": "^2.0.0",
    "terser-webpack-plugin": "^5.1.1",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.6",
    "vue-style-loader": "^4.1.2",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^5.19.0",
    "webpack-cli": "^4.4.0",
    "webpack-dev-server": "^3.11.2"
  }
}
```

```js:webpack.config.js
// 開発or本番モードの選択(development、production、noneのいずれか設定必須)
// development: 開発時のファイル出力のモード(最適化より時間短縮,エラー表示などを優先)
// production: 本番時のファイル出力のモード(最適化されて出力される)
const MODE = "development";
// ソースマップの利用有無(productionのときはソースマップを利用しない)
const enabledSourceMap = MODE === "development";

// ファイル出力時の絶対パス指定に使用
const path = require('path'),
    glob = require('glob'),
    _ = require('lodash');

// プラグイン
// js最適化
const TerserPlugin = require('terser-webpack-plugin');

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

const webpack = require('webpack');

const jsBasePath = path.resolve(__dirname, 'src/js'),
    jsDistPath = path.resolve(__dirname, 'dist/js')

String.prototype.filename = function () {
    return this.match(".+/(.+?)([\?#;].*)?$")[1];
}

var targets = _.filter(glob.sync(`${jsBasePath}/**/*.js`), (item) => {
    return !item.filename().match(/^_/) && !item.match(/node_modules/) && !item.match(/lib/)
});

// entryに入れるhash
var entries = {};

// pathも含めたfilenameからpathとfilenameでhashを作る
targets.forEach(value => {
    var path = jsBasePath.replace(/\\/g, '\/')
    var re = new RegExp(`${path}/`);
    var key = value.replace(re, '');
    key = key.replace(".js", '');

    // 確認用に取得したファイル名を出す
    //console.log('--------------------------')
    //console.log(path)
    //console.log(key)
    //console.log(value.filename())
    entries[key] = value;
});


module.exports = {
  // エントリーポイント(メインのjsファイル)
  entry: entries,
  // ファイルの出力設定
  output: {
    path: jsDistPath,
    filename: '[name].js'
  },
  mode: MODE,
  // ソースマップ有効
  devtool: 'source-map',
  // ローダーの設定
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: ["style-loader", "css-loader", "sass-loader"]
      },
      {
        test: /\.vue$/,
        loader: "vue-loader"
      },
      {
        // ローダーの対象 // 拡張子 .js の場合
        test: /\.js$/,
        // ローダーの処理対象から外すディレクトリ
        exclude: /node_modules/,
        // Babel を利用する
        loader: "babel-loader",
        // Babel のオプションを指定する
        options: {
          presets: [
            // プリセットを指定することで、ES2019 を ES5 に変換
            "@babel/preset-env"
          ]
        }
      }
    ]
  },
  // import 文で .ts ファイルを解決するため
  resolve: {
    // Webpackで利用するときの設定
    alias: {
      vue$: "vue/dist/vue.esm.js"
    },
    extensions: ["*", ".js", ".vue", ".json"]
  },
  plugins: [
    // Vueを読み込めるようにするため
    new VueLoaderPlugin()
  ],
  // mode:puroductionでビルドした場合のファイル圧縮
  optimization: {
    minimizer: MODE
      ? []
      : [
        // jsファイルの最適化
        new TerserPlugin({
          // すべてのコメント削除
          extractComments: 'all',
          // console.logの出力除去
          terserOptions: {
            compress: { drop_console: true }
          },
        }),
      ]
  },
  // js, css, html更新時自動的にブラウザをリロード
  devServer: {
    // サーバーの起点ディレクトリ
    // contentBase: "dist",
    // バンドルされるファイルの監視 // パスがサーバー起点と異なる場合に設定
    publicPath: jsDistPath,
    //コンテンツの変更監視をする
    watchContentBase: true,
    // 実行時(サーバー起動時)ブラウザ自動起動
    open: true,
    // 自動で指定したページを開く
    openPage: "index.html",
    // 同一network内からのアクセス可能に
    host: "0.0.0.0"
  }
};
```