svelte/コード配置の整理

(137d) 更新


プログラミング/svelte

特定ページでしか使わないコードはそのフォルダに置く

いろいろ書いてて分かってきたことには、 フォームのバリデータとか、ユーティリティ関数とかはなるべく src/lib に入れず、 src/routes 下の、それを使うページがあるあたりに置くのが良さそう。

いろいろ移動しよう。

superforms の zod スキーマ

src/lib/zod/ に置いていたフォームスキーマを、それを使うページのフォルダーへ移して zod-schema.ts という名前にする。

src/lib/zod/account/new.ts を src/routes/account/(logout)/new/zod-schema.ts みたいな

すると、使う側のコードはたいてい同じでいいので楽もできる。

LANG: ts
import { schema } from './zod-schema';

src/lib/zod/lib/passwordAndConfirm.ts と src/lib/zod/lib/emailRegexp.ts も

src/routes/accounts/zod-passwordAndConfirm.ts
src/routes/accounts/zod-emailRegexp.ts とする

src/lib/zod/articles/new.ts は src/routes/articles/zod-schema.ts

これだけ動かしても import は自動的に書き換えてくれるし、 最後に pnpm check でエラーがなければ大抵問題ないというのは本当にすごい。

LANG: console
$ git status
       deleted:    src/lib/zod/account/reset.ts
       modified:   src/routes/account/(login)/edit/+page.server.ts
       renamed:    src/lib/zod/account/edit.ts -> src/routes/account/(login)/edit/zod-schema.ts
       modified:   src/routes/account/(logout)/new/[token]/+page.server.ts
       renamed:    src/lib/zod/account/new.ts -> src/routes/account/(logout)/new/[token]/zod-schema.ts
       modified:   src/routes/account/(logout)/reset/[token]/+page.server.ts
       new file:   src/routes/account/(logout)/reset/[token]/zod-schema.ts
       modified:   src/routes/account/[purpose=emailVerificationPurpose]/+page.server.ts
       renamed:    src/lib/zod/account/emailVerification.ts -> src/routes/account/[purpose=emailVerificationPurpose]/zod-schema.ts
       renamed:    src/lib/zod/lib/emailRegexp.ts -> src/routes/account/zod-emailRegexp.ts
       renamed:    src/lib/zod/lib/passwordAndConfirm.ts -> src/routes/account/zod-passwordAndConfirm.ts
       modified:   src/routes/articles/(login)/new/+page.server.ts
       modified:   src/routes/articles/[...titleOrId]/(login)/edit/+page.server.ts
       renamed:    src/lib/zod/articles/new.ts -> src/routes/articles/zod-schema.ts
       modified:   src/routes/session/(logout)/new/+page.server.ts
       renamed:    src/lib/zod/session/new.ts -> src/routes/session/(logout)/new/zod-schema.ts
$ git commit -m "zod スキーマをフォームと同じフォルダに置くようにした"

db にメソッドを生やすのを止めよう

これも使うコードに近いところに置くべきだ。

articles 関連

lib/server/db にあった articleTitleEncode, articleTitleDecode, newestArticle を移して、

routes/articles/lib.ts

LANG: ts
export function encodeTitle(title: string) {
  return title
    .split('/')
    .map((str) => encodeURIComponent(str).replaceAll('%20', '+'))
    .join('/');
}

export function decodeTitle(encoded: string) {
  return encoded
    .split('/')
    .map((str) => decodeURIComponent(str.replaceAll('+', '%20')))
    .join('/');
}

とする。

また、$lib/server の path に Article を与えられるというのも所有権がはっきりしないので良くないので、 routes/articles/lib-server.ts に移した。

→ あとでさらに routes/articles/lib.ts に移した

routes/articles/lib-server.ts

import type { Article, User } from '@prisma/client';
import { db } from '$lib/server/db';
import { path as stringPath } from '$lib/server';
import { encodeTitle } from './lib';

export function path(article: Article) {
  return stringPath('/articles/' + encodeTitle(article.title));
}

export async function getNewest(article: (Article & { author: User }) | number | null) {
  if (!article) {
    return null;
  }
  while (typeof article == 'number' || article?.newRevisionId) {
    article = await db.article.findUnique({
      where: {
        id: typeof article == 'number' ? article : article.newRevisionId!,
        deletedAt: null,
      },
      include: { author: true },
    });
  }
  return article;
}

あとは pnpm check で出てくるエラーをつぶせばOK。

quickfix で必要な import を追加してくれたりするので非常に楽だ。

LANG: console
$ git add . && git commit -m "route/articles/lib.ts, route/articles/lib-server.ts へ処理をまとめた"
       modified:   src/lib/server/db.ts
       modified:   src/lib/server/index.ts
       modified:   src/routes/articles/(login)/new/+page.server.ts
       modified:   src/routes/articles/[...titleOrId]/(login)/edit/+page.server.ts
       modified:   src/routes/articles/[...titleOrId]/+page.server.ts
       modified:   src/routes/articles/[...titleOrId]/articleFromTitleOrId.ts
       new file:   src/routes/articles/lib-server.ts
       new file:   src/routes/articles/lib.ts

Role 関連

これは routes 下にはそれらしい場所がないんだよね・・・

$lib/server かなあ

src/lib/server/role.ts

LANG: ts
import { db } from '$lib/server/db';

// userId が undefined なら権限は空とみなされる
export async function getRoles(userId: string | undefined) {
  if (!userId) {
    return [];
  }
  return (
    (
      await db.user.findUnique({
        where: { id: userId },
        select: { roles: true },
      })
    )?.roles || []
  );
}

export async function getRolesString(userId: string | undefined) {
  return (await getRoles(userId)).map(role => role.name);
}

export async function hasRole(userId: string | undefined, role: string) {
  return (await getRolesString(userId)).includes(role);
}

export async function addRoles(userId: string, ...roles: string[]) {
  await db.user.update({
    where: { id: userId },
    data: {
      roles: {
        connectOrCreate: roles.map((role) => ({ where: { name: role }, create: { name: role } })),
      },
    },
  });
}

export async function removeRoles(userId: string, ...roles: string[]) {
  await db.user.update({
    where: { id: userId },
    data: {
      roles: {
        deleteMany: roles.map((role) => ({ name: role })),
      },
    },
  });
}

src/lib/server/db.ts

LANG: ts
import { PrismaClient } from '@prisma/client';

export const db = new PrismaClient();
LANG: console
$ git add . && git commit -m "$lib/server/role.ts へ処理を移した"
       modified:   src/lib/server/db.ts
       new file:   src/lib/server/role.ts
       modified:   src/routes/account/(logout)/new/[token]/+page.server.ts

雑感

これまでオブジェクト指向と言いつつクラス指向でプログラミングしてきた弊害がこういうところに現れていると感じる。

typescript だと例えば db はクラスじゃなくオブジェクトとしてグローバルに存在するので、 role.ts という別のライブラリからも簡単にアクセスできる。

グローバル変数を絶対悪としてクラス指向でプログラミングしてきた世代だと なんとなく db の「クラス」にメソッドを生やしたくなっちゃう。

この違いを認識して正していく必要がある。

urlRoot と path

・・・以下も的外れだった。Svelte 標準でちゃんと提供されていたので次のようにするのが正解:
import { base } from '$app/paths'

https://azukiazusa.dev/blog/using-environment-variables-with-sveltekit/

のような形でクライアント側から環境変数にアクセスできるので、

.env

PUBLIC_URL_ROOT=""

としておいて、

src/lib/index.ts

import { PUBLIC_URL_ROOT } from '$env/static/public';

export function path(relative: string) {
  return PUBLIC_URL_ROOT + relative;
}

とすればよかった。

  • src/lib/server/index.ts から urlRoot と path を取り除く
  • src/lib/index.ts は path は移す
  • app.d.ts の PageData に追加した urlRoot も取り除く
  • src/routes/articles/lib-server.ts の path は src/routes/articles/lib.ts に移す

最終的に、

  • path は $lib から import する
  • urlRoot を使っていたところは PUBLIC_URL_ROOT を使う

とする。

LANG: console
$ git add . && git commit -m "PUBLIC_URL_ROOT の参照の仕方を大きく変えた"
       modified:   src/app.d.ts
       modified:   src/hooks.server.ts
       modified:   src/lib/index.ts
       modified:   src/lib/server/index.ts
       modified:   src/routes/+layout.server.ts
       modified:   src/routes/+page.svelte
       modified:   src/routes/account/(login)/edit/+page.svelte
       modified:   src/routes/account/(login)/email/[token]/+page.server.ts 
       modified:   src/routes/account/(logout)/new/[token]/+page.server.ts  
       modified:   src/routes/account/(logout)/new/[token]/+page.svelte     
       modified:   src/routes/account/(logout)/reset/[token]/+page.server.ts
       modified:   src/routes/account/[purpose=emailVerificationPurpose]/+page.server.ts
       modified:   src/routes/articles/(login)/new/+page.server.ts
       modified:   src/routes/articles/[...titleOrId]/(login)/edit/+page.server.ts
       modified:   src/routes/articles/[...titleOrId]/+page.server.ts
       modified:   src/routes/articles/lib-server.ts
       modified:   src/routes/articles/lib.ts
       modified:   src/routes/session/(login)/delete/+server.ts
       modified:   src/routes/session/(logout)/new/+page.server.ts
       modified:   src/routes/session/(logout)/new/+page.svelte

非常に大規模な変更になった(汗

pnpm check がなければやってられなかった。。。


Counter: 210 (from 2010/06/03), today: 1, yesterday: 1