T-CREATOR

svelte-preprocess 導入ガイド:SCSS・PostCSS・TypeScript を安全に共存

svelte-preprocess 導入ガイド:SCSS・PostCSS・TypeScript を安全に共存

Svelte で本格的な Web アプリケーションを開発する際、TypeScript で型安全性を確保しながら、SCSS で便利なスタイル記法を使いたい、さらに PostCSS でベンダープレフィックスを自動追加したいといったニーズは非常に多いです。しかし、これらの複数のプリプロセッサを組み合わせると、設定の競合やビルドエラーに悩まされることも少なくありません。

そこで活躍するのが svelte-preprocess です。このライブラリを使えば、SCSS・PostCSS・TypeScript といった異なるプリプロセッサを 1 つの設定ファイルで管理でき、安全に共存させることができます。本記事では、svelte-preprocess の基本から実践的な設定まで、初心者の方にもわかりやすく解説していきます。

背景

Svelte における複数プリプロセッサの必要性

Svelte は .svelte という単一ファイルコンポーネント形式を採用しており、1 つのファイルに HTML・CSS・JavaScript を記述できます。しかし、実務では以下のような要望が出てきます。

  • TypeScript: 型チェックによる安全なコード記述
  • SCSS: 変数・ネスト・mixin などの便利な CSS 記法
  • PostCSS: Autoprefixer によるベンダープレフィックス自動追加や、CSS の最適化

これらを個別に設定しようとすると、それぞれのプラグインを導入し、ビルドツール(Vite や Rollup)ごとに異なる設定を記述する必要があります。設定ファイルが複雑化し、プリプロセッサ間で処理順序が競合するリスクも高まります。

svelte-preprocess の役割

svelte-preprocess は、複数のプリプロセッサを統合管理するためのライブラリです。以下の図のように、.svelte ファイルが Svelte コンパイラに渡される前に、各種プリプロセッサを順次適用してくれます。

次の図は、svelte-preprocess がどのように各プリプロセッサを統合するかを示しています。

mermaidflowchart TB
  svelteFile[".svelte ファイル"]
  preprocessor["svelte-preprocess"]
  typescript["TypeScript 変換"]
  scss["SCSS → CSS 変換"]
  postcss["PostCSS 処理"]
  compiler["Svelte コンパイラ"]
  output["最終的な JS/CSS"]

  svelteFile --> preprocessor
  preprocessor --> typescript
  typescript --> scss
  scss --> postcss
  postcss --> compiler
  compiler --> output

図で理解できる要点:

  • .svelte ファイルは svelte-preprocess を経由して各種変換が行われる
  • TypeScript → SCSS → PostCSS の順で処理され、最後に Svelte コンパイラに渡される
  • すべての処理が 1 つのプリプロセッサで統合管理される

このように、svelte-preprocess を導入することで、複数のプリプロセッサを 1 つの設定で管理でき、処理順序も明確になります。

課題

複数プリプロセッサ利用時の典型的な問題

svelte-preprocess を使わずに複数のプリプロセッサを導入すると、以下のような問題に直面します。

課題 1: 設定ファイルの複雑化

各プリプロセッサを個別に設定すると、vite.config.jsrollup.config.js が肥大化します。

typescript// vite.config.js の例(svelte-preprocess なしの場合)
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import sveltePreprocessTypescript from 'svelte-preprocess-typescript';
import sveltePreprocessScss from 'svelte-preprocess-scss';
import autoprefixer from 'autoprefixer';

export default defineConfig({
  plugins: [
    svelte({
      preprocess: [
        sveltePreprocessTypescript(),
        sveltePreprocessScss(),
        // PostCSS の設定をどこに入れる?
      ],
    }),
  ],
});

上記のコードでは、TypeScript と SCSS の設定は別々のプラグインで行っていますが、PostCSS をどこに組み込むかが不明確です。さらに、各プラグインのバージョン互換性も個別に管理する必要があり、保守性が低下します。

課題 2: 処理順序の競合

複数のプリプロセッサが同じ <style> タグや <script> タグを処理しようとすると、処理順序が不明確になり、以下のようなエラーが発生することがあります。

javascriptError: Unexpected token (1:0)
SyntaxError: Unexpected token 'export'

エラーコード: SyntaxError 発生条件: TypeScript が SCSS より後に処理されるなど、処理順序が逆転した場合 エラーメッセージ:

vbnetSyntaxError: Unexpected token 'export'
  at Function.Module._load (internal/modules/cjs/loader.js:883:27)

このエラーは、TypeScript のコードが先に SCSS プロセッサに渡されてしまい、TypeScript の構文が理解できずにエラーとなるケースです。

課題 3: ビルドツールごとの設定差異

Vite・Rollup・Webpack など、ビルドツールごとにプリプロセッサの設定方法が異なります。プロジェクトを移行する際や、複数のビルドツールを併用する場合に、設定を書き直す必要が生じます。

次の図は、課題の関係性を示しています。

mermaidflowchart LR
  A["複数プリプロセッサ導入"]
  B["設定ファイル肥大化"]
  C["処理順序の競合"]
  D["ビルドツール依存"]
  E["保守性の低下"]

  A --> B
  A --> C
  A --> D
  B --> E
  C --> E
  D --> E

図で理解できる要点:

  • 複数プリプロセッサの導入が、3 つの主要な課題を引き起こす
  • これらの課題はすべて、最終的に保守性の低下につながる

これらの課題を解決するために、svelte-preprocess が開発されました。

解決策

svelte-preprocess による統合管理

svelte-preprocess は、複数のプリプロセッサを 1 つの設定で管理できるライブラリです。以下の特徴があります。

#特徴説明
1統合設定TypeScript・SCSS・PostCSS などを 1 つの設定オブジェクトで管理
2自動検出lang="ts"lang="scss" を自動で検出し、適切なプリプロセッサを適用
3処理順序の明確化内部で最適な処理順序を自動調整
4ビルドツール非依存Vite・Rollup・Webpack すべてで同じ設定を利用可能

インストール手順

まず、必要なパッケージをインストールします。

ステップ 1: svelte-preprocess のインストール

bashyarn add -D svelte-preprocess

このコマンドで、svelte-preprocess 本体を開発依存としてインストールします。

ステップ 2: プリプロセッサ本体のインストール

次に、使用したい各プリプロセッサをインストールします。

bashyarn add -D typescript sass postcss autoprefixer

各パッケージの役割は以下の通りです。

#パッケージ役割
1typescriptTypeScript コンパイラ本体
2sassSCSS を CSS に変換するコンパイラ(Dart Sass)
3postcssCSS を変換・最適化するツール
4autoprefixerPostCSS プラグイン。ベンダープレフィックスを自動追加

ステップ 3: 設定ファイルの作成

Vite を使用している場合の設定例を見てみましょう。

typescript// vite.config.ts
import { defineConfig } from 'vite';
import { svelte } from '@sveltejs/vite-plugin-svelte';

上記は Vite と Svelte プラグインのインポート部分です。次に、svelte-preprocess をインポートします。

typescript// svelte-preprocess のインポート
import sveltePreprocess from 'svelte-preprocess';

続いて、Vite の設定を記述します。

typescript// Vite 設定の定義
export default defineConfig({
  plugins: [
    svelte({
      preprocess: sveltePreprocess({
        // TypeScript の設定
        typescript: {
          tsconfigFile: './tsconfig.json',
        },
        // SCSS の設定
        scss: {
          // グローバル変数ファイルを自動インポート
          prependData: `@import 'src/styles/variables.scss';`,
        },
        // PostCSS の設定
        postcss: {
          plugins: [require('autoprefixer')()],
        },
      }),
    }),
  ],
});

この設定により、以下が実現されます。

  1. TypeScript: tsconfig.json の設定に基づいて型チェック
  2. SCSS: すべての .svelte ファイルで variables.scss を自動インポート
  3. PostCSS: Autoprefixer によるベンダープレフィックス自動追加

ステップ 4: TypeScript 設定ファイルの作成

TypeScript を使用する場合、tsconfig.json も作成します。

json{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "isolatedModules": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

この設定により、最新の JavaScript 機能を使いながら、厳格な型チェックが行われます。

処理フローの明確化

svelte-preprocess を導入すると、以下のように処理フローが明確になります。

mermaidsequenceDiagram
  participant SvelteFile as .svelte ファイル
  participant Preprocess as svelte-preprocess
  participant TS as TypeScript
  participant SCSS as Sass
  participant PostCSS as PostCSS
  participant Compiler as Svelte コンパイラ

  SvelteFile->>Preprocess: ファイル読み込み
  Preprocess->>Preprocess: lang 属性を検出

  alt lang="ts" の場合
    Preprocess->>TS: TypeScript 変換
    TS-->>Preprocess: JavaScript 返却
  end

  alt lang="scss" の場合
    Preprocess->>SCSS: SCSS → CSS 変換
    SCSS-->>Preprocess: CSS 返却
  end

  Preprocess->>PostCSS: CSS 最適化
  PostCSS-->>Preprocess: 最適化 CSS 返却

  Preprocess->>Compiler: 変換済みコード
  Compiler-->>SvelteFile: 最終的な出力

図で理解できる要点:

  • svelte-preprocess が lang 属性を自動検出し、適切なプリプロセッサに振り分ける
  • 各プリプロセッサは順次処理され、最後に Svelte コンパイラに渡される
  • 開発者は lang 属性を指定するだけで、すべての処理が自動化される

具体例

実践的な Svelte コンポーネントの作成

svelte-preprocess を導入した環境で、TypeScript と SCSS を活用した実践的なコンポーネントを作成してみましょう。

グローバル変数ファイルの作成

まず、SCSS のグローバル変数ファイルを作成します。

scss// src/styles/variables.scss

// カラーパレット
$primary-color: #ff3e00;
$secondary-color: #676778;
$background-color: #ffffff;
$text-color: #333333;

// ブレークポイント
$breakpoint-mobile: 768px;
$breakpoint-tablet: 1024px;

// スペーシング
$spacing-small: 8px;
$spacing-medium: 16px;
$spacing-large: 24px;

このファイルには、プロジェクト全体で使用する色・ブレークポイント・スペーシングの変数を定義します。

TypeScript 型定義の作成

次に、コンポーネントで使用する型を定義します。

typescript// src/types/card.ts

export interface CardProps {
  title: string;
  description: string;
  imageUrl?: string;
  tags?: string[];
  onClickHandler?: () => void;
}

CardProps インターフェースで、カードコンポーネントのプロパティの型を定義しています。? を付けることで、オプショナルなプロパティを表現できます。

Svelte コンポーネントの作成

それでは、TypeScript と SCSS を組み合わせた Svelte コンポーネントを作成しましょう。

ステップ 1: script タグの記述(TypeScript)

svelte<!-- src/components/Card.svelte -->

<script lang="ts">
  // 型定義のインポート
  import type { CardProps } from '../types/card';

  // プロパティの定義(型安全)
  export let title: string;
  export let description: string;
  export let imageUrl: string | undefined = undefined;
  export let tags: string[] = [];
  export let onClickHandler: (() => void) | undefined = undefined;

  // ローカル状態の管理
  let isHovered: boolean = false;

lang="ts" を指定することで、svelte-preprocess が自動的に TypeScript として処理します。型アノテーションにより、プロパティの型が明確になり、誤った型の値が渡された場合は開発時にエラーが検出されます。

ステップ 2: イベントハンドラの定義

svelte  // マウスイベントハンドラ
  const handleMouseEnter = (): void => {
    isHovered = true;
  };

  const handleMouseLeave = (): void => {
    isHovered = false;
  };

  // クリックイベントハンドラ
  const handleClick = (): void => {
    if (onClickHandler) {
      onClickHandler();
    }
  };
</script>

イベントハンドラも TypeScript の恩恵を受けて、型安全に記述できます。戻り値の型を void と明示することで、意図しない返り値を防げます。

ステップ 3: マークアップの記述

svelte<!-- カードコンポーネントのマークアップ -->
<div
  class="card"
  class:hovered={isHovered}
  on:mouseenter={handleMouseEnter}
  on:mouseleave={handleMouseLeave}
  on:click={handleClick}
  role="button"
  tabindex="0"
  on:keydown={(e) => e.key === 'Enter' && handleClick()}
>
  {#if imageUrl}
    <div class="card__image">
      <img src={imageUrl} alt={title} />
    </div>
  {/if}

  <div class="card__content">
    <h3 class="card__title">{title}</h3>
    <p class="card__description">{description}</p>

    {#if tags.length > 0}
      <div class="card__tags">
        {#each tags as tag}
          <span class="card__tag">{tag}</span>
        {/each}
      </div>
    {/if}
  </div>
</div>

Svelte の制御フロー構文({#if}{#each})を使って、条件付きレンダリングとリストレンダリングを実現しています。

ステップ 4: スタイルの記述(SCSS + PostCSS)

svelte<style lang="scss">
  // variables.scss は自動インポートされる

  .card {
    background-color: $background-color;
    border-radius: $spacing-small;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
    overflow: hidden;
    transition: transform 0.3s ease, box-shadow 0.3s ease;
    cursor: pointer;

    // ホバー時のスタイル(クラスバインディング)
    &.hovered {
      transform: translateY(-4px);
      box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
    }

lang="scss" を指定することで、SCSS の記法が使えます。$background-color などの変数は、先ほど vite.config.ts で設定した prependData により自動インポートされています。

ステップ 5: ネストとレスポンシブデザイン

svelte    // 画像エリア
    &__image {
      width: 100%;
      height: 200px;
      overflow: hidden;

      img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        transition: transform 0.3s ease;
      }

      // ホバー時の画像拡大
      .hovered & img {
        transform: scale(1.05);
      }
    }

    // コンテンツエリア
    &__content {
      padding: $spacing-medium;
    }

    // タイトル
    &__title {
      color: $primary-color;
      font-size: 1.25rem;
      font-weight: bold;
      margin: 0 0 $spacing-small 0;
    }

    // 説明文
    &__description {
      color: $text-color;
      font-size: 0.9rem;
      line-height: 1.6;
      margin: 0 0 $spacing-medium 0;
    }

    // タグエリア
    &__tags {
      display: flex;
      flex-wrap: wrap;
      gap: $spacing-small;
    }

    // 個別タグ
    &__tag {
      background-color: $secondary-color;
      color: $background-color;
      padding: 4px $spacing-small;
      border-radius: 4px;
      font-size: 0.75rem;
    }

    // レスポンシブデザイン
    @media (max-width: $breakpoint-mobile) {
      &__image {
        height: 150px;
      }

      &__title {
        font-size: 1.1rem;
      }

      &__description {
        font-size: 0.85rem;
      }
    }
  }
</style>

SCSS のネスト記法(&)を使うことで、BEM のようなクラス命名規則を読みやすく記述できます。また、メディアクエリもネスト内に記述でき、コンポーネント単位でのスタイル管理が容易になります。

PostCSS の Autoprefixer が有効になっているため、transformtransitionflex などのプロパティには自動的にベンダープレフィックスが追加されます。

コンポーネントの使用例

作成したコンポーネントを、別のファイルから使用してみましょう。

svelte<!-- src/routes/+page.svelte -->

<script lang="ts">
  import Card from '../components/Card.svelte';

  // カードデータ(型安全)
  const cards = [
    {
      title: 'svelte-preprocess 導入',
      description: 'TypeScript と SCSS を安全に共存させる方法',
      imageUrl: '/images/svelte.jpg',
      tags: ['Svelte', 'TypeScript', 'SCSS'],
      onClickHandler: () => console.log('Card 1 clicked'),
    },
    {
      title: 'PostCSS の活用',
      description: 'Autoprefixer でベンダープレフィックスを自動追加',
      tags: ['PostCSS', 'CSS'],
      onClickHandler: () => console.log('Card 2 clicked'),
    },
  ];
</script>

<main>
  <h1>技術記事一覧</h1>
  <div class="card-grid">
    {#each cards as card}
      <Card {...card} />
    {/each}
  </div>
</main>

<style lang="scss">
  main {
    max-width: 1200px;
    margin: 0 auto;
    padding: $spacing-large;
  }

  h1 {
    color: $primary-color;
    margin-bottom: $spacing-large;
  }

  .card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
    gap: $spacing-large;
  }
</style>

スプレッド構文({...card})を使うことで、オブジェクトのプロパティをプロパティとして一括で渡せます。TypeScript の型チェックにより、必須プロパティが不足している場合は開発時にエラーが表示されます。

エラー時のデバッグ方法

svelte-preprocess を使用中にエラーが発生した場合の対処法を見ていきましょう。

エラー 1: TypeScript コンパイルエラー

エラーコード: TS2322 エラーメッセージ:

pythonType 'number' is not assignable to type 'string'.

発生条件: 型が一致しないプロパティを渡した場合

解決方法:

  1. エラーメッセージで指摘された行を確認する
  2. 渡しているプロパティの型を確認する
  3. 必要に応じて型を修正、または型変換を行う
typescript// 誤った例
export let title: string;
title = 123; // エラー: number は string に代入できない

// 正しい例
export let title: string;
title = '123'; // OK
// または
title = String(123); // 型変換

エラー 2: SCSS コンパイルエラー

エラーコード: SassError エラーメッセージ:

yamlSassError: Undefined variable.
   
15    color: $undefined-color;
             ^^^^^^^^^^^^^^^^
   

発生条件: 定義されていない SCSS 変数を使用した場合

解決方法:

  1. variables.scss に該当の変数が定義されているか確認する
  2. vite.config.tsprependData の設定が正しいか確認する
  3. 変数名のスペルミスがないか確認する
scss// variables.scss に追加
$undefined-color: #ff0000;

エラー 3: PostCSS プラグインエラー

エラーコード: Error エラーメッセージ:

arduinoError: Cannot find module 'autoprefixer'

発生条件: PostCSS プラグインがインストールされていない場合

解決方法:

  1. 必要なパッケージをインストールする
bashyarn add -D autoprefixer postcss
  1. vite.config.ts でプラグインを正しくインポートしているか確認する
typescript// 正しいインポート方法
import autoprefixer from 'autoprefixer';

// 設定
postcss: {
  plugins: [autoprefixer()],
}

パフォーマンス最適化

svelte-preprocess を使用する際のパフォーマンス最適化のポイントをご紹介します。

最適化 1: 開発時の型チェック無効化

開発時に TypeScript の型チェックが遅い場合、以下のように設定できます。

typescript// vite.config.ts
export default defineConfig({
  plugins: [
    svelte({
      preprocess: sveltePreprocess({
        typescript: {
          // 開発時は型チェックをスキップし、ビルド時のみ実行
          compilerOptions: {
            noEmit: true,
          },
        },
      }),
    }),
  ],
});

最適化 2: SCSS のソースマップ設定

本番環境ではソースマップを無効化してファイルサイズを削減できます。

typescript// vite.config.ts
export default defineConfig({
  plugins: [
    svelte({
      preprocess: sveltePreprocess({
        scss: {
          // 本番環境ではソースマップを無効化
          sourceMap: process.env.NODE_ENV !== 'production',
        },
      }),
    }),
  ],
});

まとめ

本記事では、svelte-preprocess を使って TypeScript・SCSS・PostCSS を安全に共存させる方法を解説しました。

svelte-preprocess を導入することで、以下のメリットが得られます。

技術面のメリット:

  • 複数のプリプロセッサを 1 つの設定ファイルで統合管理できる
  • 処理順序が自動的に最適化され、競合リスクが低減される
  • TypeScript による型安全性と、SCSS の便利な記法を同時に活用できる
  • PostCSS により、ベンダープレフィックスの自動追加など CSS の最適化が自動化される

開発体験のメリット:

  • ビルドツールを変更しても、同じ設定を使い回せる
  • lang 属性を指定するだけで、適切なプリプロセッサが自動選択される
  • 設定ファイルがシンプルになり、保守性が向上する

実践のポイント:

  • グローバル変数ファイルを prependData で自動インポートすることで、すべてのコンポーネントで共通の変数が使える
  • TypeScript の型定義を活用し、プロパティの型安全性を確保する
  • SCSS のネスト記法で、BEM などのクラス命名規則を読みやすく記述できる
  • エラー発生時は、エラーコードとメッセージから原因を特定し、段階的に解決する

svelte-preprocess は、Svelte プロジェクトで複数のプリプロセッサを扱う際の標準的なソリューションとなっています。本記事で紹介した設定方法を参考に、型安全で保守性の高い Svelte アプリケーション開発を始めてみてください。

プロジェクトの規模が大きくなるほど、統合的なプリプロセッサ管理の重要性は高まります。ぜひ、svelte-preprocess を活用して、快適な開発体験を手に入れてください。

関連リンク