プログラミング/svelte/svelte5手順覚書 の履歴(No.3)

更新


プログラミング/svelte

概要

あとで使えるように、いろいろな手順を書いておく。

開発環境の整備

git alias

~/.gitconfig

[alias]
	ss = status
	br = branch
	co = checkout
	com = commit
	comm = commit -m
	log1 = log -1
	log2 = log -2
	log3 = log -3
	logo = log --oneline --pretty=format:'%C(Yellow)%h%Creset %ad %C(Green)%s%Creset' --date=short
	logn = log --name-status --oneline --pretty=format:'%C(Yellow)%h%Creset %ad %C(Green)%s%Creset' --date=short

peco

ソフトウェア/peco で紹介したもの。

インストールは https://github.com/peco/peco/releases から peco_windows_amd64.zip を落としてきて、中の peco.exe を ~/bin に置くだけ。

~/.bash_profile に以下を追加して

export PATH=$PATH:~/bin

ログインしなおして、

LANG:console
$ ls | peco

でファイル一覧からの選択画面が表示されたら成功。

~/.gitconfig

[alias]
  # SYNTAX: git d
  #
  # 履歴一覧を表示して選択した履歴との diff を取る
  # 初期状態のまま Enter を押せば単に git diff と打ったのと同様に HEAD と working copy との間の差分をとる
  # 履歴を1つ選んで Enter を押せば、その履歴と working copy との差分を取る
  # Ctrl+space で2つ選べば、その間の差分を取る
  #
  d = !git diff --minimal --ignore-all-space `git log --oneline --branches | peco | ruby -e 'STDIN.readlines.reverse.each{|s| puts s[0..6]}'`

  # SYNTAX: git addq
  #
  # git add するファイルを peco で選択する
  addq= "!if [ \"`git status --porcelain | grep -E '^.[^ ]'`\" != \"\" ]; then git add `git status --porcelain | grep -E '^.[^ ]' | peco | sed -e 's/.* //'`; fi"

  # SYNTAX: git rmq
  #
  # git rmするファイルを peco で選択する
  rmq= "!if [ \"`git status --porcelain | grep -E '^.[^ ]'`\" != \"\" ]; then git rm `git status --porcelain | grep -E '^.[^ ]' | peco | sed -e 's/.* //'`; fi"

  # SYNTAX: git resetq
  #
  # git reset するファイルを peco で選択する
  resetq = "!if [ \"`git status --porcelain | grep -E '^[^\\? ]'`\" != \"\" ]; then git reset `git status --porcelain | grep -E '^[^\\? ]' | peco | sed -e 's/.* //'`; fi"

VSCode

  • ターミナルから code <file名> とするとエディタ上にファイルを開ける

プロジェクトの作成

LANG: console
$ pnpm create svelte@latest <projectname>
$ cd <projectname>
$ pnpm install && git init && git add -A && git commit -m "Initial commit"
$ pnpm test
$ pnpm dev

git add -A と git add . の違いは、git add -Aはレポジトリ内のどこで実行してもレポジトリ全体を処理するが、git add .はカレントディレクトリ以下のみを処理する、とのこと。 ← https://note.nkmk.me/git-add-u-a-period/

コンポーネントのアップデート

LANG: console
$ pnpm update && \
  git add -A && git commit -m "pnpm update"

prettier

.prettierrc

  {
-   "useTabs": true,
+   "useTabs": false,
    "singleQuote": true,
-   "trailingComma": "none",
+   "trailingComma": "es5",
+   "quoteProps": "consistent",
    "printWidth": 100,
    "plugins": ["prettier-plugin-svelte"],
    "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
  }
LANG: console
$ pnpm format && \
  git add -A && \
  git commit -m "prettier を通した"

prettier の自動実行

LANG: console
$ cat > .git/hooks/pre-commit
#!/bin/sh
FILES=$(git diff --cached --name-only --diff-filter=ACMR | sed 's| |\\ |g')
[ -z "$FILES" ] && exit 0

# Prettify all selected files
echo "$FILES" | xargs ./node_modules/.bin/prettier --ignore-unknown --write

# Add back the modified/prettified files to staging
echo "$FILES" | xargs git add

exit 0
^D
$ chmod u+x .git/hooks/pre-commit

pnpm

  • run を付ける必要がない
  • npx の代わりに pnpm dlx とする

テスト環境

LANG: console
$ pnpm test:integration     # playwright による結合テスト
$ pnpm test:unit            # vitest でユニットテストを行い、自動実行用に待機する
$ pnpm test                 # 上の2つを続けて行い、vitest で待機する
$ pnpm playwright test --ui # playwright の gui を立ち上げる

[vitest]

LANG: console
$ cat vite.config.ts 
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';

export default defineConfig({
        plugins: [sveltekit()],
        test: {
                include: ['src/**/*.{test,spec}.{js,ts}']
        }
});

となっているので、src/ の下のすべての .test.ts および .spec.ts がテストの対象になる。

[playwrite インストール]

LANG: console
$ pnpm dlx playwright install
$ echo /test-results >> .gitignore
$ cat playwright.config.ts 
import type { PlaywrightTestConfig } from '@playwright/test';
const config: PlaywrightTestConfig = {
        webServer: {
                command: 'pnpm build && pnpm preview', 
                port: 4173
        },
        testDir: 'tests',
        testMatch: /(.+\.)?(test|spec)\.[jt]s/
};

tests/ フォルダの *test.ts のようなファイルがテスト対象になる(test の前に . はなくてもいい)。

pnpm dev していない状況では、ファイルを更新した場合にはテスト前に pnpm build し直さなければならないっぽいのでその点には注意が必要だ。

プロジェクト

css

DaisyUI を入れる

https://daisyui.com/

LANG: console
$ pnpm dlx svelte-add@latest tailwindcss
◇  Do you want to use typography plugin?
│  Yes
$ pnpm add -D daisyui@latest

https://daisyui.com/docs/layout-and-typography/#-1 を参考に、

tailwind.config.ts

LANG: ts
+ import TailwindcssTypography from '@tailwindcss/typography';
+ import DaisyUI from 'daisyui';
   
  export default {
     //...
-   plugins: [],
+   plugins: [
+     TailwindcssTypography,
+     DaisyUI,
+   ],
+ 
+   daisyui: {
+     themes: ['light']
+   },
  } as Config;

データベース prisma

LANG: console
$ pnpm add -D prisma && pnpm add @prisma/client
$ pnpm dlx prisma init --datasource-provider sqlite && cat .env
$ echo "/prisma/*.db" >> .gitignore
$ echo "/prisma/*.db-journal" >> .gitignore
$ git add . && git commit -m "Prisma をインストールした"

認証 Lucia

https://lucia-auth.com/getting-started/sveltekit/ を見ながら

LANG: console
$ pnpm add lucia @lucia-auth/adapter-prisma

app.d.ts

LANG: ts
declare global {
  namespace App {
    ...
    // Locals を使うことで +page.server.ts 内で load から action へデータを渡せる
    interface Locals {
      user: import('lucia').User | null;
      session: import('lucia').Session | null;
    }
    interface PageData {
      user: import('lucia').User | null;
      session: import('lucia').Session | null;

フォームのバリデーション sveltekit-superforms

LANG: console
$ pnpm add -D sveltekit-superforms zod

使い方はこの辺り :

フラッシュメッセージ sveltekit-flash-message

https://github.com/ciscoheat/sveltekit-flash-message

LANG: console
$ pnpm add -D sveltekit-flash-message

app.d.ts

LANG: ts
declare global {
  namespace App {
    ...
    interface PageData {
      ...
      // https://github.com/ciscoheat/sveltekit-flash-message
      flash?: { type: 'success' | 'error'; message: string };

src\routes\+layout.server.ts

LANG: ts
import { loadFlash } from 'sveltekit-flash-message/server';

export const load = loadFlash(async (event) => {
  // その他の処理
 
  return { someData: 'data'  } // $page.data に受け渡すデータ
});

src\routes\+layout.svelte

LANG: ts
<script lang="ts">
  import { getFlash } from 'sveltekit-flash-message';
  import { page } from '$app/stores';

  const flash = getFlash(page);
  const { children } = $props();
</script>

{#if $flash}
  {@const bg = $flash.type == 'success' ? '#3D9970' : '#FF4136'}
  <div style:background-color={bg} class="flash">{$flash.message}</div>
{/if}

{@render children()}

もう少し凝りたければ、

flash-toast.svelte

LANG: html
<script lang="ts">
  /**
   * TODO: 複数のメッセージを同時に表示できるようにしたい
   */
  import { getFlash } from 'sveltekit-flash-message';
  import { page } from '$app/stores';

  let flashMessage = $state('' as string);
  let flashType = $state('success' as 'success' | 'error');
  let show = $state(false as boolean);

  // メッセージがセットされれば表示する
  const flash = getFlash(page);

  // メッセージがあれば初回表示時に表示する
  let firstTime = true;
  $effect(() => {
    if (firstTime) {
      firstTime = false;
      flash.subscribe(($flash) => {
        if ($flash) {
          let icon = $flash.type == 'success' ? '&#9989;' : '&#10060;';
          flashMessage = icon + ' ' + $flash.message;
          flashType = $flash.type;
          show = true;

          flash.set(undefined);
        }
      });
    }
  });

  function onanimationend() {
    show = false;
  }
</script>

<div class="flash-toast {flashType}" class:is-show={show} {onanimationend}>
  <!-- メッセージ部分 -->
  <div>{@html flashMessage}</div>
  <!-- 吹き出し部分 -->
  <div></div>
</div>

<!-- svelte-ignore css_unused_selector -->
<style type="postcss">
  .flash-toast {
    &.success > div {
      --color-back: #9de8af;
    }

    &.error > div {
      --color-back: #f1aeb5;
    }

    /* https://codepen.io/kandai/pen/qBEbgQv を参考にした */
    box-sizing: border-box;
    position: fixed;
    top: 10px;
    right: 0px;
    /* right: -220px; */
    transform: translateX(calc(100% + 10px));
    display: flex;
    align-items: start;

    /* メッセージ部分 */
    & > div:first-child {
      max-width: 25vw;
      color: #000;
      padding: 4px;
      border: 8px solid;
      border-radius: 8px 0px 8px 8px;
      background-color: var(--color-back);
      border-color: var(--color-back);
    }
    /* 吹き出しの三角形 */
    & > div:nth-child(2) {
      border: solid transparent;
      content: '';
      height: 0;
      width: 0;
      pointer-events: none;
      position: relative;
      border-top-width: 0px;
      border-bottom-width: 10px;
      border-left-width: 10px;
      border-right-width: 10px;
      border-radius: 0;
      border-left-color: var(--color-back);
    }

    &.is-show {
      animation: anime 4s ease 1 normal;
    }
  }

  @keyframes anime {
    /* 全部で 4s */
    12.5% {
      transform: translateX(0);
    } /* 0.5s で飛び出す */
    87.5% {
      transform: translateX(0);
    } /* 0.5s で引っ込む */
  }
</style>

のようなのを作っておいて、

src\routes\+layout.svelte

LANG: ts
<script lang="ts">
  import '../../app.css';
  import FlashToast from './flash-toast.svelte';

  const { children } = $props();
</script>

<FlashToast />
<div class="container">
  {@render children()}
</div>

のようにするとか。

メール送信 nodemailer

https://nodemailer.com/

LANG: console
$ pnpm add nodemailer && pnpm add -D @types/nodemailer

Counter: 453 (from 2010/06/03), today: 4, yesterday: 5