Hugo静的サイトジェネレーターのコンテキストと変数
公開: 2022-03-10この記事では、Hugo静的サイトジェネレーターでコンテキストがどのように機能するかを詳しく見ていきます。 データがコンテンツからテンプレートにどのように流れるか、特定の構成によって使用可能なデータがどのように変更されるか、このデータをパーシャルおよびベーステンプレートに渡す方法を調べます。
この記事はHugoの紹介ではありません。 すべての概念を最初から説明するのではなく、コンテキストと変数のメイントピックに焦点を当てているため、Hugoの経験があれば、おそらくそれを最大限に活用できます。 ただし、全体を通してHugoのドキュメントを参照すると、以前の経験がなくてもフォローできる可能性があります。
サンプルページを作成して、さまざまな概念を学習します。 サンプルサイトに必要なすべてのファイルが詳細に説明されているわけではありませんが、プロジェクト全体はGitHubで入手できます。 ピースがどのように組み合わされているかを理解したい場合は、それが良い出発点です。 また、Hugoサイトをセットアップする方法や開発サーバーを実行する方法については説明しません。例を実行する手順はリポジトリにあります。
静的サイトジェネレーターとは何ですか?
静的サイトジェネレーターの概念が初めての場合は、ここで簡単に紹介します。 静的サイトジェネレーターは、動的サイトと比較することでおそらく最もよく説明されます。 CMSのような動的サイトは、通常、訪問ごとに最初からページを組み立て、データベースからデータをフェッチし、さまざまなテンプレートを組み合わせてそれを行います。 実際には、キャッシュを使用するということは、ページがそれほど頻繁に再生成されないことを意味しますが、この比較の目的のために、そのように考えることができます。 動的サイトは、動的コンテンツ(頻繁に変更されるコンテンツ、入力に応じてさまざまな構成で表示されるコンテンツ、サイト訪問者が操作できるコンテンツ)に最適です。
対照的に、多くのサイトはめったに変更されず、訪問者からの入力をほとんど受け入れません。 アプリケーションの「ヘルプ」セクション、記事のリスト、または電子書籍は、そのようなサイトの例である可能性があります。 この場合、コンテンツが変更されたときに最終ページを1回アセンブルし、その後、コンテンツが再び変更されるまですべての訪問者に同じページを提供する方が理にかなっています。
動的サイトは柔軟性が高くなりますが、実行しているサーバーにより多くの要求が課せられます。 また、特にデータベースが関係している場合は、地理的に分散するのが難しい場合があります。 静的サイトジェネレーターは、静的ファイルを配信できる任意のサーバーでホストでき、簡単に配布できます。
これらのアプローチを組み合わせた今日の一般的なソリューションは、JAMstackです。 「JAM」はJavaScript、API、マークアップの略で、JAMstackアプリケーションの構成要素を表します。静的サイトジェネレーターはクライアントに配信する静的ファイルを生成しますが、スタックにはクライアントで実行されるJavaScriptの形式の動的コンポーネントがあります—このクライアントコンポーネントは、APIを使用して動的な機能をユーザーに提供できます。
ヒューゴ
Hugoは人気のある静的サイトジェネレーターです。 これはGoで書かれており、Goがコンパイルされたプログラミング言語であるという事実は、Hugosの利点と欠点のいくつかを示唆しています。 まず、Hugoは非常に高速です。つまり、静的サイトを非常に高速に生成します。 もちろん、これは、Hugoを使用して作成されたサイトがエンドユーザーにとってどれほど速いか遅いかには関係ありませんが、開発者にとって、Hugoが瞬く間に大きなサイトでさえコンパイルするという事実は非常に価値があります。
ただし、Hugoはコンパイル言語で記述されているため、拡張することは困難です。 他のいくつかのサイトジェネレーターを使用すると、Ruby、Python、JavaScriptなどの言語で独自のコードをコンパイルプロセスに挿入できます。 Hugoを拡張するには、コードをHugo自体に追加して再コンパイルする必要があります。そうしないと、Hugoに付属しているテンプレート関数に固執します。
さまざまな機能を提供しますが、ページの生成に複雑なロジックが含まれる場合、この事実は制限される可能性があります。 私たちが見つけたように、元々開発されたサイトを動的プラットフォームで実行していると、カスタムコードを当然のこととしてドロップする機能を利用した場合が山積みになる傾向があります。
私たちのチームは、主力製品であるTower Gitクライアントに関連するさまざまなWebサイトを維持しており、最近、これらの一部を静的サイトジェネレーターに移行することを検討しました。 私たちのサイトの1つである「Learn」サイトは、パイロットプロジェクトに特に適しているように見えました。 このサイトには、Gitに関するビデオ、電子書籍、FAQだけでなく、その他の技術トピックなど、さまざまな無料の学習資料が含まれています。
そのコンテンツは主に静的な性質のものであり、そこにあるインタラクティブ機能(ニュースレターのサインアップなど)はすでにJavaScriptを利用しています。 2020年の終わりに、このサイトを以前のCMSからHugoに変換し、現在は静的サイトとして実行されています。 当然、この過程でヒューゴについて多くのことを学びました。 この記事は、私たちが学んだことのいくつかを共有する方法です。
私たちの例
この記事は、ページをHugoに変換する作業から生まれたものであるため、例として(非常に!)簡略化された架空のランディングページをまとめるのは自然なことのようです。 私たちの主な焦点は、再利用可能ないわゆる「リスト」テンプレートです。
つまり、Hugoは、サブページを含むすべてのページにリストテンプレートを使用します。 Hugosテンプレート階層にはそれ以上のものがありますが、可能なすべてのテンプレートを実装する必要はありません。 単一のリストテンプレートが大いに役立ちます。 これは、これ以上特殊なテンプレートが利用できないリストテンプレートを必要とするあらゆる状況で使用されます。
考えられる使用例には、ホームページ、ブログインデックス、またはFAQのリストが含まれます。 再利用可能なリストテンプレートは、プロジェクトのlayouts/_default/list.html
にあります。 繰り返しになりますが、この例をコンパイルするために必要な残りのファイルはGitHubで入手できます。ここでは、これらの要素がどのように組み合わされているかを詳しく調べることもできます。 GitHubリポジトリにはsingle.html
テンプレートも付属しています。名前が示すように、このテンプレートはサブページを持たないページに使用されますが、それ自体が単一のコンテンツとして機能します。
ステージを設定し、これから何をするかを説明したので、始めましょう。
コンテキストまたは「ドット」
それはすべてドットから始まります。 Hugoテンプレートでは、オブジェクト.
—「ドット」—現在のコンテキストを指します。 これは何を意味するのでしょうか? Hugoでレンダリングされたすべてのテンプレートは、一連のデータ(そのコンテキスト)にアクセスできます。 これは最初、コンテンツと一部のメタデータを含む、現在レンダリングされているページを表すオブジェクトに設定されます。 コンテキストには、構成オプションや現在の環境に関する情報など、サイト全体の変数も含まれます。 .Titleを使用して現在のページのタイトルなどのフィールドにアクセスし、 .Title
を.Hugo.Version
て使用されているHugoのバージョンにアクセスします。つまり、のフィールドにアクセスします.
構造。
重要なのは、このコンテキストが変更され、上記の `.Title`のような参照が他の何かを指し示したり、無効になったりする可能性があることです。 これは、たとえば、 range
を使用してある種のコレクションをループしたり、テンプレートをパーシャルテンプレートとベーステンプレートに分割したりするときに発生します。 これについては後で詳しく説明します。
HugoはGoの「テンプレート」パッケージを使用しているため、この記事でHugoテンプレートを参照するときは、実際にはGoテンプレートについて話します。 Hugoは、標準のGoテンプレートでは使用できない多くのテンプレート関数を追加します。
私の意見では、コンテキストとそれを再バインドする可能性は、Hugosの最高の機能の1つです。 私にとって、ある時点でテンプレートの主な焦点となるオブジェクトを常に「ドット」で表し、必要に応じて再バインドすることは非常に理にかなっています。 もちろん、絡み合った混乱に陥ることも可能ですが、私が見た他の静的サイトジェネレーターですぐにそれを見逃し始めた程度まで、私はこれまでのところ満足しています。
これで、この例の控えめな開始点、つまりプロジェクトの場所layouts/_default/list.html
にある以下のテンプレートを確認する準備が整いました。
<html> <head> <title>{{ .Title }} | {{ .Site.Title }}</title> <link rel="stylesheet" href="/css/style.css"> </head> <body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> </ul> </nav> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> </body> </html>
テンプレートのほとんどは、スタイルシートリンク、ナビゲーション用のメニュー、およびスタイル設定に使用されるいくつかの追加要素とクラスを備えた、必要最低限のHTML構造で構成されています。 興味深いのは、中括弧の間にあります。これは、Hugoに介入して魔法をかけるように信号を送り、中括弧の間にあるものを、表現を評価し、コンテキストを操作する可能性のある結果に置き換えます。
{{ .Title }}
のとおり、タイトルタグの{{.Title}}は現在のページのタイトルを示し、 {{ .Site.Title }}
はHugo構成で設定されたサイト全体のタイトルを示します。 。 {{ .Title }}
のようなタグは、Hugoにそのタグを現在のコンテキストのフィールドTitle
のコンテンツに置き換えるように指示するだけです。
そのため、テンプレート内のページに属するいくつかのデータにアクセスしました。 このデータはどこから来たのですか? それが次のセクションのトピックです。
コンテンツとフロントマター
コンテキストで使用可能な変数の一部は、Hugoによって自動的に提供されます。 その他は、主にコンテンツファイルで定義されています。 構成ファイル、環境変数、データファイル、さらにはAPIなどの他のデータソースもあります。 この記事では、データのソースとしてのコンテンツファイルに焦点を当てます。
一般に、単一のコンテンツファイルは単一のページを表します。 一般的なコンテンツファイルには、そのページのメインコンテンツだけでなく、タイトルや作成日などのページに関するメタデータも含まれます。 Hugoは、メインコンテンツとメタデータの両方でいくつかの形式をサポートしています。 この記事では、おそらく最も一般的な組み合わせについて説明します。コンテンツは、YAMLフロントマターとしてメタデータを含むファイルでMarkdownとして提供されます。
実際には、これは、コンテンツファイルが、両端に3つのダッシュを含む行で区切られたセクションで始まることを意味します。 このセクションは前書きを構成し、ここでメタデータはkey: value
の構文を使用して定義されます(後で説明するように、YAMLはより複雑なデータ構造もサポートします)。 前書きの後に、Markdownマークアップ言語を使用して指定された実際のコンテンツが続きます。
例を見て、物事をより具体的にしましょう。 これは、1つのフロントマターフィールドと1つの段落のコンテンツを含む非常に単純なコンテンツファイルです。
--- title: Home --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
(このファイルは、プロジェクトのcontent/_index.md
にあり、 _index.md
は、サブページがあるページのコンテンツファイルを示します。ここでも、GitHubリポジトリにより、どのファイルをどこに配置するかが明確になります。)
以前のテンプレートといくつかのスタイルおよび周辺ファイル(すべてGitHubにあります)を使用してレンダリングすると、結果は次のようになります。
コンテンツファイルの前書きのフィールド名が事前に決定されているのか、それとも好きなフィールドを追加できるのか疑問に思われるかもしれません。 答えは「両方」です。 事前定義されたフィールドのリストがありますが、思いついた他のフィールドを追加することもできます。 ただし、これらのフィールドへのアクセスは、テンプレートでは少し異なります。 title
のような事前定義されたフィールドは単に.Titleとしてアクセスされますが、 author
のようなカスタムフィールドは.Title
を使用してアクセスさ.Params.author
。
(関数、関数パラメーター、ページ変数などの事前定義されたフィールドのクイックリファレンスについては、独自のHugoチートシートを参照してください!)
テンプレートのコンテンツファイルからメインコンテンツにアクセスするために使用される.Content
変数は特別です。 Hugoには「ショートコード」機能があり、Markdownコンテンツでいくつかの追加タグを使用できます。 独自に定義することもできます。 残念ながら、これらのショートコードは.Content
変数を介してのみ機能します。Markdownフィルターを介して他のデータを実行することはできますが、コンテンツ内のショートコードは処理されません。
未定義の変数に関する注意: .Date
などの事前定義されたフィールドへのアクセスは、設定していなくても常に機能します。この場合、空の値が返されます。 .Params.thisHasNotBeenSet
などの未定義のカスタムフィールドへのアクセスも機能し、空の値を返します。 ただし、 .thisDoesNotExist
などの事前定義されていないトップレベルフィールドにアクセスすると、サイトがコンパイルされなくなります。
以前の.Hugo.version
、 .Site.title
.Params.author
示されているように、連鎖呼び出しを使用して、他のデータ構造にネストされたフィールドにアクセスできます。 そのような構造を前書きで定義することができます。 コンテンツファイルのページ上のバナーのいくつかのプロパティを指定して、マップまたは辞書を定義する例を見てみましょう。 更新されたcontent/_index.md
は次のとおりです。
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days --- Home page of the Tower Git client. Over 100,000 developers and designers use Tower to be more productive!
次に、上記の方法で.Params
を使用してバナーデータを参照し、テンプレートにバナーを追加しましょう。
<html> ... <body> ... <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> </body> </html>
現在のサイトは次のようになっています。
大丈夫! 現在、問題なくデフォルトコンテキストのフィールドにアクセスしています。 ただし、前述のように、このコンテキストは固定されていませんが、変更される可能性があります。
それがどのように起こるかを見てみましょう。
フロー制御
フロー制御ステートメントはテンプレート言語の重要な部分であり、変数の値に応じてさまざまなことを実行したり、データをループしたりすることができます。 Hugoテンプレートは、条件付きロジックのif/else
、ループのrange
など、予想される構成のセットを提供します。 ここでは、Hugoでのフロー制御全般については説明しませんが(詳細については、ドキュメントを参照してください)、これらのステートメントがコンテキストにどのように影響するかに焦点を当てます。 この場合、最も興味深いステートメントはwith
とrange
です。
with
始めましょう。 このステートメントは、ある式に「空でない」値があるかどうかをチェックし、ある場合は、その式の値を参照するようにコンテキストを再バインドします。 end
タグは、 with
ステートメントの影響が停止し、コンテキストが以前の状態にリバウンドされるポイントを示します。 Hugoのドキュメントでは、空でない値をfalse、0、および長さがゼロの配列、スライス、マップ、または文字列として定義しています。
現在、私たちのリストテンプレートはあまりリストを作成していません。 リストテンプレートが実際にそのサブページのいくつかを何らかの方法で特徴づけることは理にかなっているかもしれません。 これにより、フロー制御ステートメントの例を示す絶好の機会が得られます。
おそらく、ページの上部にいくつかの注目コンテンツを表示したいと思います。 これは、ブログ投稿、ヘルプ記事、レシピなど、どのコンテンツでもかまいません。 今のところ、Towerのサンプルサイトに、その機能、ユースケース、ヘルプページ、ブログページ、および「学習プラットフォーム」ページを強調したページがあるとします。 これらはすべてcontent/
ディレクトリにあります。 ホームページのコンテンツファイルcontent/_index.md
にフィールドを追加することにより、機能するコンテンツを構成します。 次のように、コンテンツディレクトリをルートと想定して、ページはそのパスによって参照されます。
--- title: Home banner: headline: Try Tower For Free! subline: Download our trial to try Tower for 30 days without limitations featured: /features.md ... --- ...
次に、このコンテンツを表示するようにリストテンプレートを変更する必要があります。 Hugoにはテンプレート関数.GetPage
があり、これを使用すると、現在レンダリングしているもの以外のページオブジェクトを参照できます。 どのようにコンテキストを思い出してください、 .
、最初はレンダリングされているページを表すオブジェクトにバインドされていましたか? .GetPage
を使用with
、を使用すると、注目のコンテンツを表示するときにそのページのフィールドを参照して、コンテキストを別のページに一時的に再バインドできます。
<nav> ... </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} </div> </section>
ここで、 with
タグとend
タグの間の{ {{ .Title }}
、 {{ .Summary }}
、および{{ .Permalink }}
は、表示されるメインのフィールドではなく、注目のページのフィールドを参照します。
注目のコンテンツに加えて、さらにいくつかのコンテンツをリストアップしましょう。 注目のコンテンツと同様に、リストされたコンテンツは、ホームページのコンテンツファイルであるcontent/_index.md
で定義されます。 次のように、コンテンツパスのリストをフロントマターに追加します(この場合は、セクションの見出しも指定します)。
--- ... listing_headline: Featured Pages listing: - /help.md - /use-cases.md - /blog/_index.md - /learn.md ---
ブログページに独自のディレクトリと_index.md
ファイルがある理由は、ブログに独自のサブページ(ブログ投稿)があるためです。
このリストをテンプレートに表示するには、 range
を使用します。 当然のことながら、このステートメントはリストをループしますが、コンテキストをリストの各要素に順番に再バインドします。 これは、コンテンツリストにとって非常に便利です。
Hugoの観点からは、「リスト」には一部の文字列しか含まれていないことに注意してください。 「範囲」ループの反復ごとに、コンテキストはそれらの文字列の1つにバインドされます。 実際のページオブジェクトにアクセスするために、そのパス文字列(現在は.
の値)を.GetPage
への引数として指定します。 次に、 with
ステートメントを再度使用して、パス文字列ではなく、リストされたページオブジェクトにコンテキストを再バインドします。 これで、リストされた各ページのコンテンツを順番に表示するのは簡単です。
<aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
この時点でのサイトは次のようになります。
ただし、上記のテンプレートには奇妙なことがあります.GetPage
を呼び出すのではなく、 $.GetPage
を呼び出しています。 .GetPage
が機能しない理由を推測できますか?
表記.GetPage
は、 GetPage
関数が現在のコンテキストのメソッドであることを示します。 確かに、デフォルトのコンテキストにはそのようなメソッドがありますが、先に進んでコンテキストを変更しました! .GetPage
を呼び出すと、コンテキストはそのメソッドを持たない文字列にバインドされます。 これを回避する方法は、次のセクションのトピックです。
グローバルコンテキスト
上記のように、コンテキストが変更された場合がありますが、それでも元のコンテキストにアクセスしたいと思います。 ここでは、元のコンテキストに存在するメソッドを呼び出したいためです。別の一般的な状況は、レンダリングされているメインページのプロパティにアクセスしたい場合です。 問題ありません。これを行う簡単な方法があります。
Hugoテンプレートでは、グローバルコンテキストと呼ばれる$
は、コンテキストの元の値、つまりテンプレート処理が開始されたときのコンテキストを指します。 前のセクションでは、コンテキストを文字列にリバウンドしたにもかかわらず、 .GetPage
メソッドを呼び出すために使用されました。 次に、これを使用して、レンダリングされるページのフィールドにアクセスします。
この記事の冒頭で、リストテンプレートは再利用可能であると述べました。 これまでのところ、これはホームページにのみ使用しており、 content/_index.md
にあるコンテンツファイルをレンダリングしています。 サンプルリポジトリには、このテンプレートを使用してレンダリングされる別のコンテンツファイルがあります: content/blog/_index.md
。 これはブログのインデックスページであり、ホームページと同じように、注目のコンテンツが表示され、さらにいくつか(この場合はブログ投稿)が一覧表示されます。
ここで、リストされたコンテンツをホームページに少し異なる方法で表示したいとします。別のテンプレートを保証するのに十分ではありませんが、テンプレート自体の条件ステートメントを使用して実行できます。 例として、ホームページをレンダリングしていることが検出された場合、リストされたコンテンツを1列のリストではなく2列のグリッドに表示します。
Hugoには、必要な機能を正確に提供するページメソッド.IsHome
が付属しています。 ホームページを表示しているときに個々のコンテンツにクラスを追加して、CSSファイルが残りの作業を行えるようにすることで、プレゼンテーションの実際の変更を処理します。
もちろん、代わりにbody要素またはいくつかの要素を含む要素にクラスを追加することもできますが、それではグローバルコンテキストのデモンストレーションとしては十分ではありません。 リストされたコンテンツのHTMLを作成するまでに、 .
リストされたページを参照しますが、レンダリングされるメインページでIsHome
を呼び出す必要があります。 グローバルな文脈が私たちの助けになります:
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} <article{{ if $.IsHome }} class="home"{{ end }}> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article> {{ end }} {{ end }} </div> </div> </section>
ブログのインデックスは、コンテンツは異なりますが、ホームページと同じように見えます。
…しかし、ホームページに注目のコンテンツがグリッドに表示されるようになりました。
部分的なテンプレート
実際のWebサイトを構築するときは、テンプレートをパーツに分割するとすぐに便利になります。 テンプレートの特定の部分を再利用したい場合や、巨大で扱いにくいテンプレートをまとまりのある部分に分割したい場合があります。 この目的のために、Hugoの部分的なテンプレートが最適です。
コンテキストの観点から、ここで重要なことは、部分的なテンプレートを含めるときに、使用できるようにするコンテキストを明示的に渡すことです。 一般的な方法は、次のように、パーシャルが含まれているときにコンテキストをそのまま渡すことです。 {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
。 ここでのドットがレンダリングされているページを参照している場合、それがパーシャルに渡されます。 コンテキストが他の何かにリバウンドされている場合、それが受け継がれます。
もちろん、通常のテンプレートと同じように、部分的なテンプレートでコンテキストを再バインドできます。 この場合、グローバルコンテキスト$
は、レンダリングされるメインページではなく、パーシャルに渡される元のコンテキストを参照します(渡されたものでない限り)。
パーシャルテンプレートが特定のデータにアクセスできるようにしたい場合、これだけをパーシャルに渡すと問題が発生する可能性があります。 コンテキストを再バインドした後、ページメソッドにアクセスする際の問題を以前に思い出してください。 パーシャルについても同じことが言えますが、この場合、グローバルコンテキストは役に立ちません。たとえば、文字列をパーシャルテンプレートに渡した場合、パーシャルのグローバルコンテキストはその文字列を参照し、勝ちました。ページコンテキストで定義されたメソッドを呼び出すことができません。
この問題の解決策は、パーシャルを含めるときに複数のデータを渡すことです。 ただし、部分呼び出しに指定できる引数は1つだけです。 ただし、この引数を複合データ型、通常はマップ(他のプログラミング言語では辞書またはハッシュとして知られています)にすることはできます。
このマップでは、たとえば、現在のページオブジェクトにPage
キーを設定し、カスタムデータを渡すための他のキーを設定できます。ページオブジェクトは、部分的に.Page
として使用でき、他のマップの値も同様にアクセスされます。 マップは、 dict
テンプレート関数を使用して作成されます。この関数は、偶数の引数を取り、キー、その値、キー、その値などとして交互に解釈されます。
サンプルテンプレートでは、注目コンテンツとリストコンテンツのコードをパーシャルに移動してみましょう。 注目のコンテンツについては、注目のページオブジェクトを渡すだけで十分です。 ただし、リストされたコンテンツは、レンダリングされる特定のリストされたコンテンツに加えて、 .IsHome
メソッドにアクセスする必要があります。 前述のように、リストされたページのページオブジェクトでも.IsHome
を使用できますが、正しい答えは得られません。レンダリングされるメインページがホームページであるかどうかを知りたいのです。
代わりに、 .IsHome
を呼び出した結果にブールセットを渡すこともできますが、将来的には部分的に他のページメソッドにアクセスする必要があります。メインページオブジェクトとリストされたページオブジェクトを渡します。 この例では、メインページは$
にあり、リストされているページはにあり.
。 したがって、 listed
たパーシャルに渡されたマップでは、キーPage
は値$
を取得し、キー「Listed」は値を取得します.
。 これは更新されたメインテンプレートです。
<body> <nav> <a class="logo" href="{{ "/" | relURL }}"> <img src="/img/tower-logo.svg"> <img src="/img/tower-claim.svg"> </a> <ul> <li><a href="/">Home</a></li> <li><a href="/blog/">Blog</a></li> </ul> </nav> <section class="featured"> <div class="container"> {{ with .GetPage .Params.featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> <div class="container"> <h1>{{ .Title }}</h1> {{ .Content }} </div> </section> <aside> <h2>{{ .Params.banner.headline }}</h2> <p>{{ .Params.banner.subline}}</p> </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ partial "partials/listed.html" (dict "Page" $ "Listed" .) }} {{ end }} {{ end }} </div> </div> </section> </body>
「注目の」パーシャルのコンテンツは、リストテンプレートの一部であった場合と比較して変更されません。
<article> <h2>{{ .Title }}</h2> {{ .Summary }} <p><a href="{{ .Permalink }}">Read more →</a></p> </article>
ただし、リストされたコンテンツの部分は、元のページオブジェクトが.Page
にあり、リストされたコンテンツが.Listed
にあるという事実を反映しています。
<article{{ if .Page.IsHome }} class="home"{{ end }}> <h2>{{ .Listed.Title }}</h2> {{ .Listed.Summary }} <p><a href="{{ .Listed.Permalink }}">Read more →</a></p> </article>
Hugoは、サブテンプレートを含めるのではなく、共通のベーステンプレートを拡張できるベーステンプレート機能も提供します。 この場合、コンテキストは同様に機能します。ベーステンプレートを拡張するときに、そのテンプレートの元のコンテキストを構成するデータを提供します。
カスタム変数
Hugoテンプレートで独自のカスタム変数を割り当てたり再割り当てしたりすることもできます。 これらは、宣言されたテンプレートで使用できますが、明示的に渡さない限り、パーシャルまたはベーステンプレートには入りません。 if
ステートメントで指定されたような「ブロック」内で宣言されたカスタム変数は、そのブロック内でのみ使用できます。ブロック外で参照する場合は、ブロック外で宣言してから、ブロック内で変更する必要があります。必要に応じてブロックします。
カスタム変数の名前の前にはドル記号( $
)が付いています。 変数を宣言し、同時に値を指定するには、 :=
演算子を使用します。 変数への後続の代入では、 =
演算子(コロンなし)を使用します。 変数は宣言される前に割り当てることはできず、値を指定せずに宣言することはできません。
カスタム変数のユースケースの1つは、適切な名前の変数に中間結果を割り当てることにより、長い関数呼び出しを単純化することです。 たとえば、注目のページオブジェクトを$featured
という名前の変数に割り当ててから、この変数をwith
ステートメントに指定できます。 「リストされた」パーシャルに提供するデータを変数に入れて、パーシャルコールに渡すこともできます。
これらの変更を加えたテンプレートは次のようになります。
<section class="featured"> <div class="container"> {{ $featured := .GetPage .Params.featured }} {{ with $featured }} {{ partial "partials/featured.html" . }} {{ end }} </div> </section> <section class="content"> ... </section> <aside> ... </aside> <section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $context := (dict "Page" $ "Listed" .) }} {{ partial "partials/listed.html" $context }} {{ end }} {{ end }} </div> </div> </section>
Hugoでの私の経験に基づいて、テンプレートにもっと複雑なロジックを実装しようとしたらすぐに、カスタム変数を自由に使用することをお勧めします。 コードを簡潔に保つのは自然なことですが、これにより、物事が簡単にわかりにくくなり、あなたや他の人を混乱させる可能性があります。
代わりに、各ステップにわかりやすい名前の変数を使用し、2行(または3行、4行など)を使用することを心配する必要はありません。
。傷
最後に、 .Scratch
メカニズムについて説明します。 以前のバージョンのHugoでは、カスタム変数は1回しか割り当てることができませんでした。 カスタム変数を再定義することはできませんでした。 現在、カスタム変数を再定義できるため、 .Scratch
の重要性は低くなりますが、まだ用途はあります。
つまり、 .Scratch
は、カスタム変数などの独自の変数を設定および変更できるスクラッチ領域です。 カスタム変数とは異なり、 .Scratch
はページコンテキストに属しているため、たとえば、そのコンテキストをパーシャルに渡すと、スクラッチ変数が自動的に一緒に表示されます。
メソッドSet
およびGet
を呼び出すことにより、 .Scratch
で変数を設定および取得できます。 たとえば、複合データ型を設定および更新する方法は、これらよりも多くありますが、ここでのニーズには、これら2つの方法で十分です。 Set
は、設定するデータのキーと値の2つのパラメーターを取ります。 Get
は、取得するデータのキーを1つだけ取得します。
以前は、 dict
を使用してマップデータ構造を作成し、複数のデータをパーシャルに渡しました。 これは、リストされたページのパーシャルが元のページコンテキストと特定のリストされたページオブジェクトの両方にアクセスできるようにするために行われました。 .Scratch
を使用することは、必ずしもこれを行うための良い方法または悪い方法ではありません。どちらが好ましいかは、状況によって異なります。
dict
の代わりに.Scratch
を使用してデータをパーシャルに渡すリストテンプレートがどのようになるかを見てみましょう。 $.Scratch.Get
(ここでもグローバルコンテキストを使用)を呼び出して、スクラッチ変数「listed」をに設定し.
—この場合、リストされたページオブジェクト。 次に、ページオブジェクト$
だけをパーシャルに渡します。 スクラッチ変数は自動的に続きます。
<section class="listing"> <div class="container"> <h1>{{ .Params.listing_headline }}</h1> <div> {{ range .Params.listing }} {{ with $.GetPage . }} {{ $.Scratch.Set "listed" . }} {{ partial "partials/listed.html" $ }} {{ end }} {{ end }} </div> </div> </section>
これには、 listed.html
パーシャルにもいくつかの変更が必要になります。リストされたページが.Scratch
オブジェクトから取得されている間、元のページコンテキストが「ドット」として使用できるようになりました。 リストされたページへのアクセスを簡素化するために、カスタム変数を使用します。
<article{{ if .IsHome }} class="home"{{ end }}> {{ $listed := .Scratch.Get "listed" }} <h2>{{ $listed.Title }}</h2> {{ $listed.Summary }} <p><a href="{{ $listed.Permalink }}">Read more →</a></p> </article>
この方法で物事を行うための1つの議論は、一貫性です。 .Scratch
を使用すると、常に現在のページオブジェクトをパーシャルに渡し、スクラッチ変数として余分なデータを追加する習慣をつけることができます。 そうすれば、パーシャルを書いたり編集したりするときはいつでも、それを知ってい.
ページオブジェクトです。 もちろん、渡されたマップを使用して独自の規則を確立することもできます。たとえば、常にページオブジェクトを.Page
として送信します。
結論
コンテキストとデータに関しては、静的サイトジェネレーターは利点と制限の両方をもたらします。 一方では、ページへのアクセスごとに実行すると非効率的すぎる操作は、ページがコンパイルされるときに1回だけ実行すると完全に良い場合があります。 一方、主に静的なサイトであっても、ネットワーク要求の一部にアクセスできると便利な場合があります。
たとえば、静的サイトでクエリ文字列パラメータを処理するには、JavaScriptまたはNetlifyのリダイレクトなどの独自のソリューションを使用する必要があります。 ここでのポイントは、動的サイトから静的サイトへのジャンプは理論的には単純ですが、考え方が変わるということです。 最初は、古い習慣に頼るのは簡単ですが、練習すれば完璧になります。
以上で、Hugo静的サイトジェネレーターでのデータ管理について説明します。 Even though we focused only on a narrow sector of its functionality, there are certainly things we didn't cover that could have been included. Nevertheless, I hope this article gave you some added insight into how data flows from content files, to templates, to subtemplates and how it can be modified along the way.
Note : If you already have some Hugo experience, we have a nice resource for you, quite appropriately residing on our aforementioned, Hugo-driven “Learn” site! When you just need to check the order of the arguments to the replaceRE
function, how to retrieve the next page in a section, or what the “expiration date” front matter field is called, a cheat sheet comes in handy. We've put together just such a reference, so download a Hugo cheat sheet, in a package also featuring a host of other cheat sheets on everything from Git to the Visual Studio Code editor.
参考文献
If you're looking for more information on Hugo, here are some nice resources:
- The official Hugo documentation is always a good place to start!
- A great series of in-depth posts on Hugo on Regis Philibert's blog.