React をかじった後で svelte を見てみるとわかりみがある。 やってみる。

tutorial

Part1 Basic Svelte にさらっと目を通す。

vitesvelete 初期化。 markdown の posts を収集して表示する方法が解らぬ。 adapter-static が関係しそうだという感じがする。

Part3 Basic Svelte Kit も見る。 こっちの先に読んだほうがいいかも。重要。 まさに blog の例も書いてあった。

nvim

  • language-server

  • tree-sitter

入れた。

作業

mv src src.bak # 退避
npm init svelte # current に展開
# src, vite.config.ts, svelte.config.ts あたりが出現

原型

5つ作った。 普通に async load 関数で動くようなので中身作れば動きそう。

+ src/
  + lib/
    + getPosts.ts
  + routes/
    + posts/
      +page.svelte
      +page.server.ts
      [slut]/
        +page.svelte
        +page.server.ts
+ posts/ # getPosts でここを glob する
  + **/*.md
  + **/*.jpg # 未解決
  • src/lib/getPosts.ts : dummy のロジック

export type PostType = {
  slug: string;
  title: string;
};

const posts: PostType[] = [
  {
    slug: "hoge",
    title: "Hoge",
  },
  {
    slug: "fuga",
    title: "Fuga",
  },
];

export async function getPosts(): Promise<PostType[]> {
  return Promise.resolve(posts);
}

export async function getContent(slug: string): Promise<string> {
  return Promise.resolve(`Hello "${slug}" !`);
}
  • src/routes/posts/page.svelte : 投稿一覧

<script>
  export let data;
</script>

<h1>blog</h1>

<ul>
  {#each data.summaries as { slug, title }}
  <li><a href="/posts/{slug}">{title}</a></li>
  {/each}
</ul>

local directory から getPosts する実装

素直に書いたら動いた。

> npm i -D glob front-matter
import path from "node:path";
import fs from "node:fs/promises";
import * as glob from "glob";
import fm from "front-matter";

export type PostType = {
  title: string;
  slug: string;
  ext: string;
  date: Date;
  tags?: string[];
};

export async function getPosts(): Promise<PostType[]> {
  const dir = ".";
  const pattern = "posts/**/*.{md,mdx}";
  const posts: PostType[] = [];
  const matches = await glob.glob(pattern, { cwd: dir });

  for (const m of matches) {
    const res = await fs.readFile(path.join(dir, m), { encoding: "utf-8" });
    const post = fm(res).attributes as PostType;

    const matched = m.match(/^posts[\\\/](.*)(\.mdx?)$/);
    if (!matched) {
      throw new Error("not match: md|mdx");
    }

    post.slug = matched[1].replace(/\\/g, "/");
    post.ext = matched[2]; //match.substring(6, match.length - 3);
    if (post.tags) {
      post.tags = post.tags.map((tag) => tag.toLowerCase());
    }
    posts.push(post);
  }
  posts.sort((a, b) => {
    return b.date.getTime() - a.date.getTime();
  });

  return posts;
}

で、slug20XX/hoge とか 20XX/01/hoge とかが混在しているのに対処。

https://qiita.com/jwnr/items/8932978ca2f50f102e3d#ex-%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0404

[...slug] という記法があるらしい。

markdown の rendering

src/lib/getPosts.ts

export async function getContent(slug: string): Promise<string> {
  const path = `posts/${slug}.md`;
  console.log(path);
  const content = await fs.readFile(path, "utf8");
  return content;
}

https://ssssota.github.io/svelte-exmarkdown/

<script>
  import Markdown from "svelte-exmarkdown";
  export let data;
</script>

<h1>{data.post.title}</h1>
<Markdown md="{data.post.content}" />

Gfm と rehype-highlight はドキュメント通りでさくっとできた。

SSG build

https://kit.svelte.dev/docs/adapter-static

markdown からの内部リンクと画像リンクにエラーが出るが、 後回しにして握りつぶした。

https://github.com/sveltejs/kit/discussions/9723

![image](./image.jpg)

という記法を解決する必要がある。

tailwind

https://tailwindcss.com/docs/guides/sveltekit

react-daisyui に頼っていたので、素の tailwind が良くわかっていないことが判明。