SvelteKitのコンポーネントをglobでインポートする【ワイルドカードを使いたい】
まとめ
- SvelteKitのコンポーネントをglobでインポートしたい
- パス文字列にワイルドカードを使いたい
- Viteの
import.meta.glob()
を使うことで実現できる - 型や描画処理もいい感じに書ける
各記事をコンポーネントとして作成する形をとっています。
また、その記事コンポーネントから、メタ情報を変数としてエクスポートしています。
<script lang="ts" context="module">
import type { Metadata } from '$lib/posts/index.ts';
export const metadata: Metadata = {
published: true,
// 略
};
</script>
<h3>はじめに</h3>
<!-- 略 -->
このような形で管理すると、次のようなメリットがあります。
- Svelteで本文を記述できる
- 記事で用いる共通コンポーネントを整備しつつ
- 各記事の高い自由度を維持できる
- メタ情報が型安全になる
しかし、この形式には大きなハードルがありました。
コンポーネントをglobインポートする必要があるという点です。
今回はこれに対する解決案を提示します。
Viteのglobインポート
Svelte(Kit)はViteの上での利用を想定されています。
Viteはフロントエンドのビルドツールです。Svelteで記述されたファイルはViteを通して、一般的なWebサーバーで実行可能な形式にビルドされます。
そんなViteには、独自のglobインポート機能があります。
これを使うとうまくいきそうです。
実装
まずは、最終的な実装を示します。
のちに解説する3つのポイントをコメントで示しています。
import type { Component } from 'svelte';
/** 記事のメタ情報の型 */
export type Metadata = {
published: boolean;
// 略
};
// ポイント(2)
/** ポストコンポーネントの型 */
export type Post = {
metadata: Metadata;
slug: string;
default: Component;
description: string;
};
// ポイント(1)
// 動的に記事のSvelteファイルを取得する
const modules = import.meta.glob('./**/post.svelte', { eager: true }) as Record<string, Post>;
const posts: { [slug: string]: Post } = {};
Object.keys(modules).forEach((path) => {
const slug = /^.+\/(?<slug>[^/]+)\/post\.svelte$/.exec(path)?.groups?.slug;
if (slug === undefined) return;
posts[slug] = {
metadata: modules[path].metadata,
slug: slug,
default: modules[path].default
};
});
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<!-- ポイント(3) -->
<data.post.default />
3つのポイントについて、順を追って解説していきます。
import.meta.glob()
ポイント①:今回の主役です。
globインポートを可能にする、Viteの独自関数です。
この関数は2つの引数で構成されています。
順を追ってみていきます。
第1引数:パス条件
globの一般的な特殊文字である、*
や**
、!
を用いることができます。
いくつか例を示します。
// シンプルな例
const modules1 = import.meta.glob('./dir/*.ts')
// 再帰的なパスを指定する
const modules2 = import.meta.glob('./dir/**/*.ts')
// 複数の条件を指定する
const modules3 = import.meta.glob(['./dir1/*.ts', './dir2/*.ts'])
// 否定条件を指定する
const modules4 = import.meta.glob(['./dir/**/*.ts', '!./dir/SECRET/*.ts'])
また、後述しますが、ここには静的な文字列(かその配列)を指定する必要があります。
第2引数:オプション
オプションとして、次の3つが指定できます。
それぞれをキーとするオブジェクトを指定します。
いずれもオプションです。
-
import
- インポートするモジュールを明示的に指定します
-
query
- 各パスにつけるクエリを指定します
- Viteのインポート時には
url
やraw
、inline
などが指定できます[1]
-
eager
-
true
を設定することで、同期読み込みとなります - 未指定あるいは
false
では、Promiseが返されます
-
今回は、ビルド時に実行されるコードのためパフォーマンスの優先度は低いと考えました。
そのため、実装の簡素化のためにeager: true
を指定しました。
ポイント②:インポートされるコンポーネントの型
import type { Component } from 'svelte';
これで問題なさそうです。
コンポーネントはデフォルトエクスポートされているため、Postコンポーネントの型は次のように定義しています。
export type Post = {
metadata: Metadata;
slug: string;
default: Component;
description: string;
};
型の問題は、TypeScriptでトリッキーなことをする際の大きなハードルの印象です。
端的な解決に至って一安心しつつ、次に進みます。
ポイント③:コンポーネントの描画
Svelte5から、dot notationを用いたコンポーネントの指定が正しく解釈されるようになりました[2]。
初めてJSXを見たときのようなぞわぞわを感じつつも、自信をもって記述していきます。
<script lang="ts">
import type { PageProps } from './$types';
let { data }: PageProps = $props();
</script>
<data.post.default />
以上が実装における3つのポイントです。
細々としたハードルを整理すれば、ずいぶんシンプルなのではないでしょうか?
Viteのglobインポートの特徴
Viteのglobインポートにはいくつかの制限があります。
代表的なものとして、次のようなコードは書くことができません。
// これはビルド時にエラーとなる
const images = import.meta.glob(`./${slug}/*.png`)
これは、第1引数にはリテラル値(静的な値)のみを指定する必要があるためです。
Viteは、ビルド時にimport.meta.glob()
は一般に実行可能なコードへ変換します。
const modules = import.meta.glob('./dir/*.ts', { eager: true })
// ↓ ビルド時に次のようなコードに変換される ↓
import * as __glob__0_0 from './dir/module1.ts'
import * as __glob__0_1 from './dir/module1.ts'
const modules = {
'./dir/module1.ts': __glob__0_0,
'./dir/module2.ts': __glob__0_1,
}
おわりに
最終的なコードが簡潔なものとなり、大満足です!
実は、記事としてまとめる作業の中でより良い書き方をいくつか見つけることができました。
悩みぬいて「これで完璧だ!」と思ったものでも、こうやってまとめると新たな発見があるものですね。
これからも、いいネタがあれば書いていきたいなと感じた今日この頃です。
次回演奏会のご案内
Orchestra Canvas Tokyoは、都内を中心に活動するアマチュアオーケストラです。
日々の癒しに、新たなひらめきのきっかけに——
オーケストラの演奏会はいかがでしょうか?
初めての方も大歓迎!
ご来場をお待ちしています。
Orchestra Canvas Tokyo
第14回定期演奏会2025年7月12日(土)
練馬区立練馬文化センター 大ホール
シューマン / 交響曲第2番 ほか詳細はチケット販売ページにて
Discussion