カスタムメイドの静的サイトジェネレーターでスタックを簡素化
公開: 2022-03-10Jamstackムーブメントの出現により、静的にサービスが提供されるサイトが再び大流行しました。 静的HTMLを提供するほとんどの開発者は、ネイティブHTMLを作成していません。 確かな開発者エクスペリエンスを得るには、静的サイトジェネレーター(SSG)と呼ばれるツールを使用することがよくあります。
これらのツールには、大規模な静的サイトの作成を快適にする多くの機能が付属しています。 GatsbyのデータソースのようなサードパーティのAPIへの単純なフックを提供する場合でも、11tyの膨大なテンプレートエンジンのコレクションのような詳細な構成を提供する場合でも、静的サイト生成のすべての人に何かがあります。
これらのツールはさまざまなユースケース向けに構築されているため、多くの機能を備えている必要があります。 これらの機能により、強力になります。 また、新しい開発者にとっては非常に複雑で不透明になります。 この記事では、SSGを基本的なコンポーネントに落とし込み、独自のコンポーネントを作成します。
静的サイトジェネレーターとは何ですか?
静的サイトジェネレーターは、基本的に、ファイルのグループに対して一連の変換を実行して、ファイルをHTMLなどの静的アセットに変換するプログラムです。 受け入れることができるファイルの種類、ファイルの変換方法、および出力されるファイルの種類によって、SSGが区別されます。
初期の人気のあるSSGであるJekyllは、Rubyを使用してLiquidテンプレートとMarkdownコンテンツファイルをHTMLに処理します。
GatsbyはReactとJSXを使用して、コンポーネントとコンテンツをHTMLに変換します。 次に、さらに一歩進んで、静的に提供できる単一ページのアプリケーションを作成します。
11tyは、Liquid、Handlebars、Nunjucks、JavaScriptテンプレートリテラルなどのテンプレートエンジンからHTMLをレンダリングします。
これらの各プラットフォームには、私たちの生活を楽にする追加機能があります。 テーマ設定、ビルドパイプライン、プラグインアーキテクチャなどを提供します。 機能が追加されるたびに、複雑さが増し、魔法が増し、依存関係が増えます。 確かに、これらは重要な機能ですが、すべてのプロジェクトで必要なわけではありません。
これらの3つの異なるSSGの間に、別の共通のテーマがあります。データ+テンプレート=最終サイトです。 これは、ジェネレータ静的サイトのコア機能のようです。 これは、SSGのベースとなる機能です。
静的サイトジェネレーターは、基本的に、ファイルのグループに対して一連の変換を実行して、ファイルをHTMLなどの静的アセットに変換するプログラムです。
「「
新しい静的サイトジェネレーターのテクノロジースタック:ハンドルバー、Sanity.io、Netlify
SSGを構築するには、テンプレートエンジン、データソース、およびSSGを実行してサイトを構築できるホストが必要です。 多くのジェネレーターはMarkdownをデータソースとして使用しますが、それをさらに一歩進めて、SSGをCMSにネイティブに接続した場合はどうなるでしょうか。
- データソース:Sanity.io
- データのフェッチとテンプレート化:ノードとハンドルバー
- ホストと展開:Netlify。
前提条件
- NodeJSがインストールされました
- Sanity.ioアカウント
- Gitの知識
- コマンドラインの基本的な知識
- Netlifyなどのサービスへのデプロイに関する基本的な知識。
注:フォローするために、GitHubのこのリポジトリにあるコードを見つけることができます。
HTMLでのドキュメント構造の設定
ドキュメント構造を開始するために、プレーンHTMLを記述します。 まだ問題を複雑にする必要はありません。
プロジェクト構造では、ソースファイルが存在する場所を作成する必要があります。 この場合、 src
ディレクトリを作成し、その中にindex.html
を配置します。
index.html
で、必要なコンテンツの概要を説明します。 これは、ページについては比較的簡単です。
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Title of the page!</title> </head> <body> <h1>The personal homepage of Bryan Robinson</h1> <p>Some pagraph and rich text content next</p> <h2>Bryan is on the internet</h2> <ul> <li><a href="linkURL">List of links</a></li> </ul> </body> </html>
これを単純にしましょう。 ページのh1
から始めます。 経歴情報のいくつかの段落でそれに続き、詳細を表示するためのリンクのリストでページを固定します。
HTMLをデータを受け入れるテンプレートに変換する
基本的な構造ができたら、これをある程度のデータと組み合わせるプロセスを設定する必要があります。 これを行うには、Handlebarsテンプレートエンジンを使用します。
基本的に、HandlebarsはHTMLのような文字列を受け取り、ドキュメントで定義されたルールを介してデータを挿入し、コンパイルされたHTML文字列を出力します。
ハンドルバーを使用するには、package.jsonを初期化してパッケージをインストールする必要があります。
npm init -y
を実行して、デフォルトのコンテンツを含むpackage.jsonファイルの構造を作成します。 これができたら、ハンドルバーをインストールできます。
npm install handlebars
ビルドスクリプトはノードスクリプトになります。 これは、ローカルでビルドに使用するスクリプトですが、デプロイメントベンダーとホストがライブサイトのHTMLをビルドするために使用するスクリプトでもあります。
スクリプトを開始するには、 index.js
ファイルを作成し、上部に2つのパッケージが必要です。 1つ目はハンドルバーで、2つ目は現在のファイルシステムにアクセスするためのNodeのデフォルトモジュールです。
const fs = require('fs'); const Handlebars = require('handlebars');
fs
モジュールを使用して、ソースファイルにアクセスしたり、配布ファイルに書き込んだりします。 ビルドを開始するには、呼び出されたときにファイルを実行するためのmain
関数と、データとマークアップを組み合わせるためのbuildHTML
関数を作成します。
function buildHTML(filename, data) { const source = fs.readFileSync(filename,'utf8').toString(); const template = Handlebars.compile(source); const output = template(data); return output } async function main(src, dist) { const html = buildHTML(src, { "variableData": "This is variable data"}); fs.writeFile(destination, html, function (err) { if (err) return console.log(err); console.log('index.html created'); }); } main('./src/index.html', './dist/index.html');
main()
関数は、HTMLテンプレートへのパスとビルドされたファイルを保持するパスの2つの引数を受け入れます。 メイン関数では、ある程度のデータを含むテンプレートソースパスでbuildHTML
を実行します。
ビルド関数は、ソースドキュメントを文字列に変換し、その文字列をハンドルバーに渡します。 ハンドルバーは、その文字列を使用してテンプレートをコンパイルします。 次に、コンパイルされたテンプレートにデータを渡し、Handlebarsは新しいHTML文字列をレンダリングして、変数またはテンプレートロジックをデータ出力に置き換えます。
その文字列をmain
関数に返し、ノードのファイルシステムモジュールによって提供されるwriteFile
メソッドを使用して、ディレクトリが存在する場合は、指定された場所に新しいファイルを書き込みます。
エラーを防ぐには、 .gitkeep
ファイルを含むdist
ディレクトリをプロジェクトに追加します。 ビルドされたファイルをコミットしたくありません(ビルドプロセスがこれを行います)が、スクリプト用にこのディレクトリがあることを確認する必要があります。
このページを管理するCMSを作成する前に、それが機能していることを確認しましょう。 テストするために、渡したばかりのデータを使用するようにHTMLドキュメントを変更します。 Handlebars変数構文を使用して、 variableData
コンテンツを含めます。
<h1>{{ variableData }}</h1>
HTMLに変数が設定されたので、ノードスクリプトを実行する準備が整いました。
node index.js
スクリプトが終了すると、 /dist/index.html
にファイルが作成されます。 これをブラウザで開くと、マークアップがレンダリングされているだけでなく、「これは可変データです」という文字列も表示されます。
CMSへの接続
テンプレートを使用してデータをまとめる方法があります。次に、データのソースが必要です。 このメソッドは、APIを持つすべてのデータソースで機能します。 このデモでは、Sanity.ioを使用します。
Sanityは、コンテンツを構造化データとして扱うAPIファーストのデータソースです。 彼らは、編集者と開発者の両方にとってデータの管理と追加をより便利にするオープンソースのコンテンツ管理システムを持っています。 CMSは、「ヘッドレス」CMSと呼ばれることが多いものです。 データがプレゼンテーションに緊密に結合されている従来の管理システムの代わりに、ヘッドレスCMSは、任意のフロントエンドまたはサービス(および場合によっては同時に多数)が使用できるデータレイヤーを作成します。
Sanityは有料サービスですが、無料の「標準」プランがあり、このようなサイトに必要なすべての機能を備えています。
正気を設定する
新しいSanityプロジェクトを起動して実行する最も簡単な方法は、SanityCLIを使用することです。 それをグローバルにインストールすることから始めます。
npm install -g @sanity/cli
CLIを使用すると、管理、展開、および作成するためのヘルパーのグループにアクセスできます。 始めるために、 sanity init
を実行します。 これにより、Studio(SanityがオープンソースCMSと呼んでいるもの)をブートストラップするのに役立つアンケートが実行されます。
Select a Project to Use: Create new project HTML CMS Use the default dataset configuration? Y // this creates a "Production" dataset Project output path: studio // or whatever directory you'd like this to live in Select project template Clean project with no predefined schemas
この手順では、Sanityアカウントに新しいプロジェクトとデータセットを作成し、Studioのローカルバージョンを作成して、データとCMSを結び付けます。 デフォルトでは、 studio
ディレクトリはプロジェクトのルートに作成されます。 大規模なプロジェクトでは、これを別のリポジトリとして設定することをお勧めします。 このプロジェクトでは、これを結び付けておくのは問題ありません。
Studioをローカルで実行するには、ディレクトリをstudio
ディレクトリに変更し、 sanity start
を実行します。 これにより、Studioがlocalhost:3333
で実行されます。 ログインすると、「空のスキーマ」があることを知らせる画面が表示されます。 それでは、スキーマを追加します。これにより、データが構造化および編集されます。
正気スキーマの作成
Sanity Studio内でドキュメントとフィールドを作成する方法は、 schemas/schema.js
ファイル内にスキーマを作成することです。
このサイトでは、「詳細について」というスキーマタイプを作成します。 スキーマはHTMLから流れます。 一般に、ほとんどのWebページを単一のリッチテキストフィールドにすることができますが、コンテンツを分離して構造化することをお勧めします。 これにより、将来このデータをどのように使用するかについて、より柔軟に対応できます。
私たちのウェブページには、以下を含む一連のデータが必要です。
- タイトル
- フルネーム
- 伝記(リッチテキスト編集付き)
- 名前とURLが記載されたWebサイトのリスト。
スキーマでこれを定義するには、ドキュメントのオブジェクトを作成し、そのフィールドを定義します。 フィールドtype
が記載されたコンテンツの注釈付きリスト:
- タイトル—文字列
- フルネーム—文字列
- 伝記—「ブロック」の配列
- Webサイトリスト—名前とURL文字列フィールドを持つオブジェクトの配列。
types: schemaTypes.concat([ /* Your types here! */ { title: "About Details", name: "about", type: "document", fields: [ { name: 'title', type: 'string' }, { name: 'fullName', title: 'Full Name', type: 'string' }, { name: 'bio', title: 'Biography', name: 'content', type: 'array', of: [ { type: 'block' } ] }, { name: 'externalLinks', title: 'Social media and external links', type: 'array', of: [ { type: 'object', fields: [ { name: 'text', title: 'Link text', type: 'string' }, { name: 'href', title: 'Link url', type: 'string' } ] } ] } ] } ])
これをスキーマタイプに追加して保存すると、Studioが再コンパイルして最初のドキュメントを表示します。 ここから、新しいドキュメントを作成して情報を入力することにより、コンテンツをCMSに追加します。
再利用可能な方法でコンテンツを構造化する
この時点で、なぜ「フルネーム」と「タイトル」があるのか疑問に思われるかもしれません。 これは、コンテンツが多目的になる可能性があるためです。 タイトルだけに名前を含めるのではなく、名前フィールドを含めることで、そのデータをより有効に活用できます。 次に、このCMSの情報を使用して、履歴書ページまたはPDFを強化することもできます。 伝記の分野は、他のシステムやWebサイトでプログラムで使用できます。 これにより、この特定のサイトの直接の使用例によって指示されるのではなく、このコンテンツの多くについて信頼できる唯一の情報源を持つことができます。
私たちのデータを私たちのプロジェクトに引き込む
APIを介してデータを利用できるようになったので、それをプロジェクトに取り込みましょう。
SanityJavaScriptクライアントをインストールして構成します
まず、Nodeのデータにアクセスする必要があります。 Sanity JavaScriptクライアントを使用して、その接続を確立できます。
npm install @sanity/client
これにより、JavaScriptSDKがフェッチされてインストールされます。 ここから、前に設定したプロジェクトからデータをフェッチするように構成する必要があります。 そのために、 /utils/SanityClient.js
にユーティリティスクリプトを設定します。 SDKにプロジェクトIDとデータセット名を提供し、メインスクリプトで使用する準備が整いました。
const sanityClient = require('@sanity/client'); const client = sanityClient({ projectId: '4fs6x5jg', dataset: 'production', useCdn: true }) module.exports = client;
GROQを使用したデータの取得
index.js
ファイルに戻り、データをフェッチするための新しい関数を作成します。 これを行うには、Sanityのネイティブクエリ言語であるオープンソースのGROQを使用します。
変数にクエリを作成し、クエリに基づいてデータをフェッチするように構成したクライアントを使用します。 この場合、 about
というプロパティを持つオブジェクトを作成します。 このオブジェクトでは、特定のドキュメントのデータを返します。 そのために、ドキュメントの作成時に自動的に生成される_id
に基づいてクエリを実行します。
ドキュメントの_id
を見つけるには、Studioでドキュメントに移動し、URLからコピーするか、「検査」モードに移動してドキュメント上のすべてのデータを表示します。 Inspectに入るには、右上の「kabob」メニューをクリックするか、ショートカットCtrl + Alt + Iを使用します。 このビューには、 _id
を含むこのドキュメントのすべてのデータが一覧表示されます。 Sanityはドキュメントオブジェクトの配列を返すため、簡単にするために、 0th
のエントリを返します。
次に、クエリをSanityクライアントのfetch
メソッドに渡すと、ドキュメント内のすべてのデータのJSONオブジェクトが返されます。 このデモでは、すべてのデータを返すことは大したことではありません。 より大規模な実装の場合、GROQでは、オプションの「プロジェクション」で必要な明示的なフィールドのみを返すことができます。
const client = require('./utils/SanityClient') // at the top of the file // ... async function getSanityData() { const query = `{ "about": *[_id == 'YOUR-ID-HERE'][0] }` let data = await client.fetch(query); }
リッチテキストフィールドのHTMLへの変換
データを返す前に、リッチテキストフィールドで変換を行う必要があります。 多くのCMSはHTMLを直接返すリッチテキストエディタを使用していますが、SanityはPortableTextと呼ばれるオープンソース仕様を使用しています。 Portable Textは、リッチテキストのスタイルとリンク、脚注、その他の注釈などのプロパティに関するすべてのデータを含むオブジェクトの配列(段落やその他のメディアブロックのリストとしてリッチテキストを考えてください)を返します。 これにより、音声アシスタントやネイティブアプリなど、HTMLをサポートしていないシステムでテキストを移動して使用できるようになります。
このユースケースでは、オブジェクトをHTMLに変換する必要があることを意味します。 ポータブルテキストをさまざまな用途に変換するために使用できるNPMモジュールがあります。 この例では、block-content-to-htmlというパッケージを使用します。
npm install @sanity/block-content-to-html
このパッケージは、リッチテキストエディタからのすべてのデフォルトのマークアップをレンダリングします。 各タイプのスタイルは、ユースケースに必要なマークアップに準拠するようにオーバーライドできます。 この場合、パッケージに作業を任せます。
const blocksToHtml = require('@sanity/block-content-to-html'); // Added to the top async function getSanityData() { const query = `{ "about": *[_type == 'about'][0] }` let data = await client.fetch(query); data.about.content = blocksToHtml({ blocks: data.about.content }) return await data }
ハンドルバーでのSanity.ioのコンテンツの使用
データが使用できる形になっているので、これをデータ引数としてbuildHTML
関数に渡します。
async function main(src, dist) { const data = await getSanityData(); const html = buildHTML(src, data) fs.writeFile(dist, html, function (err) { if (err) return console.log(err); console.log('index.html created'); }); }
これで、新しいデータを使用するようにHTMLを変更できます。 テンプレートでより多くの変数呼び出しを使用して、ほとんどのデータをプルします。
リッチテキストcontent
変数をレンダリングするには、変数に中かっこを追加する必要があります。 これにより、HTMLを文字列として表示する代わりに、HTMLをレンダリングするようにハンドルバーに指示されます。
externalLinks
配列では、Handlebarsの組み込みループ機能を使用して、Studioに追加したすべてのリンクを表示する必要があります。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>{{ about.title }}</title> </head> <body> <h1>The personal homepage of {{ about.fullName }}</h1> {{{ about.content }}} <h2>Bryan is on the internet</h2> <ul> {{#each about.externalLinks }} <li><a href="{{ this.href }}">{{ this.text }}</a></li> {{/each}} </ul> </body> </html>
デプロイメントのセットアップ
これをライブにしましょう。 これを機能させるには、2つのコンポーネントが必要です。 まず、ファイルをビルドする静的ホストが必要です。 次に、CMSでコンテンツが変更されたときに、サイトの新しいビルドをトリガーする必要があります。
Netlifyへのデプロイ
ホスティングには、Netlifyを使用します。 Netlifyは静的サイトホストです。 静的アセットを提供しますが、サイトをスムーズに機能させる追加機能があります。 これらには、ノードスクリプトを実行できる組み込みのデプロイメントインフラストラクチャ、ビルドをトリガーするWebhook、およびHTMLページが迅速に提供されるようにするグローバルに分散されたCDNがあります。
Netlifyは、GitHubでリポジトリを監視し、ダッシュボードに追加できるコマンドに基づいてビルドを作成できます。
まず、このコードをGitHubにプッシュする必要があります。 次に、Netlifyのダッシュボードで、新しいリポジトリをNetlifyの新しいサイトに接続する必要があります。
それが接続されたら、プロジェクトのビルド方法をNetlifyに指示する必要があります。 ダッシュボードで、[設定]> [ビルドとデプロイ]> [ビルド設定]に移動します。 この領域では、「Buildコマンド」を「nodeindex.js」に変更し、「Publishdirectory」を「./dist」に変更する必要があります。
Netlifyがサイトを構築すると、コマンドが実行され、リストされているフォルダーでコンテンツが確認され、その中にコンテンツが公開されます。
Webhookのセットアップ
また、誰かがコンテンツを更新したときに新しいバージョンを公開するようにNetlifyに指示する必要があります。 そのために、サイトを再構築する必要があることをNetlifyに通知するWebhookを設定します。 Webhookは、別のサービス(Sanityなど)がプログラムでアクセスして、オリジンサービス(この場合はNetlify)でアクションを作成できるURLです。
Netlifyダッシュボードの[設定]> [ビルドとデプロイ]> [ビルドフック]で特定の「ビルドフック」を設定できます。 フックを追加し、名前を付けて保存します。 これにより、Netlifyでビルドをリモートでトリガーするために使用できるURLが提供されます。
次に、変更を公開するときにこのURLにリクエストを送信するようにSanityに指示する必要があります。
これを実現するためにSanityCLIを使用できます。 /studio
ディレクトリ内で、 sanity hook create
を実行して接続できます。 このコマンドは、名前、データセット、およびURLを要求します。 名前は任意の名前にすることができ、データセットは当社の製品のproduction
環境であり、URLはNetlifyが提供したURLである必要があります。
これで、Studioでコンテンツを公開するたびに、Webサイトが自動的に更新されます。 フレームワークは必要ありません。
- コードはこのGitHubリポジトリにあります→
次のステップ
これは、独自のツールを作成するときに実行できることの非常に小さな例です。 ほとんどのプロジェクトでは、よりフル機能のSSGが必要になる場合がありますが、独自のミニSSGを作成すると、選択したジェネレーターで何が起こっているかを理解するのに役立ちます。
- このサイトは1ページしか公開していませんが、ビルドスクリプトに少し余分なものがあれば、さらに多くのページを公開することができます。 ブログ投稿を公開することもできます。
- 「開発者の経験」は、リポジトリに少し欠けています。 Nodemonのようなパッケージを実装するか、BrowserSyncのような「ホットリロード」を追加することで、任意のファイル保存でNodeスクリプトを実行できます。
- Sanityに存在するデータは、複数のサイトやサービスに電力を供給することができます。 これを使用して、Webページの代わりにPDFを公開する履歴書サイトを作成できます。
- CSSを追加して、これを実際のサイトのように見せることができます。