ビルド後に組み込み作業アリ用のAstro開発環境
はじめに
案件を受託するという仕事の性質上、書き出されるデータに制約が存在します。
CMS やフォームが含まれたWebサイトをまずは静的に組み、フロントエンドがビルドしたデータをバックエンドが編集する(組み込む)場合や、クライアントのガイドラインで assets ファイルの名前や構成が制限される場合などです。要件に合わせ、柔軟な対応が求められます。
開発環境の分類
自分の使用する開発環境を分類しました。Web アプリではなく、コーポレートサイトや LP のような Webサイトの制作を想定したものです。(個人的に好きなので Svelte を使用していますが、React や Vue を使う人は置き換えて下さい)。
この記事でターゲットにしてるのは「Astro 限定活用」の開発環境です。
GitHub
制作した開発環境のデータは以下のリポジトリにあります。
方向性の整理
- バックエンドの組み込みやすさを考慮する
- html の可読性を高くする
- アセットファイル名にhashをつけない
- Astro コンポーネントのスタイルのスコープは使わない
- できるだけ効率的に開発できるようにする
- Astro を使用してコンポーネントで設計&開発
- 制約が強い場合を考慮する
- バックアッププランを用意
assets ファイルの Build 例
最終的に、Build した assets ファイルは以下のような内容になります。
htdocs
└── assets
├── css
│ ├── common.css
│ ├── components
│ │ ├── accordion.css
│ │ └── section-block.css
│ ├── second.css
│ └── top.css
├── image
│ └── sample.png
├── js
│ ├── chunk
│ │ ├── Accordion.astro.GR6HRlk6.js
│ │ └── matchMediaController.CCgelFRS.js
│ ├── common.js
│ └── top.js
└── svg
└── icons.svg
1. CSS
記述場所
コンポーネントとして管理しやすくするために、CSS は astro ファイル内の style タグに is:global
属性を付与して書きます。
<style lang="scss" is:global>...</style>
global なので、astro ファイルごとのスコープは効きません。ユニークになる命名規則を採用する必要があります。front-matter 内で scss ファイルを import しても効果は同じなのですが、管理しやすいように global style を採用しました。
ファイル名の設定
CSS のファイル名は astro.config.mjs の assetFileNames で設定します。
export default defineConfig({
vite: {
build: {
rollupOptions: {
output: {
assetFileNames: (info) => {
const exNameMatchImportant = String(info.source).match(/--output-file-name-important:\s*([^}]+)/);
const exNameMatches = [...String(info.source).matchAll(/--output-file-name:\s*([^}]+)/g)];
const exNameMatch =
exNameMatches.length > 0
? exNameMatches.map((item) => item[1].replace(/\.css$/, '')).join('-') + '.css'
: null;
const fileName = exNameMatchImportant
? exNameMatchImportant[1]
: exNameMatch
? `components/${exNameMatch}`
: info.names[0];
return `${assetsDir}/[ext]/${fileName}`;
},
},
},
});
デフォルトのままだと呼び出したファイル名に影響され、index-1.css や index-2.css といった名称になります。ファイル名をコントロールするために、CSS のカスタムプロパティ(CSS変数)を使用することにしました。source から抽出しています。
ファイル名指定用カスタムプロパティ
「ファイルの種類」によって、2種類の指定方法があります。
カスタムプロパティ名 | 使用CSS | 説明 |
---|---|---|
--output-file-name | 複数ページ用CSS | |
--output-file-name-important | 全ページ共通CSS、ページ固有CSS | 優先される |
:host {
--output-file-name: sample.css;
}
CSSファイルの種類
CSS は3種類に分類できます。CSS を使用する範囲に合わせ、Astro が自動で振り分けます。
- 全ページ共通 CSS
- ページ固有 CSS
- 複数ページ用 CSS
あるコンポーネントが複数ページで使用され、且つ全ページにはない場合、そのコンポーネント用の CSS ファイルは個別に書き出され、該当するページで読み込まれます。これが「複数ページ用 CSS」です。
どのコンポーネントがどのページで使用されるかということを、あらかじめ全て決めるのは難しいと思います。ファイル名の指定が複数混ざった時のために、優先度の高くファイル名を設定できる --output-file-name-important
を用意しました。「全ページ共通CSS」と「ページ固有CSS」で使用しています。
:host {
--output-file-name-important: common.css;
}
注意事項
- ビルドしたhtml内で、CSSファイルの読み込まれる順番が制御できません。
-
@layer
を使って、優先度を設計する必要があります(例:reset、base、それ以外)
-
バックアッププラン
もし、複数ページ用 CSS をつくらず、CSS ファイルの数を制御したい場合、以下の対応が必要です。
- CSS の記述場所を astro ファイル内の style ではなく、外部の scss ファイルに変更する。
- 全ページ共通 CSS、ページ固有 CSS から
@forward
で読み込む(手動管理)。 - 複数ページ用 CSSは、全て全ページ共通 CSS にまとめる。
- あるいは、すべて全ページ共通 CSS として1ファイルにまとめる。
一気に管理が面倒になるので、可能な限り避けたいところです。
2. JavaScript
記述場所
ts ファイルに書きます。astro ファイル内に記述すると、コンポーネントの位置に script タグが生成されるので使用しません。
ファイルはコンポーネントと同じフォルダに置き、名前も合わせます。
└ sample/
├── Modal.astro
└── Modal.astro.ts
配置方法
script タグの配置場所を指定するため、script タグは head タグ内に書きます。書き出したい js の種類に合わせて使用する Layout を変更します。たとえば、common.js と top.js を使用する場合は、以下のように設定します。
---
import Script_Common from '@/components/Script/Common.astro';
import Script_Top from '@/components/Script/Top.astro';
---
<head>
...
<Script_Common />
<Script_Top />
...
</head>
ビルドしたjsファイルの名前は、読み込んでいる Astro ファイル名に依存します。
上の Head.astro で import している「Common.astro」は命名のための Astro ファイルです。中身には scrpit タグを1つ書き、ts ファイルを読み込みます。
<script src="@/scripts/common.ts"></script>
それぞれの ts ファイルで、必要なスクリプトを import します。
import { Modal } from '@/components/Sample/Modal.astro.ts';
ファイル名の設定
js のファイル名は astro.config.mjs で設定します。
export default defineConfig({
build: {
assets: `assets/chunk`,
},
vite: {
build: {
rollupOptions: {
output: {
entryFileNames: (info) => {
let fileName = 'violation';
if (info.facadeModuleId) {
const match = info.facadeModuleId?.toLowerCase().match(/\/([^\/]+)\.astro/);
if (match && match[1]) fileName = `${match[1]}`;
}
return `assets/js/${fileName}.js`;
},
},
});
1つ厄介な問題があります。chunk された js ファイルです。
複数箇所で使用している共通のスクリプトは、chunk として外部ファイル化されます。問題は chunk ファイルの名前を設定する chunkFileNames
です。Astro でこちらを使用すると、不要なファイルがたくさん生成されます(Astro はサーバーサイドとクライアントサイドで2回の build がはしり、サーバーサイドで不要なファイルが生成されます。根本的な原因は不明)
chunk はそのまま使用することとします。ファイル名に hash がつくのですが、参照元がjsファイル内なので html に差分は生まれず、サーバーサイドの組み込み作業に影響はないからです。
バックアッププラン
もし、chunk ファイルをつくらず、jsファイルを制御したい場合、以下の対応が必要です。
- 書き出される js ファイルは1つに制限し、すべてそこに import する。
- (もしくは、「同じ内容のコードをコピーして、chnuk されないようにする」という方法もあるが、開発効率が悪くなるので却下とします)
3. 画像
方針
Astro の画像の扱い方は、public 画像とsrc 画像があるのですが、public 画像のみ使用します。src 画像(Astro 画像コンポーネント)は自動でサイズ指定、webp 化などができるのですが、ファイル名にハッシュがついてしまうためです。
webp
jpg や png を webp に変換する script を用意しました。ターミナルから実行します。
npm run convert-webp
デフォルトではプロジェクトルールにある convert
フォルダ内で変換する設定です。プロジェクトの要件に合わせて変更可能です。
svg
たくさんのアイコン画像を扱う時に便利な svg スプライトを生成する script を用意しました。個別のsvg ファイルを1つにまとめ、symbol 化します。script は、ターミナルから実行できます。
npm run make-svg-sprite
デフォルトでは以下の設定になっています。
ファイル | 場所 |
---|---|
元svgファイル | /src/svg/ |
svgスプライト | /public/assets/svg/icons.svg |
元 svg ファイルの名前がそのまま id として設定されます。汎用的な Icon コンポーネントを用意してます。use タグで配置するので、css で color の変更が可能です。
---
const { name } = Astro.props;
const svgPath = `/assets/svg/icons.svg#${name}`;
---
<span class="sample-icon">
<svg viewBox="0 0 24 24"><use href={svgPath}></use></svg>
</span>
4. Build
Astro の標準の build と別に納品データ作成用の build(delivery)を用意しました。
以下のことができます。
- build フォルダにデータを作成(バージョン管理の対象)
- Prettier で整形した後、空白行を削除。
- ファイルリストを生成
delivery コマンドで実行します。
npm run delivery
NGワードチェック
Build コマンドに、NG ワードチェック機能を追加してます。
NG ワードを登録しておき、書き出されたファイル内に存在した場合、メッセージ付きのエラーを表示します。プロジェクト特有のNGワードがある場合に使用できます。
5. その他
- npm install 時にプロジェクト名を登録できます。
- package.json の name を上書きします。
- 一般的なコンポーネント(Modal、Accordion、Carousel、Navigation)を追加してます。
- GitHub Copilot 用のカスタムインタラクションを設定してます。
まとめ
Astro を使用し、「制約」と「開発しやすさ」のバランスをとった開発環境をつくりました。
CSS ファイル名用のカスタムプロパティを設定する必要があったり、JavaScript 埋め込み用の Astro コンポーネントを用意する必要があるのは面倒なのですが、ある程度の開発のしやすさは担保できたのではないかと思います。
Discussion