HOME BLOG JavaScript 【フロントエンジニア向け】webpack4を使ったVue.jsのビルド環境構築方法

【フロントエンジニア向け】webpack4を使ったVue.jsのビルド環境構築方法

このMT HPのWordPress(ワードプレス)のテンプレートの制作とは別の仕事でVue.jsを導入することがありました。
その際にVue.jsのビルド環境をwebpack4を使って構築したので、備忘録がてらその内容を書きます。

Vue.jsのビルド環境の構築方法について

Vue.jsのビルド環境の構築においては大きくやり方が2つあります。

  • webpackを直接設定して実行する 今回紹介する方法
  • Vue CLIを利用する

Vue CLIは楽なのですが、今回は最低限の設定でビルドをおこなえるようにしたかったので、webpackを直接使う方法にしました。

Vue.jsのビルド環境の構築手順

npmのプロジェクト作成

まずはnpmのプロジェクトを作成します。

Node.jsのインストールがされているパソコンの前提で本記事は進めます。(Node.jsのインストール方法はまた別記事で書きます。)

適当なフォルダを作成し、Macならターミナル、Windowsならコマンドプロンプトでそのフォルダに移動し、以下を実行してください。(今回はsample-webpackというフォルダを作成しました。)

npm init

すると、色々と聞かれますが全てエンターでとりあえずはOKです。

実行が終わると、package.jsonが作成されます。

sample-webpack
└── package.json
{
  "name": "sample-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

npmの必要なパッケージをインストール

以下のコマンドを実行します。

npm i -D @babel/core @babel/preset-env babel-loader axios vue vue-loader vue-template-compiler webpack webpack-cli webpack-stream

※npm i -Dはnpm install –save-devの省略形です。

実行するとpackage.jsonは以下のようになります。

{
  "name": "sample-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "axios": "^0.20.0",
    "babel-loader": "^8.1.0",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.3",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-stream": "^6.1.0"
  }
}

ざっくりではありますが、以下にインストールした各パッケージについての説明を書きます。

axios

Ajaxを利用するためのパッケージです。今回のサンプルでは使っていませんが、大抵の場合JavaScriptからシステムのAPIを呼ぶことになると思います。その際に必要のため、とりあえずインストールしています。

@babel/core, @babel/preset-env, babel-loader

babelはJavaScriptのES6などで書かれている部分をES5の記法に変換するライブラリです。

vue, vue-loader, vue-template-compiler

Vueを利用するのに必要です。

webpack, webpack-cli, webpack-stream

webpackは複数にまたがるjsやvueのファイルを、それぞれの依存性を解決し、1つのjsファイルにまとめるライブラリです。
ざっくりいうと、今回のビルドではwebpackがvueを読み取って、babelを呼ぶことでES5の記法に変換して、最後に1つのファイルにして出力してくれます。

webpack.config.jsの作成

ビルドの設定をするwebpack.config.jsを作成します。

sample-webpack
├── node_modules/
├── package-lock.json
├── package.json
└── webpack.config.js
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin')

module.exports = {
    // 一旦開発モードで
    mode: 'development',
    // source mapを出力する
    devtool: 'source-map',
    entry: path.join(__dirname, 'src/js/app.js'),
    output: {
        path: path.join(__dirname, 'dist/js'),
        filename: 'app.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                // ES6を変換する用
                                '@babel/preset-env',
                            ]
                        }
                    }
                ],
                exclude: /node_modules/,
            },
            {
                test: /\.vue$/,
                use: [
                    {
                        loader: 'vue-loader',
                    }
                ],
            },
        ]
    },
    resolve: {
        extensions: ['.js', '.vue'],
        alias: {
            // npm install したvueはtemplete機能のないランタイム限定ビルドなので、こっちを使うようエイリアスをはる
            vue: 'vue/dist/vue.esm.js'
        },
    },
    plugins: [
        // Vueを読み込めるようにするため
        new VueLoaderPlugin()
    ],
};

動作確認

Vueファイルの作成

ビルド環境はできたので、実際にビルドするためのVueファイルを作りましょう。
app.js、Sample.vueというファイルをsrc/js内に作成します。
また、表示するためのindex.htmlも作成します。

内容はテキストボックスを2つ配置し、それぞれに入力された数値の合計値を表示する、という簡単なページです。

sample-webpack
├── index.html
├── node_modules
├── package-lock.json
├── package.json
├── src
│   └── js
│       ├── Sample.vue
│       └── app.js
└── webpack.config.js
import Vue from 'vue'

Vue.component('sample', require('./Sample.vue').default)

new Vue({
    el: '#app',
})
<template>
    <div>
        <p>
            <input type="text" v-model="input1">
            <input type="text" v-model="input2">
        </p>
        <p>{{ input1 }} + {{ input2 }} は {{ result }} です。</p>
    </div>
</template>

<script>
export default {
    props: [],
    data: function() {
        return {
            input1: '',
            input2: '',
        };
    },
    computed: {
        result: function() {
            let result = parseInt(this.input1) + parseInt(this.input2)
            return isNaN(result) ? '' : result
        }
    },
    mounted() {
    },
    methods: {
    },
};
</script>
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Sample Vue</title>
        <script src="./dist/js/app.js" defer></script>
    </head>
    <body>
        <main>
            <div id="app">
                <sample></sample>
            </div>
        </main>
    </body>
</html>

ビルドの実行

npx webpackでビルドしてみます。

sample-webpack $ npx webpack
Hash: 7f66b8337c9a517db7d7
Version: webpack 4.44.2
Time: 927ms
Built at: 2020/09/26 19:01:07
     Asset     Size  Chunks                   Chunk Names
    app.js  346 KiB    main  [emitted]        main
app.js.map  424 KiB    main  [emitted] [dev]  main
Entrypoint main = app.js app.js.map
[./node_modules/babel-loader/lib/index.js?!./node_modules/vue-loader/lib/index.js?!./src/js/Sample.vue?vue&type=script&lang=js&] ./node_modules/babel-loader/lib??ref--0-0!./node_modules/vue-loader/lib??vue-loader-options!./src/js/Sample.vue?vue&type=script&lang=js& 361 bytes {main} [built]
[./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./src/js/Sample.vue?vue&type=template&id=5239adc7&] ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./src/js/Sample.vue?vue&type=template&id=5239adc7& 1.4 KiB {main} [built]
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 472 bytes {main} [built]
[./src/js/Sample.vue] 1.06 KiB {main} [built]
[./src/js/Sample.vue?vue&type=script&lang=js&] 370 bytes {main} [built]
[./src/js/Sample.vue?vue&type=template&id=5239adc7&] 204 bytes {main} [built]
[./src/js/app.js] 110 bytes {main} [built]
    + 5 hidden modules
sample-webpack $ 

無事にビルドは成功し、dist/jsにapp.jsとapp.js.mapが出力されます。

sample-webpack
├── dist
│   └── js
│       ├── app.js
│       └── app.js.map
├── index.html
├── node_modules/
├── package-lock.json
├── package.json
├── src
│   └── js
│       ├── Sample.vue
│       └── app.js
└── webpack.config.js

実際に画面を開いてみます。

無事にテキストボックス2個と結果を表示する部分の画面が表示されました。

テキストボックス2つに適当な数字を入力してみましょう。

入力した2つの数字を足し算した結果が表示されます。

追加設定

ビルドモードの使い分け

ここまででVue.jsを使えるようにはなったのですが、webpackにはビルドの際にビルドのモードを開発モードと本番モードを選ぶことができます。

一般的には開発時には開発モード、本番に配置するファイルは本番モードでビルドしますので、これらモードを使い分けられるように設定を追加します。

設定の追加

cross-envというライブラリを使いたいので、以下のコマンドを実行し、追加でインストールします。

npm i -D cross-env

package.jsonのdevDependenciesにcross-envが追加されます。
また、package.jsonにscriptを追加します。(6〜10行目部分)

{
  "name": "sample-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "cross-env NODE_ENV=development webpack",
    "watch": "cross-env NODE_ENV=development webpack -w",
    "prod": "cross-env NODE_ENV=production webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "axios": "^0.20.0",
    "babel-loader": "^8.1.0",
    "cross-env": "^7.0.2",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.3",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-stream": "^6.1.0"
  }
}

あとはwebpack.config.jsを一部書き換えることで、ビルドモードの使い分けとwatchモード(ファイルの変更を検知すると自動で再ビルドをおこなってくれるモード)の利用が可能となります。
以下でハイライトしている部分を書き換えてください。

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

const MODE = process.env.NODE_ENV;

module.exports = {
    // NODE_ENVでmodeは指定する
    mode: MODE,
    // developmentの場合のみsource-mapを出力
    devtool: (MODE == 'development') ? 'source-map' : '',
    entry: path.join(__dirname, 'src/js/app.js'),
    output: {
        path: path.join(__dirname, 'dist/js'),
        filename: 'app.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                // ES6を変換する用
                                '@babel/preset-env',
                            ]
                        }
                    }
                ],
                exclude: /node_modules/,
            },
            {
                test: /\.vue$/,
                use: [
                    {
                        loader: 'vue-loader',
                    }
                ],
            },
        ]
    },
    resolve: {
        extensions: ['.js', '.vue'],
        alias: {
            // npm install したvueはtemplete機能のないランタイム限定ビルドなので、こっちを使うようエイリアスをはる
            vue: 'vue/dist/vue.esm.js'
        },
    },
    plugins: [
        // Vueを読み込めるようにするため
        new VueLoaderPlugin()
    ],
};

動作確認

以下のコマンドで必要に応じたビルドがおこなえます。

npm run build

前述のnpx webpackと同様のビルドがおこなえます。

npm run watch

npx webpackとビルド結果は同じですが、コマンド実行後そのままにしておくと、jsやvueのファイルを変更して上書き保存した際に自動で再ビルドをおこなってくれます。

基本的に開発中はこちらを実行し、vueやjsファイルを編集しブラウザを再読み込みして確認する、という使い方が良いです。
(browsersyncなどを利用すれば、ファイル変更時再ビルドだけでなく、ブラウザの再読み込みまで自動でおこなってくれ、さらに開発効率が上がるのですが、そちらはまた機会があれば書きます。)

npm run prod

本番公開用にビルドをおこないます。デバッグを無効にしたり、ファイル圧縮をした状態でビルドしてくれます。本番環境にはこちらで生成されたファイルを配置しましょう。

スタイルの利用

Vue.jsはvueファイルにstyleタグを書き、CSSで書く内容をvueファイル内に書くことができます。
scopedのオプションをつけると、コンポーネント設計をした際に、styleをそのコンポーネントにのみ適用するということができます。
他のコンポーネントにスタイルが影響する心配をする必要がないので、便利です。

試しに先ほどのSample.vueにscopedのstyleタグを追記し、足し算の結果の部分のみ赤文字になるようにしています。

設定の追加

Vueのstyleタグを利用するには追加で以下の2つのパッケージが必要になります。

  • css-loader
  • style-loader

以下のコマンドを実行し、追加でインストールをします。

npm i -D css-loader style-loader

package.jsonは以下のようになります。

{
  "name": "sample-webpack",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "cross-env NODE_ENV=development webpack",
    "watch": "cross-env NODE_ENV=development webpack -w",
    "prod": "cross-env NODE_ENV=production webpack"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.11.6",
    "@babel/preset-env": "^7.11.5",
    "axios": "^0.20.0",
    "babel-loader": "^8.1.0",
    "cross-env": "^7.0.2",
    "css-loader": "^4.3.0",
    "style-loader": "^1.2.1",
    "vue": "^2.6.12",
    "vue-loader": "^15.9.3",
    "vue-template-compiler": "^2.6.12",
    "webpack": "^4.44.2",
    "webpack-cli": "^3.3.12",
    "webpack-stream": "^6.1.0"
  }
}

webpack.config.jsにも設定を追加します。

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

const MODE = process.env.NODE_ENV;

module.exports = {
    // NODE_ENVでmodeは指定する
    mode: MODE,
    // developmentの場合のみsource-mapを出力
    devtool: (MODE == 'development') ? 'source-map' : '',
    entry: path.join(__dirname, 'src/js/app.js'),
    output: {
        path: path.join(__dirname, 'dist/js'),
        filename: 'app.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                use: [
                    {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                // ES6を変換する用
                                '@babel/preset-env',
                            ]
                        }
                    }
                ],
                exclude: /node_modules/,
            },
            {
                test: /\.vue$/,
                use: [
                    {
                        loader: 'vue-loader',
                    }
                ],
            },
            {
                test: /\.css$/,
                use: [
                    'style-loader',
                    'css-loader',
                ]
            }
        ]
    },
    resolve: {
        extensions: ['.js', '.vue'],
        alias: {
            // npm install したvueはtemplete機能のないランタイム限定ビルドなので、こっちを使うようエイリアスをはる
            vue: 'vue/dist/vue.esm.js'
        },
    },
    plugins: [
        // Vueを読み込めるようにするため
        new VueLoaderPlugin()
    ],
};
補足

ネットで検索するとstyle-loaderでなく、vue-style-loaderを使用すると書かれていたのですが、2020年9月現在、vue-style-loaderとcss-loaderの最新版の組み合わせではビルドはできても、画面に反映がされませんでした。

少し気になったので、npmのページで更新状況を調べてみました。
vue-style-loader
css-loader
style-loader

css-loaderが1ヶ月以内、style-loaderが5ヶ月前に対して、vue-style-loaderはなんと2年前です・・・
css-loaderの新しいバージョンだとvue-style-loaderとうまく噛み合わなくなったようです。css-loaderのバージョンを下げるのもなんだかなと思ったので、代わりにstyle-loaderを使用することにしました。

Vueファイルへstyleタグの追加

Sample.vueにstyleタグを追加します。

<template>
    <div>
        <p>
            <input type="text" v-model="input1">
            <input type="text" v-model="input2">
        </p>
        <p>{{ input1 }} + {{ input2 }} は <span class="result">{{ result }}</span> です。</p>
    </div>
</template>

<script>
export default {
    props: [],
    data: function() {
        return {
            input1: '',
            input2: '',
        };
    },
    computed: {
        result: function() {
            let result = parseInt(this.input1) + parseInt(this.input2)
            return isNaN(result) ? '' : result
        }
    },
    mounted() {
    },
    methods: {
    },
};
</script>

<style scoped>
.result {
    color: red;
}
</style>>

動作確認

npm run buildを実行して再度ビルドし、画面を再度ブラウザで開き、テキストボックスに数字を入力すると、結果の部分が赤く表示されました。

このstyleタグの部分は追加の設定をすることで、SCSSの記法も使えるようになるのですが、それはまた機会があれば追記したいと思います。

まとめ

Vue.jsはJavaScript内の変数とHTML内の要素を簡単に同期できるため、フロントで動きをつける場合にjQueryよりもソースコードの管理がしやすくなります。
簡単なホームページであればjQueryで事足りると思いますが、少し複雑なシステムでjQueryのコードだと煩雑になるようなケースであれば、Vue.jsやReactなどの導入を検討してみるのも良いと思います。

VueはReactに比べると、コード量が少なく簡単に書けるので、学習コストを少なく習得できるのがメリットです。
jQueryしか使ったことがないというエンジニアの方もぜひ学習をしてみることをお勧めします。