AstroとSvelteでStaticサイトを作って、GitHub Actions で定期的に情報を取得更新するようにした

 / #Astro #Svelte #GitHubActions #CloudflarePages

はじめに

趣味領域で作っている Next.js StaticExport 製の静的サイトがあるのですが、ページ数が5000弱であること、Next.js の高い更新頻度に付いていくのが大変であるなどの理由から、リプレースを考えていました。その時にちょうど Astro が V2.2に達した Tweet を見かけたので、React で動いているoriverk.devを Astro の Playground 代わりにしようと思いました。

Astroとは

Astro is the all-in-one web framework designed for speed. Pull your content from anywhere and deploy everywhere, all powered by your favorite UI components and libraries.

速度重視で、他の UI フレームワークも使えるオールインワンの Web フレームワークです(意訳)。実際に Astro では SSG と SSR の両方を作ることが出来、React や Vue、Svelte なども混ぜて使うことができます。

コード先頭に markdown の frontmatter の様なものがあることを除けば、ぱっと見は Svelte や Vue の様で、if 文や繰り返し箇所は React の JSX の様な感じです。

index.astro
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
---
<Layout title="Welcome to Astro.">
<main>
<h1>Welcome to <span class="text-gradient">Astro</span></h1>
<ul role="list" class="link-card-grid">
{[...Array(2)].map((_, i) => (
<Card
href={`https://astro.build/docs/${i === 0 ? '' : i}`}
title={`Documentation ${i === 0 ? '' : i}`}
/>
))}
</ul>
</main>
</Layout>
<style>
main {
margin: auto;
padding: 1.5rem;
max-width: 60ch;
}
h1 {
font-size: 3rem;
font-weight: 800;
margin: 0;
}
</style>

AstroとSvelteを使った感想

よかったこと

  • Astro 自体が非常に単純で理解しやすい(Next.js 比
  • astro.Config で md ファイルの取り扱いが設定でき、あれやこれやと until function を書かずとも frontmatter を取得したり、html にコンパイルできる
  • よいこととは実感してないけどリポジトリサイズが非常に小さい

よくはなかったこと・ふつごうだったこと

  • .astroでの event handling にはdocument.querySelectorなどと書く必要がある
  • Astro の構文が React と Vue/Svelte の中間みたいで、if 文や each 文を書くときに困惑する
    • 経験により解消されるとは思う。
  • .astro上で UI フレームワークコンポネントを呼び出す際に、両者との微妙な違いにより困ることがある。
  • GitHub上で script や style 領域がハイライトされない
    • Svelte も Vue もされない

Image from Gyazo

サイトについて

主に次のような機能をもったサイトにしたいと考えました。

  • Static Site である
  • GitHub の Pinned Repos と Contribution Calendar(GitHub 草)を表示できる
  • blog.oriverk.devのコンテンツを取得表示できる
  • Cloudflare Pages にデプロイし、サイトデータを自動で更新できる

また、以前にoriverk.devを React で作ったときの感じを踏襲したいとも考えていました。

Image from Gyazo

主に使用したもの

Astro だけでもサイトは作れますが、Astro と他 UI フレームワークを使った場合の感じを知りたかったので、軽量さに共通点を持つ Svelte を UI フレーム枠に採用しました。全体的な view?の/pages は astro ファイルで作り、components は svelte という風に使い分けました。

Init astro app

npm create astro@latest -- --template basics

Astro と Astronaut を掛けているのか、Houston という名前の顔文字が動いてて可愛いかったです。

npm create astro@latest

basics

npm i -D npm-run-all
npm i -D @commitlint/{config-conventional,cli}
# echo '{"extends": ["@commitlint/config-conventional"]}' > .commitlintrc.json
npm install -D eslint @typescript-eslint/parser eslint-plugin-{astro,jsx-a11y,import} eslint-import-resolver-typescript
npm install -D prettier prettier-plugin-astro eslint-config-prettier
# echo {} > .prettierrc.json
npx husky-init && npm install
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
npx astro add svelte
npm i sass cssnano autoprefixer

code linterの設定

Astro と Svelte を混ぜるので当然なのですが、両者用の設定が必要でした。なので、init svelte app の箇所に加えて

npm i -D eslint-plugin-svelte3 prettier-plugin-svelte

ESLint Config

他レポジトリで使っていた svelte 用の Config と混ぜる形で作りました。Svelte は今年に入って触ったばかりなので、設定が正しい状態にあるかはわかりませんが、動いてます。

.eslintrc.yml
extends:
- plugin:astro/recommended
- plugin:jsx-a11y/recommended
- plugin:import/recommended
- plugin:import/typescript
- prettier
overrides:
- files:
- '*.astro'
parser: astro-eslint-parser
parserOptions:
parser: '@typescript-eslint/parser'
extraFileExtensions:
- .astro
rules: {}
- files:
- '*.svelte'
processor: svelte3/svelte3
parserOptions:
parser: '@typescript-eslint/parser'
extraFileExtensions:
- .svelte
rules: {}
settings:
svelte3/typescript: true
parser: '@typescript-eslint/parser'
parserOptions:
ecmaVersion: latest
sourceType: module
plugins:
- svelte3
- '@typescript-eslint'
ignorePatterns:
- './dist/**/*'
settings: {}

Prettier Config

trailingComma: es5
tabWidth: 2
semi: false
singleQuote: true
plugins:
- prettier-plugin-astro
- prettier-plugin-svelte
pluginSearchDirs: false

ChatGPTとGitHub GraphQL API

GitHub API は以前に何度か利用したことがあって、ドキュメントが割と重く長いことを覚えていたので、時間節約のために ChatGPT を利用しました。ChatGPT に尋ねたところ、ChatGPT のバージョンは3.5でデータは2021年9月までのものらしく、例えば21年10月以降に変わった内容については正確には答えることができません。なので、ChatGPT が出力したクエリを GitHub GraphQl API Explorer で試して正常に動くかを確認し、クエリを調整することにしました。

GitHub GraphQL API を用いて、ユーザ名oriverkのpinned repository と contribution calendar のデータを取得せよ
ChatGPT-3.5 出力結果
query {
user(login: "oriverk") {
pinnedItems(first: 6) {
nodes {
... on Repository {
name
description
url
stargazers {
totalCount
}
forkCount
}
}
}
contributionsCollection {
contributionCalendar {
totalContributions
weeks {
contributionDays {
contributionCount
date
}
}
}
}
}
}

Explorer で問題なく動くことを検証し、必要/不必要なデータを取得するために公式ドキュメントを片手に適宜クエリを修正し、利用しました。

Contribution Calendar(GitHub草)

Image from Gyazo

描画するための Svelte ライブラリは複数ありましたが、その多くが更新を止めていました。なので、ライブラリを使わずに作ることにしました。

RSS fetcher

基本的に CatNose 氏の下記「RSS 集約サイト」に倣いました。なので割愛します。

GitHub ActionsによるCloudflare Pagesへの定期的デプロイ

GitHub GraphQL API へのアクセスを少なするために、prebuild にて GitHub のユーザー情報と別レポジトリからの履歴書用 md ファイル、oriverk.devの RSS を取得して、/contents下に json ファイルとして保存し、これら json ファイルを利用して build しています。

{
"scripts": {
"dev": "astro dev --project tsconfig.json",
"start": "astro dev",
"prebuild": "run-s prebuild:*",
"build": "astro build",
"preview": "astro preview",
}
}

GitHub Actions でもこれを利用するようにしました。

workflows/deploy.yml
name: continuous-deployment
on:
push:
branches:
- main
- dev
schedule:
- cron: "0 2 * * *"
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
name: build and deploy to Cloudflare Pages
steps:
- name: checkout
uses: actions/checkout@v3
# Run a build step here if your project requires
- name: setup node
uses: actions/setup-node@v3
with:
node-version: 18
- name: install packages and build
run: |
npm install
npm run build
env:
MODE: production
SECRET_GITHUB_PERSONAL_ACCESS_TOKEN: ${{ secrets.SECRET_GITHUB_PERSONAL_ACCESS_TOKEN }}
PUBLIC_GA_MEASUREMENT_ID: ${{ secrets.PUBLIC_GA_MEASUREMENT_ID }}
- name: deploy to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: astro-site
directory: dist

Image from Gyazo

AstroでGoogle Analytics

エラー類

Astro とその他の UI フレームワーク(今回は Svelte)を混ぜる構成なので、どちらに起因するか見極める必要性があり、また両者の組み合わせによるエラーは公式ドキュメントには当然書いてないので、そういったところは大変だなあと感じました。

date-fns/locale

svelte と date-fns

Directory import '/home/oriverk/Codes/oriverk/astro-site/node_modules/date-fns/locale/ja' is not supported resolving ES modules imported from /home/oriverk/Codes/oriverk/astro-site/dist/entry.mjs
Did you mean to import date-fns/locale/ja/index.js?
- import { ja } from 'date-fns/locale'
+ import ja from 'date-fns/locale/ja/index.js'

CSS Logical Media Query error

Media Queries Level 4からの次の様な書き方は、Svelte においては次のバージョンから使える模様。

@media (max-width: 30em) { ... }

サイトキャプチャ

Image from Gyazo

Image from Gyazo

Image from Gyazo