Nodejsフロントエンドとバックエンドの開発(Quasarフロントエンド編)
はじめに
TypeScriptベースで、Vue + Quasarのフロントエンド、Nodejsのバックエンド構成のSPA型のWebアプリケーションをつくります。
まずはVite + Vue3 + Quasarでフロントエンドを作り、フロントエンドをExpressのバックエンドに組み込んでWebアプリケーションとして統合します。
フロントエンド、バックエンドの開発プロジェクトを分けて、そのまま別ポートで運用する方法もありますが、統合して1アプリケーションにしてしまいます。
開発時はフロントエンド、バックエンドを別々のポートでテストし、統合ビルドすると1ポート配信の1アプリケーションになります。
今回はフロントエンドの開発です。Quasarの機能自体にはあまり触れないで、フロントエンドの開発スタイルやビルド処理を説明します。
Vite
Viteは開発ツールです。フロントエンドをテストするのにサーバ機能としてViteを使用します。Viteにはビルド機能もついているので、これで開発からビルドまで行います。
Viteを使った場合、十分に速く、ややこしい設定が不要、開発からビルドまでシームレスに行える、などの利点があると思います。
今回フロントエンドとしてビルドされたモジュール(dist
ディレクトリ)はそのままバックエンドに取り込まれることになります。
開発環境
開発環境は以下です。
- Windows 11
- Visual Studio Code
- Node.js v18(Windows Installerでインストール)
サンプルプログラム
サンプルはここにあります。簡単なメモアプリです。
機能はVue RouterとPiniaだけを追加した最小限構成です。
使い方
yarn install
でモジュールをインストールします。起動コマンドyarn dev
を実行すると、Viteの開発サーバが立ち上がります。
後編の統合編に合わせるために使用ポートは3001です。(B3、B4)
# yarnの場合
yarn install
yarn dev
# npmの場合
npm install
npm run dev
デバッグ
VSCode上でオンラインデバッグすることができます。
ターミナルからyarn dev
でViteの開発サーバを起動した状態にします。
VSCodeの実行とデバッグのLaunch Chrome
からブラウザを起動するとデバッガが起動します。
ビルド
yarn build
でフロントエンドモジュールをビルドします。ビルドしたモジュールはdist
ディレクトリに作成されます。
Viteの静的配信サーバを通して、yarn preview
でビルドしたモジュールの動作確認ができます。
# yarnの場合
yarn build
yarn preview
# npmの場合
npm run build
npm run preview
ソースの解説
サンプルプログラムからフロントエンド開発のポイントを解説します。
サンプルのディレクトリ構成は以下です。
このプロジェクトは、最終的にフロントエンドモジュールをdist
ディレクトリに生成するのが目的です。
フロントエンドのプログラムソースに該当するのが、src
、public
ディレクトリおよびindex.html
、.env
ファイルです。その他は設定ファイルです。
ディレクトリ名や設定ファイル名は基本的にデフォルト値を使用しています。
.
├── .vscode/
│ ├── extensions.json
│ ├── launch.json
│ └── settings.json
├── dist/
│ ├── assets/
│ ├── favicon.ico
│ └── index.html
├── node_modules/
├── public/
│ └── favicon.ico
├── src/
│ ├── assets/
│ │ ├── logo.svg
│ │ ├── quasar.png
│ │ ├── vite.svg
│ │ └── vue.png
│ ├── components/
│ │ └── HelloWorld.vue
│ ├── layouts/
│ │ └── MainLayout.vue
│ ├── pages/
│ │ ├── About.vue
│ │ ├── Index.vue
│ │ └── NotFound.vue
│ ├── router/
│ │ ├── index.ts
│ │ └── routes.ts
│ ├── stores/
│ │ └── note.ts
│ ├── styles/
│ │ └── quasar-variables.sass
│ ├── App.vue
│ ├── env.d.ts
│ └── main.ts
├── .env
├── .eslintrc.cjs
├── .gitignore
├── .prettierignore
├── .prettierrc
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
package.json
A1部分でコマンドを定義しています。
開発サーバの起動(yarn dev
)ではvite
コマンドが実行されます。
vite
コマンドではViteの設定ファイルvite.config.ts
が使用されます。
ビルド処理(yarn build
)では、Viteのビルドコマンドvite build
を実行する前にvue-tsc --noEmit
が実行されます。vue-tsc
はtsc
のVue拡張版で、TypeScriptの型チェックを行います。Viteには型チェック機能がないので前処理を入れて処理を補います。
設定ファイルはTypeScriptのコンパイラの設定tsconfig.json
が使用されます。
package.jsonに追加するパッケージを見ていきます。
{
"name": "vue3-quasar-frontend",
"version": "1.0.0",
"license": "MIT",
"type": "module",
"scripts": { // [A1]
"dev": "vite",
"build": "vue-tsc --noEmit && vite build",
"preview": "vite preview",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore"
},
"dependencies": {
"@quasar/extras": "^1.16.9",
"pinia": "^2.1.7",
"quasar": "^2.14.1",
"vue": "^3.3.11",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@quasar/vite-plugin": "^1.6.0",
"@rushstack/eslint-patch": "^1.6.0",
"@types/node": "^20.10.4",
"@vitejs/plugin-vue": "^4.5.2",
"@vue/eslint-config-prettier": "^8.0.0",
"@vue/eslint-config-typescript": "^12.0.0",
"eslint": "^8.55.0",
"eslint-plugin-vue": "^9.19.2",
"prettier": "^3.1.1",
"sass": "1.69.5",
"typescript": "^5.3.3",
"vite": "^5.0.7",
"vue-tsc": "^1.8.25"
}
}
Viteの機能拡張
ViteがVue、Quasarの構文を処理できるようにプラグインを追加します。
Vueプラグイン
パッケージ@vitejs/plugin-vue
をインストールします。
Quasarプラグイン
以下のパッケージをインストールします。
- quasar
- sass
- @quasar/vite-plugin
- @quasar/extras(オプション)
G1部分のようにフォントやアイコンフォントを追加しない場合は@quasar/extras
は不要です。利用可能なリソースは以下から参照できます。
Viteの設定
Viteの設定ファイルvite.config.ts
にVueとQuaserのプラグインの記述を追加します。(B1)
import path from 'node:path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { quasar, transformAssetUrls } from '@quasar/vite-plugin'
export default defineConfig({
plugins: [ // [B1]
vue({
template: { transformAssetUrls }
}),
quasar({
autoImportComponentCase: 'pascal',
sassVariables: 'src/styles/quasar-variables.sass'
})
],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src') // [B2]
}
},
server: { // [B3]
port: 3001
},
preview: { // [B4]
port: 3001
}
})
プラグインの記述の場所で設定も行います。
autoImportComponentCase: 'pascal'
で、タグの記述方法をPascal形式にしています。
<q-btn /> ⇒ <QBtn />
ESLintのVue対応
.vue
ファイルに対応するために以下のパッケージを追加します。Prettierも併用するので設定の衝突回避用のパッケージも追加します。
- eslint-plugin-vue
- @rushstack/eslint-patch
- @vue/eslint-config-prettier
- @vue/eslint-config-typescript
ESLintの設定
.eslintrc.cjs
にESLintの設定を行います。
ESLintとPrettierは処理が衝突するのでeslint-config-prettier
のVue拡張版@vue/eslint-config-prettier
というモジュールで回避します。これをextends
の最後に追加します。(C1)
他は好みで設定します。
/* eslint-env node */
require('@rushstack/eslint-patch/modern-module-resolution')
module.exports = {
root: true,
extends: [
'plugin:vue/vue3-essential',
'eslint:recommended',
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier' // [C1]
],
parserOptions: {
ecmaVersion: 'latest'
},
rules: {
'no-extra-boolean-cast': 'off',
'no-unreachable': 'error',
'vue/multi-word-component-names': 'off'
}
}
開発環境に関係するパッケージとプロジェクトへの組み込み方法は以上です。
index.html
プロジェクトルートにあるindex.html
はただのHTMLファイルではなくてViteの設定ファイル的なファイルです。src
ディレクトリ外に配置されているのはViteの仕様です。index.htmlの位置を変更すると泥沼にはまるようです。
ビルド処理では、Viteがindex.htmlを解析し、ここから参照されているVueモジュールをビルド対象とします。Vueモジュールのビルドが完了すると、Vueモジュールへのリンクが更新されたdist/index.html
が生成されます。
index.htmlから/favicon.ico
のパスで参照されるfavicon.ico
ファイルはpublic
ディレクトリに配置します。ビルドするとpublic
ディレクトリのすべてのファイルがdist
ディレクトリにコピーされます。
結果としてリソースファイル類はpublic
とsrc/assets
に配置することになります。
Viteのビルド対象はindex.htmlから検出するという仕組みのために、この部分はあまりすっきりしない仕様です。
環境変数の取得
Viteの実行環境で利用できる環境変数はVITE_XXXXX
の形式で.env
ファイルに定義します。Viteの開発サーバを起動すると.envファイルは自動的に読み込まれます。
読み込まれた環境変数は、index.html
ファイルや.vue
ファイルで、%VITE_APP_XXXXX
やimport.meta.env.VITE_XXXXX
のように取得できます。(D1、E1)
ビルドモジュールでも同じように取得できるようにするには、TypeScriptコンパイラの設定ファイルtsconfig.json
にtypes:vite/client
を追加します。(F1)
.envファイルの内容はビルド時にビルドモジュール内に取り込まれるので、.envファイルを変更した場合は再ビルドが必要になります。
VITE_APP_TITLE="プロジェクトタイトル"
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%VITE_APP_TITLE%</title> <!-- [D1] -->
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
<template>
<QLayout view="hHh lpR fFf">
<QHeader class="bg-primary" elevated>
<QToolbar>
<QToolbarTitle>
<router-link to="/">
<QAvatar>
<img src="@/assets/logo.svg" />
</QAvatar>
</router-link>
<router-link
to="/"
class="text-white"
style="text-decoration: none; vertical-align: middle"
> {{ title }}
</router-link>
</QToolbarTitle>
<QSpace />
<QBtn stretch flat label="About" to="/about" />
</QToolbar>
</QHeader>
<QPageContainer>
<router-view />
</QPageContainer>
</QLayout>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const title = ref(import.meta.env.VITE_APP_TITLE) // [E1]
</script>
{
"compilerOptions": {
"target": "esnext",
"types": ["vite/client"], // [F1]
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
],
"skipLibCheck": true,
"allowJs": true,
"paths": {
"@/*": ["./src/*"] // [F2]
}
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
]
}
env.d.ts
VSCode上で環境変数の入力補完が効くようにTypeScriptの型定義ファイルsrc/env.d.ts
を追加します。
interface ImportMetaEnv {
readonly VITE_APP_TITLE: string
}
パスエイリアス
相対パス表記をsrc
ディレクトリをルートとした表記に変更します。
開発サーバとビルド用と別々に設定が必要です。Viteの設定ファイルvite.config.ts
とTypeScriptコンパイラの設定ファイルtsconfig.json
に設定を追加します。(B2、F2)
import HelloWorld from '../components/HelloWorld.vue'
⇒ import HelloWorld from '@/components/HelloWorld.vue'
main.ts
index.html
から/src/main.ts
のパスで検出されるモジュールです。
Vue RouterとPiniaの初期処理が含まれています。
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import { Quasar } from 'quasar'
import quasarLang from 'quasar/lang/ja' // 日本語対応
// CSS読み込み
import '@quasar/extras/material-icons/material-icons.css' // アイコンフォント [G1]
import 'quasar/src/css/index.sass' // Quasar CSS
import App from './App.vue'
import router from './router'
// アプリケーション作成
const app = createApp(App)
// 言語、プラグイン追加
app.use(Quasar, {
lang: quasarLang,
plugins: {} // プラグイン
})
// Pinia初期化
app.use(createPinia())
// ルータ初期化
app.use(router)
// index.htmlに連結
app.mount('#app')
Vueルータ、Pinia
サンプルでは非常に簡単なパターンで使用しているため説明は省略します。
おわりに
初歩的な知識はとばして、ポイントに絞って簡単にフロントエンドの開発を説明しました。
要点はソースコードの方に入っているので、サンプルを確認してもらうといいと思います。
次はバックエンドの開発からフロントエンドを統合までの解説の
「Nodejsフロントエンドとバックエンドの開発(統合編)」に続きます。
関連するサンプル
同様の主旨のVite + Vueフロントエンドプロジェクト
Vuetify
Tailwind + Flowbite
Discussion