2024年10月20日日曜日

Nuxt + PrimeVue をお試し中

Windows環境にあれこれ設定するのも嫌・・・

というかそもそもWindowsで開発するのが嫌なので、

うちに常時稼働しているRaspberryPiで開発することにする。

(別にWSLでもいいんだけど)

うちのRaspberryPiにはUbuntuが入っているので、VSCodeからリモートでつないでターミナルを起動


■全部すっ飛ばしてNuxtを使ってみる

まずはnpmを入れる

sudo apt install npm

終わったら次はプロジェクトを作成するので、適当なフォルダをつくる

mkdir nuxt_test

cd nuxt_test

続いて、プロジェクトを作成する

npx nuxi@latest init timer

プロジェクト名はtimerにしてみた

(タイマーを設定して動かすだけのページでも作ろうかと思ってみた)

しばらくするとプロジェクトができるので、ターミナルから起動してみる

npm run dev -- -o

しばらく待つと、http://localhost:3000で起動される

ラズパイのIPじゃなくてlocalhostなんだけど、なぜか動く(なんで?)


nuxtのGetting Startedのページを読み進めると、Nuxtのフォルダ構造が出てきた

pagesフォルダを作るのがいいらしい

timer/pages/index.vueを作る

中身はコレ

<template>
  <h1>Index page</h1>
</template>


つづいて、pagesを使う時にはapp.vueを書き換えるらしい

timer/app.vueはすでに存在しているので、中身を変える

<template>
  <div>
    <!-- Markup shared across all pages, ex: NavBar -->
    <NuxtPage />
  </div>
</template>

NuxtPageはpagesフォルダを使う設定らしい

これによって、pages/index.vueがhtmlとして解釈されて表示されるようになる。

ブラウザでIndex pageと表示されていればオッケー

続いて、今動いているターミナルを一度Ctrl+Cで止め、primeVueを入れる

npm install primevue

npm install --save-dev @primevue/nuxt-module

primeVueのテーマを入れる

npm install @primevue/themes

アイコンも入れとく

npm install primeicons


つづいて、nuxt.config.tsを編集

// https://nuxt.com/docs/api/configuration/nuxt-config
import Aura from '@primevue/themes/aura';
export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  modules: [
    '@primevue/nuxt-module'
  ],
  primevue: {
    options: {
      ripple: true,
      inputVariant: 'filled',
      theme: {
        preset: Aura,
        options: {
          prefix: 'p',
          darkModeSelector: 'system',
          cssLayer: false
        }
      }
    }
  }
})

ついでにpages/index.vueへボタンを追加してみる

<template>
  <h1>Index page</h1>
  <Button label="Check" icon="pi pi-check" />
</template>


ターミナルからnpm run devsして、

こんな感じで表示されればオッケー

ん?なんかCheckボタン形偏ってない?わざと?



まぁ気を取り直して

pages/index.vueを編集

IftaLabelというのを使ってみる

<script lang="ts">
import IftaLabel from 'primevue/iftalabel';
</script>


<template>
  <h1>Index page</h1>
  <Button label="Check" icon="pi pi-check" />
  <IftaLabel>
    <InputText id="username" v-model="value" />
    <label for="username">Username</label>
  </IftaLabel>
</template>


動かしっぱなしでもホットスワップ?
ページが変わってくれるのは便利


にしてもこの緑、気に入らない・・・
なんとかならんのかと説明を読んでいたら、Noirモードの定義をしているところがあった。
テーマのAuraをベースにして、あれこれカスタマイズしてNoirを設定している
nuxt.config.tsに結構書き込むことになるけどこんな感じ

// https://nuxt.com/docs/api/configuration/nuxt-config
import PrimeVue from 'primevue/config';
import { definePreset } from '@primevue/themes';
import Aura from '@primevue/themes/aura';

const Noir = definePreset(Aura, {
  semantic: {
      primary: {
          50: '{zinc.50}',
          100: '{zinc.100}',
          200: '{zinc.200}',
          300: '{zinc.300}',
          400: '{zinc.400}',
          500: '{zinc.500}',
          600: '{zinc.600}',
          700: '{zinc.700}',
          800: '{zinc.800}',
          900: '{zinc.900}',
          950: '{zinc.950}'
      },
      colorScheme: {
          light: {
              primary: {
                  color: '{zinc.950}',
                  inverseColor: '#ffffff',
                  hoverColor: '{zinc.900}',
                  activeColor: '{zinc.800}'
              },
              highlight: {
                  background: '{zinc.950}',
                  focusBackground: '{zinc.700}',
                  color: '#ffffff',
                  focusColor: '#ffffff'
              }
          },
          dark: {
              primary: {
                  color: '{zinc.50}',
                  inverseColor: '{zinc.950}',
                  hoverColor: '{zinc.100}',
                  activeColor: '{zinc.200}'
              },
              highlight: {
                  background: 'rgba(250, 250, 250, .16)',
                  focusBackground: 'rgba(250, 250, 250, .24)',
                  color: 'rgba(255,255,255,.87)',
                  focusColor: 'rgba(255,255,255,.87)'
              }
          }
      }
  }
});

export default defineNuxtConfig({
  compatibilityDate: '2024-04-03',
  devtools: { enabled: true },
  modules: [
    '@primevue/nuxt-module'
  ],
  primevue: {
    autoImport: true,
    options: {
      ripple: true,
      inputVariant: 'filled',
      theme: {
        preset: Noir,
        options: {
          darkModeSelector: false,
        }
      },
    }
  }
})



ついでに、アイコンも試そう

pages/index.vueを以下のように編集
<script setup lang="ts">
import Button from 'primevue/button';
import IftaLabel from 'primevue/iftalabel';
import 'primeicons/primeicons.css'
var value = "aiueo";
</script>


<template>
  <h1>Index page</h1>
  <Button label="Check" icon="pi pi-check" />
  <div class="card flex flex-col items-center gap-4">
    <div class="flex flex-wrap gap-4 justify-center">
      <Button icon="pi pi-home" aria-label="Save" />
      <Button label="Profile" icon="pi pi-user" />
      <Button label="Save" icon="pi pi-check" iconPos="right" />
    </div>
    <div class="flex flex-wrap gap-4 justify-center">
      <Button label="Search" icon="pi pi-search" iconPos="top" />
      <Button label="Update" icon="pi pi-refresh" iconPos="bottom" />
    </div>
  </div>
  <IftaLabel>
    <InputText id="username" v-model="value" />
    <label for="username">Username</label>
  </IftaLabel>
</template>

こんな感じの表示になる
あーそうか、Checkの左にチェックマークがあったから偏ってたのか・・・


アイコンも出たし、ようやくまともになってきた。
わたしがPrimeVueすごいなと思ったのは、カレンダーとか補完テキストボックスとか、
自分で作るのがめちゃくちゃ嫌なパーツがそろっていた点

■ちょっと落ち着いてどうなってるのか考察

そもそもVueとはJavaSctiptのフレームワークで、UIを構築できる。
UIはどうもブラウザUIだけではないらしい。

では、vueのアプリを作ってみる
mkdir ~/vue_test
cd vue_test
npm create vue@latest

途中プロジェクト名を聞かれるので、firstProjと入力しあとはただエンターを押した
cd firstProj

vscodeでfirstProjを開いてみた
ターミナルを開いて
npm install
npm run dev

ターミナルにlocalhost:xxxxと出るので、Ctrl+クリックで開くとページが表示される


次にターミナルからCtrl+cで止め、
npm run build

distフォルダにあれこれファイルが出来上がる
これを適当なWEBサーバにおいてあげればきっと動くのだろう
こうしてみるとNuxtの説明とあまり変わらないのだけど
さきほどの画面を見ると、vite+vue3と書かれている
どうやらviteというのがimport なんたら from なんたらという書き方をしているもので、
viteの作者とvueの作者は同じ人であり、viteはvueのビルドツール?という位置づけらしい
本気でわからなくなってきた

出来上がったものだけで判断すると・・・
nuxtはサーバサイドも開発できるもので、サーバはNitroを使っているという。
根っこはvueで、さらにビルドツールとしてviteがあるって感じなのだろうか
しかし、実際のビルドはnpmでやってるわけで・・・
npmの作者はたしか、npmの爆発問題を何とかするためにGoでdenoを作っていて、
途中からRustに移行してるんじゃなかったか?

コンポーネントベースで開発するというやり方を勉強するには、
まずはvueを勉強した方がよさそうなのだけど、
Nuxtでも結局同じようにコンポーネント単位で機能を実装していくことになるため、
vueのテストはここまでにして、
このままNuxtでコンポーネントの作り方を調査することとする。

■JSONのキーとバリュー再帰的にIftaLabelで表示したい

ではIftaLabelを簡単に表示するコンポーネントを作る
components/dispjs.vueを作成する
中身は
<template>
    <IftaLabel>
        <InputText id={{keyStr}} v-model=valStr readOnly=true />
        <label for={{keyStr}}>{{ keyStr }}</label>
    </IftaLabel>
</template>

<script setup>
const keyStr = defineModel('keyStr')
const valStr = defineModel('valStr')
</script>


続いて、index.vueにJSON入れたりあれこれしてdispjsを呼び出す

<script setup lang="ts">
import Button from 'primevue/button';
import IftaLabel from 'primevue/iftalabel';
import 'primeicons/primeicons.css'
import dispjs from '~/components/dispjs.vue';

const contentJSON = {
  Subject: "件名",
  date: "2024-10-19",
  entry: [
    { resource: { resourceType: "目次" } },
    { resource: { resourceType: "はじめに" }, display: "本書を書くにあたり、多くの方にご助力いただきました事を感謝します。", author: ["Aさん", "Bさん", "Cさん"] },
    { resource: { resourceType: "一章" }, display: "まずは動かしてみましょう。" },
    { resource: { resourceType: "二章" }, display: "つぎは応用してみましょう。" },
    { resource: { resourceType: "あとがき" }, display: "最後までお読みいただきありがとうございます。" },
  ]
}

// function dispRecurse(d: any) {
//   if (typeof d === 'object' && d !== null) {
//     for (const key in d) {
//       if (d.hasOwnProperty(key)) {
//         console.log(`${key}:`)
//         dispRecurse(d[key]);
//       }
//     }
//   } else {
//     console.log(`${d}`)
//   }
// }
// dispRecurse(contentJSON);
</script>


<template>
  <h1>Index page</h1>
  <div v-for="(val, key) in contentJSON">
    <dispjs :keyStr="key" :valStr="val" />
  </div>
</template>

ちょっとJavaScriptでどうなるのか試したコードがあるけどコメントアウトしとく・・・
v-forでやってるんで再帰できてない・・・
画面表示はこんな感じ

entryの中は配列で入っているので、上のソースの再帰処理できちんと出せるんだけど、
これをコンポーネント化する方法がまだよくわかってない。
dispjsはラベルとインプットを一緒くたにしたIftaLabelというものを使って表示している。
この際、引数ですごく悩んだ。
export propsとかinterfaceとかやったけど、InputTextでv-model使っててうまくいかなかった。
そこでコンポーネントでもv-modelで引数を設定してやると、
双方向でバインドできるらしく、
Vue3.4以降はdefineModelを使うと楽よ?という情報は手に入ったのでやってみたらうまくいった。

■まとめ
正直全然わかってない・・・
コンポーネントとの引数のやりとりは、v-modelでひと段落したということになるのだろうか?それともまだまだ変わるのだろうか?

Componentで関数書いて再帰処理とかのやり方もまだわからない。

いや、分かった
しかし、よく考えたらそんなことせずとも
scriptで再帰してデータをフラット化して
DataTableやv-forで表示した方がVueとしては正当なやり方だと考えついた。
ので、再帰はできるけど敢えてやらない