Astroコンポーネントを作る
はじめに
まえがき
HTML/CSSで作成していたポートフォリオサイトを、Astroを使用して作り直すことにした。その際の作業記録や躓いた箇所のメモを書いていく。
目標
- HTML/CSSでコーディング済みのものをAstro仕様に作り変える。
- 他のプロジェクトでも使い回せる汎用的なAstroコンポーネントを作る。
開発環境
下記の記事で制作した環境をベースに作っていく。
全体像
手順
1.コンポーネントを作成
2.レイアウトを構築
htmlファイルを一つずつ上から見ていき漏れがないようにする。
コンポーネントとレイアウトの区分
- レイアウト:セクション要素やdivを用いてデザインを組み立てる
- コンポーネント:パーツを成している各要素、あるいはその集合
コンポーネント
分類
現場のプロから学ぶ CSSコーディングバイブルのCSS設計を参考に整理する。
- elements:最小単位のパーツコンポーネント
- components:複数要素で構成されるパーツコンポーネント
- layouts:レイアウト用Astroコンポーネント
レイアウトも広義ではコンポーネントなので厳密には3段階。
小単位のコンポーネントはより大きい単位のコンポーネントを構成するパーツになり得る。
elements→components→layoutsの順に作成していくことで共通部分のコンポーネント化を図る。
作成指針
- HTMLの構成が同じ場合は、異なるCSSを割り当てるにしても一つのコンポーネントにまとめる
- AstroPropsを介し子コンポーネントからクラスを受け取れるようにする→特定のクラス名を渡すことで適用スタイルを切り替えるため
- h要素は一つのコンポーネントとして作り、使用時に見出しレベルを決定する
- 汎用性を意識する
- map()メソッドを使用した動的なコンテンツ生成を積極的に取り入れ、使用時の記述量を削減する→コンテンツテキストは極力JSONデータに切り出しする
- ビルド後に極力JSが入らないようにする→サイトの軽量化を目指すため。HTML/CSSのみで実現できる機能は極力JSを使用しない
作成予定コンポーネント
- ロゴ
- 見出し
- ナビゲーション
- ヒーロー画像/テキスト
- プロフィールカード
- ボタン
- スキルカード
- カード(画像+リンク)
- 画像カルーセル
- テーブル
- フォーム
CSS/SCSS設計
AstroでCSS設計は必要か
Astroコンポーネントでは、CSSはHTMLと一緒に各コンポーネント内に記述していく。
CSSの記述はコンポーネントごとにスコープ化される。(ビルド時はCSSに各コンポーネント特有のハッシュを付与することで出力後もスコープ性を保つ)(個人的にはハッシュに任意の値を指定できるようになってほしい。ビルド後のCSSを見通しよくしたい)
そのためクラス名によるCSS設計を厳密に行わなくても、意図しない場所に意図しないスタイルが適用されるリスクは減っている。
しかし、コンポーネント内での記述にCSS設計のルールを定めることでどのコンポーネントも統一的な記述になるというメリットもある。考え方を忘れないためにも続けていくことにする
どのように行っていくか
現場のプロから学ぶ CSSコーディングバイブルのCSS設計を参考に。BEMをアレンジ。
- 接頭辞-コンポーネント名_パーツ名
- 接頭辞-コンポーネント名-派生名
のようにクラス名を付与していく。
アレンジ部分
elementsの中でも更に汎用性が高い
- img
- text
- box
等はグローバルSCSSに組み込んだ。
→textとboxはutilityに当たる。この2つは勝手が違うので更にファイルを分けるべきか?検討。
コンテンツデータをどこで取得するか
まだ自分なりの答えは出していない。
各ページを作成するために使用するAstroファイル
1つのページを作る際に複数個のAstroファイルを使用する。
各ページ、コンポーネントでデータを取得するのが候補になるかと思う。
- 各ページ:ページの種類に応じたレイアウトを読み込む
- レイアウト:レイアウトの構成要素である他のレイアウトやコンポーネントを読み込む
- コンポーネント:元となるパーツ。パーツのボリューム次第では更に粒度が小さいコンポーネントを読み込む可能性がある
コンポーネントの使い回しで考える
同一プロジェクト内で複数使い回すコンポーネントを使用するページ
各ページでコンテンツを取得する。
複数プロジェクトで使い回すが同一プロジェクト内では1回しか使わないコンポーネントを使用するページ
複雑な作りのページの場合、各ページで取得すると任意の値や要素を流し込むのが大変になる。
JSONからデータを読み込み動的にコンテンツを生成する場合
各ページでデータを取得
コンポーネント自体はシンプル。
各ページがデータの取得を必要とするコンポーネントを複数持つ場合、1つのファイルで複数のJSONファイルを扱い煩雑になるデメリットがある。
コンポーネントでデータを取得
使用場面ごとにコンポーネントを切り分け、データの流し込みをコンポーネント側で行う。
1つのコンポーネントファイルを使い回せるところを、わざわざ複数個作成しなければならないというデメリットがある。
再利用性/記述の分散のどちらを取るか
再利用性を取る
各ページでデータを取得するのが適している。
記述の分散
使用場面ごとにコンポーネントを切り分け、データの流し込みをコンポーネント側で行うのが適している。
サイトの規模
小さい規模のサイト
使用場面ごとにコンポーネントを切り分けても、コンポーネントファイルの数が膨大になりすぎる恐れは少ない。
大きい規模のサイト
コンポーネントを増やしすぎるとコンポーネントファイルの整理が必要となる、各ページに対応するコンポーネントを探しづらくなる恐れがある。
各ページのコンテンツ量
多い場合
各ページで取得すると、複数のJSONファイルのインポートの読み込みやmap()メソッドの記述、props変数名が必要となり記述が膨大化する恐れがある。
コンポーネントで取得することで記述を分散可能。
少ない場合
各ページで取得する負担がない。
JSONファイルを複数ページ/同一ページの複数コンポーネントで使用する場合
複数ページで使用する
コンポーネントで取得することで、JSONファイルの読み込みを一度で済ませることが可能。
同一ページの複数コンポーネントで使用する
各ページで取得することで、JSONファイルの読み込みを一度で済ませることが可能。
map()メソッドで使用する引数名
基本
元となるデータ名の頭文字を小文字にし引数とする。
---
import Data from '@data/data.json';
---
<div>
{
Data.map((data) =>(
<div>{data.name}</div>
))}
</div>
JSONファイルのプロパティ名を指定しそこから生成する場合
プロパティ名+Itemを引数にする。
---
import Data from '@data/data.json';
---
<div>
{
Data.map((data) =>(
<div>{data.name}</div>
<ul>
{data.list.map((listItem) => (
<li>{listItem}</li>
))}
</ul>
))}
</div>
Formコンポーネント
<form>要素は項目内容によっては比較的多くの要素で構成されることになる。
可能な限りシンプルな記述にしていきたい。
方針
<input>や<textarea>等、各パーツをそれぞれコンポーネント化。
Formコンポーネントを親コンポートとし、そこに使用する子コンポーネントを読み込み配置していく。
ディレクトリ構造
conponentsフォルダ内にformフォルダを作成。
Formコンポートを含めて全てのフォーム関係コンポートをそこで管理する。
/
└─ src
└─ components
└─ components
└─ form
├─ Form.astro
├─ Input.astro
├─ Textarea.astro
├─ Check.astro
├─ Radio.astro
└─ Submit.astro
フォーム読み込み時
各ページにフォームを設置する場合はFormコンポーネントを読み込む。
具体的な作成内容
Formコンポーネント
各パーツを配置していない初期の状態。
---
---
<form id="entryForm" action="" class="c-form" method="post">
<table class="c-form_inner">
<caption class="c-form_caption"> 大項目名 </caption>
<tbody>
<!-- ここに各パーツコンポーネントを配置していく -->
</tbody>
</table>
</form>
各パーツコンポーネント
---
export interface Props {
name: string;
required?: boolean;
}
// 子コンポーネントから受け取るpropsを定義
const {
name: nameName,
required = false,
...rest
} = Astro.props;
---
<tr>
<th class="c-form_item">
<label for={nameName} class={required ? 'is-required' : ''}><slot /></label>
</th>
<td class="c-form_input">
<!-- <input>や<textarea>等メインパーツを記述 -->
<!-- フォーム全体で共通規格の注釈 -->
<slot name="attention" />
</td>
</tr>
<style lang="scss"></style>
注釈がある場合のパーツコンポーネント要素の記述
<Input type="" gormId="" name="" autocomplete="">
項目名
<small class="c-form_attention" slot="attention">フォームの注釈事項</small>
</Input>