はじめに
本記事は『フロントエンド環境構編』です。続編は『バックエンド環境編』をご覧ください。
Vue.jsのコンポーネントとは、HTMLのテンプレート(+CSSスタイル)に、JavaScriptのデータとメソッドが1セットになった画面構成部品の事です。SFC(単一ファイルコンポーネント)は、このコンポーネントを1つのファイルで定義する仕組みのことです(慣例的に拡張子を.vueとするファイルを作成します)。
詳細は、以下のページをご覧ください。
Vue.jsのSFC(単一ファイルコンポーネント)について
SFCを使うには、webpackやBrowserifyなどのビルドツールの導入が必須です。
本記事は、Vue.jsのSFCをwebpackでビルドできるように設定を行い、バックエンドにPHPを組み合わせた構成で、Webアプリケーションを構築する一連の流れを説明する記事です。
前置き
めちゃくちゃ端的に言うと、私の日々の仕事は、企業で利用するWebアプリケーションの開発です。
では、Webアプリケーションとは一体どのような要素で構成されるのでしょうか?
Webアプリケーションの構成要素を表現する際の一つの切り口として、フロントエンド・バックエンドという役割に分けて説明することができます。
フロントエンドは、ユーザーが利用する画面などを指します。これらは、HTML・JavaScript・CSSなどを用いて、ブラウザに画面として表示するわけですね。
バックエンドは、フロントエンドから送られてきたデータを処理する層です。
こちらの層では、PHPなどのプログラミング言語を用いて、フロントエンドから受信したデータをDBに登録・更新・削除したり、フロントエンドの要求に応じて適切な情報をDBから検索して、フロントエンド側に返却します。
このように、Webアプリケーションというものを作る際には、フロントエンドとバックエンドの両方が必要であり、どちらが欠けても成り立ちません。
※バックエンドでは、入力チェックしたり、はたまた帳票を出力するようなこともあるので、バックエンドの役割はシステムの内容によって多彩です。上図は簡略化して描いているので場合によっては不正確かもしれません。
長年、Webアプリケーションを開発してきたプログラマ(例えば10年選手のような人たち)は、どちらかと言うとバックエンド層を得意とする人たちが多いと考えます。私個人の話をすると、JavaでStruts全盛の時代に新卒でIT業界に飛び込んだ人間です。そのような時代を生きてきた人間として言えることは、昔ながらのWebアプリケーションと言うものは、フロントエンドに求められる表現は最小限なものでした。
その昔、IE6が主流だったWebアプリ開発の現場においては、JavaScriptの関数の利用はご法度(利用したとしても最小化すべし)の時代です。HTMLソースは全てバックエンドでレスポンスとして出力されブラウザはただそれを表示する。バックエンドにロジックが偏ることが当たり前で、それを疑いもしない時代でした。
ですが年を追うごとにコンピュータの性能は上がり、ユーザーインターフェースの表現の幅も増加の一途を辿り、フロントエンドに求められる要求も日を追うごとに強まる一方です。そしてそれを実現するための技術スキルも大幅にアップデートされているのが実情で、Webアプリケーションを開発する側の人間の知識もアップデートを求められていると言っても過言ではありません。
このように、フロントエンドの要求が高まるにつれ、開発効率向上のための(フロントエンド側で使用する)ビューフレームワークなるものが栄枯盛衰されてきました。その一つが、本記事で紹介するVue.Jsです。
Vue.Jsとの付き合いは、かれこれ1年以上2年未満です。Vue.Jsを学習していて思った事に、チュートリアルにはフロントエンドの事しか書かれておらず、バックエンド技術(PHPなど)との組み合わせ方法が掲載されていないことが、大きな不満であり戸惑いの元でした。
以下は、Vue.Jsの学習において感じた、難しいことや不満だったことです。
- webpackって何だろう。バックエンドでもビルド(JavaとかC#の場合は)するのにフロントエンドでもビルドするの面倒だ(導入に対する精神的障壁の大きさがあり辛さを感じる)
- SPA(シングルページアプリケーション)なんか作りたくない、普通のアプリが作りたいだけです。
- Vue CLIをおすすめされたけど勝手にディレクトリ構成決められちゃう。どこにPHPモジュール置けばいいの?
学習の最終目標は、Vue.jsを業務プロジェクトへと導入することにあり、プロジェクトの導入を進めるには費用対効果を考えなくては行けません。ただ、「今流行っているから導入してみよう」という理由だけでは、後の世代に負の遺産を残してしまう事にもなりかねません。
このような点に真正面から向き合い、自分なりの答えを導いたのが本記事です。結論から言うと、僕はSFC(の利用に伴うwebpackの導入)に未来を見ました。
本記事が、堅牢さと枯れた技術をこよなく愛する硬派なバックエンドエンジニアの方々にもご満足いただけるような、Vue.jsの紹介記事になれば幸いです。
本記事をおすすめしたい人
以下に当てはまる方におすすめしたいです。
- Vue.jsを多少なりとも利用したことがある人
- Vue.js+SFCを使って一からWebアプリケーションの土台を作りたい人
- でも、Vue CLIは使いたくない人(余分なものは入れたくない)
- でも、Vue.jsのSFC(単一ファイルコンポーネント)は使いたい
- JavaScriptのES6にそろそろ慣れておきたい人
- Webアプリ開発にwebpackを導入することのメリットについて、理解しておきたい人
解説すること、しないこと
解説すること
- npmで導入したパッケージについて
- webpackのビルド設定内容とビルドの実施について
- Vue.jsのSFCについて
- ソース全体のディレクトリ構成について
解説しないこと
- 各種プログラミング言語の構文の詳細など
- 公式リファレンスに掲載されている基本的な事柄など
説明の流れ
Windows10で環境構築しました。
Macなどの他OSユーザーは、コマンドプロンプトをターミナルにするなど、適宜読み替えてください。
基本的にはどんなOSでも同じように実現できるはずです。
以下の大項目にそれぞれ分けて説明して行きます。
- フロントエンド側の環境構築
- バックエンド側の環境構築
注意
今回はフロントエンド側の環境構築のみの説明となります。バックエンド側の環境構築は次回の記事で説明します。
フロントエンド側の環境構築
ソースのルートディレクトリを決定する
本記事では、hello-vuejs
をプロジェクト名とします。
例として以下のディレクトリをルートディレクトリとして話を進めます。
C:\hello-vuejs
HINT
分かりやすさ重視でCドライブの直下にしました。適宜変更してOKです。
ただし、日本語ディレクトリやスペース文字がディレクトリ名として含まれると、トラブルの元になのでなるべく避けることをおすすめします。
インストールライブラリのまとめ
フロントエンド側で必要なものは以下の通りです。
ライブラリ | バージョン | 説明 |
---|---|---|
Node.js | 最新 | npm使用のためにインストール |
webpack | ^4.42.1 | webpack本体 |
webpack-cli | ^3.3.11 | webpackコマンドセット |
webpack-dev-server | ^3.10.3 | webpack開発サーバー |
webpack-merge | ^4.2.2 | webpack設定ファイルのマージに使用 |
babel-loader | ^8.1.0 | ES6のトランスパイル |
@babel/core | ^7.9.0 | 〃 |
@babel/preset-env | ^7.9.0 | 〃 |
css-loader | ^3.4.2 | CSSをjs上にロードするモジュール |
file-loader | ^6.0.0 | ファイルをjs上にロードするモジュール |
vue | ^2.6.11 | Vue.js本体 |
vue-loader | ^15.9.1 | SFCをjs上にロードするモジュール |
vue-template-compiler | ^2.6.11 | Vue 2.0テンプレートをレンダリング関数にプリコンパイル |
NodeJs(npm)で必要なパッケージを導入する
まず最初に、NodeJSをインストールするところから始めます。
Node.JSにはnpmというパッケージ管理ツールが付属しており、こちらを使用するためにNode.Jsをインストールします。
Vue.jsで『SFC(単一ファイルコンポーネント)』を使うには何はともあれ、NodeJsをインストールするところから始めてください。
以下のページから最新版をダウンロードしてインストールしましょう。
HINT
パッケージ管理ツールは、今やモダンな開発には無くてはならないものになっています。
バックエンド側のパッケージ管理ツールには、PHPを使用するならComposer、ASP.NetならNuGetが該当します。パッケージ管理ツールは、著者の観測範囲では使用するプログラミング言語によって異なります。
npmの初期化
npmでパッケージを追加するには、該当ディレクトリで初期化が必要です。
コマンドプロンプトを開いて、C:\hello-vuejs
にcd
しましょう。
そして、最初に以下を実行してください。
色々聞かれますが、全てEnterを押して、最後にyesを入力すればOKです。
詳細が知りたい人は、ググってみてください。
npm init
npmでパッケージインストール
次に、以下を実行して行きます。
npm install -D webpack webpack-cli webpack-dev-server webpack-merge
npm install -D babel-loader @babel/core @babel/preset-env
npm install -D css-loader
npm install -D file-loader
npm install -D vue
npm install -D vue-loader vue-template-compiler
HINT
npm installコマンドは、パッケージをインストールするコマンドです。-Dオプションは開発時に必要なライブラリ(ビルドに必要なもので、リリースに必要なランタイムモジュールではないという意味)です。
ちなみに、上の例では、-gオプションを付けていません。そのため、全てローカル(C:\hello-vuejs\node_modules
配下)にインストールされます。ローカルインストールすることで、ライブラリ同士の衝突の可能性を考慮せずに済むので、特別な理由が無ければローカルインストールが良いかと思います。
-D
は、--save-dev
のエイリアスです。
install
は、i
で置き換えることもできます。
全て実行し終えると、以下のようなファイルが出来上がっているはずです。
c:\hello-vuejs
+ node_modules
+ package.json
+ package-lock.json
node_modulesには、インストールしたライブラリファイルが格納されています。こちらは、バージョン管理が不要なディレクトリです。
package.jsonには、npm initした際の各種設定と、何をインストールしたかの情報が記載されています。
package-lock.jsonには、インストール時の厳密なライブラリのバージョン番号のスナップショットが記載されています。
こちらの2ファイルはコミットが必要です。
他の開発者は、こちらのpackage.jsonまたはpackage-lock.jsonから、npm installを実行して必要なライブラリをインストールすることになります。
したがって、本節で実施した、npm init
とnpm install -D [ライブラリ名]
は、一度限りの実行になります。
他の開発者は、バージョン管理ツールからチェックアウトしたソースに対して、npm install
を実行するだけで必要なライブラリを揃えることができます。
package.jsonのscripts編集
package.jsonを編集します。
C:\hello-vuejs
+ ...
+ package.json
修正箇所は、"scripts"
の部分です。
"start"
と"build"
のコマンドを以下のように追加しておきます。
"test"
は不要です。
{
...
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
...
}
このようにすることで、npm run [scriptsに定義したコマンド名]
でコマンドを実行できます。
startを実行する際は、npm run start
とコマンドプロンプトに打ち込むことで実行可能です。
start
は開発時に使用するコマンドです。コーディングの前に本コマンドを実行し、自動ビルドやHMR・自動更新を有効にするというイメージです。
build
はリリース時に使用するコマンドです。bundle.jsという最適化されたファイルが出力されます。
各コマンド引数の--config [ファイル名]
部分は使用する設定ファイル名の指定です。こちらは次の手順で作成します。
webpack設定ファイルの作成
webpackの設定ファイルを作成します。
webpackの説明は割愛します。もし、webpackについて詳しく知りたければ、"webpackの基本"のようなキーワードでググると記事がたくさん出てくるので、それらを参考にすると良いかと思います。ただし、本記事で使用しているwebpackはバージョンが4なので、参考にする際はwebpack4の記事を見るようにしてください。
ファイル構成は以下の通りです。
webpack.common.jsが全環境で共通となる設定ファイルで、devとprodがそれぞれの環境に対応した設定ファイルです。
C:\hello-vuejs
+ ...
+ webpack.common.js ... 共通設定ファイル
+ webpack.dev.js ... 開発時の設定ファイル
+ webpack.prod.js ... リリース時の設定ファイル
続いて、設定ファイルの内容を以下に示します。
とりあえず、細かい説明は割愛します。雰囲気で読み取ってみてください。
// webpack common setting
const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');
const prefixUri = "/hello-vuejs";
const settings = {
resolve: {
modules: [
path.resolve('./src'),
path.resolve('./node_modules')
]
},
entry: path.resolve(__dirname, 'src', 'main.js'),
output: {
path: path.resolve(__dirname, 'public'),
filename: 'dist/bundle.js',
publicPath: prefixUri + "/"
},
module: {
rules: [
{
test: /\.vue$/,
loader: 'vue-loader'
},
// this will apply to both plain `.js` files
// AND `<script>` blocks in `.vue` files
{
test: /\.js$/,
loader: 'babel-loader'
},
// this will apply to both plain `.css` files
// AND `<style>` blocks in `.vue` files
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader'
]
}
]
},
plugins: [
new VueLoaderPlugin()
]
};
module.exports = {
prefixUri: prefixUri,
settings: settings
};
// webpack development setting
const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const prefixUri = common.prefixUri;
module.exports = merge(common.settings, {
mode: 'development',
devtool: 'inline-source-map',
// Configuration for dev server
devServer: {
// 生成されたファイルをディスクに書き込むかどうか
writeToDisk: false,
// ブラウザの表示有無
open: true,
// ブラウザの表示ページ
openPage: prefixUri.substring(1) + '/index.html',
// HMR (Hot Module Replacement) の有無
hot: true,
// Webページリロードの有無
//inline: true,
// Webサーバーの公開ディレクトリ
contentBase: path.join(__dirname, 'public'),
contentBasePublicPath: prefixUri,
// Webサーバーの公開ディレクトリの監視有無
watchContentBase: true,
// Webサーバーの公開URL
publicPath: prefixUri + "/"
}
});
// webpack production setting
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common.settings, {
mode: 'production'
});
設定ファイルの作成は以上です。
一先ず、こういった設定でVue.jsのSFCがビルドできるようになったんだなー、と理解してもらえればと思います。
深堀したい場合は、公式サイトのリファレンスをご覧になると良いかと思います。
index.htmlの作成
index.html
は、Webアプリケーションのフロントコントローラになります。
ユーザーが、hello-vuejs
というWebアプリケーションにアクセスする際には、ブラウザで必ずindex.html
を指定することになります。
C:\hello-vuejs
+ ...
+ public
+ index.html
ファイルの内容は以下の通りです。コンテンツとしては空の状態に近いです。
特徴的なのは、<div id="app" v-cloak></div>
という部分です。こちらは、Vue.jsのコンポーネントを表示する領域です。
また、<script src="./dist/bundle.js" ></script>
は、webpackでビルドした結果のファイルをロードしています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="Mon, 26 Jul 1997 05:00:00 GMT">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale = 1.0, maximum-scale = 1.0, user-scalable = no">
<title>Hello Vue.js</title>
</head>
<body class="layout-base">
<div id="app" v-cloak>
</div>
<script src="./dist/bundle.js" ></script>
</body>
</html>
main.jsの作成(webpackのエントリポイント)
webpackにおけるビルドを簡単に説明すると…
- エントリポイント(main.js)と呼ばれるファイルの内容を読み取る
- そこに書かれているimport構文からサードパーティライブラリとして何が使用されるのかを解析する
- それらのファイルをまとめて繋ぎ合わせ、最終的に一つのファイルに出力する(bundle.js)
以上のようになります。
ここで作成するmain.jsとはwebpackのエントリポイントのファイルの事を指します。
C:\hello-vuejs
+ ...
+ src
+ main.js
main.jsで実施している処理は以下の通りです。
- Vue.jsをインポートする
- GETパラメータから表示対象のコンポーネントパス(
componentPath
変数)を取得 - コンポーネントパスに対応するモジュールを動的インポートする
- Vueインスタンスを生成して動的インポートしたコンポーネントを描画するように指示する
ポイントは、GETパラメータによる表示対象コンポーネントの変更と動的インポートです。
// -----------------------------------------------------------------------------
// ライブラリのインポート
// -----------------------------------------------------------------------------
// VueJs
import Vue from 'vue';
// -----------------------------------------------------------------------------
// GETパラメータの取得
// -----------------------------------------------------------------------------
let queryObject = {};
if (window.location.search) {
// 1文字目がクエスチョン'?'になっているので、substringで2文字目以降を取得する
const queryString = window.location.search.substring(1);
// '&'キーワードで分解する
var parameters = queryString.split('&');
for (let i = 0, ilen = parameters.length; i < ilen; i++) {
// '='キーワードでキーと値に分解する
var element = parameters[i].split('=');
// デコードを忘れずに実施する
var paramName = decodeURIComponent(element[0]);
var paramValue = decodeURIComponent(element[1]);
queryObject[paramName] = paramValue;
}
}
// ------------------------------------------------------------------------------
// 動的インポートを実施
// ------------------------------------------------------------------------------
// URL指定例)
// http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Hello/Front/View/Hello
// http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Goodbye/Front/View/Goodbye
if (typeof queryObject['componentPath'] !== 'undefined') {
// componentPathを以下のルールで変換
// 例)
// /Func/Hello/Front/View/Hello
// ↓
// ./Func/Hello/Front/View/Hello.vue
const componentPath = queryObject['componentPath'];
const componentId = componentPath.substring(componentPath.lastIndexOf('/') + 1);
const componentLoadPromise = import("./" + componentPath.replace(/^\//, "") + ".vue");
componentLoadPromise.then(function (value) {
// 動的インポート完了
// VueのComponentのグローバル登録は、Vueインスタンス生成前に実施する必要あり
Vue.component(componentId, value.default /* import関数の戻り値に default があるので、そちらを使用する(defaultエクスポート定義を読み込むという意味になる) */);
// Vueインスタンスの生成
const vm = new Vue({
el: '#app',
render: h => h(componentId)
});
});
} else {
console.warn('componentPathパラメータが見つかりませんでした。');
}
コンポーネントの作成
コンポーネントは、HelloとGoodbyeの2つを作成することにします。
ディレクトリ階層が深く見えるかもしれませんが、今後PHPモジュールと絡ませる予定なので、このような階層にしました。
Func
は機能別にモジュールを配置するためのルートディレクトリです。
Front
はフロントエンドとバックエンドを区別するためにあえて作成したディレクトリです。
C:\hello-vuejs
+ ...
+ src
+ Func
+ Hello\Front\View
+ Hello.vue
+ Hello.html
+ Hello.css
+ Hello.js
+ Goodbye\Front\View
+ Goodbye.vue
+ Goodbye.html
+ Goodbye.css
+ Goodbye.js
+ ...
特別説明するようなこともない、シンプルな内容です。
<template src="./Hello.html"></template>
<script src="./Hello.js"></script>
<style src="./Hello.css" scoped></style>
<div class="hello-component" v-cloak ref="view">
<div>
This is Hello Component.<br>
{{message}}
</div>
</div>
.hello-component {
color: blue;
}
export default {
data() {
return {
message: 'Hello Vue.js'
};
},
methods: {
}
}
<template src="./Goodbye.html"></template>
<script src="./Goodbye.js"></script>
<style src="./Goodbye.css" scoped></style>
<div class="goodbye-component" v-cloak ref="view">
<div>
This is Goodbye Component.<br>
{{message}}
</div>
</div>
.goodbye-component {
color: red;
}
export default {
data() {
return {
message: 'Goodbye Vue.js'
};
},
methods: {
}
}
動かしてみる
処理イメージは下図の通りです。
C:\hello-vuejs
で以下のコマンドを実行してください。すると、ブラウザが起動してページが表示されるはずです。
npm run start
最初は、コンポーネントの表示指定が無いので、以下のような白い画面が表示されるはずです。
この状態で、Helloコンポーネントを表示するためにURLに以下を指定します。
http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Hello/Front/View/Hello
この状態で、Hello.jsを開いて、dataのmessageを編集してみましょう。
...
data() {
return {
message: 'こんにちは Vue.js'
};
},
...
すると、画面の内容が以下のように変化(リアルタイム)しました。
ちなみに、Goodbyeコンポーネントを表示する画面は以下のURLです。
http://localhost:8080/hello-vuejs/index.html?componentPath=/Func/Goodbye/Front/View/Goodbye
どうでしょうか。便利だと思いませんか?
webpack(正確には、webpack-dev-server)を利用する大きなメリットの一つに、**HMR(Hot Module Replacement)**があります。編集内容をリアルタイムで確認できるので、開発効率が向上すること間違いなしです。また、該当ファイルが保存された瞬間に自動でビルドが実行されるので、ビルドシステムが一つ余計に増えたからと言って、開発効率が落ちることはありません。
HINT
ファイルの変更内容によっては(JavaScriptコードの大幅な変更など)、HMRでコンテンツの置き換えができないことがあります。
その場合は、表示中のページがリロードされます。
リリースビルドの実行
C:\hello-vuejs
で以下のコマンドを実行してください。すると、bundle.js
が生成されます。
npm run build
C:\hello-vuejs
+ ...
+ public
+ dist
+ bundle.js
+ ...
HINT
npm run start
の場合は、bundle.jsが出力されません。ビルド内容はメモリ上に保持されます。
最終的なファイル構成
ファイルの最終的な構成は以下の通りです。
C:\hello-vuejs
+ node_modules ... npmでインストールしたライブラリ(バージョン管理対象外とするディレクトリ)
+ public ... Webサーバーの公開ディレクトリ
+ index.html ... フロントコントローラ
+ src
+ Func ... 機能別ディレクトリ
+ Hello\Front\View ... Helloコンポーネント
+ Hello.vue
+ Hello.html
+ Hello.css
+ Hello.js
+ Goodbye\Front\View ... Goodbyeコンポーネント
+ Goodbye.vue
+ Goodbye.html
+ Goodbye.css
+ Goodbye.js
+ main.js ... webpackエントリポイントファイル
+ package.json ... npmに関する設定ファイル
+ package-lock.json ... npm installのスナップショットファイル
+ webpack.common.js ... webpackの共通設定ファイル
+ webpack.dev.js ... webpackの開発用設定ファイル
+ webpack.prod.js ... webpackのリリース用設定ファイル
フロントエンドのまとめ
ここまで読み進めて頂いた方、もしくは実践的に試して頂いた方なら、SFC(webpack)の凄さが実感できたのではないでしょうか。覚えることも多いですが実際に導入してみると、それを上回る恩恵があるかと思います。
今回作成したソースはGitHubにアップしておきました。以下のページにアクセスして確認してみてください。
hello-vuejsソース
ちなみに、フロントコントローラがindex.html(静的ページ)なので、GETパラメータの読み取りのみ可能で、POSTパラメータの読み取りはできません。そのため、動的コンテンツに対応したWebアプリケーション(Webアプリはそもそも動的コンテンツですよね…、という突っ込みもあるかと思いますが)を今回の構成で作成することはできません。静的なWebページならこちらの構成で作成可能です。
※シングルページアプリケーションなら、今回の構成でも構築可能かと思います(ページ間のパラメータ連携とか考えなくて良いはずなので)。
動的コンテンツに対応するには、今回作成したindex.htmlをindex.phpに変更して、こちらのファイルをフロントコントローラにする必要があります。
そちらに関しては、『バックエンド環境編』をご覧ください。