Astroを試す
いままで自分の個人サイトにGatsby.jsを使っていたが、さくらのレンタルサーバーを使用しているため手作業でFTPする必要があったためこれが面倒くさくてサイトの更新が億劫になっていた。
Gatsbyは中身が殆どないサイトでも大量のjsファイルと大量の画像ファイルを吐き出すのでこれが面倒臭さを感じる原因となっていた。
このjsファイルの正体は未だにわかっていない。本当は無視して良いファイルなのかもしれない。でもなんか知らんファイルが大量にある、というだけですでに面倒くさい。
一方、最近出てきた静的サイトジェネレータのAstroというやつは、最低限のHTMLファイルと必要な分のjsファイルしか生成しないらしいではないか。FTPの手間が減りそう 完全に乗り換えるかは別として、とりあえずAstroがどんなもんか試してみようと思う。
準備
インストール
コマンドラインから
npm
npm create astro@latest
yarn
yarn create astro
あとはフォルダ名とか追加インストールとかTypescript使うかなどのウィザードに答える。基本的にRecommendに従えばいいっぽい
VSCodeのextensionがあるのでそれをインストールする。
VSCodeのextensionsタブで「Astro」で検索。
githubとNetlifyのアカウントは必須ではないと思うが、チュートリアルでは持ってる前提で話が進むので必要に応じて作っておく。
開発モードの起動
cd <プロジェクトフォルダ名>
npm run dev
表示されたURLをブラウザでアクセス。
ソースファイルは *.astroで、srcフォルダ内にある。
Astroのextensionがインストールされていればハイライト、構文チェック、オートフォーマット、IntelliSenseも効く。
ホットリロードに対応しており、VSCode上でファイルを更新・保存するだけブラウザ上のページも自動で更新される。超ラク。
ビルド
npm run build
destフォルダ内にHTMLが出力される。
サンプルプロジェクトでは、faviconとひとつのHTMLファイルしか生成しない。シンプル万歳。
フォルダ構成とファイル名
src/pages/ フォルダ内の Astro(*.astro)ファイルやマークダウン(*.md)ファイルは自動的にHTMLページに変換される
src/contents/内のフォルダは「コンテンツコレクション」として扱われる
それ以外はだいたい同じ扱いのようだが、以下のようなフォルダ分けが慣例っぽい。
コンポーネントは src/components/
レイアウトはsrc/layouts/
JSスクリプトはsrc/scripts/
CSSはsrc/styles/
ファイル名の命名規則は以下っぽい
コンポーネントは大文字で始める(必須)、それ以外は小文字始まり。
大文字で始めたファイル名(つまりコンポーネント)はキャメルケース(例:BlogLayout.astro)
小文字で始めたファイル名(つまりコンポーネント以外))はケバブケース(例:blog-page-1.md)
Astro構文
基本構文
code:jsx
---
//↑コードフェンス
//この「コードフェンスに挟まれたエリア」を「フロントマター」という
//ここにJavascriptを書く。
import "../styles/global.css"; //<--グローバルCSSのインポート
const pageTitle = "私について";
const identity = {
firstName: "サラ",
country: "カナダ",
occupation: "技術ライター",
};
const skills = [
"HTML",
"CSS",
"JavaScript",
"React",
"Astro",
"ドキュメントの執筆",
];
const happy = true;
const finished = false;
const goal = 3;
const skillColor = "navy";
//↓コードフェンス
---
<html lang="ja">
<head>
<title>{pageTitle}</title>
<!-- ※フロントマター内で定義された変数の内容を中カッコで引用できる -->
<style define:vars={{ skillColor }}> <!-- ※CSS変数はsyle冒頭で宣言する。二重中括弧に注意 -->
.skill {
color: var(--skillColor); <!-- ※CSS変数からの引用 var(--変数名) -->
font-weight: bold;
}
</style>
</head>
<body>
<ul>
<li>私の名前は{identity.firstName}です。</li>
<li>{identity.country}に住んでおり、{identity.occupation}として働いています。</li>
{identity.hobbies.length >= 2 &&
<li>私の2つの趣味は{identity.hobbies0}と{identity.hobbies1}です。</li> }
<!-- ※中カッコ内に式も書ける -->
</ul>
<p>私のスキルは以下の通りです。</p>
<ul>
{skills.map((skill) => <li class="skill">{skill}</li>)}
<!-- ※リストの列挙にはmapを利用する -->
<!-- ※ここでの文字色はCSS変数によりnavyに設定される -->
</ul>
{happy && <p>I am happy to be learning Astro! Astroを学べて幸せです!</p>}
{finished && <p>I finished this tutorial! このチュートリアルを終えました!</p>}
{goal === 3 ? <p>My goal is to finish in 3 days. 3日で終えるのが目標です。</p> : <p>My goal is not 3 days. 3日が目標ではありません。</p>}
<!-- 式 && ...then... : ...else... で変数の内容、式の結果でレンダリングの有無を切り替える -->
<!-- このあたりはJSXの書式と同様 -->
</body>
動的タグ
タグ名を変数化することができる
この場合の変数名は大文字ではじまること。
先頭が小文字だとAstroが普通のhtmlタグと認識してしまい、そのままレンダリングしようとする
code:jsx
---
import MyComponent from "./MyComponent.astro";
const Element = 'div'
const Component = MyComponent;
---
<Element>Hello!</Element> <!-- <div>Hello!</div> としてレンダリングされます -->
<Component /> <!-- <MyComponent /> としてレンダリングされます -->
コメント
HTML(Astro構文)パートでは<!-- コメント -->と{ /* コメント */ }あるいは
code:js
{
//コメント
function somefunc() {
//コメント
}
}
が使える。が、<!-- コメント -->はビルド後のソースに残るらしいので非推奨。見た感じそんなことはなかったけど。
astro構文とGatsby(JSX)のざっくりとした違い
コードフェンス(---)内にJSを書いてプロパティの受け取りや計算を行い、それ以降のJSXにはそれらをレンダリングするための記述に集中する、というのが基本スタイル。計算部分とデザイン部分がはっきり別れているこの思想は、ReactよりもVueに近いかもしれない。
標準ではインタラクティブなページを作るのが大変、というかそれ向けのフレームワークではない。ただし部分的に実行時用のスクリプト記述したり、他のフレームワークを借りてきたり(ハイドレート)することでインタラクティブなページにすることも出来る。
<Link>とか<Image>とか使わずに普通に<a>とか<img>を使う
classNameとか使わずに普通にclassを使う
<></>とか使わずに複数行をそのまま書ける。
onClickなどのイベントに直接JS関数をアタッチできない。かわりにaddEventListenerが必要。
AstroもGraphQLを扱えるが、標準でもっと便利にデータ検索ができるので使う機会は殆どない。
スタイル
基本的には<style>内に書く
コンポーネント内で書いたスタイルは基本的にそのコンポーネント内のみに適用される
グローバルCSS
サイト全体にスタイルを適用するには
src/styles/global.cssを作成
フロントマター内に import "../styles/global.css";を記述
ローカル(ページ内に直接<style>を記述した場合)とグローバルではローカルが優先される
レイアウトコンポーネント内で書いたCSSを、それを利用する全てのページに適用したい場合はis:globalを使う
code:html
<style is:global>
h1 { color: red; }
</style>
SASSなどの利用
インストールすればSASSなども使える
npm install sass
<style>にlang="saas"を指定すればSASS形式で書ける。グローバルもOK
code:html
<style is:global lang="sass">
//リンク
a
display: inline-block
color: blue
text-decoration: underline
padding: 0
</style>
コンポーネント
コンポーネントの基本
ヘッダーやフッターなどの共通要素を定義するのがコンポーネント。
コンポーネント名は大文字で始める
先頭が小文字だとAstroが普通のhtmlタグと認識してしまい、そのままレンダリングしようとする
ひとつのファイル内で複数のコンポーネントを定義することはできない
reactだと関数コンポーネントみたいにいくつも定義できたけどそんなものはない
code:astro:index.astro
---
import Navigation from "../components/Navigation.astro";
---
<html lang="ja">
...
<body>
<Navigation />
<h1>私のAstroサイト</h1>
</body>
</html>
code:astro:Navigation.astro
---
---
<a href="/">ホーム</a>
<a href="/about/">概要</a>
<a href="/blog/">ブログ</a>
コンポーネントにプロパティを渡す
const { <変数名>=<初期値>, ... } = Astro.props;をコンポーネントのフロントマターに書くことでプロパティ(HTML attributes)を受け取れる
呼び出し側は<コンポーネント名 <変数名>=<値> ...>
プロパティ名は変数名そのままになるみたい
Astroでは、すべてのHTML属性にkebab-case形式を使用します。
なら下記のusernameはuser-nameでも良いわけね
code:astro:Social.astro
---
const { platform, username="anonymous" } = Astro.props;
---
<a href={https://www.${platform}.com/${username}}>{platform}</a>
<style> <!-- コンポーネント内で書いたstyleはコンポーネント内のみで有効 -->
</style>
code:astro:Footer.astro
---
import Social from "./Social.astro";
---
<footer>
<Social platform="twitter" username="astrodotbuild" />
<Social platform="github" username="withastro" />
<Social platform="youtube" username="astrodotbuild" />
</footer>
レイアウトコンポーネントとスロット
ページの中にコンポーネントを埋め込むには<コンポーネントタグ名>で出来た。
逆にページファイルの中で(レイアウト)コンポーネントの中に自分自身を埋め込ませる=自分の前後を他のコンポーネントに管理させるには、<slot>を使う
要するに、コンポーネントタグで囲まれた内容(innerHTML)がそのコンポーネントに渡され、それが<slot>によって呼び出せるという話
code:js:index.astoro
---
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout>
<h2>私の素晴らしいブログのサブタイトル</h2>
</BaseLayout>
code:astro:BaseLayout.astro
---
import '../styles/global.css';
const pageTitle = "ホームページ";
---
<html lang="ja">
<head>
...
<title>{pageTitle}</title>
</head>
<body>
<h1>{pageTitle}</h1>
<slot /> <!-- この位置にBaseLayoutで囲んだ内容が挿入される-->
</body>
</html>
インタラクティブなスクリプト
実行時にJavascriptを走らせる
基本、ページ構築のために記述するJavascriptは、ビルド時に実行され、レンダリング時には使用できない。
しかし、<script>タグ内のJavascriptは、ビルド時ではなくブラウジング中に有効。
TypeScript対応。
タグ内にonClickなどでイベントを記述することはできない
なのでイベントの追加にはaddEventListenerが必須になる。これはastro標準の仕様
code:js
<button id="button">Click</button>
<script>
document.querySelector('#button').addEventListener('click', () => alert("clicked"));
</script>
import文、あるいはsrc指定で外部スクリプトを呼び出せる、ので以下でもアリ
TypeScript(*.ts)も読める
code:js:test.js
document.querySelector('#button').addEventListener('click', () => alert("clicked"));
code:astro
<button id="button">Click</button>
<script>
import "../scripts/test.js";
</script>
あるいは
<script src="../scripts/test.js" />
scriptタグ内のスクリプトは、ビルド時に別ファイルに切り出される(バンドル操作)
_astro/hoisted****.js がそれ
これが嫌ならscriptタグに is:inline を追加すると切り出されずページに埋め込まれる
ちなみにビルド後のソースを見るとスペースやタブさえも全く処理されずに残る。正直かっこわるい
code:astro
<button id="button">Click</button>
<script is:inline>
document.querySelector('#button').addEventListener('click', () => alert("clicked"));
</script>
コンポーネント内のスクリプト
コンポーネント内で記述したscriptは、コンポーネント内のみで有効になる
ドキュメント内で何度コンポーネントを利用しても、スクリプトが実行されるのは1回のみ
ハイドレート
client:loadディレクティブをつけることによってコンポーネントは「ハイドレートされた」状態になる。この状態では他のJSフレームワークを利用でき、すなわち静的サイトジェネレータであるAstroのみでは出来ないインタラクティブな反応が可能になる。
たとえばPreactを使う場合はPreactをプロジェクトにインストールしたのち、JSXファイル(*.jsx)を用意する。
この場合のJSXファイルはコンポーネントとして機能する。
一応、インタラクティブにするだけならページ内で<script>タグを使用して生JSをがんばって書いても同じことができる。ハイドレートは「他のJSフレームワークが使える」ことが利点らしい
カスタム要素の利用
Astroの機能ではないが「カスタム要素」を利用することでカスタム要素とJSコンストラクタを紐づけ、ドキュメント内の対象のすべてのカスタム要素に機能をもたせることができる カスタム要素の名称はケバブケース(kebab-case)でなければならない
documentのかわりにthisを使うことでカスタム要素内の子要素のみを検索できる
code:js:AstroHeart.astro
<!-- カスタム要素 "astro-heart" で囲みます。 -->
<astro-heart>
<button aria-label="Heart">💜</button> × <span>0</span>
</astro-heart>
<script>
class AstroHeart extends HTMLElement {
constructor() {
super();
let count = 0;
// this....でドキュメント全体ではなくコンポーネント内のみを対象にできます
const heartButton = this.querySelector('button');
const countSpan = this.querySelector('span');
// ボタンがクリックされるごとにカウントを更新する。
heartButton.addEventListener('click', () => {
count++;
countSpan.textContent = count;
});
}
}
// <astro-heart>要素としてAstroHeartクラスを利用することをブラウザに教える
customElements.define('astro-heart', AstroHeart);
</script>
Markdown
Markdown標準対応
Markdownに標準で対応しているため、*.astroのかわりに*.mdファイルを置くだけで勝手にHTMLページに変換してくれる。超便利
Markdownのフロントマター
Markdownのフロントマターではyaml形式(プロパティ名: 値)でプロパティを定義できる。
layout プロパティは特別で、レイアウトコンポーネントを指定できる
レイアウト側ではconst { frontmatter } = Astro.props;でオブジェクトとして取得できる
本文は<slot>で取得。(HTML変換済)
code:markdown:post-1,md
---
title: '私の最初のブログ記事'
pubDate: 2022-07-01
description: 'これは私の新しいAstroブログの最初の記事です。'
author: 'Astro学習者'
image:
alt: 'Astroのロゴ。'
layout: ../../layouts/MarkdownPostLayout.astro
---
Astroの学習についての私の _新しいブログ_ へようこそ!ここでは、新しいウェブサイトを作りながら、私の学習過程を共有します。
...
code:astro:MarkdownPostLayout.astro
---
const { frontmatter } = Astro.props;
---
<h1>{frontmatter.title}</h1>
<p>投稿日: {frontmatter.pubDate.slice(0, 10)}</p>
<p>著者: {frontmatter.author}</p>
<p>投稿日: {frontmatter.image}</p>
<p><em>{frontmatter.description}</em></p>
<p>{frontmatter.tags.map((item: string) => <span>{item}</span>)}</p>
<img src={frontmatter.image.url} width="300" alt={frontmatter.image.alt} />
<slot />
ディレクトリ内ファイルの列挙
Astro.globを使う
戻り値はAstroInstanceのリスト
内容は後述↓
グロブパターンは、以下のいずれかで始まる必要がある。
./ (カレントディレクトリで起動する)
../ (親ディレクトリから開始する場合)
/ (プロジェクトのルートから開始する場合)
注意:Astroは静的サイトジェネレータであるため、Astro.globで検出できるのはビルドコマンドを打った時点でのディレクトリの内容であることに注意。ビルド後にファイルを追加しても反映されない。
code:jsx
...
const allPosts = await Astro.glob("../pages/posts/*.md");
...
<ul>
{allPosts.map((post) => (<li><a href={post.url}>{post.frontmatter.title}</a></li>))
</ul>
戻りオブジェクト
code:js
//Markdown(*.md)の場合
export interface MarkdownInstance<T extends Record<string, any>> {
frontmatter: T; //YAML frontmatter
file: string; //file path(local)
url: string | undefined; //rendered path(url)
Content: AstroComponent; //Astro contents
getHeadings(): Promise<{ depth: number; slug: string; text: string }[]>;
//h1...h6要素のリストを取得(概要表示に使える)
}
//Astro(*.astro)の場合
export interface AstroInstance {
file: string; //file path(local)
url: string | undefined; //rendered path(url)
default: AstroComponent; //Astro contents
}
//その他の場合
//Typescriptでinterfaceを定義できる…らしい
ビルド時ページ自動生成
ファイル名に角括弧(例:[tag].astro)が使われているとそのファイルがあるディレクトリにアクセスしたときのURLをパラメータとして取得できる
たとえば src/tags/[tag].astroファイルを作っておくと、/tags/適当な文字列でアクセスできる。このときパラメータtagを通して適当な文字列が取得できる。
ただし、静的サイトジェネレータとしてはあらゆる文字列に対応することはできないため、あらかじめ候補をある程度絞っておかなくてはならない。
その候補のリスト「ページルートの配列」を設定するのが getStaticPaths()
返すのは { params: <オブジェクト>, props: <オブジェクト> } というオブジェクト(propsは省略可)のリスト
code:jsx
---
export async function getStaticPaths() {
const allPosts = await Astro.glob('../posts/*.md');
return [
{ params: { tag: "astro" } props: {posts: allPosts}},
{ params: { tag: "成功" } props: {posts: allPosts}},
{ params: { tag: "コミュニティ" } props: {posts: allPosts}},
{ params: { tag: "ブログ" } props: {posts: allPosts}},
{ params: { tag: "後退" } props: {posts: allPosts}},
{ params: { tag: "公開学習" } props: {posts: allPosts}},
];
}
const { tag } = Astro.params; //タグ=URLの一部
const { posts } = Astro.props; //ディレクトリ内のすべてのファイル
//全てのファイルのタグを調べ、指定のタグを含むファイルを絞り込む
const filteredPosts = posts.filter((post) => post.frontmatter.tags?.includes(tag));
---
<BaseLayout pageTitle={tag}>
<p>{tag}のタグが付いた記事</p>
<ul>
{filteredPosts.map((post) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
</ul>
</BaseLayout>
上記方法ではあらかじめ決まったタグのみしか対応できない。ブログ記事が増えるたびに(そしてビルドするたびに)全記事を調べてタグリストを更新する方法は以下
code:js
...
export async function getStaticPaths() {
//ディレクトリ内の記事一覧取得
const allPosts = await Astro.glob("../posts/*.md");
//それぞれの記事からタグを取得するとともに、集合(Set)を利用して重複を削除
//タグの数だけリスト(生成URLパターンのリスト)を返す
return uniqueTags.map((tag) => {
const filteredPosts = allPosts.filter((post) =>
post.frontmatter.tags.includes(tag),
);
return {
params: { tag },
props: { posts: filteredPosts },
};
});
}
...
外部モジュールの利用
Tailwind
CSSフレームワークのTailwindを導入する。
インストール
code:bash
npx astro add tailwind
yarn astro add tailwind
設定
まずプロジェクトのルートにあるastro.config.mjsを開く
以下を参考にして行を追加
ファイルの他の内容が環境によって変わるので多少違ってても雰囲気で追加してくれ
astro addしてから編集すること、順番を間違えるとエラーが出て止まる。当たり前だが…
code:js
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind'; //<-- これ
export default defineConfig({
});
次に以下のコマンドでtailwind.config.jsを作成する
npx tailwindcss init
出来たtailwind.config.jsを開いて以下を参考に行を追加
code:js
/** @type {import('tailwindcss').Config} */
export default {
content: './src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', //<-- これ theme: {
extend: {},
},
plugins: [],
}
ページごとにimportする必要はない。
Bootstrap
結局Bootstrapにした
インストール
code:bash
npm install astro-bootstrap
# yarn
yarn add astro-bootstrap
設定
code:bash
npm install bootstrap @popperjs/core @types/bootstrap
# yarn
yarn add bootstrap @popperjs/core @types/bootstrap
これでbootstrap/というパスが仮想的に通ったので
code:js
---
import 'bootstrap/dist/css/bootstrap.css'
---
を一番親のレイアウトコンポーネントに追加する。
アイコン
なにげにfont awesomeのアイコンもしれっと含まれている。全部じゃないと思うが。
インストール
npm i astro-icon
or
yarn add astro-icon
usage
code:js
---
import { Icon } from 'astro-icon'
---
<Icon name="mdi:account" />
ただしこのままだと画面いっぱいのクソデカアイコンが表示され、色もバラバラなため、CSSでスタイル指定する必要がある。
Iconタグ全体に有効な[astro-icon]セレクタも用意されている。
code:html
<Icon name="fa:youtube" class="inline-icon" />
...
<style>
color: black;
}
.inline-icon {
height: 1em;
}
</style>
コンテンツコレクション
src/content/以下はコンテンツコレクションとして扱われる。このフォルダ以下のファイルに含まれる情報をAstroの標準機能で簡単に引き出せるようになる。
下準備としてsrc/content/直下にconfig.tsを作ると、Typescriptによるデータ型定義、型チェック、VSCodeのintelisenceなどなど便利な機能が使えるようになる(一応無くても動く)
Astro.blobでMarkdownファイルをスキャンする場合との違い
frontmatterの内容であれば問題なく取れるが、マークダウン本文の内容をレンダリングできないので直接*.mdファイルににアクセスする(=propでファイルまるごと受け取る)以外の方法でマークダウンをレンダリングしたければコンテンツコレクションとして読み出すしかない。
code:js
// 1. astro:contentからユーティリティをインポート
import { z, defineCollection } from 'astro:content';
// 2. コレクションを定義
const newsCollection = defineCollection({
schema: z.object({
title: z.string(),
imageSrc: z.string(),
date: z.date(),
linkHref: z.string(),
})
});
// 3. コレクションを登録するために、単一のcollectionsオブジェクトをエクスポート
// このキーは、"src/content"のコレクションのディレクトリ名と一致する必要があります。
export const collections = {
'news': newsCollection,
};
zってなんじゃい、って思うがこれはZODという、入力されたデータを型チェックするライブラリらしい(特にインストール作業なしで使える)。 上記のあと、src/content/newsフォルダ以下に以下のようなmdファイルを置く
code:md
---
title: ニュース記事その1
date: 2023-10-13
---
これはテストです
- これはテストです
- これはテストです
するとフロントマターで名前: 値の形で定義したデータはそのままコレクションとして登録される(スペースに注意)
取得する方法は
code:js
---
import Topic from "../components/Topic.astro";
import { getCollection } from "astro:content"; <-- getCollectionはコレクションをまるごと取得する
const allNewsTopics = await getCollection("news"); <--
---
...
<article id="news">
<h2>最新情報</h2>
{allNewsTopics.map((newsTopic) => <Topic newsTopic={newsTopic} />)}
</article>
という感じでエントリーをまとめて取得したのち単一エントリーごとに(重要)コンポーネントに投げる
code:js
---
import type { CollectionEntry } from "astro:content";
interface Props { <-- CollectionEntryで
newsTopic: CollectionEntry<"news">; <-- 受け取った単一エントリを型定義できます
}
const { newsTopic } = Astro.props;
const { Content, headings } = await newsTopic.render(); <-- render()で内容をHTML化します
<-- なお newsTopic.body で生文字列が出ます
---
<h4>{newsTopic.data.title}</h4> <-- entry.data.<プロパティ名>で入っている
<span>{newsTopic.data.date.toLocaleDateString()}</span> <-- 日付文字列はごちゃっと出ますけどここでは省略
<Content /> <-- HTML化済コンテンツを出力する特殊なコンポーネント
<-- これは「単一エントリ」でないと使えないっぽい
<a href={newsTopic.data.linkHref}>
<img src={newsTopic.data.imageSrc} alt={newsTopic.data.title} />
</a>
単一エントリを受け取ったコンポーネントは<Content>を利用してHTMLを出力できます
【備考】フロントマッター(yaml)記述法あれこれ
フロントマッターはyaml形式での記述になっている。以降yaml記述法をメモ。
yamlではタブ文字使えない。字下げはスペースを使う。スペースなら何文字下げても良いが、字下げの文字数は揃えること。
null, 空文字, 数値
数値は直接書く。文字列はクォーテーション("でも'でもOK)で囲む。
空文字は""
数値の先頭の0は除いたほうが良い。例えば07では数値扱いだが08では文字列扱いになってしまう。
特殊文字は\でエスケープ。ただしクォーテーションで囲まないと正しく認識されない。
なにも書かないとnull扱いだが、明示的にnullと記述したほうがベター。
下記でいうtext5など未定義のプロパティを取得しようとするとnullが返る。
またTypescriptのinterfaceでDate型も定義できるが避けたほうが良さげ。実行時内部的には文字列型として扱うらしくエディタ上の型チェックとの齟齬が生まれてしまう。
コメントは#
code:yaml
---
# コメント <-- コメント
text1: <-- null扱い
text2: null <-- 正しい(明示的な)null
text3: "" <-- 空文字
text4: "\"" <-- 特殊文字は"\"でエスケープ。必ずクオーテーションで囲むこと
number1: 1 <-- 数値
number2: 08 <-- ※避けたほうがいい。07は数値だが08は文字扱いになる
numberText: "01" <-- 明示的に文字列
---
// 結果 {
// "text1":null,
// "text2":null,
// "test3":"",
// "number1":1,
// "number2":"08",
// "numberText":"01",
//}
リスト
ハイフン-記号がリストの区切りになっている。
オブジェクトのネストは字下げをする。
code:yaml
---
textlist:
- item1 //ハイフンで要素を区切る。字下げを行う。
- item2
- item3
objlist:
- //ハイフン行以降を空にしてもOK。オブジェクト配列の場合はこっちのほうが見やすい
name: obj1
text: hoge
-
name: obj2
text: hage
---
// 結果: {
// }
改行を含む文字列を記述
改行を含む文字列は先頭行に以下の文字を入れ、以降を字下げする
指定なし:各行はスペースで区切られる。字下げが揃っていないとき(余計なスペースがあるとき)、1つだけスペースが追加される。
|:各行は改行コードで区切られる。字下げの分だけスペースが追加される。
|-:"|"に追加して、最後の改行コードが取り除かれる。
|+:"|"に追加して、文末の空行を許可する。
※なお-や+単品だけは効果がない(エラーが出たり余計な+が入ったりする)。|+-もエラー。
code:yaml
---
multilines: |- <-- "|-"記号。
eins <-- 字下げする
zwei
drei <-- 字下げが揃っていないとき、スペースが増える
---
// 結果は { miltilines: "eins\nzwei\n drei" }
code:js
<ul>
{ Astro.props.frontmatter.multilines.split("\n").map((line) => <li>{line}</li>) }
</ul>
個人的ひっかかりポイント
ファイルとデータ構造のありかた
私のサイトの場合でデータベース化したかったのは「新着情報」と「ディスコグラフィー」
特にディスコグラフィーはサイト内のあらゆるページから情報を参照するので使いやすい形にデータベース化することは必須だった。
またアルバム個別ページを作りたかったため直接ファイルにアクセスしてもページとして表示できる構造にもしたかった。
というわけで構造的には「ブログ」に近い形式になった。つまりアルバム1枚につき1つのマークダウンファイルを作成し、アルバム情報をそこに集約するという形である。
逆に「そのページでしか使われないデータベース」もいくつかあった。
例えばproductページ(今まで作ってきたものが雑多にまとめられているページ)である。これはこのページ内でしか参照されず、頻繁に更新されることもない。しかしながら各アイテムごとに同じデザインで統一したいのですべてをHTMLでベタで書き下すことはしたくない。
というわけで思い切ってastroファイル内に全アイテムの情報を含んだjsonファイルを直接定義した。ファイルを外だしする必要すら感じられなかったので直接ソース内にjsonを書いた。めったに更新されないものなのでこれで良いのである。
「そのページでしか使われない」にもかかわらず「データベース化したい」ものもあった。
それは「新着情報」である。新着情報はトップページにしか使われない。うちのサイトはブログにはしたくなかったのでニュース記事に直接アクセスすることもない。しかしながら、各記事を個別にファイルで管理したかった。なぜなら更新頻度が高く、複数記事がひとつのファイルにまとめられていると更新が面倒になるからである。
この条件を満たすにはコンテンツコレクションを導入する必要があった。何度かglobでマークダウンファイルを列挙する方法を試してみたが(そのほうが型定義が楽なので)、その方法ではマークダウン本文をまとめてリスト化して扱うのに支障があった。もしかしたらうまくやる方法があるのかもしれないが結局みつけられなかった。
*進捗*
サイト構築完了