Header image

クラッソーネの開発者がエンジニアリングに関することもそうでないことも綴っています!

pathpidaをSapper(Svelte)で使ってみた

pathpidaをSapper(Svelte)で使ってみた

こんにちは!プロダクト開発部の町田です!

私事ですが、沖縄から東京への引っ越しが完了してやっと一息つきました。
デスク周りを整理するいい機会なので、新居では FlexiSpot のスタンディングデスクを新調し、4K の縦画面モニターも新しく配置しました。

ほんの少しでも開発がしやすくなるといいな〜と思ってる今日この頃です。

foreword

今回の記事ですが、フロントエンドフレームワークの Sapper(Svelte)で pathpida を使ってみた話をしていきたいと思います。

pathpida とは

pathpida は Next.js や Nuxt.js や Sapper の内部リンクを取得するためのライブラリです。

概要

各フレームワークのディレクトリによるルーティング規約に最適化されており、型安全に内部リンクを取得することができます。

pathpida を Sapper で使ってみる

前述にもある通り、今回は Sapper(Svelte)で pathpida を使っていきたいと思います。
Sapper(Svelte)は既にインストール済みの前提で進めます。

下記が、執筆時点でのバージョンです。

svelte: 0.28.0
pathpida: 0.17.0

インストール

npm-run-all もインストールします。
npm-run-all を導入することで、シンプルな記述で npm-scripts を順次・並列実行できます。
特に依存してるわけではないのですが、入れておくと便利です。

  • npm

npm install pathpida npm-run-all --save-dev

  • yarn

yarn add pathpida npm-run-all --dev

開発時に pathpida が動くように設定します

package.json
{
  "scripts": {
    "dev": "run-p dev:*",
    "dev:sapper": "sapper dev",
    "dev:path": "pathpida --ignorePath .gitignore --watch",
    "build": "pathpida --ignorePath .gitignore && sapper build --legacy",
    "export": "pathpida --ignorePath .gitignore && sapper export --legacy"
    // ...other
  }
}

以上です。
pathpida は設定なしに動作します。

あとは開発サーバーを立ち上げるだけで内部リンク取得に必要なオブジェクトが生成されます。
特に複雑な設定など要らず簡単に設定できました。

型安全に内部リンクの取得

以下のように/posts/1に遷移するリンクがあります。

routes/about.svelte
<script>
  import { pagesPath } from "$path"
</script>

<a href="posts/1">記事1</a>

上の例だと、url の/posts/1が内部リンクとして適してるかどうかは型安全に判定できません。
もしroutes/posts/[id]/index.svelteというファイルが存在しなければページの遷移に失敗してしまいます。

このような内部リンクを型安全に取得し、利用できるようにしてくれるのが pathpida です。

routes が以下のようなディレクトリの場合

routes/index.svelte
routes/about.svelte
routes/posts/index.svelte
routes/posts/[id]/index.svelte

以下の TypeScript のファイルが生成されます

node_modules/$path.ts
/* eslint-disable */

// prettier-ignore
export const pagesPath = {
  about: {
    $url: (url?: { hash?: string }) => `/about${url?.hash ? `#${url.hash}` : ''}`
  },
  posts: {
    _id: (id: string | number) => ({
      $url: (url?: { hash?: string }) => `/posts/${id}${url?.hash ? `#${url.hash}` : ''}`
    }),
    $url: (url?: { hash?: string }) => `/posts${url?.hash ? `#${url.hash}` : ''}`
  },
  $url: (url?: { hash?: string }) => `/${url?.hash ? `#${url.hash}` : ''}`
}

// prettier-ignore
export type PagesPath = typeof pagesPath

svelte で使うイメージはこういう感じです

routes/about.svelte
<script context="module" lang="ts">
  import { pagesPath } from "$path"
</script>

<a href="{pagesPath.posts._id(1).$url()}">記事1</a>

型安全に取得したオブジェクトでページ遷移ができました。

型安全なので、オブジェクトから取得できないリンクはエラーが出て気づけたり、型推論によってリンクの候補が出てくるので、候補から取得したいリンクを選択するだけでよい点も便利ですね。

オブジェクトから取得できないリンクを指定した場合
vscode_01

型推論でリンク候補の表示
vscode_02

静的ファイルのパスを型安全に利用する

--enableStaticオプションを追加してください。

package.json
{
  "scripts": {
    "dev": "run-p dev:*",
    "dev:sapper": "sapper dev",
    "dev:path": "pathpida --ignorePath .gitignore --enableStatic --watch",
    "build": "pathpida --ignorePath .gitignore --enableStatic && sapper build --legacy",
    "export": "pathpida --ignorePath .gitignore --enableStatic && sapper export --legacy"
    // ...other
  }
}

static ディレクトリが以下のような場合

static/favicon.png
static/manifest.json
static/posts/post_1.json
static/posts/post_2.json

$path.ts に以下の オブジェクトが生成されます

node_modules/$path.ts
// prettier-ignore
export const staticPath = {
  favicon_png: '/favicon.png',
  manifest_json: '/manifest.json',
  posts: {
    post_1_json: '/posts/post_1.json',
    post_2_json: '/posts/post_2.json'
  }
} as const

// prettier-ignore
export type StaticPath = typeof staticPath

pagesPath の時と同様に自動生成されたstaticPathを利用して静的ファイルのパスを型安全に取得できます。

クエリパラメータを指定する

pathpida が生成する URL にクエリパラメータを含めたい場合は svelte コンポーネントからQueryorOptionalQuery を export すると、あとは pathpida がよしなに組み込んでくれます。

クエリパラメータが必須なページの場合はQuery、必須でない場合はOptionalQueryで使い分けます
以下がpostsページにクエリパラメータを指定する場合の例です。
Query Type を export してpostsのリンクを pathpida で取得する際にクエリパラメータの指定を行います。

routes/posts/index.svelte
<script context="module" lang="ts">
  import { pagesPath } from "$path"

  export type Query = {
    page: number;
    limit: number;
  }

</script>

svelte で使うイメージはこういう感じです

routes/about.svelte
<script context="module" lang="ts">
	import { pagesPath } from "$path"
</script>

<a href="{pagesPath.posts.$url({ query: { page: 1, limit: 10 } })}">記事一覧</a>

また、ドキュメントにもありますが、reference types をクエリパラメータの型に含める際は、絶対パスを利用した dynamic import を利用してください。

TypeScript の制限により、.svelte ファイルから export された型は import できないからです。

https://github.com/aspida/pathpida#warning-in-the-case-of-svelte-file-queryoptionalquery-type-must-not-contain-any-reference-types

公式にある方法とは異なりますが、私は tsconfig.json の設定を変更して path のエイリアスを登録して使う方が好みです

tsconfig.json
{
  "extends": "@tsconfig/svelte/tsconfig.json",
  "compilerOptions": {
    "lib": ["DOM", "ES2017", "WebWorker"],
    "types": ["svelte", "node"],
    "baseUrl": "./",
    "paths": { "@routes/*": ["src/routes/*"] }
  },
  "include": ["src/**/*", "src/node_modules/**/*"],
  "exclude": ["node_modules/*", "__sapper__/*", "static/*"]
}

routes/posts/index.ts
export type Page = number;
export type Limit = number;
routes/posts/index.svelte
<script context="module" lang="ts">
  import { pagesPath } from "$path"

  export type Query = {
    page: import('@routes/posts').Page;
    limit: import('@routes/posts').Limit
  }

</script>

ここらは各々の気に入った方法でコーディングするといいと思います。

まとめ

pathpida を使うことで静的ファイルのパスや内部リンクを型安全に取得し、ページ遷移のリンクに利用することができました!

個人的にはうっかりしたリンクのタイプミスをなくせるのがすごく助かってます。
プロジェクト内には、文字列で書かれたページ遷移のリンクがまだ複数あるので、少しずつ pathpida で取得したリンクに置き換えていこうと思います。

皆さんも是非携わっているプロジェクトに pathpida を導入してみてください!

おわりに

クラッソーネでは一緒に働くクルーを大募集してます!
プロダクトをより良いものにしたいきたいエンジニア、常に改善を続けていきたいエンジニアの皆さんからのご連絡をお待ちしております。

少しでも興味を持っていただけた方、ぜひ一度カジュアルにお話ししましょう!

https://www.crassone.co.jp/recruit/engineer/