Blog

オールインワンのWeb制作環境Astro!こんなのがほしかった。

※2023/1/7追記:esbuildでcssを出力している場合、index.cssがかちあう件の解決
※2023/2/15 JSバンドルについて、誤記あり。修正
※2023/3/8 ルートパスありきの運用に合わせる旨の記述を削除し(相対パス変換方法が現れたため)記事全体編集
※2023/3/8 CSSの設定についてWordPressを前提とするものについての注意書き追加

※2023/9/3 Astro3.0 HTML最適化 の無効について追加

Astroについて、大歓迎の理由

積年の思い(?)があるので長いです。詳しい紹介をお求めの方はインストールからどうぞ。

Astroは、コンテンツにフォーカスした高速なWebサイトを構築するためのオールインワンWebフレームワークです。

個人的に、やっとこういうのが出てきてくれたか!!という感じです。
ないなら作ろうかと思っていたくらいです。(できない)
実際、自分でWeb制作をする際は、Gulp + ejs + Sass + esbuildで欲しい環境を作っていました。

Web制作の制作環境のあれこれについては、他の記事でも色々と書いていますが、
Web開発の現場にはReactやVueなど便利なライブラリやフレームワークがあるのに、なぜWeb制作にはないのだ…と常々思っていました。

Gulpをnpm-scriptに変えようとしたり、SassをPostCSSにしてみようと思ったり、webpackをesbuildに変えたり、
新しいものが話題になるたび、またそれぞれの技術がバージョンアップするたびに、
常に検証・勉強をしなくては…という思いに駆られました。

これらのバージョンアップなどをまるごと引き受けてくれるフレームワークはないのか…と思っていたところ、彗星の如く現れたAstro(個人的主観)。

Astroの特徴としては、

  • サーバーサイドのレンダリングの活用により高速であること
  • すでに使ったことあるコンポーネント言語(React、Preact、Svelte、Vue、Solid、Lit)経験者なら学習コストほぼなし

ということを公式でも挙げています。

本来Webサイトの基本の形(HTML+CSSで構築されたマルチページアプリケーション)を考えれば、高速であることはある意味当たり前ですが、
だんだん複雑なことが要求されてきたWeb制作の制作環境に、Web開発の開発体験をうまく融合したという点がすごい。むしろそれがすごい。
速いとかは二の次。

Web開発者が使いやすいだけでなく、Web制作者がとっつきやすいフレームワークにすることをコンセプトにしたものって今までなかったんじゃないかと思っています。

そもそもWeb制作には下記のような課題が挙げられます。

  • ヘッダーフッターなどの共通部分をどうするか
  • レスポンシブのCSSを効率よく書くためにはどうするか
  • JSを手軽に導入したい、軽くしたい。またファイルを整理するにはどうするか
  • のちにCMSを入れることを前提としたファイル群を作るには?
  • チームで制作するときどうすれば効率がよいか?

これらの課題を解決する環境が必要でしたが、Web制作だけやってる人にとって、開発の環境を作るのはちょっと難易度が高いのです。作るのも使うのも難しいのが現状です。

Gulp(とかnpm-scriptでもいいですが)のパッケージを組み合わせて環境作っている人ってどれくらいいるんでしょうか。いままでGulpの記事何個か公開しましたが、需要のなさをアクセス数がものがたっ……………(そこは自問自答)

もちろん、Gulpを使わなくてもWeb制作はできるので、環境構築をごりごりするのは必須ではないかもしれませんが、
Web開発時にReact(とかVite、Next.js、Nuxt.js)などを使った経験があれば、Web制作でも似たような環境が欲しくなります。それだけ便利です。
でもReactなどのフレームワークはWeb制作に応用するにはかなり遠回りです。

開発環境は欲しいけど、Web制作に応用するにはレベルが必要だし、そもそも「公開もその後の更新もうちでやります」とか「SPAでリッチなん作りましょう」みたいなことが言えるような案件じゃないと使いづらかったです。なぜならWeb制作に関わる人全員が「ビルド」作業ができるとは限らないからです。

Web制作で作りたいのは純粋なHTMLとCSSとJS。
これらが最終的に生成できないと、少なくとも「コーディングのみ」の案件では使えませんでした。

なので、Web開発用の便利なフレームワークを使わず、上に挙げたようなWeb制作の課題を解決するために、
HTMLはejsを使い、CSSはSassで書き、JSはesbuildを使い、それらをまとめて実行するためにGulpを使い、納品物を作ってきました。

…が、これがしんどい。だれかやってほしい。

そう思ってたら、Web制作の課題を一手に引き受けるAstroキタ!というわけでした。

正直言うと触ってみた感じ、自分の独自制作環境でできていることすべてがオールインワンなわけではなく
手動でやらなくてはならなかった部分もありました。

でもAstroのコンセプト「MPA vs. SPA」は、自分の脳内を覗かれたと思うほどだったので、方向性としてこれらの解決も視野に入れて作られたものだと確信しています。

結論 惜しかったところ

欲しいアウトプット

  • 静的HTML
  • CSS
  • JS(できればminify状態と選べる)
  • 画像

この静的HTMLが容易に作れるという点でAstroはとても良かったのですが、下記3点が解決すればもっとよかった…。という感じでした。

  • headに書くcssやjs、imgのパスが全てルートパスになる。相対パスにできない。
    (追記:プラグインで可能になりました)
  • 共通CSSのファイル名(common.cssなど)の名前が自由に決められない(ページコンポーネント以外のコンポーネント)
  • CSSをminifyしない場合、JSもminifyできない。

今後に期待したいところです。もし解決方法があれば知りたい。

今までのやり方を変えよう(Astroに合わせよう)と考えてるところ

<今まで>保存した時点でHTML/CSS/JSそれぞれがビルドされる
➡︎ <これから>ビルドコマンド(npm run build)をたたいたときにビルドされる。

Web制作もリモートでチーム制作できる時代になりました。
Gitを使えば、マージしたときにビルドが勝手に走ってサーバーにデプロイするまでやってくれるようにすることもできます。
なので、Gitの使い方さえわかればビルドの仕方がわからない人も対応可能になります。

上記を変えようと思った大きな理由

今までは、「制作フローにおいてエンジニア以外の人が手を出しやすいように」していた部分があり、それをやめて上記2点を受け入れようと思ったというのが大きな理由です。

「ビルドコマンドをたたいて最終納品物が完成する」ことは、開発のフレームワークでは普通ですが、Web制作ではSassのコンパイルぐらいしかないこともよくある(HTMLとJSは手書きで書いたものをそのまま使用)ため、修正があるたびにビルドコマンドをたたかないといけない制作フローの現場は少ないと思います。

エンジニアではないデザイナーやディレクターがHTMLを少し修正するくらいはよくあることだと思っており、そのたびにビルドが必要だと、修正のたびにエンジニアに依頼しないといけなくなります。そうすると人件費がかかるので、制作費が膨れていきます。

Web制作の費用感はWeb開発の費用感とは大きく違います。安いです。

人件費ギリギリを攻めてくる案件は結構多く、公開前の少しの修正をエンジニア(ないしコーダー)に差し戻すことは、効率が悪いです。

開発向きのフレームワークは、制作からデプロイまでを基本的にエンジニアが担当することが前提になっているので問題ないですが、Web制作ではそれらのフレームワークを使われると、細かい修正のときに困るという一面があります。

…という理由があって、長らくビルドコマンドありきの制作フローは避けてきましたが、最近は時代が変わってきたと思っています。

というか、その「人件費ギリギリを攻めてくる」Web制作はどうなんだと思っています。

Web制作にも便利な環境が必要で、構築はそこそこ難しいことであるにも関わらず、
「HTMLとCSSって簡単だしプログラミングじゃないんだから、安くてもいいでしょ」みたいな思い込みが、Web制作の市場価格を押し下げる要因になっている。

Web制作に携わる人間も日々勉強が必要だし専門家なのだから、ビルドが必要なことは前提にして、そこまでの制作を全部エンジニアやコーダーに任せて欲しい。その分かかった費用は払うことも前提としてほしい。

…と思ったので、「ビルドコマンドを叩いてビルド」は妥当だと判断しました。

インストールと準備

最新版をインストールします。

npm create astro@latest

このまま、プロジェクトフォルダの名前やgitのことなどを聞かれるので入力や選択をし、その流れでnpm installまでコマンドで実行できます。

プロジェクトフォルダに移動し、

npm run dev

で開発スタートです。localhost:3000でAstroのサンプルページが開きます。

エディタを使いやすくするため、Visual Studio Codeに「Astro」という拡張機能があるので、インストールしておきます。

ハイライトや補完が利くようになります。

他のエディタでもセットアップ用の拡張機能があります。

レイアウトコンポーネントを作る

一般的に、src/layoutsディレクトリに配置されます。

「npm create astro@latest」でプロジェクトを作っていたら、「src/layouts/Layout.astro」ができています。

Web制作では、「headerだけ、footerだけを別ファイルにして、各ページでインクルードする」やり方の方が馴染みがあるかもしれませんが、
ちょっと考え方がちがって、イメージ的には「head・header・footer込みのテンプレートを作って、mainタグの中だけを別ファイルで作る」ような感じです。

Astroでは、Astroコンポーネント内のコンポーネントスクリプトを識別するためにコードフェンス(---)を使用します。Markdownを書いたことがある方なら、すでにfront-matterという同様の概念に馴染みがあるかもしれません(by公式。私はなかった。)

このLayout.astroコンポーネントの内部でJavaScriptコードを実行しても、ビルド後には取り除かれます。

つまり、コードフェンス外のものでコンポーネントタグがHTMLに差し代わったものが最終htmlとなります。

---
import Header from './Header.astro';
import Footer from './Footer.astro';
---

<!DOCTYPE html>
<html lang="ja">
<head>
  <title>{title}</title>
</head>
<body>
  <Header />
    <slot />
  <Footer />
</body>
</html>

<slot />にはsrc/pages/hoge.astroのHTMLが差し込まれます。

Header.astroFooter.astroを作り、コードフェンス内にimport文を書きます。

importで読み込んだHeaderは<Header />としてHTML内に入れると、ビルド後にそこにHeader.astroの内容が入ることとなります。

各ページから受け取ったProps(動的に変えたい部分)を扱うときもコードフェンス内に書きます。

---
const { title, description } = Astro.props;
---

<html lang="ja">
<head>
  <title>{title}</title>
  <meta name="description" content={description}>
</head>

各ページで指定したpropsは、Astro.propsから受け取り、{hoge}という形で利用できます。JSXに慣れていると馴染みがあります。
各ページでは下記のようにpropsを指定します。

<Layout title="タイトル" description="説明文">
  ほげほげ
</Layout>

TypeScriptを使う場合は、Propsの型指定ができます。コードフェンス内にinterfaceを書きます。

export interface Props {
	title?: string;
	description?: string;
}

また、後述しますがcssのlinkタグや自分オリジナルのjsのscriptタグは、ビルド後に差し込まれるので入れません。

画像パスなどは、「/」から始まるパスにしておかないとビルド後にリンク切れを起こします。
ルートパスでは運用上辛いという場合は、相対パスにする方法もあります

CSS

scssをcssにコンパイルしてlinkタグで読み込む…のはやめて、astroファイル内でimportする

最初は、SassファイルをcssにコンパイルしてそれをHTMLのリンクタグで読み込むことを考えましたが、Astroでは「SassコンパイルからのCSS生成」ではなく「astroファイルにscssファイルをimportし、ビルドでcssが生成される」のが自然な方法になります。

追記:以下、importの方法で進めていますが、後にWordPressのテンプレートに使いたい場合は、若干の工夫が必要になってきます。npm-scriptでsassをコンパイルする方が取り回しがきくような気がしていますので、以下は静的ファイルを作る場合のみ参考にしてください。

importのほかに<style lang="scss"><style lang="sass">のようにstyleタグをastroファイルに書くことでも使用可能です。

このようにSassはサポートされていますが、任意のディレクトリから任意のディレクトリに吐き出すという処理に対応しているわけではありません。

結果、Astroの想定する方法でCSSを生成することにしました。

scssファイルはsrc/styles/というフォルダを作り、そこに格納します。
astroファイルで、必要なscssをimportします。

まず共通レイアウトのLayout.astroコンポーネントでは、全体で使用するscssをimportしました。

---
import '../styles/foundation/reset.scss'
import '../styles/foundation/global.scss'
import '../styles/common/common.scss'
---

TOPページのastroファイルを作ります。topページだけのscssを記載したtop.scssを読み込みました。

---
import Layout from '../layouts/Layout.astro';
import '../styles/pages/top.scss';
---

これをbuildするとどうなるかというと、index.4e2121ea.css のようなcssファイルが生成され、TOPページのhtmlファイルには下記のlinkタグが追加されます。

<link rel="stylesheet" href="/assets/index.4e2121ea.css" />
<link rel="stylesheet" href="/assets/index.58d29ceb.css" />

上記の二つのCSSファイルは、それぞれ「src/layouts/Layout.astroにimportしたscss」と「src/pages/index.astroにimportしたscss」です。

ここで気になるのが、

  • CSSのファイル名が微妙。
  • CSSが最適化されている

の2点でした。

cssのファイル名をわかりやすいものに

index.58d29ceb.cssのようなCSSファイル名は、viteのさらにrollup.jsというツールで設定されています。

viteの設定はastro.config.mjsにvite追加の設定項目を書けるようになっています。astro.config.mjsは、コマンドラインでプロジェクトを作成したら、ルートフォルダに生成されています。

rollupの設定を変えるには下記のようにします。

import { defineConfig } from "astro/config";

export default defineConfig({
  vite: {
    build: {
      rollupOptions: {
        output: {
          assetFileNames: "[ext]/[name][extname]",
        },
      },
    },
  },
});

astroには内部的にviteが使用されており、astroのconfigにてviteの設定をすることができます。viteのbuildプロパティrollupOptionsに指定します。

公式:出力するファイル名のカスタマイズ

assetFileNamesプロパティに上書きすれば、ビルド後にassetsにフォルダに入っていたものの保存先を変えることができます。
[ext]は拡張子のドットなし、[name]は拡張子を除いたアセットのファイル名、[extname]はドットありの拡張子。詳しくはrollup.jsのドキュメントへ

これで、トップページでimportしたcssはdist/css/index.cssに保存され、下層の例えばabout/index.astroでimportしたcssはdist/css/about-index.cssに保存されます。

※assetsにはjsや画像も含まれます。上記の設定ですべてフォルダ分けされますので、任意のフォルダにしたい場合は、publicフォルダに入れるとビルド後そのまま配置されます(バンドル処理や最適化などはされません)。

common系のcssファイル名について

ここで、src/layouts/Layout.astroでimportしたcssのファイル名はどうなるかというと、
about-index-1.cssというファイル名になってしまいました。
なぜ下層ページの名前がつかわれているのかというと、まずLayout.astroはpageコンポーネントではなく、about/index.astroで下記のように間接的にインポートされています。

---
import Layout from '../../layouts/Layout.astro';
---
---
import '../styles/foundation/reset.scss'
import '../styles/foundation/global.scss'
import '../styles/common/common.scss'
---

なのでassetFileNamesのルールの[name]に入るものがないから…だと思うのですが、せめてLayout.cssみたいな名前になってほしかった…。rollup.jsを掘り下げたらなんとかなるんでしょうか。。ちょっと見てみたけどよくわからなかったので諦めました。

esbuildでcssを出力している場合、index.cssがかちあう件の解決

上記のように「assetFileNames: "[ext]/[name][extname]",」と出力ファイル名をカスタマイズした場合、もう一つハマる点がありました。

javascriptをバンドルしている場合で、js内でcssをインポートしている場合、そのcssの出力ファイル名とトップページの出力ファイル名が被ってしまうようで、ファイル内でインポートしたcssの内容がindex.cssにはきだされ、トップページのCSSが上書きされてしまうということがありました。

これを解決するためには、esbuildでcssをインポートせず、astroファイル内でインポートします。

main.tsなどでimportしてたcss部分はなくす↓

import baguetteBox from "baguettebox.js";
// import 'baguettebox.js/dist/baguetteBox.min.css'

Layout.astroでimportすると、全体のCSSに入ります。

---
import 'baguettebox.js/dist/baguetteBox.min.css'
---

プラグインのCSSをastroファイルでインポートすると、プラグインに関する記述が分裂してるようでちょっと気持ち悪い気もしますが…。esbuildのアウトプットのファイル名を変えられたら一番よいのですが、astro.config.mjsでいろいろやってみてもできなさそうだったので、諦めました。

CSSのminifyをなくす

デフォルトでは、CSSはminifyされるので、ビルド後は改行が全てなくなった状態になります。

CSSのminifyをするかどうかは、「納品後にエンジニア以外の人が修正をする可能性があるかどうか」に大きく関わり、これまでは基本minifyはしない状態で納品するようにしていました。

個人的な考えでは上記のここで書いた通り、その辺りの配慮はもうしなくてよいような時代になってほしいので、CSSのminifyは許容したいです。…が、まだちょっとminifyするかどうかは選べるくらいであってほしいという気持ちがあり、方法を調べました。

内部的にViteの設定項目となるので、astro.configで無効化することができます。

export default defineConfig({
  vite: {
    build: {
      minify: false
    },
  },
});

しかし、minifyをfalseにするとJSのminifyもなくなります。

個人的にはjsファイルはminifyしてほしいと思っていたのでどうにかならないかと方法を探しましたが、見つけることができませんでした。

build.minify が true のとき、全てのミニファイ最適化はデフォルトで適用されます。特定の側面を無効化するために、esbuild.minifyIdentifiers 、esbuild.minifySyntax 、esbuild.minifyWhitespace のいずれかを false に設定してください。build.minify を上書きするために esbuild.minify を利用できないことに注意してください。

https://ja.vitejs.dev/config/shared-options.html

esbuildのminify設定でtrueに上書きすることはできないみたいです。

外部のライブラリをimportする時点で、そもそもminifyのものをimportするなどすると、容量を小さくすることはできます。

ちなみに、メディアクエリをまとめる場合は、postcss-merge-queriesを使えばできます。
postcss-merge-queriesをnpmインストールし、astro.config.mjsに下記追加します。

import postcssMergeQueries from "postcss-merge-queries";

export default defineConfig({
  vite: {
    css: {
      postcss: {
        plugins: [postcssMergeQueries],
      },
    },
  },
});

JSのバンドル

自分で書いたJSをバンドルして読み込ませたい場合

私はもともとesbuildのバンドル処理を行なっていたので、ローカルスクリプトのインポートを参考にしました。

Astro は、スクリプト バンドル ルールに従って、これらのスクリプトを作成、最適化し、ページに追加します。

Astroのバンドル処理でされること

  • Any imports will be bundled, allowing you to import local files or Node modules.
  • The processed script will be injected into your page’s <head> with type="module".
  • TypeScript is fully supported, including importing TypeScript files.
  • If your component is used several times on a page, the script will only be included once.

訳)

・すべてのインポートはバンドルされ、ローカルファイルやNodeモジュールをインポートできます。
・処理されたスクリプトは、type="module"としてページの<head>に挿入されます。
・TypeScriptを対応しており、TypeScriptファイルもインポートできます
・コンポーネントがページに複数回使われている場合、スクリプトは一度だけ含まれます。

自分のスクリプトをsrc/js/app.tsに書いているとします。まず、viteのbuild設定で、entryFileNamesを設定しておきます。

export default defineConfig({
  vite: {
    build: {
      rollupOptions: {
        output: {
          entryFileNames: "js/app.js",
        },
      },
      minify: false,
    },
  },
});

src/components/LocalScripts.astroを作り、スクリプトタグでapp.tsを読み込みます。付随するcssがあればそれもここで読み込みます。

---
import "swiper/swiper-bundle.css";
import "baguettebox.js/dist/baguetteBox.min.css";
---
<script src="../js/app.ts"></script>

それをLayout.astroでインポートします。

import '../components/LocalScripts.astro'

すると、build後にすべてのHTMLファイルにscriptタグが差し込まれ、jsファイルが生成されます。

ちなみに、わざわざLocalScripts.astroを作成してscriptタグに記述するのではなく、
直接Layout.astroimport '../js/app.ts'を書けば良さそうに思えますが、それをしなかったのは、JS内でwindowやdocumentにアクセスする処理を行なっているからです。

Astro components run on the server, so you can’t access these browser-specific objects within the frontmatter.

Framework components run on the server by default, so this error can occur when accessing document or window during rendering.

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

Astro コンポーネントはサーバー上で実行されるため、frontmatter 内でこれらのブラウザ固有のオブジェクトにアクセスすることはできません。

Frameworkコンポーネントはデフォルトでサーバー上で実行されるため、レンダリング中にドキュメントまたはウィンドウにアクセスするとエラーが発生する可能性があります。

Web制作で自分で書くJSはDOMにアクセスするものがほとんどなので、このような方法になりました。

publicフォルダに置いたJSやCDNから読み込む場合

is:inlineディレクティブを含めるとAstroによるバンドル処理がスキップされます。

headに、普通にscriptタグを含め、is:inlineディレクティブをつけます。

<!-- public/my-script.jsにある場合 -->
<script is:inline src="/js/my-script.js"></script>

<!-- CDNの読みこみの場合 -->
<script is:inline src="https://my-analytics.com/script.js"></script>

これをbuildすると、publicフォルダのjsはそのままdistフォルダにコピーされるような形になり、htmlファイルには<script src="/js/my-script.js"></script>が差し込まれます。

絶対パスで読み込んだCDNは、そのまま移行されていました。

ビルドで相対パスへ変換

例えばlayout.astroなどの、ページ共通コンポーネントの画像やリンクタグのパスについて、パスをどうすればよいかという問題がありますが、Astroは基本的に相対パスでの生成に対応していません。

相対パスを使わないなら画像やlinkタグもルートパスを使って書けばよいのですが、テストアップにデモのドメインを用意するなどの手間がかかります。

これを手間というのもどうなのかと思い、一度はルートパスを使って運用する方法を考えましたが、ありがたいことに、ビルド後に相対パスに変換してくれるプラグインを開発してくださる方がいました。

astro-relative-links

使い方などは制作者様のページに載っています。使い方簡単です。
https://zenn.dev/ixkaito/articles/astro-relative-links

今まで相対パスの運用はやめようと覚悟を決めていましたが、個人的にすごく助かりました。

絶対パスでの運用が辛い方にはおすすめです。

HTMLの最適化を無効にする

Astro3.0で、ビルドによるHTMLの最適化がデフォルトになりました。
自分でデプロイする場合は問題ないですが、HTMLファイルを納品するなど、最適化が気になる場合は下記の設定で無効にできます。

export default defineConfig({
  compressHTML: false
});

まとめ

一番最初にかいた、Astroのようなフレームワークを待ち侘びた系の話が「まとめ」にくるのが本来だと思うのですが、前置きがだいぶ長くなってしまいました。

他にも、Astroコンポーネントが便利だということが書きたかったですが、Layout.astroだけになってしまったので、また本格的に使ってから記事書いてみようと思います。

CSSファイル名やルートパスに関してはまだ、どうかなぁと思ってる節はあり、結局は今のところgulpのように設定を組んでいく必要性は感じます。が、これから使ってみて、コーディングだけ案件やWordPress案件などにもうまく使っていけるよう考えていきたいと思います。
パッケージ探しをしなくてよくなるだけでも御の字です。

おすすめの記事 recommend blog

新着 new blog

github