Zenn
Open7

Svelte (5) のパッケージングに関する雑記

ykrodsykrods

基本的には公式ドキュメントを読めば大体わかる。というか、 $ npx sv createSvelte library テンプレートを選んでおけば後は書くだけなので、補足や気になった点を書いていく。

ykrodsykrods

コンポーネントの公開

例えば svelte の UI ライブラリなどを作成する場合、 package.jsonexports を以下のように設定する ( 単純な例として一つのコンポーネントのみ公開 )。

package.json
{
  // 省略
  "exports": {
    ".": {
      "svelte": "./index.svelte",
      "types": "./index.svelte.d.ts"
    }
  }
}
  • 型情報を提供するには型定義ファイルが必要で、 tsc では svelte コンポーネントを扱えないためツールを使う必要がある(後述)
  • .svelte.ts ( .svelte.js ) の中では rune が利用できるが、これらのファイルは "svelte" キーでなく "default" などでも公開は可能。
    • ただし、 svelte のコンパイラを通さないと rune が参照できずに $state is undefined のようなエラーが起きるため "svelte" キーでよいように思われる [1]
脚注
  1. svelte 環境以外でも使える関数を作るような場合が思い当たるが、そういった場合は先に svelte でコンパイルして配布するのでは?という気もする ↩︎

ykrodsykrods

@sveltejs/package

@sveltejs/packagesvelte-package コマンドで以下の処理が行われる

  • svelte component のプリプロセス [1]
  • TypeScript のトランスパイル
  • 型定義ファイルの生成

例として、以下のコンポーネントに svelte-package を実行した結果をのせる

Hello.svelte
<!--
@component
Hello My Component!

- foo
- bar
-->
<script lang="ts">
  let { name }: {
    /** your name */
    name: string
  } = $props()
</script>
<p>Hello { name }</p>

結果の型定義ファイル:

Hello.svelte.d.ts
type $$ComponentProps = {
    /** your name */
    name: string;
};
/**
 * Hello My Component!
 *
 * - foo
 * - bar
 */
declare const Hello: import("svelte").Component<$$ComponentProps, {}, "">;
type Hello = ReturnType<typeof Hello>;
export default Hello;
  • コンポーネント内の ts は変換されない模様( svelte5 では コンパイラが直接 ts を解釈するため )
  • 若干複雑な記述で、手書きでコンポーネントの型定義をするのは想定されていない感がある
脚注
  1. プリプロセスでは <style lang="sass"> を生のcss に変換するなどしているらしいが、これはパッケージとして配布後でもできるのでは? vite では標準で変換しているけど他のツールでは同じとは限らないから、そういったケースへの対応? ↩︎

ykrodsykrods

テスト

大きく以下の手法がある

  • rune を使う関数などのユニットテスト (vitest)
  • コンポーネントのテスト (vitest + @testing-library/svelte )
  • e2e テスト ( Playwright 等)

個人的には基本方針としてはユニットテストが書けるならユニットテスト書いた方がいい派なのだが、ユニットテスト・コンポーネントテストのための設定がいまいち煩雑というかよくわからない感があり、svelte では e2e で書いても良いような気がしている。

上の issue にあるように jsdom が無いと $effect が起動しないなど

ykrodsykrods

ユニットテストする場合

  • 基本的な設定に関しては ドキュメント通りで、 Svelte library テンプレートを選べば設定済みになっているので省略
  • 上述の issue にあるように $effect をテストするには environment: "jsdom" が必要(詳細は不明)

コード例

example.svelte.spec.ts
import { flushSync } from 'svelte';
import { expect, test } from 'vitest';

test('Effect', () => {
  let count = $state(0);
  let double = $state(100);

  const cleanup = $effect.root(() => {
    $effect(() => {
      // 通常はこのような場合 $derived を使うが、 $effect の簡単な動作例として以下のように書く
      double = count * 2;
    });
  });

  expect(double).toEqual(100);

  flushSync(); // effect が実行されるのを待つ
  expect(double).toEqual(0);

  count = 1
  flushSync();
  expect(double).toEqual(2);

  cleanup();
});
  • テストファイルでも rune の利用にはファイル名が .svelte.ts or .svelte.js である必要があるが、テストの場合 .svelte.spec.ts でも .spec.svelte.ts でも良い模様
    • テスト対象を *.spec.ts で指定したりするので前者の方が無難か
  • $effect.root$effect を管理するためのスコープを新たに作成する関数で、主にコンポーネント外部で $effect を作成したい時に使う。通常はコンポーネント初期化時にスコープが作成されるので $effect.root を使う必要がない
    • コンポーネント初期化時に作成されるスコープはコンポーネント破棄のタイミングで自動的にクリーンアップが実行されるが、 $effect.root で作成したスコープは 手動でクリーンアップを行う必要がある
    • $effect をコンポーネント外で定義された関数の中で呼び出すことは問題ないが、その関数の呼び出しはスコープ内で行う必要がある。
ykrodsykrods

その他メモ

  • npm pack で tarball を作成 & npm install foo-1.0.0.tgz で配布物のテストができる
ykrodsykrods

コンポーネントテストをする場合

  • こちらも設定はドキュメント通りでOK
  • 記事によっては svelte5 では @testing-library/svelte/svelte5 を使うように書いてあるものもあるが、 @testing-library/svelte@5.2.0 でルートのエントリーポイントが svelte5 に切り替わったので今は @testing-library/svelte をインポートすれば良い
ログインするとコメントできます