Open9

久しぶりにAstro

CbrnexCbrnex

構築

余談

今の段階ではランタイムはBunを使おうと思ってる
問題が起きたらpnpmにスワップすればいいね。Bunにあんまりメリットはない。

構築

Terminal
bunx create-astro@latest [your project name]

通常はcreateでいいんだけど、既存プロジェクトなのでbun initする。
正直、bun initしても必要のないファイルが作られるだけで、最初からbun add astroとかでもいい。
それでpackage.jsonも自動的に作られるし。

Terminal
bun add astro
package.json
{
  "type": "module",
  "private": true,
  "name": "pages",
  "scripts": {
    "dev": "astro dev",
    "build": "astro build",
    "preview": "astro preview"
  },
  "dependencies": {
    "astro": "^5.7.9"
  }
}

https://docs.astro.build/ja/install-and-setup/#手動セットアップ

手順通りにやる。まあ、testとstartはいらないかな

設定

.editorconfig
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4

[{*.html,*.css,*.styl,*.ts,*.js,*.mjs,*.astro,*.md,*.mdx}]
indent_size = 2

[{*.md,*.mdx}]
trim_trailing_whitespace = false
.vscode/settings.json
{
    "editor.fontFamily": "'Fira Code', 'HackGen35 Console NFJ', monospace",
    "editor.fontLigatures": true,
    "editor.insertSpaces": true,
    "editor.defaultFormatter": "esbenp.prettier-vscode",
    "[json]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[jsonc]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[javascript]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[typescript]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[javascriptreact]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[typescriptreact]": {
        "editor.defaultFormatter": "biomejs.biome"
    },
    "[markdown]": {
        "editor.defaultFormatter": "DavidAnson.vscode-markdownlint"
    },
    "[stylus]": {
        "editor.defaultFormatter": "esbenp.prettier-vscode"
    },
    "prettier.configPath": "./.prettierrc",
    "prettier.ignorePath": "./.prettierignore",
    "stylelint.validate": ["css", "postcss", "stylus", "styl"],
    "markdownlint.run": "onType"
}

ここはあまり変わらないのでコピってきた。
Stylelintってbunで使えたっけ?
とりあえずnode関係抜きで設定できるのはこれくらいか

https://github.com/tonsky/FiraCode
https://github.com/yuru7/HackGen

CbrnexCbrnex

フォーマッタ

Biome

bun add -D --exact @biomejs/biome

Biomeを追加する

bunx biome init

bunxというコマンドが無い。bun biome initとした。
Scoopで入れた影響かな?

biome.json
{
    "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
    "files": {
        "ignore": ["node_modules/**"]
    },
    "formatter": {
        "enabled": true,
        "formatWithErrors": true,
        "indentStyle": "space",
        "indentWidth": 4,
        "lineWidth": 120
    },
    "javascript": {
        "formatter": {
            "indentWidth": 2
        }
    },
    "json": {
        "parser": {
            "allowComments": true
        }
    }
}

Biomeはインデントのスタイルがタブらしい。なんで?

Prettier

bun add -D --exact prettier

Prettierをインストール。Biomeだけだとすべてを網羅できないから。

bun add -D prettier-plugin-astro

Astro用のプラグインも入れる。

prettierrc
{
    "printWidth": 120,
    "tabWidth": 4,
    "astroSkipFrontmatter": true,
    "plugins": ["prettier-plugin-astro"]
}
.prettierignore
# JS/TS Codes
*.js
*.ts
*.mjs

# JSON files
*.json
*.jsonc
.prettierrc

# Markdown
*.md
*.mdx

とりあえず、Biome優先であとはPrettierに投げる。

CbrnexCbrnex

Linter

oxlint

bun add -D oxlint

完全な互換性はないけど、速いらしいので使ってみる

.oxlintrc.json
{
    "plugins": ["oxc", "typescript", "import", "node", "promise", "jsx-a11y"]
}

Stylelint

bun add -D stylelint stylelint-config-html
stylelint.config.mjs
/** @type {import('stylelint').Config} */

export default {
  extends: [
    "stylelint-config-html",
    "stylelint-config-html/svelte",
    "stylelint-config-html/astro"
  ],
  customSyntax: "postcss-html"
};

とりあえずこいつは保留。使うかどうかも分からないんだよね

CbrnexCbrnex

TypeScript

tsconfig.json
{
    "extends": "astro/tsconfigs/base",
    "compilerOptions": {
        "module": "nodenext",
        "moduleResolution": "nodenext",
        "resolveJsonModule": true,
        "baseUrl": ".",
        "verbatimModuleSyntax": true,
        "paths": {
            "@/*": ["src/*"],
            "@pages/*": ["src/pages/*"],
            "@layouts/*": ["src/layouts/*"],
            "@styles/*": ["src/styles/*"],
            "@components/*": ["src/components/*"],
            "@content/*": ["src/content/*"],
            "@icons/*": ["src/icons/*"],
            "@scripts/*": ["scripts/*"],
        }
    }
}

言語仕様関係は最新にしておく。CommonJSは読めないから。

bun add -D typescript @types/node

追加する必要はないかもしれない。
ただ、node:pathとか使うときとかに必要だったような気がする。

CbrnexCbrnex

動作確認

.vscode/launch.json
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Install dependencies",
            "type": "node-terminal",
            "request": "launch",
            "command": "bun i"
        },
        {
            "name": "Launch development server",
            "type": "node-terminal",
            "request": "launch",
            "command": "bun dev"
        },
        {
            "name": "Build",
            "type": "node-terminal",
            "request": "launch",
            "command": "bun build"
        },
        {
            "name": "Preview",
            "type": "node-terminal",
            "request": "launch",
            "command": "bun prev"
        }
    ]
}
src/pages/index.astro
---
---
<p>Hello, world!</p>

とりあえず簡単に動かす。

❯ bun dev
$ astro dev
Debugger attached.
01:03:37 [types] Generated 2ms
01:03:37 [content] Syncing content
01:03:37 [content] Synced content

 astro  v5.7.9 ready in 239 ms

┃ Local    http://localhost:4321/

まあ、普通に動く。
こんな速かったっけ?

CbrnexCbrnex

Tips

astro.config.ts
import { defineConfig } from "astro/config";
import { getAliases } from "./scripts/alias.ts";
import tsconfig from "./tsconfig.json" with { type: "json" };

export default defineConfig({
  vite: {
    resolve: {
      alias: getAliases(tsconfig.compilerOptions.paths),
    },
  },
});
scripts/alias.ts
import path from 'node:path';
import type { MapLike } from 'typescript';
import type { AliasOptions } from 'vite';

export function getAliases(paths: MapLike<string[]>, alias: AliasOptions = {}): AliasOptions {
  let aliasesArray: AliasOptions = alias;

  for (const [_alias, _targetPath] of Object.entries(paths)) {
    if (_targetPath.length <= 0) {
      continue;
    }

    aliasesArray = {
      ...aliasesArray,
      ...{
        [_alias]: path.resolve(_targetPath[0]),
      },
    };
  }

  return aliasesArray;
}

こうするとtsconfig.jsonで設定したpathsを自動で適用してくれる。
ただ、もしかしたらこれもいらないかもしれない

CbrnexCbrnex

UnoCSS + daisyUI w/ Astro

bun add unocss daisyui @ameinhardt/unocss-preset-daisy

UnoCSSでもdaisyUIを使うことが出来る。
このプリセット自体は公式によるものではないが、公式ドキュメントで紹介されている。
完全には互換性がないんだとか。

astro.config.ts
import UnoCSS from 'unocss/astro'

export default defineConfig({
  integrations: [UnoCSS()],
unocss.config.ts
import { defineConfig } from 'unocss';
import presetWind4 from "@unocss/preset-wind4";
import { presetDaisy } from "@ameinhardt/unocss-preset-daisy";

export default defineConfig({
  presets: [presetDaisy(), presetWind4()],
});

まあ、手順通りにね。presetUnoは無くなった模様。@unocss/preset-wind4ってのがそれになる。
いつも思うけど、daisyUIってほかのコンポーネントライブラリと比べても要素数が少なく綺麗にまとめられていると思う。
daisyはdigital accessible information systemの略で、アクセシビリティの規格らしい

https://daisyui.com/docs/install/unocss/
https://w.wiki/DxNB

CbrnexCbrnex
bun add -D sass postcss
bun add postcss-preset-env autoprefixer
postcss.config.mjs
import presetEnv from "postcss-preset-env";
import autoprefixer from "autoprefixer";

export default {
  plugins: [presetEnv, autoprefixer],
};

とりあえず追加しておく。
SassとかってTailwindCSSとかUnoCSSと併用しない方がいいって言われているけど
完全に分離できるなら話は別なんじゃないかな

CbrnexCbrnex
uno.config.ts
import { defineConfig } from 'unocss';
import presetWind4 from "@unocss/preset-wind4";
import { presetDaisy } from "@ameinhardt/unocss-preset-daisy";

export default defineConfig({
  presets: [
    presetWind4(),
    presetDaisy({
      styled: false,
      themes: ["emerald --default", "dracula --prefersdark"],
    }),
  ],
});
<html data-theme="emerald">

このdata-themeをhtml要素に設定すると、デバイス側のモードがなんであれ、それになる。

<input type="checkbox" value="dracula" class="toggle theme-controller" />

このTheme Controllerを使っていて、気づいたことがある。
data-themeが設定されていない状況で、inputのvalueがdraculaとかダーク系のテーマだとする。
デバイス側のモードがダークモードだとこのトグルは機能しない。

https://daisyui.com/components/theme-controller/

<input
    id="theme-controller"
    type="checkbox"
    value={DARK_THEME}
    class="toggle theme-controller"
    data-light-theme={LIGHT_THEME}
  />
<script>
  const controller = window.document.getElementById("theme-controller");

  if (window.matchMedia("(prefers-color-scheme: dark)").matches && controller != null) {
    controller.setAttribute("value", controller.dataset.lightTheme ?? "light");
  }
</script>

こうした。最初のモードが分かってればいいからね。
もっと良いやり方はあるかもね。