🙈

Nodejsフロントエンドとバックエンドの開発(Quasarフロントエンド編)

2023/12/15に公開

はじめに

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 RouterPiniaだけを追加した最小限構成です。

https://github.com/czbone/vue3-quasar-frontend

使い方

yarn installでモジュールをインストールします。起動コマンドyarn devを実行すると、Viteの開発サーバが立ち上がります。
後編の統合編に合わせるために使用ポートは3001です。(B3B4)

# 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ディレクトリに生成するのが目的です。
フロントエンドのプログラムソースに該当するのが、srcpublicディレクトリおよび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-tsctscのVue拡張版で、TypeScriptの型チェックを行います。Viteには型チェック機能がないので前処理を入れて処理を補います。
設定ファイルはTypeScriptのコンパイラの設定tsconfig.jsonが使用されます。

package.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プラグイン

https://quasar.dev/start/vite-plugin

以下のパッケージをインストールします。

  • quasar
  • sass
  • @quasar/vite-plugin
  • @quasar/extras(オプション)

G1部分のようにフォントやアイコンフォントを追加しない場合は@quasar/extrasは不要です。利用可能なリソースは以下から参照できます。

https://www.npmjs.com/package/@quasar/extras

Viteの設定

Viteの設定ファイルvite.config.tsにVueとQuaserのプラグインの記述を追加します。(B1)

vite.config.ts
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)
他は好みで設定します。

.eslintrc.cjs
/* 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ディレクトリにコピーされます。
結果としてリソースファイル類はpublicsrc/assetsに配置することになります。
Viteのビルド対象はindex.htmlから検出するという仕組みのために、この部分はあまりすっきりしない仕様です。

環境変数の取得

Viteの実行環境で利用できる環境変数はVITE_XXXXXの形式で.envファイルに定義します。Viteの開発サーバを起動すると.envファイルは自動的に読み込まれます。
読み込まれた環境変数は、index.htmlファイルや.vueファイルで、%VITE_APP_XXXXXimport.meta.env.VITE_XXXXXのように取得できます。(D1E1)

ビルドモジュールでも同じように取得できるようにするには、TypeScriptコンパイラの設定ファイルtsconfig.jsontypes:vite/clientを追加します。(F1)
.envファイルの内容はビルド時にビルドモジュール内に取り込まれるので、.envファイルを変更した場合は再ビルドが必要になります。

.env
VITE_APP_TITLE="プロジェクトタイトル"
index.html
<!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>
MainLayout.vue
<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"
            >&nbsp;{{ 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>
tsconfig.json
{
  "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を追加します。

env.d.ts
interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
}

パスエイリアス

相対パス表記をsrcディレクトリをルートとした表記に変更します。
開発サーバとビルド用と別々に設定が必要です。Viteの設定ファイルvite.config.tsとTypeScriptコンパイラの設定ファイルtsconfig.jsonに設定を追加します。(B2F2)

import HelloWorld from '../components/HelloWorld.vue'import HelloWorld from '@/components/HelloWorld.vue'

main.ts

index.htmlから/src/main.tsのパスで検出されるモジュールです。
Vue RouterPiniaの初期処理が含まれています。

main.ts
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

https://github.com/czbone/vue3-vuetify-frontend

Tailwind + Flowbite

https://github.com/czbone/vue3-tailwind-flowbite-frontend

Discussion