Svelteで自分のサイトを開発する。
技術選定
サイト・管理画面共通
- SvelteKit
- Cloudflare
- Workers, D1
- R2
- DrizzleORM
- Zod
- Tailwind CSS
- shadcn-svelte
- Lucide Svelte
- svelte-exmarkdown
管理画面
- Superforms
- Lucia
開発環境
- Neovim
- TypeScript
- 今後はJS + JSDocに移行したいところ
- Eslint
- Prettierは使用していない
情報を集める際に、次のように分けて集めると良いかも知れない(SvelteKitとD1、DrizzleORMの組み合わせをピンポイントで扱っている記事があまり見当たらないので)。
- SvelteKitとD1
- SvelteKitとDrizzleORM
- Cloudflare D1とDrizzleORM
参考リソース
- SvelteKit x TypeScriptの書き方
- SvelteKitとD1
- SvelteKitとDrizzleORM
- Cloudflare D1とDrizzleORM
- hoge
公式のためになるリンク
記事投稿とコメントのテーブルを考えてみた。
DrizzleORMでは現段階ではCHECK制約はサポートされていないようなので、フロント側で弾くことになりそう。
自分で考えたSQL
-- 投稿した記事の管理
create table posts (
id integer primary key,
title text not null check (length(title) > 0),
content text not null check (length(content) > 0),
created_at text not null default current_timestamp,
updated_at text not null default current_timestamp,
deleted_at text not null default (datetime('1990-12-31 23:59:59')),
is_published boolean default true
);
-- 記事に対するコメントの管理
create table comments (
id integer primary key,
post_id integer not null,
author_name text not null check (length(author_name) > 0),
content text not null check (length(content) > 0),
created_at text not null default current_timestamp,
updated_at text not null default current_timestamp,
deleted_at text not null default (datetime('1990-12-31 23:59:59')),
foreign key (post_id) references posts (id) on delete cascade
);
DrizzleORMでの実装
import { relations, sql } from 'drizzle-orm';
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
export const posts = sqliteTable('posts', {
id: integer('id').primaryKey(),
title: text('title').notNull(),
content: text('content').notNull(),
createdAt: text('created_at')
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
updatedAt: text('updated_at')
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
deletedAt: text('deleted_at')
.notNull()
.default(sql`(datetime('1990-12-31 23:59:59'))`),
isPublished: integer('is_published', { mode: 'boolean' }).default(true),
});
export const postsRelations = relations(posts, ({ many }) => ({
comments: many(comments),
}));
export const comments = sqliteTable('comments', {
id: integer('id').primaryKey(),
postId: integer('post_id')
.notNull()
.references(() => posts.id, { onDelete: 'cascade' }),
authorName: text('author_name').notNull(),
content: text('content').notNull(),
createdAt: text('created_at')
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
updatedAt: text('updated_at')
.notNull()
.default(sql`CURRENT_TIMESTAMP`),
deletedAt: text('deleted_at')
.notNull()
.default(sql`(datetime('1990-12-31 23:59:59'))`),
});
export const commentsRelations = relations(comments, ({ one }) => ({
post: one(posts, {
fields: [comments.postId],
references: [posts.id],
}),
}));
DrizzleORMによって生成されたSQL
CREATE TABLE `comments` (
`id` integer PRIMARY KEY NOT NULL,
`post_id` integer NOT NULL,
`author_name` text NOT NULL,
`content` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`deleted_at` text DEFAULT (datetime('1990-12-31 23:59:59')) NOT NULL,
FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `posts` (
`id` integer PRIMARY KEY NOT NULL,
`title` text NOT NULL,
`content` text NOT NULL,
`created_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updated_at` text DEFAULT CURRENT_TIMESTAMP NOT NULL,
`deleted_at` text DEFAULT (datetime('1990-12-31 23:59:59')) NOT NULL,
`is_published` integer DEFAULT true
);
トラブルシューティング
SvelteKitとD1の開発環境起動
platform.env は本番向けビルドでのみ利用することができます。ローカルでテストするには wrangler を使ってください
起動方法がまだ分かっていない。
→wrangler d1 execute <DATABASE_NAME> --file xxx/xxx.sql --local
を実行し、ローカル環境のDBにSQL実行した後に、wrangler dev
でローカル環境立ち上げでできた。
各種設定パッケージ・設定ファイルの詳細がよく分からないので調べていきたい。
パッケージ
キャッチアップ済み | パッケージ名 | キャッチアップ内容 |
---|---|---|
x | typescript-eslint/eslint-plugin | ESLintがTypeScriptをサポートできるようにするためのパッケージ。TypeScript用のLintが用意されている。 |
x | typescript-eslint/parser | 上記のTypeScript用パーサ |
x | eslint | ESLint |
x | eslint-config-prettier | Prettier公式パッケージ。Prettierと競合するESLintルールを無効化する。 |
x | eslint-plugin-svelte | ESLintがSvelteをサポートできるようにするためのパッケージ。Svelteのパーサも含まれている? |
x | prettier | Prettier |
x | prettier-plugin-svelte | PrettierがSvelteをサポートできるようにするためのパッケージ。 |
x | vite | ヴィート。ビルドツール。詳細はまだよく分かっていない。 |
設定ファイル
キャッチアップ済み | ファイル名 | キャッチアップ内容 |
---|---|---|
x | .eslintignore | ESLintのリント対象外にするファイル、ディレクトリを指定。 |
x | .eslintrc.cjs | ESLintの設定ファイル。 |
x | .gitignore | Gitの管理対象外にするファイル、ディレクトリを指定。 |
x | .npmrc | npmの設定ファイル。コンソール表示を無効にできたり挙動を変えられるそうだが詳細はよく分かっていない。 |
x | .prettierignore | Prettierの対象外にするファイル、ディレクトリを指定。 |
x | .prettierrc | Prettierの設定ファイル。 |
x | svelte.config.js | SvelteKitの設定ファイル。 |
x | tsconfig.json | TypeScriptのコンパイラオプションなどの挙動を設定。 |
x | vite.config.ts | Viteの設定ファイル。いまいちよく分かっていない。 |
ダークモードの対応方法について
この方法が良さそう?
cookieの有効期限はこれでいいのかという気がするが。
https://www.davidwparker.com/posts/dark-mode-in-sveltekit-with-and-without-javascript
→SvelteKitのバージョンが古いのか、記述方法が変わっている部分が多く解読が困難なため断念。
代わりにこのコードを参考にするのが良さそう。
別スクラップで対応できたのでメモ。
このパッケージほんと助かる。
記事詳細部分ではこのパッケージに乗っかることにした。
投稿詳細ページのMarkdownのスタイル候補。
導入済み
-
https://github.com/remarkjs/remark-gfm
- svelte-exmarkdownに同梱されているパッケージ
- https://github.com/rehypejs/rehype-external-links
- https://github.com/rehypejs/rehype-highlight
要検討
-
https://rehype-pretty-code.netlify.app/
-
シンタックスハイライトはこれでいい気がする- この問題にぶち当たったので、結局rehype-highlightでハイライトするように戻した。ライト、ダークモード両方でも見やすさが変わらない(コード部分の背景色がページの背景色と同化しにくい)tokyo-night-dark.cssを当てた。
-
スタイル調整をだいぶ頑張って見た目がいい感じになった。
Typography - shadcn-svelteのタイポグラフィーに少し自分好みのアレンジを加えた見た目にしたかったので、tailwindcss/typographyは使わなかった。
TODO
- Styles
- footnote
- scrollbar
- Markdown support
- Twitter(X), YouTube embed
-
<img width="" height="" />
support
- Font
- OGP
- head, meta
- <title />
- <meta name="author" />
- <meta name="description" />
- Twitter(X)
- Page
- 404
SvelteKitにフォントを導入する手順はこの記事が詳しい。
FontsourceがGoogle Fontsも含めてnpmパッケージとして提供しているそうで強い。
この記事はSvelteKit + Tailwind CSS でのフォント変更について解説していて分かりやすい。
Google Fontsのおすすめフォントがまとまってて分かりやすい。
個人的にはこのフォントが好きで使いたいので、ライセンスなどを確認しておく。
対応できたのでここにまとめた。
最初に書く記事の案
- SvelteKitでSSRに対応したダークモード対応を行う方法
- SvelteKit + Tailwind CSSのフォント差し替え
管理画面の実装に着手した。
なるべくサイトと同じようにシンプルに作りたいところ。
記事の管理画面にはshadcn-svelteのData Tableを使うのがちょうど良さそう。
投稿管理画面を仮でおいた。
管理画面ではヘッダー右側のPostsをクリックすると、投稿一覧画面へ遷移するようにする予定。
サイトと同じ見た目のヘッダーを使いつつ、サイト側ではPostsをクリックすると閲覧用の投稿一覧画面が表示され、管理画面側では投稿の管理画面が表示されるようにしている。
管理画面の実装進捗。
結構いい感じのシンプルな見た目になったんではないか。
OGPの実装をした。動的に画像を生成するのは面倒そう(satoriで実装する方法)だったので、一旦静的な画像ファイルを返しているだけ。
この後、色々あってT3 Stack(Next.js) + Vercel(サイトデプロイ、Vercel Postgres, Vercel Blob)を使っていくことにした。
只今絶賛書き換え中。
- Next.js(App Router)
- markdown-it