T-CREATOR

SolidJS アドオン&エコシステム最新事情

SolidJS アドオン&エコシステム最新事情

SolidJS は 2025 年において、React の代替として注目を集める軽量で高性能な JavaScript フレームワークです。きめ細かなリアクティビティシステムと優れたパフォーマンスで、多くの開発者から支持されています。

この記事では、SolidJS のエコシステムの現状から、状態管理、ルーティング、UI コンポーネント、開発ツールまで、2024 年から 2025 年にかけての最新動向を詳しくご紹介します。SolidJS を導入検討中の方や、既存プロジェクトの拡張を考えている方に役立つ情報をお届けいたします。

SolidJS エコシステムの現状

SolidJS エコシステムは急速な成長を遂げており、2024 年には重要な転換点を迎えました。React や Vue ほど大きくはないものの、活発なコミュニティと継続的な開発により、実用的なライブラリが充実してきています。

エコシステムの成長状況

mermaidflowchart TD
    community[SolidJSコミュニティ] --> hackathon[SolidHack ハッカソン]
    community --> fellowship[Solid Fellowship プログラム]

    hackathon --> components[コンポーネントライブラリ開発]
    fellowship --> devtools[開発ツール整備]
    fellowship --> docs[ドキュメント改善]

    components --> kobalte[Kobalte]
    components --> hope[Hope UI]
    components --> solid_ui[Solid UI]

    devtools --> debugger[Solid Rewind]
    devtools --> browser_ext[ブラウザ拡張機能]

SolidJS コミュニティは、OpenCollective を通じて SolidHack ハッカソンを 2 回開催し、不足していたエコシステムの部分を補完しました。また、Solid Fellowship プログラムにより、開発ツールやドキュメント整備に資金を提供しています。

2025 年の位置づけ

現在の SolidJS は、以下の特徴により注目されています。

項目詳細
パフォーマンス直接的な DOM 更新により、React より高速なレンダリング
TypeScript 対応標準で TypeScript をサポート
バンドルサイズコンパイル時最適化により小さなバンドルサイズ
学習コストReact ライクな構文で習得しやすい

状態管理アドオン

SolidJS の状態管理は、内蔵されたリアクティブプリミティブが中心となっています。外部ライブラリに依存することなく、効率的な状態管理を実現できます。

Solid Signal Store

SolidJS の状態管理の核となるのが、createSignal を中心としたリアクティブシステムです。

javascriptimport { createSignal, createEffect } from 'solid-js';

// 基本的なSignalの使用
const [count, setCount] = createSignal(0);

// リアクティブな計算値
const doubled = () => count() * 2;

// 副作用の定義
createEffect(() => {
  console.log(`カウント: ${count()}, 2倍値: ${doubled()}`);
});

このコードでは、count の値が変更されるたびに、doubled と createEffect が自動的に再実行されます。

Solid Context

複数コンポーネント間での状態共有には、Solid Context を活用します。

typescriptimport {
  createContext,
  useContext,
  createSignal,
} from 'solid-js';
import type { ParentComponent } from 'solid-js';

// コンテキストの型定義
interface AppContextType {
  user: () => string;
  setUser: (name: string) => void;
}

// コンテキストの作成
const AppContext = createContext<AppContextType>();
typescript// プロバイダーコンポーネントの実装
const AppProvider: ParentComponent = (props) => {
  const [user, setUser] = createSignal('');

  return (
    <AppContext.Provider value={{ user, setUser }}>
      {props.children}
    </AppContext.Provider>
  );
};

// コンテキストを使用するコンポーネント
const UserProfile = () => {
  const context = useContext(AppContext);

  return (
    <div>
      <p>ユーザー名: {context?.user()}</p>
      <button
        onClick={() => context?.setUser('新しいユーザー')}
      >
        ユーザー変更
      </button>
    </div>
  );
};

外部状態管理ライブラリとの連携

SolidJS は、既存の状態管理ライブラリとの連携も可能です。

typescript// Zustandライクなストア実装例
import { createSignal } from 'solid-js';

interface CounterStore {
  count: () => number;
  increment: () => void;
  decrement: () => void;
  reset: () => void;
}

const createCounterStore = (): CounterStore => {
  const [count, setCount] = createSignal(0);

  return {
    count,
    increment: () => setCount((c) => c + 1),
    decrement: () => setCount((c) => c - 1),
    reset: () => setCount(0),
  };
};

図で理解できる要点:

  • Signal ベースの状態管理で依存関係を自動追跡
  • Context を使った階層的な状態共有
  • 外部ライブラリとの柔軟な連携

ルーティングソリューション

SolidJS のルーティングは、@solidjs/router を中心に発展しており、2024 年には大幅な機能拡張が行われました。

Solid Router v2 の最新機能

2024 年にリリースされた Solid Router v2 では、以下の新機能が追加されました。

typescriptimport { Router, Route, A } from '@solidjs/router';
import { lazy } from 'solid-js';

// 遅延ロードコンポーネント
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const UserProfile = lazy(
  () => import('./pages/UserProfile')
);
typescript// ルーターの基本設定
const App = () => {
  return (
    <Router>
      <nav>
        <A href='/'>ホーム</A>
        <A href='/about'>概要</A>
        <A href='/user/123'>ユーザー</A>
      </nav>

      <Route path='/' component={Home} />
      <Route path='/about' component={About} />
      <Route path='/user/:id' component={UserProfile} />
    </Router>
  );
};

ファイルベースルーティング

SolidStart と組み合わせることで、Next.js ライクなファイルベースルーティングが利用できます。

rubysrc/
  routes/
    index.tsx          // '/' にマップ
    about.tsx          // '/about' にマップ
    user/
      [id].tsx         // '/user/:id' にマップ
      profile.tsx      // '/user/profile' にマップ
typescript// src/routes/user/[id].tsx
import { useParams } from '@solidjs/router';

export default function UserDetail() {
  const params = useParams();

  return (
    <div>
      <h1>ユーザーID: {params.id}</h1>
      <p>ユーザーの詳細情報を表示します</p>
    </div>
  );
}

動的ルーティングの実装

動的ルーティングでは、パラメータやクエリストリングを効率的に処理できます。

typescriptimport {
  useParams,
  useSearchParams,
} from '@solidjs/router';
import { createResource } from 'solid-js';

const ProductDetail = () => {
  const params = useParams();
  const [searchParams] = useSearchParams();

  // 動的データの取得
  const [product] = createResource(
    () => params.id,
    async (id) => {
      const response = await fetch(`/api/products/${id}`);
      return response.json();
    }
  );

  return (
    <div>
      <h1>{product()?.name}</h1>
      <p>カテゴリ: {searchParams.category || 'なし'}</p>
    </div>
  );
};
mermaidsequenceDiagram
    participant User as ユーザー
    participant Router as SolidRouter
    participant Component as コンポーネント
    participant API as APIサーバー

    User->>Router: /user/123?tab=profile
    Router->>Component: パラメータ解析
    Component->>API: ユーザーデータ取得
    API->>Component: JSONレスポンス
    Component->>User: レンダリング完了

この図は、動的ルーティングでのデータフローを示しています。ユーザーがアクセスすると、ルーターがパラメータを解析し、コンポーネントが必要なデータを取得してレンダリングします。

UI コンポーネントライブラリ

SolidJS の UI エコシステムは 2024 年に大きく成熟し、様々な用途に対応したコンポーネントライブラリが登場しました。

Solid UI

Solid UI は、Kobalte と Tailwind CSS を組み合わせた美しいデザインのコンポーネントライブラリです。

bashyarn add @solid-ui/core @solid-ui/components
typescriptimport {
  Button,
  Card,
  TextField,
} from '@solid-ui/components';
import { createSignal } from 'solid-js';

const LoginForm = () => {
  const [email, setEmail] = createSignal('');
  const [password, setPassword] = createSignal('');

  const handleSubmit = (e: Event) => {
    e.preventDefault();
    console.log('ログイン:', email(), password());
  };

  return (
    <Card class='max-w-md mx-auto p-6'>
      <h2 class='text-2xl font-bold mb-4'>ログイン</h2>
      <form onSubmit={handleSubmit} class='space-y-4'>
        <TextField
          label='メールアドレス'
          value={email()}
          onInput={(e) => setEmail(e.currentTarget.value)}
          type='email'
          required
        />
        <TextField
          label='パスワード'
          value={password()}
          onInput={(e) =>
            setPassword(e.currentTarget.value)
          }
          type='password'
          required
        />
        <Button
          type='submit'
          variant='primary'
          class='w-full'
        >
          ログイン
        </Button>
      </form>
    </Card>
  );
};

Kobalte

Kobalte は、アクセシビリティを重視した低レベルコンポーネントライブラリです。WAI-ARIA ガイドラインに完全準拠しています。

bashyarn add @kobalte/core
typescriptimport { Button, Dialog } from '@kobalte/core';
import { createSignal } from 'solid-js';

const ConfirmDialog = () => {
  const [isOpen, setIsOpen] = createSignal(false);

  return (
    <>
      <Button.Root onClick={() => setIsOpen(true)}>
        削除
      </Button.Root>

      <Dialog.Root open={isOpen()} onOpenChange={setIsOpen}>
        <Dialog.Portal>
          <Dialog.Overlay class='dialog-overlay' />
          <Dialog.Content class='dialog-content'>
            <Dialog.Title class='dialog-title'>
              確認
            </Dialog.Title>
            <Dialog.Description class='dialog-description'>
              この操作は元に戻せません。本当に削除しますか?
            </Dialog.Description>

            <div class='dialog-buttons'>
              <Dialog.CloseButton class='button button-secondary'>
                キャンセル
              </Dialog.CloseButton>
              <Button.Root class='button button-danger'>
                削除実行
              </Button.Root>
            </div>
          </Dialog.Content>
        </Dialog.Portal>
      </Dialog.Root>
    </>
  );
};

Hope UI

Hope UI は、Chakra UI ライクなデザインシステムを提供する包括的なコンポーネントライブラリです。

bashyarn add @hope-ui/solid
typescriptimport {
  HopeProvider,
  Box,
  SimpleGrid,
  Card,
  CardHeader,
  CardBody,
  Text,
  Badge,
} from '@hope-ui/solid';

const ProductGrid = () => {
  const products = [
    {
      id: 1,
      name: 'ノートパソコン',
      price: 89800,
      category: 'PC',
    },
    {
      id: 2,
      name: 'マウス',
      price: 2980,
      category: 'アクセサリ',
    },
    {
      id: 3,
      name: 'キーボード',
      price: 12800,
      category: 'アクセサリ',
    },
  ];

  return (
    <HopeProvider>
      <SimpleGrid
        columns={{ base: 1, md: 2, lg: 3 }}
        spacing={4}
      >
        {products.map((product) => (
          <Card key={product.id} variant='elevated'>
            <CardHeader>
              <Text size='lg' fontWeight='bold'>
                {product.name}
              </Text>
              <Badge colorScheme='info'>
                {product.category}
              </Badge>
            </CardHeader>
            <CardBody>
              <Text
                fontSize='2xl'
                color='green.500'
                fontWeight='bold'
              >
                ¥{product.price.toLocaleString()}
              </Text>
            </CardBody>
          </Card>
        ))}
      </SimpleGrid>
    </HopeProvider>
  );
};

カスタムコンポーネントライブラリ構築

独自のデザインシステムを構築する場合の基本的なアプローチです。

typescript// components/Button/Button.tsx
import { splitProps, JSX } from 'solid-js';
import { Dynamic } from 'solid-js/web';
import './Button.css';

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  as?: keyof JSX.IntrinsicElements;
  children: JSX.Element;
}

export const Button = (
  props: ButtonProps &
    JSX.ButtonHTMLAttributes<HTMLButtonElement>
) => {
  const [local, others] = splitProps(props, [
    'variant',
    'size',
    'as',
    'children',
    'class',
  ]);

  const classes = () => {
    const baseClasses = 'btn';
    const variantClass = `btn--${
      local.variant || 'primary'
    }`;
    const sizeClass = `btn--${local.size || 'md'}`;
    const customClass = local.class || '';

    return `${baseClasses} ${variantClass} ${sizeClass} ${customClass}`;
  };

  return (
    <Dynamic
      component={local.as || 'button'}
      class={classes()}
      {...others}
    >
      {local.children}
    </Dynamic>
  );
};

図で理解できる要点:

  • Solid UI は美しいデザインと使いやすさを両立
  • Kobalte はアクセシビリティ重視の低レベルプリミティブを提供
  • Hope UI は包括的なデザインシステムを実現

開発ツール&ユーティリティ

SolidJS の開発体験を向上させるツール群が 2024 年に大きく進歩しました。

Solid DevTools

Solid DevTools は、SolidJS アプリケーションのデバッグを支援するブラウザ拡張機能です。

typescript// 開発環境でのDevToolsセットアップ
import { render } from 'solid-js/web';
import { DEV } from 'solid-js';

if (DEV) {
  // DevToolsの初期化
  import('solid-devtools').then(({ attachDevtools }) => {
    attachDevtools();
  });
}

render(() => <App />, document.getElementById('root')!);

DevTools の主な機能:

機能説明
コンポーネントツリーリアルタイムでコンポーネント階層を表示
Signal 追跡Signal の値変更を時系列で追跡
パフォーマンス監視レンダリング回数と実行時間を計測
State Inspector現在の状態を詳細に検査

Vite プラグイン

SolidJS の Vite プラグインには、開発効率を向上させる機能が豊富に含まれています。

javascript// vite.config.js
import { defineConfig } from 'vite';
import solid from 'vite-plugin-solid';

export default defineConfig({
  plugins: [
    solid({
      // Hot Module Replacement の設定
      hot: true,
      // TypeScript の型チェック
      typescript: true,
      // 開発サーバー用の設定
      dev: true,
    }),
  ],
  server: {
    port: 3000,
    open: true,
  },
  build: {
    target: 'esnext',
  },
});

TypeScript サポート強化

SolidJS 2.0 では、TypeScript サポートがさらに強化されました。

typescript// 厳密な型定義の例
import { Component, JSX, createSignal } from 'solid-js';

interface User {
  id: number;
  name: string;
  email: string;
}

interface UserListProps {
  users: User[];
  onUserSelect: (user: User) => void;
}

const UserList: Component<UserListProps> = (props) => {
  const [selectedId, setSelectedId] = createSignal<
    number | null
  >(null);

  const handleUserClick = (user: User): void => {
    setSelectedId(user.id);
    props.onUserSelect(user);
  };

  return (
    <div class='user-list'>
      {props.users.map((user) => (
        <div
          key={user.id}
          class={`user-item ${
            selectedId() === user.id ? 'selected' : ''
          }`}
          onClick={() => handleUserClick(user)}
        >
          <h3>{user.name}</h3>
          <p>{user.email}</p>
        </div>
      ))}
    </div>
  );
};
typescript// ジェネリクス型を使った再利用可能なコンポーネント
interface ListProps<T> {
  items: T[];
  renderItem: (item: T, index: number) => JSX.Element;
  keyExtractor: (item: T) => string | number;
}

function List<T>(props: ListProps<T>): JSX.Element {
  return (
    <div class='list-container'>
      {props.items.map((item, index) => (
        <div
          key={props.keyExtractor(item)}
          class='list-item'
        >
          {props.renderItem(item, index)}
        </div>
      ))}
    </div>
  );
}
mermaidflowchart LR
    dev[開発者] --> vite[Vite + Solid Plugin]
    vite --> hmr[HMR]
    vite --> typecheck[TypeScript Check]
    vite --> devtools[Solid DevTools]

    hmr --> browser[ブラウザ]
    typecheck --> ide[IDE/エディタ]
    devtools --> debugger[デバッグ情報]

    browser --> feedback[即座のフィードバック]
    ide --> errors[型エラー検出]
    debugger --> insight[パフォーマンス分析]

この開発フローでは、Vite プラグインが中心となって、HMR、型チェック、デバッグツールを連携させ、効率的な開発環境を提供します。

メタフレームワーク

SolidJS のメタフレームワークは、2024 年 5 月の SolidStart 1.0 リリースにより大きな転換点を迎えました。

SolidStart

SolidStart 1.0 は、Next.js の SolidJS 版として位置づけられる包括的なメタフレームワークです。

bash# SolidStartプロジェクトの作成
yarn create solid-start my-app
cd my-app
yarn dev

SolidStart の基本的なプロジェクト構造:

arduinomy-app/
  src/
    routes/
      index.tsx
      about.tsx
      api/
        users.ts
    components/
      Header.tsx
    app.tsx
    entry-client.tsx
    entry-server.tsx
  vite.config.ts
  app.config.ts

Solid App Router

SolidStart は内蔵の App Router を提供し、ファイルベースルーティングを実現します。

typescript// src/routes/blog/[slug].tsx
import { useParams } from '@solidjs/router';
import { createResource } from 'solid-js';

interface BlogPost {
  title: string;
  content: string;
  publishedAt: string;
}

export default function BlogPost() {
  const params = useParams();

  const [post] = createResource(
    () => params.slug,
    async (slug): Promise<BlogPost> => {
      const response = await fetch(`/api/posts/${slug}`);
      if (!response.ok) {
        throw new Error('記事が見つかりません');
      }
      return response.json();
    }
  );

  return (
    <article>
      <h1>{post()?.title}</h1>
      <time>{post()?.publishedAt}</time>
      <div innerHTML={post()?.content} />
    </article>
  );
}
typescript// src/routes/api/posts/[slug].ts
import { APIEvent } from '@solidjs/start/server';

export async function GET({ params }: APIEvent) {
  const { slug } = params;

  // データベースから記事を取得
  const post = await getPostBySlug(slug);

  if (!post) {
    return new Response('記事が見つかりません', {
      status: 404,
    });
  }

  return new Response(JSON.stringify(post), {
    headers: {
      'Content-Type': 'application/json',
    },
  });
}

async function getPostBySlug(slug: string) {
  // 実際のデータベース操作
  return {
    title: `記事: ${slug}`,
    content: '<p>記事の内容</p>',
    publishedAt: new Date().toISOString(),
  };
}

SSR/SSG 対応状況

SolidStart は、Server-Side Rendering と Static Site Generation の両方をサポートしています。

typescript// SSRでのデータ取得
import { createResource } from 'solid-js';
import { isServer } from 'solid-js/web';

export default function HomePage() {
  const [data] = createResource(async () => {
    if (isServer) {
      // サーバー側でのデータ取得
      const response = await fetch(
        'http://localhost:3000/api/data'
      );
      return response.json();
    } else {
      // クライアント側でのデータ取得
      const response = await fetch('/api/data');
      return response.json();
    }
  });

  return (
    <div>
      <h1>ホームページ</h1>
      <div>データ: {JSON.stringify(data())}</div>
    </div>
  );
}
javascript// app.config.ts - SSG設定
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
  ssr: true,
  prerender: {
    routes: [
      '/',
      '/about',
      '/blog/first-post',
      '/blog/second-post',
    ],
  },
  server: {
    experimental: {
      islands: true, // アイランドアーキテクチャのサポート
    },
  },
});
mermaidstateDiagram-v2
    [*] --> Build

    state "SSR\nssr: true" as SSR
    state "SSG\nprerender: true" as SSG
    state "SPA\nssr: false" as SPA

    Build --> SSR
    Build --> SSG
    Build --> SPA

    SSR --> ServerRendering
    SSG --> StaticGeneration
    SPA --> ClientRendering

    ServerRendering --> Hydration
    StaticGeneration --> OptionalHydration
    ClientRendering --> [*]

    Hydration --> Interactive
    OptionalHydration --> Interactive
    Interactive --> [*]

この状態図は、SolidStart の様々なレンダリング戦略を示しています。設定に応じて、SSR、SSG、SPA のいずれかを選択でき、それぞれ適切なハイドレーション戦略が適用されます。

テスト環境

SolidJS のテスト環境は 2024 年に大幅に改善され、実用的なテストツールが充実しました。

Solid Testing Library

Solid Testing Library は、React Testing Library にインスパイアされたテストユーティリティです。

bashyarn add -D @solidjs/testing-library @testing-library/jest-dom
typescript// __tests__/Counter.test.tsx
import {
  render,
  fireEvent,
  screen,
} from '@solidjs/testing-library';
import '@testing-library/jest-dom';
import { Counter } from '../src/components/Counter';

describe('Counter Component', () => {
  test('初期値が0で表示される', () => {
    render(() => <Counter />);

    expect(
      screen.getByText('カウント: 0')
    ).toBeInTheDocument();
  });

  test('増加ボタンをクリックするとカウントが増える', async () => {
    render(() => <Counter />);

    const incrementButton = screen.getByText('増加');
    fireEvent.click(incrementButton);

    expect(
      screen.getByText('カウント: 1')
    ).toBeInTheDocument();
  });

  test('減少ボタンをクリックするとカウントが減る', async () => {
    render(() => <Counter initialValue={5} />);

    const decrementButton = screen.getByText('減少');
    fireEvent.click(decrementButton);

    expect(
      screen.getByText('カウント: 4')
    ).toBeInTheDocument();
  });
});
typescript// テスト対象のCounterコンポーネント
import { createSignal } from 'solid-js';

interface CounterProps {
  initialValue?: number;
}

export const Counter = (props: CounterProps) => {
  const [count, setCount] = createSignal(
    props.initialValue || 0
  );

  return (
    <div>
      <p>カウント: {count()}</p>
      <button onClick={() => setCount((c) => c + 1)}>
        増加
      </button>
      <button onClick={() => setCount((c) => c - 1)}>
        減少
      </button>
    </div>
  );
};

Vitest との統合

Vitest は SolidJS プロジェクトで推奨されるテストランナーです。

javascript// vitest.config.js
import { defineConfig } from 'vitest/config';
import solid from 'vite-plugin-solid';

export default defineConfig({
  plugins: [solid()],
  test: {
    environment: 'jsdom',
    setupFiles: ['./src/test-setup.ts'],
    globals: true,
  },
});
typescript// src/test-setup.ts
import '@testing-library/jest-dom';
typescript// __tests__/api.test.ts
import { describe, it, expect, vi } from 'vitest';
import { createResource } from 'solid-js';

// APIフェッチ関数のテスト
describe('API Functions', () => {
  it('ユーザーデータを正しく取得する', async () => {
    const mockUser = { id: 1, name: 'テストユーザー' };

    // fetchをモック
    global.fetch = vi.fn().mockResolvedValue({
      ok: true,
      json: () => Promise.resolve(mockUser),
    });

    const [user] = createResource(async () => {
      const response = await fetch('/api/users/1');
      return response.json();
    });

    // リソースの解決を待つ
    await new Promise((resolve) => {
      const checkResolved = () => {
        if (user.state === 'ready') {
          resolve(user());
        } else {
          setTimeout(checkResolved, 10);
        }
      };
      checkResolved();
    });

    expect(user()).toEqual(mockUser);
    expect(fetch).toHaveBeenCalledWith('/api/users/1');
  });
});

E2E テストソリューション

Playwright や Cypress を使用した E2E テストの設定例です。

bashyarn add -D @playwright/test
typescript// e2e/example.spec.ts
import { test, expect } from '@playwright/test';

test('ホームページが正しく表示される', async ({ page }) => {
  await page.goto('/');

  // タイトルの確認
  await expect(page).toHaveTitle(/SolidJS App/);

  // ヘッダーの確認
  const heading = page.getByRole('heading', {
    name: 'Welcome to SolidJS',
  });
  await expect(heading).toBeVisible();
});

test('ユーザー登録フローのテスト', async ({ page }) => {
  await page.goto('/register');

  // フォームの入力
  await page.fill('[data-testid="username"]', 'testuser');
  await page.fill(
    '[data-testid="email"]',
    'test@example.com'
  );
  await page.fill(
    '[data-testid="password"]',
    'password123'
  );

  // 送信ボタンのクリック
  await page.click('[data-testid="submit-button"]');

  // 成功メッセージの確認
  await expect(
    page.getByText('登録が完了しました')
  ).toBeVisible();

  // リダイレクトの確認
  await expect(page).toHaveURL('/dashboard');
});
javascript// playwright.config.js
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './e2e',
  use: {
    baseURL: 'http://localhost:3000',
  },
  webServer: {
    command: 'yarn dev',
    port: 3000,
    reuseExistingServer: !process.env.CI,
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
    {
      name: 'firefox',
      use: { ...devices['Desktop Firefox'] },
    },
  ],
});

図で理解できる要点:

  • Solid Testing Library でコンポーネント単体テストを実現
  • Vitest による JavaScript/TypeScript 統合テスト環境
  • Playwright での E2E テストによる包括的なテスト戦略

パフォーマンス最適化ツール

SolidJS の最大の魅力の一つは優れたパフォーマンスです。2024 年にはさらなる最適化ツールが登場しました。

Bundle サイズ最適化

SolidJS は、コンパイル時最適化により小さなバンドルサイズを実現します。

javascript// vite.config.js - バンドル最適化設定
import { defineConfig } from 'vite';
import solid from 'vite-plugin-solid';
import { visualizer } from 'rollup-plugin-visualizer';

export default defineConfig({
  plugins: [
    solid(),
    visualizer({
      filename: 'dist/stats.html',
      open: true,
    }),
  ],
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['solid-js'],
          ui: ['@kobalte/core', '@hope-ui/solid'],
        },
      },
    },
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true,
      },
    },
  },
});

バンドル分析のためのスクリプト:

typescript// scripts/bundle-analysis.ts
import { analyzeBundle } from 'rollup-plugin-analyzer';

const analyzeBundleSize = async () => {
  const analysis = await analyzeBundle('./dist');

  console.log('Bundle Size Analysis:');
  console.log(`Total size: ${analysis.totalSize} KB`);
  console.log(`Gzipped size: ${analysis.gzippedSize} KB`);

  analysis.modules.forEach((module) => {
    if (module.size > 10000) {
      // 10KB以上のモジュール
      console.log(
        `Large module: ${module.name} (${module.size} bytes)`
      );
    }
  });
};

コード分割戦略

効果的なコード分割により、初期ロード時間を短縮できます。

typescript// ルートレベルでのコード分割
import { lazy, Suspense } from 'solid-js';
import { Router, Route } from '@solidjs/router';

// 遅延ロードコンポーネント
const HomePage = lazy(() => import('./pages/HomePage'));
const ProductsPage = lazy(
  () => import('./pages/ProductsPage')
);
const AdminPanel = lazy(() => import('./pages/AdminPanel'));

const App = () => {
  return (
    <Router>
      <Suspense fallback={<div>読み込み中...</div>}>
        <Route path='/' component={HomePage} />
        <Route path='/products' component={ProductsPage} />
        <Route path='/admin' component={AdminPanel} />
      </Suspense>
    </Router>
  );
};
typescript// コンポーネントレベルでの動的インポート
import { createSignal, lazy, Show } from 'solid-js';

const HeavyChartComponent = lazy(
  () => import('./HeavyChart')
);

const Dashboard = () => {
  const [showChart, setShowChart] = createSignal(false);

  return (
    <div>
      <h1>ダッシュボード</h1>

      <button onClick={() => setShowChart(true)}>
        チャートを表示
      </button>

      <Show when={showChart()}>
        <Suspense
          fallback={<div>チャート読み込み中...</div>}
        >
          <HeavyChartComponent />
        </Suspense>
      </Show>
    </div>
  );
};

リアクティビティ最適化

SolidJS のリアクティビティシステムを効率的に使用するためのベストプラクティスです。

typescript// メモ化による最適化
import { createSignal, createMemo, For } from 'solid-js';

interface Item {
  id: number;
  name: string;
  category: string;
  price: number;
}

const ProductList = () => {
  const [items, setItems] = createSignal<Item[]>([]);
  const [searchTerm, setSearchTerm] = createSignal('');
  const [selectedCategory, setSelectedCategory] =
    createSignal('all');

  // 重い計算処理をメモ化
  const filteredItems = createMemo(() => {
    const term = searchTerm().toLowerCase();
    const category = selectedCategory();

    return items().filter((item) => {
      const matchesSearch = item.name
        .toLowerCase()
        .includes(term);
      const matchesCategory =
        category === 'all' || item.category === category;
      return matchesSearch && matchesCategory;
    });
  });

  // カテゴリーリストもメモ化
  const categories = createMemo(() => {
    const uniqueCategories = new Set(
      items().map((item) => item.category)
    );
    return ['all', ...Array.from(uniqueCategories)];
  });

  return (
    <div>
      <input
        type='text'
        placeholder='商品を検索...'
        value={searchTerm()}
        onInput={(e) =>
          setSearchTerm(e.currentTarget.value)
        }
      />

      <select
        value={selectedCategory()}
        onChange={(e) =>
          setSelectedCategory(e.currentTarget.value)
        }
      >
        <For each={categories()}>
          {(category) => (
            <option value={category}>{category}</option>
          )}
        </For>
      </select>

      <div class='product-grid'>
        <For each={filteredItems()}>
          {(item) => (
            <div class='product-card' key={item.id}>
              <h3>{item.name}</h3>
              <p>{item.category}</p>
              <p>¥{item.price.toLocaleString()}</p>
            </div>
          )}
        </For>
      </div>
    </div>
  );
};
typescript// バッチ更新による最適化
import { batch, createSignal } from 'solid-js';

const useCounter = () => {
  const [count, setCount] = createSignal(0);
  const [lastUpdated, setLastUpdated] = createSignal(
    new Date()
  );

  const increment = () => {
    // 複数の更新をバッチ化
    batch(() => {
      setCount((c) => c + 1);
      setLastUpdated(new Date());
    });
  };

  const reset = () => {
    batch(() => {
      setCount(0);
      setLastUpdated(new Date());
    });
  };

  return { count, lastUpdated, increment, reset };
};
mermaidflowchart TD
    source[データソース] --> signal[Signal]
    signal --> memo[Memo/Derived]
    signal --> effect[Effect]
    memo --> component[コンポーネント]
    effect --> sideeffect[副作用]

    component --> dom[DOM更新]
    sideeffect --> api[API呼び出し]

    subgraph optimization[最適化ポイント]
        memo2[重い計算のメモ化]
        batch2[バッチ更新]
        lazy2[遅延ロード]
    end

    memo --> memo2
    signal --> batch2
    component --> lazy2

この図は、SolidJS のリアクティビティシステムと最適化ポイントを示しています。Signal から派生した Memo と Effect が効率的に DOM 更新と副作用を処理し、各段階で最適化が可能です。

まとめ

SolidJS のエコシステムは 2024 年から 2025 年にかけて飛躍的な発展を遂げており、本格的な Web アプリケーション開発に十分な成熟度に達しています。

エコシステムの成熟度

SolidJS は以下の分野で特に優れた進歩を見せています:

状態管理: 内蔵のリアクティブプリミティブにより、外部ライブラリなしで効率的な状態管理を実現。createSignal と createEffect の組み合わせで、複雑なアプリケーション状態も管理できます。

ルーティング: Solid Router v2 の登場により、Next.js レベルの機能を提供。ファイルベースルーティングと動的ルーティングにより、大規模アプリケーションにも対応可能です。

UI コンポーネント: Kobalte、Hope UI、Solid UI など、用途に応じた選択肢が充実。アクセシビリティ重視からデザインシステムまで、幅広いニーズに対応しています。

2025 年の展望

SolidJS は以下の理由で今後さらなる成長が期待されます:

  1. パフォーマンス優位性: React を上回るレンダリング性能により、UX を重視するプロジェクトで採用が加速
  2. 学習コストの低さ: React 経験者なら短期間で習得可能な構文とコンセプト
  3. TypeScript 完全サポート: 型安全性を重視する現代の開発トレンドに合致
  4. SolidStart 1.0: メタフレームワークの安定版リリースにより、エンタープライズ導入の障壁が解消

導入時の考慮点

SolidJS 導入を検討される際は、以下の点をご考慮ください:

  • チーム習熟度: React の知識があれば短期間で習得可能
  • エコシステム成熟度: 必要なライブラリが揃っているかの事前確認
  • 長期サポート: コミュニティの活発さと継続的な開発体制の確認

SolidJS エコシステムは、現在進行形で成長を続けており、2025 年には更なる飛躍が予想されます。パフォーマンスを重視し、モダンな開発体験を求めるプロジェクトにとって、SolidJS は最適な選択肢の一つとなるでしょう。

関連リンク