Open28

はじめてのAstro

kzk4043kzk4043

Astroを触ってみたのでメモ

実施環境
項目 詳細
PC MacBook Pro(14 インチ、2021)Apple M1 Pro
OS MacOS Ventura 13.6
Node.js v16.14.2

Astroとは

What is Astro?
Astro is an all-in-one web framework for building fast, content-focused websites.

Key Features

  • Component Islands: A new web architecture for building faster websites.
  • Server-first API design: Move expensive hydration off of your users’ devices.
  • Zero JS, by default: No JavaScript runtime overhead to slow you down.
  • Edge-ready: Deploy anywhere, even a global edge runtime like Deno or Cloudflare.
  • Customizable: Tailwind, MDX, and 100+ other integrations to choose from.
  • UI-agnostic: Supports React, Preact, Svelte, Vue, Solid, Lit and more.

なんかいろいろ早そうなフロントエンドフレームワークという印象。

https://docs.astro.build/en/concepts/why-astro/

↑Astroについて更に詳しい解説をもとに意訳

  • 柔軟性や多機能を犠牲にして?、速度とかシンプルさを優先しているっぽい。コンテンツに集中できるフレームワークとのこと(具体的にどう違うんだろ)。
  • SPAじゃなくMPA
  • デフォルトで Zero JS らしく、早い
  • React, Preact, Svelte, Vue, Solid, Lit, and several othersがサポートされている

Astro Islands

https://docs.astro.build/en/concepts/islands/
最近よく聞くアイランドアーキテクチャ。
Astro Islandsは、まずなるべくjsを削ぎ落とす(デフォルトではjsをゼロにする)。そしてページの中にjsが必要な部分があったら、その部分だけ独立した島(Islands)のように扱ってレンダリングする。そうすることで島以外はただのHTMLなので高速だし、島と島は並列にロードできるし、タイミング等も指定できる。とのこと。

kzk4043kzk4043

Tutorial

チュートリアルが用意されているので、見ながら構築→ここに格納

npm create astro@latest

初期設定簡単でいい感じ

🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀

kzk4043kzk4043

パス

  • src/pages配下の.astroファイル、.mdファイルがそのままURLになる
  • [パス名].astroで動的なパス生成
    • getStaticPathsのreturnでパス名を返す
src/pages/tags/[tag].astro
---
import BaseLayout from '../../layouts/BaseLayout.astro';

export async function getStaticPaths() {
  return [
    { params: { tag: "astro" } },
    { params: { tag: "successes" } },
    { params: { tag: "community" } },
    ...
  ];
}

const { tag } = Astro.params;
---
<BaseLayout pageTitle={tag}>
  <p>Posts tagged with {tag}</p>
</BaseLayout>
kzk4043kzk4043

.astroファイルの書き方基礎

---
// frontmatter領域
import '../styles/global.css';
import Navigation from '../components/Navigation.astro';

const pageTitle = "About Me";
const titleColor = "navy";
const fontWeight = "bold";
---

<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
    <meta name="viewport" content="width=device-width" />
    <meta name="generator" content={Astro.generator} />
    <title>{pageTitle}</title>
    <style define:vars={{ titleColor, fontWeight }}>
    h1 {
      color: purple;
      font-size: 4rem;
      color: var(--titleColor);
      font-weight: var(--fontWeight);
    }
    </style>
  </head>
  <body>
    <Navigation />  
    <h1>{pageTitle}</h1>
    <script>
      document.querySelector(".hamburger").addEventListener("click", () => {
        document.querySelector(".nav-links").classList.toggle("expanded");
      });
    </script>
  </body>
</html>
  • 上下3点ハイフン---で区切られたfrontmatter領域と、HTMLの領域に分かれる
    • frontmatter領域では変数が定義でき、{変数}でHTML領域で使える→svelteが近い?
    • {}内は式がかける{finished && <p>I finished this tutorial!</p>}
  • CSSはstyleタグ内に書く
    • <style define:vars={{ titleColor, fontWeight }}>のようにすると、style内で変数が使えるcolor: var(--titleColor);
    • グローバルCSSはimportして適用import '../styles/global.css';
  • インタラクションを加えるためにフロントに送るjsは<style>タグ内に記載する
src/components/Social.astro
---
const { platform, username } = Astro.props;
---
<a href={`https://www.${platform}.com/${username}`}>{platform}</a>
  • コンポーネントはimportして、<コンポーネント名 />で使える
  • コンポーネントではconst { platform, username } = Astro.props;の形でPropsを定義できる
  • 渡す時は<Social platform="twitter" username="astrodotbuild" />
blog.astro
---
import BaseLayout from '../layouts/BaseLayout.astro'
const allPosts = await Astro.glob('../pages/posts/*.md');
const pageTitle = "My Astro Learning Blog";
---
<BaseLayout pageTitle={pageTitle}>
  <p>This is where I will post about my journey learning Astro.</p>
  <ul>
    {allPosts.map((post) => <li><a href={post.url}>{post.frontmatter.title}</a></li>)}
  </ul>
</BaseLayout>
  • const allPosts = await Astro.glob('../pages/posts/*.md');'../pages/posts/*.md'の情報が配列で受け取れる
kzk4043kzk4043

.mdファイルの書き方基礎

MarkdownPostLayout.astro
---
const { frontmatter } = Astro.props;
---

<h1>{frontmatter.title}</h1>
<p>Written by {frontmatter.author}</p>
<slot />
post.md
---
layout: ../../layouts/MarkdownPostLayout.astro
title: My Second Blog Post
author: Astro Learner
description: "After learning some Astro, I couldn't stop!"
image:
  url: "https://docs.astro.build/assets/arc.webp"
  alt: "Thumbnail of Astro arcs."
pubDate: 2022-07-08
tags: ["astro", "blogging", "learning in public", "successes"]
---

After a successful first week learning Astro, I decided to try some more. I wrote and imported a small component from memory!
  • layoutは.astroファイルに記述でき、const { frontmatter } = Astro.props;でfrontmatter情報にアクセスできる
    • それを.mdファイルでlayout: ../../layouts/MarkdownPostLayout.astroとすると適用できる
kzk4043kzk4043

islands

---
import BaseLayout from '../layouts/BaseLayout.astro';
import Greeting from '../components/Greeting';
const pageTitle = "Home Page";
---

<BaseLayout pageTitle={pageTitle}>
  <h2>My awesome blog subtitle</h2>
  <Greeting messages={["Hi", "Hello", "Howdy", "Hey there"]} /> // クライアント側で動かないコード
  <Greeting client:load messages={["Hej", "Hallo", "Hola", "Habari"]} /> // こっちは動く
</BaseLayout>

クライアント側でインタラクションするためには、client:load等を付ける必要がある。client:visibleだと、そのコンポーネントが見えた時にjsをロードする。

kzk4043kzk4043

テンプレを使って実装

こちらのテンプレを使ってみる→github

公開中

kzk4043kzk4043

npmの依存関係壊れてた。。

npm ERR! code ERESOLVE
npm ERR! ERESOLVE unable to resolve dependency tree
npm ERR! 
npm ERR! While resolving: @example/docs@0.0.1
npm ERR! Found: react@18.0.0
npm ERR! node_modules/react
npm ERR!   react@"^18.0.0" from the root project
npm ERR! 
npm ERR! Could not resolve dependency:
npm ERR! peer react@">= 16.8.0 < 18.0.0" from @docsearch/react@3.0.0
npm ERR! node_modules/@docsearch/react
npm ERR!   @docsearch/react@"^3.0.0" from the root project
npm ERR! 
npm ERR! Fix the upstream dependency conflict, or retry
npm ERR! this command with --force or --legacy-peer-deps
npm ERR! to accept an incorrect (and potentially broken) dependency resolution.
npm ERR! 
npm ERR! 
npm ERR! For a full report see:

え、yarnだといけた…動作結構違うのか…大丈夫なのか…笑

kzk4043kzk4043

Astro.props使ってるページでcannot find name 'Astro'でてる…
いや、行けてるとこもあるな、なんで?HeadSEO.astroだけいけてる

kzk4043kzk4043

うわ、SP対応してないな…ドキュメントだからPCメインだろうってこと…?対応しないと

kzk4043kzk4043

フォント変えたい
https://docs.astro.build/ja/guides/fonts/

Source Han Code JPにしたいけどフォントいまいちよくわからない。

https://wa3.i-3-i.info/word14138.html
https://renandpenta.com/font-formats/

  • フォント
    • ビットマップ:ラスタ画像的な
    • スケーラブル:ベクタ画像的な
      • ストローク:文字を線で表現
      • アウトライン:文字を輪郭線で表現、多分今はほとんどこれ
        • TrueType:歴史が長く、安価
          • .ttf
          • ttfを複数詰め込んだのが.ttc
        • OpenType:TrueType発展版、PostScriptを包括、高機能&高価
          • TrueType形式:.otf/.ttf
          • PostScript形式:.otf
          • フォントコレクション:.otc/.ttc
        • WOFF:「Web Open Font Format」の略。
          • OpenType又はTrueType形式のフォントを圧縮
          • 軽いのでWebフォントに適している
          • WOFF2は改良版

厳密にはTrueType = .ttf、OpenType = .otfってわけでも無いらしいが、一旦ざっくりで。
PCとかで使うならttc系、ウェブならWOFF2がいいってことかな?WOFF2の対応状況も問題なさそう。

ttcをwoff2に変換して使う感じかな
オンラインコンバータ良さげなのがなかったので、これをダウンロードして使用。
30.2MB(.ttc)が9KB(.woff2)になった。違いすぎん…?笑

フォントが当たってない。Whatsfontでは当たってることになってるが、Whatsfont結構適当やな…たぶんCSS見に行ってるだけか。

https://shogo-log.com/confirm-font/

Failed to decode downloaded font: http://localhost:4321/fonts/SourceHanCodeJP.woff2
introduction:1 OTS parsing error: invalid sfntVersion: -2147378942

https://naokiotsu.com/2016-11-11-font-awesome-error/

これ?→変化なし
ttcからではなくotfから変換したファイルで試したらエラーは消えたが適用はされず。

よくわからんけどFira Codeのwoff2は適用されたので、SourceHanCodeJPのファイル、または変換が良くなかった…?
一旦これで進める。

kzk4043kzk4043
yarn upgrade --latest astro
-> error   Cannot read properties of undefined (reading 'postcss')

yarn upgrade --latest @astrojs/tailwind
-> error   Cannot find package 'tailwindcss' imported from /node_modules/@astrojs/tailwind/dist/index.js

yarn add --dev tailwindcss
-> `yarn dev`できた!

yarn upgrade --latest

https://docs.astro.build/en/guides/integrations-guide/tailwind/

別のエラー

 error   Invalid URL
  File:
    /src/components/HeadSEO.astro:13:27
  Code:
    12 | const imageSrc = content?.image?.src ?? OPEN_GRAPH.image.src;
    > 13 | const canonicalImageSrc = new URL(imageSrc, Astro.site);
         |                           ^
      14 | const imageAlt = content?.image?.alt ?? OPEN_GRAPH.image.alt;
      15 | ---
[markdown] [/src/pages/core/colors.md] Astro now supports MDX! Support for components in ".md" (or alternative extensions like ".markdown") files using the "setup" frontmatter is no longer enabled by default. Migrate this file to MDX.

このテンプレいろいろ古すぎるな…まあAstro v1だったもんな…笑
この辺か?

kzk4043kzk4043

マークダウン内でjsを使う

mdxを使えるようにする。
https://docs.astro.build/en/guides/integrations-guide/mdx/

---
title: General Introduction
description: general intro
layout: ../../layouts/MainLayout.astro
---

import { SITE } from "../../config";

{SITE.portfolio} // これでimportした値が使える

## 筆者について

都内でフロントエンドエンジニアとして勤務しています。詳細は<a href={SITE.portfolio}>ポートフォリオサイト</a>を作っていますので、そちらを参照いただけると嬉しいです。

マークダウンのリンクの書き方([テキスト](リンク))だとうまくいかなかったが、aタグにすればいけた。

kzk4043kzk4043

ga4入れる

chatgptできいてみる。プロパティがサイト単位、ストリームはウェブとかスマホとかそういう単位の様子。

Google Analytics 4(GA4)では、「ストリーム」は「プロパティ」の一部です。

プロパティ(Property): プロパティは、ウェブプロパティやアプリなどのデジタルプロパティ全体を指します。これは、データを収集、分析する単位となります。一般的に、1つのプロパティが1つのウェブサイトやアプリに対応します。

ストリーム(Stream): ストリームはプロパティ内の特定のデータソースを表します。例えば、ウェブプロパティ内には複数のウェブサイトがある場合、それぞれのウェブサイトに対応するストリームを作成します。これにより、異なるデータソースをプロパティ内で区別できます。

簡単に言えば、プロパティは全体のデジタルプロパティを指し、ストリームはそのプロパティ内の特定のデータソースを表します。

https://qiita.com/asahina820/items/db06b71b53a8d167243f

別にPartyTownいらんくない…?と思ったので一旦なしでやってみる。
headのすぐ下に下記追加。gtag()が引数がないのに引数入れてるエラーが出るのでgtag(...args: any[])とした。

  <!-- Google tag (gtag.js) -->
  <script async src="https://www.googletagmanager.com/gtag/js?id=[GA4のID]"
  ></script>
  <script>
    window.dataLayer = window.dataLayer || [];
    function gtag(...args: any[]) {
      dataLayer.push(arguments);
    }
    gtag("js", new Date());

    gtag("config", "[GA4のID]");
  </script>

dataLayerがないエラーもでたので、下記を作成。

src/types/global.d.ts
declare global {
  interface Window {
    dataLayer:any;
  }
}

export {};
kzk4043kzk4043

mermaid入れたい

https://mermaid.js.org/intro/getting-started.html

mermaidは3つのパートで構成されている

  • Deployment
  • Syntax
  • Configuration

mermaidを導入する方法

  • Using the Mermaid Live Editor at mermaid.live. -> 多分オンラインエディタで画像作る感じなので却下
  • Using mermaid plugins with programs you are familiar with. -> なさそう?Gatsbyはあるんだ…
  • Calling the Mermaid JavaScript API. -> 可能性ありそう
  • Deploying Mermaid as a dependency. -> 可能性ありそう

https://mermaid.js.org/config/usage.html

layoutでmermaidをimportして、preで使う方法はなぜか失敗した

// Layout.astro
    <script type="module">
      import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
      mermaid.initialize({ startOnLoad: true });
    </script>

// hoge.mdx
<pre class="mermaid">
  graph TD 
    A[Client] --> B[Load Balancer] 
    B --> C[Server01] 
    B --> D[Server02]
</pre>


astroファイル内であればうまくいったので、mdxの生成プロセスの問題で何か起こってる?mdxをHTMLに変換するところとかどのタイミングでどうやってるんやろか。そこはどうやったら調べられるんや…

innerHTMLが効かない

<script type="module">
  import mermaid from './mermaid.esm.mjs';
  mermaid.initialize({ startOnLoad: false });

  // Example of using the render function
  const drawDiagram = async function () {
    element = document.querySelector('#graphDiv');
    const graphDefinition = 'graph TB\na-->b';
    const { svg } = await mermaid.render('graphDiv', graphDefinition);
    element.innerHTML = svg;
  };

  await drawDiagram();
</script>

svgは生成できてそうなのに、element.innerHTMLがうまく行かない。危ないからAstroで禁止してる…?set:htmlというのはあったが、クライアントサイドで生成したものは無理そう?サーバサイドで生成すればいいのだが、mermaid.renderがdocumentを参照してる…?エラーでとまるdocument is not defined

APIの種類

  • mermaid.run
    • By default, mermaid.run will be called when the document is ready, rendering all elements with class="mermaid".
    • mermaid.initialize({startOnLoad: false}) will prevent mermaid.run from being called automatically after load.
    • クラス名変えるくらいしかできない?(そんな感じの名前ではないが…)
  • mermaid.render
    • graphDefinitionのテキストからsvgを生成する関数っぽい
kzk4043kzk4043

reactのdangerouslySetInnerHTMLでいけないか?

https://docs.astro.build/ja/guides/integrations-guide/react/#_top

yarn astro add react
yarn add -D @types/react

https://push.tokyo/astro-react/

// MermaidDiagram.tsx
import mermaid from "../../../node_modules/mermaid/dist/mermaid";
import { useEffect, useState } from "react";

type MermaidDiagramProps = {
  graphDefinition: string;
};

const MermaidDiagram: React.FC<MermaidDiagramProps> = ({ graphDefinition }) => {
  const [svgDiagram, setSvgDiagram] = useState<string>("");

  useEffect(() => {
    mermaid.initialize({
      startOnLoad: false,
    });
    mermaid.render("graphDiv", graphDefinition).then((svg) => {
      setSvgDiagram(String(svg));
    });
  }, [graphDefinition]);

  return (
    <>
      <div>{graphDefinition}</div>
      <div>{svgDiagram}</div>
    </>
  );
};
export default MermaidDiagram;

// hoge.mdx
<MermaidDiagram2 client:only="react" graphDefinition="graph TB\na-->b" />

error document is not definedむむむ。

https://docs.astro.build/en/guides/troubleshooting/#document-or-window-is-not-defined

useEffect + client:onlyの組み合わせでも出てしまう。。
うーん…というかmdxはたぶんサーバでビルドするから(それもどうやって確かめるのか…)、クライアントサイドでのsvg生成と相性がわるいのか…?普通の.astroファイルならいけるもんなあ…
というかmermaidがdocumentを参照するのやめてほしいな…無理か。

mdxのパースとかレンダリングを関数で明示的にやる…?それともその処理になにか別の処理を挟ませることができるのか。
if (typeof window !== "undefined") {で分岐しても error document is not defined出るもんな…仕組みがわからん過ぎてわからん。

kzk4043kzk4043

Markdown -> HTML の変換に使われる事が多い、Remark / Rehype について

https://qiita.com/sankentou/items/f8eadb5722f3b39bbbf8

remark-mermaidjs入れたらいけたぞ…なんでや…
https://github.com/remcohaszing/remark-mermaidjs?tab=readme-ov-file#browser

npm install remark-mermaidjs
npx playwright install --with-deps chromium
astro.config.mjs
...
import remarkMermaid from "remark-mermaidjs";

// https://astro.build/config
export default defineConfig({
  ...
  markdown: {
    remarkPlugins: [remarkMermaid],
  },
});
hoge.mdx
```mermaid
flowchart LR
    Start --> Stop
\```

でもplaywrightの依存関係package.jsonに反映されてないけど本番環境でもいけるの…?

kzk4043kzk4043

まとめ

使いやすそう!特に個人開発の静的ページとかはめちゃ良さそうな印象を受けました。公式でも複雑なことするならNext.js使ってねとのことだが、サクッと作りたいときにはかなり良さそう。

  • 前提知識が少ない
    • ほぼ素のHTML/CSS/jsでいける
    • MPAでシンプル
    • UIライブラリ(ReactとかVueとか)が簡単に、部分的に使える
  • テンプレが多く、サクッと構築したいとき便利
  • 日本語ドキュメントがある
  • ちゃんと比べたわけじゃないけど、ビルドとかいろいろ早そうな体感はあった
    • jsをデフォでゼロにしているわけだし、描画も早いはず
  • まだちゃんと比較したわけじゃないけど、超個人的にはGatsbyよりAstroの方がいい印象。GatsbyはプラグインまわりとかGraphQLまわりとかやっぱり少し学習コストがある気がしたが、AstroはよりWebスタンダードに近くてシンプルに素早く構築するには有利に感じた。

Next.jsの複雑化や独自化に対する揺り戻しが起きつつあるなかで、その一つの解なのかも?もっとちゃんとAstroの概念を勉強したい