Vue CLIを利用することでvue.jsの開発環境を簡単に構築することができます。Vue CLIで簡単に環境構築、アプリケーションの開発を行うことができますが裏側で何が行われているのかわからない人も多いのではないでしょうか。Vue CLIを利用することなしでWebpackをインストールを行いSingle File Component(単一ファイルコンポーネント)を使ってVueのアプリケーションを構築できる環境を作成してみたいという人向けに本文書を作成しています。本文書ではwebpackのインストールからSFCを使えるまでの手順をStep By Stepで丁寧に説明していますので一通り読み終えるとVueで開発を行うためにwebpackで必須な設定を理解することができます。 Vueの開発環境の構築に関する知識も深まります。

Single File Componentとは

VueのSingle File Componentはtemplate, script, styleの3つのセクションから構成されるVue用のコンポーネントファイルで拡張子はvueです。一つファイル内でHTML, CSS, JavaScriptを記述することができるのでコードを効率的に記述することができます。この形式はvue.js特有の形なのでvueファイルを作成しただけではブラウザで動作させることができません。そのためブラウザが理解できる形に変換する必要があります。その変換に利用するのがWebpackです。


<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello Vue Component'
    }
  }
}
</script>

<style scoped>
.example{
  font-weight: bold;
}
</style>

Webpackのインストール

webpackのインストールを行うために任意の名前のディレクトリを作成します。ここではwebpack-vueという名前をつけています。


 $ mkdir webpack-vue

webpackのインストールを行う前にnpm init -yコマンドでpackage.jsonファイルを作成します。


 $ npm init -y

webpackとwebpackのコマンドラインインターフェイスであるwebpack-cliの2つパッケージをインストールします。


 $ npm install --save-dev webpack webpack-cli

これでwebpackのインストールは完了です。

初めてのwebpackコマンド

srcフォルダを作成しその中にindex.jsファイルを作成します。


 $ mkdir src
 $ cd src
 $ touch index.js

ファイルを作成後にsrcフォルダから上のフォルダに戻ります。その時のwebpack-vueフォルダの中身は下記のようになっています。


 % ls
node_modules		package.json
package-lock.json	src

npx webpackコマンドを使ってwebpackの処理を実行します。コマンドを実行するとWARNINGが出ますがwebpackによるビルド処理は完了しdistディレクトリの下にmain.jsファイルが作成されます。


 % npx webpack
asset main.js 0 bytes [emitted] [minimized] (name: main)
./src/index.js 1 bytes [built] [code generated]

WARNING in configuration
The 'mode' option has not been set, webpack will fallback to 'production' for this value.
Set 'mode' option to 'development' or 'production' to enable defaults for each environment.
You can also set it to 'none' to disable any default behavior. Learn more: https://webpack.js.org/configuration/mode/

webpack 5.52.0 compiled with 1 warning in 175 ms
webpackコマンドのみを実行するとパスが通っていないためエラーになります。npxをつけることでnode_modules/.bin/ディレクトリの中から指定したコマンドを自動的に探し出して実行します。

webpackのデフォルトではsrc/index.jsファイルを自動で見つけ、ビルドを行いdistディレクトリの下にmain.jsファイルを作成します。

またコマンド実行時に表示されたWARNINGはmodeを設定していないことによる警告なのでオプションにmodeを設定することで消えます。


 $ npx webpack --mode development
asset main.js 1.18 KiB [emitted] (name: main)
./src/index.js 1 bytes [built] [code generated]
webpack 5.52.0 compiled successfully in 66 ms
or 
 $ npx webpack --mode production
developmentは開発用でproductionは本番用で使用するコマンドです。

webpack.config.jsファイルの作成

webpackの設定はwebpack.config.jsを使って行うことができます。webpack-vueフォルダの下にwebpack.config.jsファイルを作成してmodeの設定を行います。


module.exports = {
  mode: 'development'
}

webpack.config.jsファイルにmode設定を行うと先程まで設定したmodeのオプションをnpx webpack実行時に設定する必要がなくなります。

vue.jsのインストール

npmコマンドを利用してvue.jsのみ追加でインストールを行います。


 $ npm install vue

added 1 package, and audited 123 packages in 1s

17 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

vue.jsの動作確認

プロジェクトフォルダ(webpack-vue)の下にindex.htmlファイルを作成します。


<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>Webpack Vue.js</title>
</head>
<body>
	<div id="app">
		{{ message }}
	</div>
	<script src="./dist/main.js"></script>
</body>
</html>

srcディレクトリのindex.jsにvue.jsのコードを記述します。インストールしたvueは先頭でimortを行って利用しています。


import Vue from 'vue'

const app = new Vue({
	el: "#app",
	data: {
		message: 'Hello World'
	}
})

index.htmlファイルとindex.jsファイルを作成後にwebpackコマンドでビルドします。


 $ npx webpack

ブラウザで確認すると画面上には{{ messsage}}が表示されvue.jsは動作しません。ブラウザのデベロッパツールのコンソールには以下のメッセージが表示されます。modeをproductionにしている場合はメッセージは表示されません。


vue.runtime.esm.js:623 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

(found in )
webpack5を利用している場合は画面には{{ message }}が一瞬表示されその後消えます。

vue.jsを動作させるためにはvue.jsのマニュアルに記載されているwebpackの設定をwebpack.config.jsを作成して記述します。記述後にnpx webpackコマンドを実行してください。


module.exports = {
  mode: 'development',
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js', 
    },
  },
};

ビルドが完了するとブラウザ上にHello Worldが表示されます。webpackの環境下でvue.jsを動作することができました。

[Vue warn]: Cannot find element: #appが表示された場合はdist/main.jsを読み込む場所をindex.htmlの上部ではなく#appのタグよりも下に移動してください。

webpack.config.jsファイルではresolve.aliasを利用してvue/dist/vue.esm.jsを設定してるのでindex.jsで直接vue/dist/vue.esm.jsからimportすることもできます。この時にはwebpack.config.jsファイルのresolve.aliasの設定は必要ではありません。


import Vue from 'vue/dist/vue.esm.js';

const app = new Vue({
  el: '#app',
  data: {
    message: 'Hello World',
  },
});

なぜimportするファイルを変更するとエラーがなくなるのか再度コンソールログで確認した以下のメッセージを確認します。


vue.runtime.esm.js:620 [Vue warn]: You are using the runtime-only build of Vue where the template compiler is not available. Either pre-compile the templates into render functions, or use the compiler-included build.

訳すとtemplateコンパイラが利用できないVueのRuntime Only Build(vue.runtime.esm.js)を利用しています。render関数に変更するかコンパイラーを含むbuildを利用してください。と記述されています。

このメッセージとこれまでの動作確認からvueをそのままimportするとそれはRuntime Only Build(vue.runtime.esm.js)ということがわかり、vue/dist/vue.esm.jsはコンパイラービルドということがわかります。コンパイラービルドを利用する必要があるためブラウザが記述したコードを理解するためにはコンパイルが必要となりその処理をvue.esm.jsが行うということがわかります。

render関数の利用

もう一つrender関数を利用する方法もあるとメッセージの中にあったのでrender関数で書き換えてみましょう。


import Vue from 'vue';

const app = new Vue({
  el: '#app',
  data: {
    message: 'Hello World',
  },
  render: function (createElement) {
    return createElement('h1', this.message);
  },
});

importしているのはvueでwebpack.config.jsファイルにはresolve.aliasを設定していない状態です。この場合はエラーメッセージの表示はなくなりh1タグを利用しているので文字が大きく太文字で表示されます。render関数を利用すればvue.esm.jsを利用する必要がないこともわかります。

render関数を利用して描写
render関数を利用して描写

render関数を利用する場合とvue.esm.js利用した場合の違いは作成されるmain.jsファイルのサイズを確認するとわかりやすいです。vue.esm.jsではコンパイルの処理の中で追加で情報が登録されるためファイルサイズが大きくなります。今回のコードでも40%ほどサイズが大きくなっていました。

SFC(Single File Components)の動作確認

vue.jsが動作することが確認できたので単一ファイルコンポーネントの動作確認を行うためにsrcフォルダの下にcomponentsフォルダを作成しその下にexample.vueファイルを作成します。render関数は利用しないのでwebpack.config.jsにresolveのaliasを設定した状態で動作確認を進めていきます。


<template>
  <div class="example">{{ msg }}</div>
</template>

<script>
export default {
  data () {
    return {
      msg: 'Hello Vue Component'
    }
  }
}
</script>

index.jsファイルからimportを使った作成したexample.vueファイルを読み込みます。


import Vue from 'vue'
import Example from './components/example.vue'

const app = new Vue({
	el: "#app",
	components:{
		Example,
	},
	data: {
		message: 'Hello World'
	}
})

更新が完了したらwebpackでビルドを行います。


% npx webpack
asset main.js 336 KiB [emitted] (name: main)
runtime modules 1.13 KiB 5 modules
cacheable modules 321 KiB
  ./src/index.js 189 bytes [built] [code generated]
  ./src/components/example.vue 172 bytes [built] [code generated] [1 error]
  ./node_modules/vue/dist/vue.esm.js 320 KiB [built] [code generated]

ERROR in ./src/components/example.vue 1:0
Module parse failed: Unexpected token (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
> 
 @ ./src/index.js 2:0-47 7:4-11

webpack 5.52.0 compiled with 1 error in 352 ms

メッセージを読むとYou may need an appropriate loader to handle ths file type(このファイルタイプを処理するためには適切なローダーが必要)と記述されているのでLoaderが必要であることがわかります。ここまでのwebpackの設定ではLoaderの設定を行なっていないためvueファイルを処理するためにはLoaderが必要であることがわかります。

vue-loaderのインストール

エラーメッセージだけではどのLoaderを利用していいのかわかりません。Vue.jsのドキュメントの中にvue-loaderについて記載されています。vue-loaderで検索してみてください。ドキュメントでは “The official loader that provides Vue SFC support in webpack.”と記載されています。vue-loaderはVueのSFCをサポートする公式のloaderであることがわかります。

vue-loaderは、vueファイルに記述したtemplate, script, styleタグの中身を解析し、ブラウザに認識できる形に変換するために必要となるLoaderです。

npm installコマンドでインストールを行います。


% npm install --save-dev vue-loader

added 44 packages, and audited 167 packages in 3s

20 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilitie

vue-loaderのインストールが完了したら、webpack.config.jsにloader設定を追加します。設定により.vueの拡張子が付いたファイルにvue-loaderが適用されます。


module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' webpack 1 cӬ
    },
  }
}

webpackのLoaderについてわからない場合は下記の文書も参考になります。

webpack.config.jsの設定が完了したらwebpackでビルドを行います。さらに3件の別のエラーが発生しました。


  % npx webpack                      
asset main.js 336 KiB [emitted] (name: main)
runtime modules 1.13 KiB 5 modules
cacheable modules 321 KiB
  ./src/index.js 189 bytes [built] [code generated]
  ./src/components/example.vue 39 bytes [built] [code generated] [3 errors]
  ./node_modules/vue/dist/vue.esm.js 320 KiB [built] [code generated]

ERROR in ./src/components/example.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
[vue-loader] vue-template-compiler must be installed as a peer dependency, or a compatible compiler implementation must be passed via options.
 @ ./src/index.js 2:0-47 7:4-11

ERROR in ./src/components/example.vue
Module Error (from ./node_modules/vue-loader/lib/index.js):
vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.
 @ ./src/index.js 2:0-47 7:4-11

ERROR in ./src/components/example.vue
Module build failed (from ./node_modules/vue-loader/lib/index.js):
TypeError: Cannot read property 'parseComponent' of undefined
    at parse (/Users/mac/Desktop/webpack-vue/node_modules/@vue/component-compiler-utils/dist/parse.js:15:23)
    at Object.module.exports (/Users/mac/Desktop/webpack-vue/node_modules/vue-loader/lib/index.js:67:22)
 @ ./src/index.js 2:0-47 7:4-11

2 errors have detailed information that is not shown.
Use 'stats.errorDetails: true' resp. '--stats-error-details' to show it.

webpack 5.52.0 compiled with 3 errors in 545 ms

vue-template-comilerのインストール

1件目のエラーのメッセージにはvue-template-comilerが必要とのことなのでインストールを行います。


 $ npm install --save-dev vue-template-compiler

再度npx webpackを実行すると一つ目のvue-template-compilerに関するエラーは消えますが、残り2件のエラーは残ったままです。

VueLoderPluginに関する設定

1つのエラー内容からVueLoderPluginをwebpack.config.jsに含めないといけないということなのでwebpack.config.jsへの追加を行います。設定方法についてはVue Loaderのドキュメントのマニュアルセットアップに記述されています。


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

module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      }
    ]
  },
  plugins: [
    // make sure to include the plugin!
    new VueLoaderPlugin()
  ],
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' webpack 1 cӬ
    },
  }
}

再度ビルドを実行するとエラーが表示されなくなりました。


% npx webpack
asset main.js 346 KiB [emitted] (name: main)
runtime modules 891 bytes 4 modules
cacheable modules 325 KiB
  modules by path ./src/ 2.07 KiB
    ./src/index.js 189 bytes [built] [code generated]
    ./src/components/example.vue 1.07 KiB [built] [code generated]
    ./src/components/example.vue?vue&type=template&id=82b452ec& 205 bytes [built] [code generated]
    ./src/components/example.vue?vue&type=script&lang=js& 262 bytes [built] [code generated]
    ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/components/example.vue?vue&type=template&id=82b452ec& 267 bytes [built] [code generated]
    ./node_modules/vue-loader/lib/index.js??vue-loader-options!./src/components/example.vue?vue&type=script&lang=js& 103 bytes [built] [code generated]
  modules by path ./node_modules/ 323 KiB
    ./node_modules/vue/dist/vue.esm.js 320 KiB [built] [code generated]
    ./node_modules/vue-loader/lib/runtime/componentNormalizer.js 2.71 KiB [built] [code generated]
webpack 5.52.0 compiled successfully in 735 ms

index.htmlファイルを開いた作成したExampleコンポーネントを追加します。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>Webpack Vue.js</title>
  </head>
  <body>
    <div id="app">{{ message }}<example /></div>
    <script src="./dist/main.js"></script>
  </body>
</html>

ブラウザで確認するとHello Worldの下にExampleコンポーネントに記述したHello Vue Componentが表示されました。

Exampleコンポーネントの中身を確認
Exampleコンポーネントの中身を確認

styleタグの追加

template, scriptタグは処理できましたが、styleタグについては設定を行なっていなかったのでexmple.vueファイルに追加します。


<style scoped>
.example{
  font-weight: bold;
}
</style>

scriptタグを追加後にwebpackコマンドを実行するとエラーが発生します。


 $ npx webpack
  ・
//略
Module parse failed: Unexpected token (15:0)
File was processed with these loaders:
 * ./node_modules/vue-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
| 
| 
> .example {
|   font-weight: bold;
| }
//略

追加のLoaderが必要なのでcssに関連するvue-style-loaderとcss-loaderの2つをインストールします。


 $ npm install --save-dev vue-style-loader css-loader

2つのLoaderが必要なのはそれぞれ役割が異なるためです。css-loaderはファイルからcssの情報を取り出してJavaScriptファイルに組み込むために利用されます。vue-style-loaderはstyle-loaderと同じ役割を持っておりhtmlファイルの<head>タグの中に設定したclass情報を含むstyleタグを埋め込む処理を行います。

cssに関する処理が追加されたmain.jsファイル(Loader処理を行ったファイル)を読み込んだ後にブラウザのデベロッパーツールからstyleタグを確認するとheadタグの中にstyleタグが入っていることが確認できます。

vue-style-loaderによって埋め込まれたstyleタグ
vue-style-loaderによって埋め込まれたstyleタグ

Loaderのインストール後はwebpack.config.jsファイルに設定を追加する必要があります。vue-style-loaderとcss-loaderの順番も重要なので注意してください。後に記述されたLoaderから実行されます。css-loaderでvueファイルに記述されたclassがmain.jsファイルの中に書き込まれ、そのCSS情報をvue-style-loaderによってstyleタグでhtmlに追加する処理を行います。


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

module.exports = {
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader'
      },
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader',
        ]
      }
    ]
  },
  plugins: [
    // make sure to include the plugin!
    new VueLoaderPlugin()
  ],
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js' // 'vue/dist/vue.common.js' webpack 1 cӬ
    },
  }
}
css-loaderのみを設定した場合はclass情報を解析できるためwebpackコマンドでエラーは発生しませんが、vue-style-loaderのみを設定した場合はclass情報が解析できないためLoaderが必要だというエラーメッセージが表示されます。

ビルド後にブラウザで見るとHello Vue Componentの文字列にCSSが適用され太文字で表示されることが確認できます。

styleタグのclassが適用された結果
styleタグのclassが適用された結果

Babelの設定

Single File Componentsの内容をブラウザ上に表示することができましたがせっかくなのでBabelの設定も行っておきましょう。BabelはES6(ECMAScript2015)から使えるようになったアロー関数などの記述方法をサポートしていないブラウザに対応できるようにコード変換を行ってくれるトランスパイラです。

webpackでBabelを利用するためにはLoaderが必要となります。LoaderとBabelの本体であるcoreを一緒にインストールします。


 % npm install --save-dev babel-loader @babel/core

added 43 packages, and audited 213 packages in 7s

24 packages are looking for funding
  run `npm fund` for details

Loader以外にもpresetが必要となるのでpresetのインストールを行います。babelではそれぞれの記述方法に対応するプラグインが存在します。例えばlet, constなどをvarに変換するためには@babel/plugin-transform-block-scopingというプラグインがあります。個別にプラグインをインストールし設定を行う代わりにプラグインを複数含んだpresetをインストールすることで一括で設定を行うことができます。


 % npm install @babel/preset-env --save-dev

インストールが完了したらwebpack.config.jsファイルにBabelの設定を追加します。


const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
  mode: 'development',
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
      },
      {
        test: /\.css$/,
        use: ['vue-style-loader', 'css-loader'],
      },
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env'],
          },
        },
      },
    ],
  },
  plugins: [
    // make sure to include the plugin!
    new VueLoaderPlugin(),
  ],
  resolve: {
    alias: {
      vue$: 'vue/dist/vue.esm.js',
    },
  },
};

設定後に変換が行われるのか確認するためにExample.vueファイルのライフサイクルメソッドのcreatedでconstを利用します。constはES6から利用できるキーワードです。それ以前はvarキーワードが利用されるのでconstからvarに変換されたらbabelは動作していることになります。


export default {
  data() {
    return {
      msg: 'Hello Vue Component',
    };
  },
  created() {
    const webpack_babel = 'Babel check';
    console.log(webpack_babel);
  },
};

追加が完了したら、npx webpackを実行してdistに作成されるmain.jsの中身を確認してwebpack_babelを検索してください。main.jsの中ではvarになっていることが確認できます。


//略
function data() {\n    return {\n      msg: 'Hello Vue Component'\n    };\n  },\n  created: function created() {\n    var webpack_babel = 'Babel check';\n    console.log(webpack_babel);\n  }\n});\n\n/
//略

babelの設定を解除して再度npx webpackを実行します。babelを利用しない場合はconstがそのまま表示されることが確認でき変換が行われていないことがわかります。


//略
data() {\n    return {\n      msg: 'Hello Vue Component'\n    };\n  },\n\n  created() {\n    const webpack_babel = 'Babel check';\n    console.log(webpack_babel);\n  }\n\n});
//略

webpack.config.jsファイルの中にpresetの設定を行いましたがプロジェクトフォルダの下にbabel.config.jsonファイルを作成して以下を記述しても同じように動作します。


{
  "presets": ["@babel/preset-env"]
}

まとめ

webpackのインストールから始まりvueのインストールやcompiler, loaderやwebpack.config.jsファイルの設定を行うことでようやくSingle File Componentsの内容をブラウザ上に表示させることができるようになりました。Vue CLIを利用するとこのような設定が必要であることも意識することなくVueの開発を行うことができます。しかし何かwebpack絡みで問題が発生した場合や追加設定が必要な場合に対応方法がわかりません。しかし本文書を通してwebpack周りでどのような設定が必須なのか理解ができたと思うので以前よりもwebpack周りでの問題や設定に対応できるはずです。入門者の人であれば少しだけVueの理解度が深まったと思います。