Zenn
Open5

SolidStart@v1.1 + Tailwindcss@v4 で、SolidJSを触る

yunayuna

これまで、Frontendの実装にVue3, Nuxtを利用してきましたが、
大規模プロジェクトや細かいレンダリングコントロールができる技術も身につけるべく、
JSXベースの書き方ができるSolidJSを使ってみたので、そのメモです。

VueやSvelteのテンプレートベースのフレームワークと、ReactやSolidJSなどの、JSXベースの書き方の、
どちらがAI主導のコーディングスタイルに合うのか?も検証したかったのが選定の理由です。
また、Reactは大規模プロジェクトには良いかもしれませんが、小さめのプロダクトでも回しやすそうな、SolidJSのよりシンプルな仕様も魅力的でした。

Netlifyからの企業スポンサーシップのもとで開発が進められており
Nuxt.jsやNext.jsに相当する、SolidStart(Solid版メタフレームワーク)も昨年1.0版が出て、開発が進んでいますし、
Solid本体も、次期versionの2.0に向けて開発が積極的に進んでいることから、
今後の発展も見込まれています。

SolidJS

https://www.solidjs.com/

SolidStart

https://start.solidjs.com/

SolidJSの特徴

  • JSXベースの関数コンポーネント:

    • SolidJSはコンポーネントを純粋なJavaScript関数として定義し、JSX記法を利用してUIを記述します。これにより、Reactに似た開発体験を提供し、既存のJavaScriptツールチェーンとの互換性も高いです。
  • Fine-grainedリアクティビティ:

    • シグナルやエフェクトを利用した細粒度のリアクティブシステムにより、依存関係が明確に追跡され、必要な部分のみ効率的に再レンダリングされます。
  • 仮想DOMを使用しない:

    • コンパイル時にJSXを低レベルなDOM操作コードに変換するため、仮想DOMによる中間処理がなく、直接DOM操作を行います。これにより、不要なオーバーヘッドが削減されています。
  • 高いパフォーマンス:

    • Fine-grainedリアクティビティや直接DOM操作のアプローチにより、特に大規模なアプリケーションや頻繁な更新が求められる場合でも高速な動作が実現されています。
  • Reactとの親和性:

    • JSX記法やコンポーネントの書き方がReactと似ているため、Reactに慣れている開発者は学習コストが低く、スムーズに移行できます。
  • 柔軟な状態管理:

    • シグナルやストアを用いた状態管理により、局所的な状態更新が容易に実現でき、不要な再計算やレンダリングを最小限に抑えられます。
  • 活発な開発体制とサポート:

    • Ryan Carniato氏を中心に、Netlifyなど大手の支援を受けながら開発が進められており、定期的なアップデートや新機能の追加が行われています。
  • エコシステムの拡大:

    • SolidStartなどの周辺ツールやライブラリが整備され、今後さらに豊富なエコシステムの構築が期待されています。
yunayuna

始め方

私は普段Javascriptエンジンにbunを使っているので、
SolidJS, bun, TypeScriptベースでのプロジェクト開始。
(詳しくはSolidStartのサイト参照)

サンプルを作る時、いろんなテンプレートを選べるようになってます。

$ bun create solid
  
 Create-Solid v0.6.2
│
◆  Project Name
│  solid-project
◆  Is this a SolidStart project?
│  ● Yes / ○ No
└
◆  Which template would you like to use?
│  ● basic
│  ○ bare
│  ○ hackernews
│  ○ notes
│  ○ todomvc
│  ○ with-auth
│  ○ with-authjs
│  ○ with-drizzle
│  ○ with-mdx
│  ○ with-prisma
│  ○ with-solid-styled
│  ○ with-tailwindcss
│  ○ with-tanstack-router
│  ○ with-trpc
│  ○ with-unocss
│  ○ with-vitest
│  ○ experiments
◆  Use Typescript?
│  ● Yes / ○ No
└
◇  Project created 🎉
│
◇  To get started, run: ─╮
│                        │
│  cd solid-project      │
│  bun install           │
│  bun dev               │
│                        │
├────────────────────────╯
yunayuna

ディレクトリ構造

tailwindscss利用前提、中規模〜大規模プロジェクトを想定した構成です。

my-solidstart-app/
├── public/              # 公開アセット(そのまま配信される静的ファイル)
│   ├── favicon.ico      # 例: Favicon。アクセスは「/favicon.ico」
│   └── images/          # 例: 画像ディレクトリ
│       └── logo.png     # 例: ロゴ画像。アクセスは「/images/logo.png」
├── src/                 # アプリのソースコード
│   ├── routes/          # ページコンポーネント置き場(ルーティング定義)
│   │   ├── index.tsx        # ルートページ「/」に対応するページ
│   │   ├── about.tsx        # 「/about」に対応するページ
│   │   ├── blog.tsx         # 「/blog/*」のレイアウト兼ページ(ネストレイアウト)
│   │   ├── (admin)/        # ()で囲むディレクトリは、URLパスには影響を与えず、ファイルをグルーピングする
│   │   │   ├── setting.tsx
│   │   ├── blog/
│   │   │   ├── index.tsx    # 「/blog」トップページ(※blog.tsxとは別にトップ用が必要なら)
│   │   │   ├── [id].tsx     # 「/blog/:id」に対応する動的ページ
│   │   │   └── ...          # その他ブログ記事ページ
│   │   ├── api/             # APIルート(サーバーサイド処理用エンドポイント)
│   │   │   └── articles.tsx # 例: GET/POSTをexportしてAPI実装
│   │   └── [...404].tsx     # キャッチオールルート(存在しないページ用の404)
│   ├── components/      # 再利用可能なコンポーネント群
│   │   ├── ui/              # 細かなUI部品(ボタン、入力欄など)
│   │   │   ├── Button.tsx
│   │   │   ├── Input.tsx
│   │   │   └── ... 
│   │   ├── Header.tsx       # ヘッダーコンポーネント
│   │   ├── Footer.tsx       # フッターコンポーネント 
│   │   └── ...              # その他共通コンポーネント
│   ├── layouts/          # (必要に応じて)ページ横断的なレイアウトコンポーネント
│   │   └── MainLayout.tsx   # 例: 全ページ共通レイアウト
│   ├── lib/ or utils/    # ユーティリティ/共通ロジック置き場
│   │   └── utils.ts         # 例: 汎用的な関数(cn関数など)
│   ├── app.css           # グローバルCSS(Tailwindの@importや全体スタイル)
│   ├── app.tsx           # アプリケーションのルートコンポーネント(共通レイアウトや<Router>定義)
│   ├── entry-client.tsx  # クライアントエントリポイント(ブラウザでのHydration設定)
│   └── entry-server.tsx  # サーバーエントリポイント(リクエスト受付とレンダリング)
├── tailwind.config.cjs   # Tailwind CSSの設定ファイル(テーマのカスタマイズ等)
├── solid.config.ts       # SolidStartの設定ファイル(ビルドモードやプリレンダ設定等)
├── package.json
└── ...
  • public/

    • 静的資産(画像、フォント、faviconなど)を配置
    • ビルド後、そのまま公開ディレクトリとして提供される
  • src/routes/

    • ファイルベースのルーティングでページコンポーネントを定義
    • 動的ルートやAPIエンドポイント(例: api/フォルダ)もここに配置
  • src/components/

    • ページ間で再利用するUIコンポーネント(ボタン、入力フォーム、ナビゲーションなど)を管理
    • 小規模な部品はcomponents/ui/などのサブディレクトリに整理可能
  • src/layouts/

    • 全ページまたは特定のページ群で共通のレイアウト(ヘッダー、フッター、サイドバーなど)を定義
    • ページごとのレイアウトを整理し、再利用性を向上
  • **src/lib/ または src/utils/

    • 共通のユーティリティ関数、ヘルパー、ビジネスロジックをまとめる
    • プロジェクト全体で使う共通処理や関数(例: 日時フォーマット、クラス名の結合など)を配置
  • src/app.css

    • Tailwind CSSのディレクティブ(@tailwind base;, @tailwind components;, @tailwind utilities;)を含むグローバルスタイル
    • 全体の基本スタイルやテーマ設定を行う
  • src/app.tsx

    • アプリ全体のルートコンポーネント
    • 共通レイアウトの設定や、ファイルベースのルーティングコンポーネント(例: <FileRoutes />)を読み込む
  • src/entry-client.tsx

    • クライアントサイドのエントリポイント
    • ブラウザ上でのHydrationやインタラクションの初期化を担当
  • src/entry-server.tsx

    • サーバーサイドのエントリポイント
    • リクエストを受けてHTMLをレンダリングするSSR/SSGの処理を実行
  • tailwind.config.cjs

    • Tailwind CSSのテーマ、プラグイン、カスタム設定を定義する設定ファイル
  • solid.config.ts

    • SolidStartのビルドやプリレンダリング設定を行うための構成ファイル
  • package.json

    • プロジェクトの依存関係、スクリプト、メタ情報を管理するファイル
yunayuna

メモ

scoped style (うまく動きませんでした)

Vueの

<style scoped>

これを実現するためには、css moduleを利用します。
▼公式参照
https://docs.solidjs.com/solid-start/building-your-application/css-and-styling

css(scss)ファイルを、module.css に変更し、

│   ├── routes/          # ページコンポーネント置き場(ルーティング定義)
│   │   ├── index.tsx        # 
│   │   ├── sample.module.scss #cssをmoduleで作成

sample.module.scss
.container {
  p {
    margin: 1em auto;
  }

  .form-label {
    @apply block uppercase tracking-wide text-base mb-2;
  }
}

それを、index.tsxでmoduleとして読み込みます。

index.tsx
import styles from './sample.module.scss';

export default function Contact() {
return (
    <main class={styles.container}>
      <h1>サンプル</h1>
      <p class="text-red-500">※注意※</p>
      <div>
            <label class="form-label mb-2">
              <p class="text-red-500">message text</p>
            </label>
            <input class="form-text mb-3" type="text" />
      </div>
    </main>
)
}

styleが壊れた

上記を行ったところ、

  • @apply が効かない
  • このページを読み込んだあと、別のページに遷移すると、globalで設定設定しているstyleが消える

などの問題が生じました。
諸々調査しましたが、現時点ではtailwindのv4で、この問題は解決しませんでした。

取り急ぎ、大規模なプロジェクトの実装予定はないので、
css.module.scss として各ページで読み込む事をやめることで回避しました。

yunayuna

SSGの設定

Solidでは、以下の説明の通り、 app.config.jsでビルドの設定をします。

Building

Solidアプリは、異なる環境へのデプロイメントを最適化するための presets で構築されます。

デフォルトでは、npm run build が Node アプリを生成し、npm start で実行できます。別のプレセットを使用するには、package.json の devDependencies に追加し、app.config.js で指定します。

設定

appp.config.ts
import { defineConfig } from '@solidjs/start/config';

export default defineConfig({
  // 静的サイト生成の設定
  server: {
    preset: "static",
    // 生成するページのパスを指定
    prerender: {
      routes: [
        '/',
        '/about',
        '/contact',
      ],
      // 動的ルートの処理方法
      crawlLinks: true,
      autoSubfolderIndex: true,
    }
  }
}); 

詳細:
※ここにはstaticの記載なかったので少しハマりました。
https://docs.solidjs.com/solid-start/reference/config/define-config#configuring-nitro

ログインするとコメントできます