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