SvelteKitを使ってコンポーネントライブラリを作成してみよう！

# はじめに
この記事は [Svelte Advent Calendar 2021](https://qiita.com/advent-calendar/2021/svelte) の1日目の記事です。

SvelteKitおよび、本記事で紹介するパッケージング機能は執筆時点(2021/12/1)ではバージョン1.0に到達しておらず、β版の状態となっていますので今後変更される可能性があります。

# SvelteKitとは
SvelteでハイパフォーマンスなWebアプリを構築するためのフレームワーク。
ReactでいうNext、VueでいうNuxtの立ち位置と考えてもらうとわかりやすいと思います。

# コンポーネントライブラリを作成する
SvelteKitは主にWebアプリを作成するフレームワークですが、コンポーネントライブラリも作成することができます。
実際に簡単なコンポーネントライブラリを作って一連の流れを説明していきます。

## 1.環境を構築する
```bash
npm init svelte my-component-library
```
上記のコマンドを使ってSvelteKitの環境を構築することができます。

```bash
✔ Which Svelte app template? › Skeleton project
✔ Add type checking? › TypeScript
✔ Add ESLint for code linting? … Yes
✔ Add Prettier for code formatting? … Yes
✔ Add Playwright for browser testing? … Yes
```
どのテンプレートを使うか聞かれるので`Skeleton project`を選択
TypeScriptやESLint、Prettierを使うかどうかも聞かれるので今回はすべてYesで作成していきます。

```bash
npm install

npm run dev
```

![スクリーンショット 2021-11-22 15.46.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217487/0ceba9f5-2309-bc89-0a77-77969569578a.png)

上記のように`Welcome to SvelteKit`と表示されれば環境構築完了です！

## 2.ディレクトリ構成を確認する
`src`配下に`routes`フォルダがあります。ここに`.svelte`ファイルを置くとそれがページになります。
コンポーネントライブラリを作る際は`src/routes`をドキュメントサイトやデモサイトとして公開してもいいですし、開発に使用するサンドボックスとして使用することができます。

コンポーネントライブラリ自体のファイルは`src/lib`に置きます。

## 3.Tailwind CSSとエイリアスの設定をする
**※この章の設定に関してはSvelteKitでコンポーネントライブラリを作成する為に必須な設定ではありませんので不必要な方は飛ばしても大丈夫です。**
今回はTailwind CSSを使用したいのでインストールと、エイリアスの設定をしたいと思います。

### 1.Tailwind CSSをインストールする
今回スタイルは[Tailwind CSS](https://tailwindcss.com/)を使用します。
Tailwind CSSをSvelteKitで使うためには本来いろいろと面倒な設定が必要ですが、[Svelte Add](https://github.com/svelte-add/svelte-add)というものを利用することで1つコマンドを叩くだけで自動で設定できます。

```bash
npx svelte-add@latest tailwindcss
```

```bash 
PostCSS
 ✅ successfully set up!
Create or find an existing issue at https://github.com/svelte-add/svelte-add/issues if this is wrong.

Tailwind CSS
 ✅ successfully set up!
Create or find an existing issue at https://github.com/svelte-add/svelte-add/issues if this is wrong.
```
このように表示されれば設定完です。

設定された新しい依存関係アップデートするために`npm install`をしてください。

### 2.エイリアスを設定する
`routes`配下を開発中のサンドボックスとして使いたいので実際のライブラリと同様のかたちで呼び出せるように設定します。この設定をすることで以下の`エイリアス設定あり`のように呼び出すことができます。

```js:エイリアス設定なし
import { Button } from '$lib/Button.svelte';
```

```js:エイリアス設定あり
import { Button } from 'my-component-library';
```

エイリアスを設定するには`svelte.config.js`と`tsconfig.json`をそれぞれ設定します。

```diff_javascript:svelte.config.js
import adapter from '@sveltejs/adapter-auto';
import preprocess from 'svelte-preprocess';

/** @type {import('@sveltejs/kit').Config} */
const config = {
  // Consult https://github.com/sveltejs/svelte-preprocess
  // for more information about preprocessors
  preprocess: [
    preprocess({
      postcss: true
    })
  ],

  kit: {
    adapter: adapter(),
+   alias: {
+      'my-component-library': 'src/lib'
+   }
  }
};

export default config;
```

## 4.コンポーネントを作成する
準備ができたので`src/lib`配下にコンポーネントを作っていきます。
今回は簡単にButtonコンポーネントを作成したいと思います。

JSDocで型定義もしておきます。

```html:src/lib/Button.svelte
<script>
  /** @type {'default' | 'pill' | 'outline'} */
  export let style = 'default';

  /** @type {string} */
  export let href = null;
</script>

{#if href}
  <a {href}> 
   <button class={style}>リンクボタン</button>
  </a>
{:else}
  <button class={style}>ボタン</button>
{/if}

<style>
  .default {
    @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded;
  }

  .pill {
    @apply bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full;
  }

  .outline {
    @apply bg-transparent hover:bg-blue-500 text-blue-700 font-semibold hover:text-white py-2 px-4 border border-blue-500 hover:border-transparent rounded;
  }
</style>
```

```js:src/lib/index.js
export { default as Button } from './Button.svelte';
```

## 5. 実際に表示して試す
`src/routes/index.svelte`で呼び出して動くか試します。

```html:src/routes/index.svelte
<script>
  import { Button } from 'my-component-library';
</script>

<Button />
<Button style="pill" />
<Button style="outline" />

<Button href="https://www.google.com/" />
```

上記のように`index.svelte`でButtonコンポーネントを呼び出して、`npm run dev`で起動して確認します。

![スクリーンショット 2021-11-29 19.00.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/217487/eab08b4b-8d77-3f7d-3180-6f55e4b89ec6.png)

正常に動作していることが確認できました!

## 6. パッケージング機能を使ってパッケージングする
コンポーネントライブラリが完成したのでSvelteKitのパッケージング機能を使ってnpmにpublishできるようにパッケージングします。

型定義を自動生成させるには`svelte2tsx`と`typescript`をインストールしておく必要があります。

今回は`typescript`は既にインストールしているので`svelte2tsx`をインストールします。

```bash
npm install -D svelte2tsx
```

これで準備ができたので`npm run package`コマンドを実行するとプロジェクト直下に`package`フォルダが生成されます！

`package`フォルダの中を見てみると、型定義ファイルが生成されていたり、Tailwind CSSで書いたスタイルがCSSとして出力されていたりして、ライブラリとしてこのままリリースできるようになっています。

```html:package/Button.svelte
<script>
	/** @type {'default' | 'pill' | 'outline'} */
	export let style = 'default';

	/** @type {string} */
	export let href = null;
</script>

{#if href}
	<a {href}> <button class={style}> ボタン </button></a>
{:else}
	<button class={style}> ボタン </button>
{/if}

<style>
	.default {

    border-radius: 0.25rem;

    --tw-bg-opacity: 1;

    background-color: rgba(59, 130, 246, var(--tw-bg-opacity));

    padding-top: 0.5rem;

    padding-bottom: 0.5rem;

    padding-left: 1rem;

    padding-right: 1rem;

    font-weight: 700;

    --tw-text-opacity: 1;

    color: rgba(255, 255, 255, var(--tw-text-opacity))
}

.default:hover {

    --tw-bg-opacity: 1;

    background-color: rgba(29, 78, 216, var(--tw-bg-opacity))
}

	.pill {

    border-radius: 9999px;

    --tw-bg-opacity: 1;

    background-color: rgba(59, 130, 246, var(--tw-bg-opacity));

    padding-top: 0.5rem;

    padding-bottom: 0.5rem;

    padding-left: 1rem;

    padding-right: 1rem;

    font-weight: 700;

    --tw-text-opacity: 1;

    color: rgba(255, 255, 255, var(--tw-text-opacity))
}

	.pill:hover {

    --tw-bg-opacity: 1;

    background-color: rgba(29, 78, 216, var(--tw-bg-opacity))
}

	.outline {

    border-radius: 0.25rem;

    border-width: 1px;

    --tw-border-opacity: 1;

    border-color: rgba(59, 130, 246, var(--tw-border-opacity));

    background-color: transparent;

    padding-top: 0.5rem;

    padding-bottom: 0.5rem;

    padding-left: 1rem;

    padding-right: 1rem;

    font-weight: 600;

    --tw-text-opacity: 1;

    color: rgba(29, 78, 216, var(--tw-text-opacity))
}

	.outline:hover {

    border-color: transparent;

    --tw-bg-opacity: 1;

    background-color: rgba(59, 130, 246, var(--tw-bg-opacity));

    --tw-text-opacity: 1;

    color: rgba(255, 255, 255, var(--tw-text-opacity))
}</style>
```

```ts:package/Button.svelte.d.ts
/** @typedef {typeof __propDef.props}  ButtonProps */
/** @typedef {typeof __propDef.events}  ButtonEvents */
/** @typedef {typeof __propDef.slots}  ButtonSlots */
export default class Button extends SvelteComponentTyped<{
    style?: "default" | "pill" | "outline";
    href?: string;
}, {
    [evt: string]: CustomEvent<any>;
}, {}> {
}
export type ButtonProps = typeof __propDef.props;
export type ButtonEvents = typeof __propDef.events;
export type ButtonSlots = typeof __propDef.slots;
import { SvelteComponentTyped } from "svelte";
declare const __propDef: {
    props: {
        style?: 'default' | 'pill' | 'outline';
        href?: string;
    };
    events: {
        [evt: string]: CustomEvent<any>;
    };
    slots: {};
};
export {};
```

これでコンポーネントライブラリが作成できました！
`npm publish ./package`でnpmにpublishしたり、煮るなり焼くなりしてください！

# おわりに
駆け足ではありましたが、SvelteKitを使ってコンポーネントライブラリを作成する流れを説明してきました！
Svelteでコンポーネントライブラリを作成するのって意外と簡単！とかSvelteKitでコンポーネントライブラリ作れるって知らなかった！と思ってくださった方がいれば嬉しいです！

# 参考資料
- [SvelteKit#packaging](https://kit.svelte.dev/docs#packaging)