Hugo 정적 사이트 생성기의 컨텍스트 및 변수
게시 됨: 2022-03-10이 기사에서는 Hugo 정적 사이트 생성기에서 컨텍스트가 어떻게 작동하는지 자세히 살펴보겠습니다. 데이터가 콘텐츠에서 템플릿으로 어떻게 흐르는지, 특정 구성이 사용 가능한 데이터를 어떻게 변경하는지, 이 데이터를 부분 및 기본 템플릿으로 전달할 수 있는지 조사할 것입니다.
이 기사는 Hugo에 대한 소개가 아닙니다 . Hugo에 대한 경험이 있는 경우 모든 개념을 처음부터 검토하지 않고 컨텍스트 및 변수의 주요 주제에 초점을 맞추므로 최대한 활용하게 될 것입니다. 하지만 Hugo 문서를 전체적으로 참고하시면 경험이 없어도 잘 따라하실 수 있습니다!
예제 페이지를 구성하여 다양한 개념을 학습합니다. 예제 사이트에 필요한 모든 단일 파일을 자세히 다루지는 않겠지만 전체 프로젝트는 GitHub에서 사용할 수 있습니다. 조각이 어떻게 서로 맞는지 이해하고 싶다면 좋은 출발점입니다. 또한 Hugo 사이트를 설정 하거나 개발 서버를 실행하는 방법은 다루지 않습니다. 예제 실행 지침은 저장소에 있습니다.
정적 사이트 생성기란 무엇입니까?
정적 사이트 생성기의 개념이 처음인 경우 여기에 간략한 소개가 있습니다! 정적 사이트 생성기는 동적 사이트와 비교하여 가장 잘 설명될 수 있습니다. CMS와 같은 동적 사이트는 일반적으로 각 방문에 대해 처음부터 페이지를 구성하며, 아마도 데이터베이스에서 데이터를 가져오고 이를 위해 다양한 템플릿을 결합할 수 있습니다. 실제로 캐싱을 사용한다는 것은 페이지가 그렇게 자주 재생되지 않는다는 것을 의미하지만, 이 비교를 위해 그렇게 생각할 수 있습니다. 동적 사이트는 동적 콘텐츠 에 매우 적합합니다. 자주 변경되는 콘텐츠, 입력에 따라 다양한 구성으로 표시되는 콘텐츠, 사이트 방문자가 조작할 수 있는 콘텐츠입니다.
대조적으로, 많은 사이트는 거의 변경되지 않으며 방문자의 입력을 거의 받지 않습니다. 응용 프로그램에 대한 "도움말" 섹션, 기사 목록 또는 eBook이 그러한 사이트의 예일 수 있습니다. 이 경우 콘텐츠가 변경될 때 최종 페이지 를 한 번 조합한 다음 콘텐츠가 다시 변경될 때까지 모든 방문자에게 동일한 페이지를 제공하는 것이 더 합리적입니다.
동적 사이트는 유연성이 더 높지만 실행 중인 서버에 대한 수요가 더 많습니다. 또한 특히 데이터베이스가 관련된 경우 지리적으로 배포하기 어려울 수 있습니다. 정적 사이트 생성기 는 정적 파일을 전달할 수 있는 모든 서버에서 호스팅할 수 있으며 배포하기 쉽습니다.
이러한 접근 방식을 혼합한 오늘날의 일반적인 솔루션은 JAMstack입니다. "JAM"은 JavaScript, API 및 마크업을 나타내며 JAMstack 애플리케이션의 빌딩 블록을 설명합니다. 정적 사이트 생성기는 클라이언트에 전달하기 위해 정적 파일 을 생성하지만 스택에는 클라이언트에서 실행되는 JavaScript 형태의 동적 구성 요소가 있습니다. 그러면 이 클라이언트 구성 요소는 API를 사용하여 사용자에게 동적 기능을 제공할 수 있습니다.
휴고
Hugo는 널리 사용되는 정적 사이트 생성기입니다. Go로 작성되었으며 Go가 컴파일된 프로그래밍 언어라는 사실은 Hugo의 장점과 단점을 암시합니다. 하나는 Hugo가 매우 빠르기 때문에 정적 사이트를 매우 빠르게 생성한다는 의미입니다. 물론 이것은 Hugo를 사용하여 만든 사이트가 최종 사용자에게 얼마나 빠르거나 느린지와는 관계가 없지만 개발자에게는 Hugo가 눈 깜짝할 사이에 큰 사이트를 컴파일한다는 사실이 상당히 귀중합니다.
그러나 Hugo는 컴파일된 언어로 작성되어 있기 때문에 확장이 어렵습니다 . 일부 다른 사이트 생성기를 사용하면 Ruby, Python 또는 JavaScript와 같은 언어로 된 자체 코드를 컴파일 프로세스에 삽입할 수 있습니다. Hugo를 확장하려면 코드를 Hugo 자체에 추가하고 다시 컴파일해야 합니다 . 그렇지 않으면 Hugo가 기본 제공하는 템플릿 기능을 사용해야 합니다.
다양한 기능을 제공하지만 페이지 생성에 복잡한 논리가 포함되는 경우 이 사실이 제한될 수 있습니다. 우리가 발견한 바와 같이, 원래 개발된 사이트가 동적 플랫폼에서 실행되도록 하면 사용자 정의 코드를 삽입하는 기능을 당연하게 여겼던 경우가 쌓이는 경향이 있습니다.
우리 팀은 주요 제품인 Tower Git 클라이언트와 관련된 다양한 웹 사이트를 유지 관리하고 있으며 최근에 이들 중 일부를 정적 사이트 생성기로 옮기는 방법을 살펴보았습니다. 우리 사이트 중 하나인 "Learn" 사이트는 파일럿 프로젝트에 특히 적합해 보였습니다. 이 사이트에는 동영상, eBook, Git에 대한 FAQ를 비롯한 다양한 무료 학습 자료와 기타 기술 주제가 포함되어 있습니다.
그 콘텐츠는 대체로 정적인 성격을 띠며 뉴스레터 가입과 같은 대화형 기능이 무엇이든 이미 JavaScript로 구동됩니다. 2020년 말에 이 사이트를 이전 CMS에서 Hugo로 변환 했으며 현재는 정적 사이트로 실행됩니다. 당연히 이 과정에서 Hugo에 대해 많은 것을 배웠습니다. 이 기사는 우리가 배운 몇 가지를 공유하는 방법입니다.
우리의 예
이 기사는 페이지를 Hugo로 변환하는 작업에서 비롯되었기 때문에 (매우!) 단순화된 가상의 랜딩 페이지를 예로 든 것이 자연스러워 보입니다. 우리의 주요 초점은 재사용 가능한 소위 "목록" 템플릿입니다.
간단히 말해서, Hugo는 하위 페이지가 포함된 모든 페이지에 목록 템플릿을 사용합니다. Hugos 템플릿 계층 구조에는 그 이상의 것이 있지만 가능한 모든 템플릿을 구현할 필요는 없습니다. 단일 목록 템플릿은 먼 길을 간다. 더 이상 특수화된 템플릿을 사용할 수 없는 목록 템플릿을 요청하는 모든 상황에서 사용됩니다.
잠재적인 사용 사례에는 홈페이지, 블로그 색인 또는 FAQ 목록이 포함됩니다. 재사용 가능한 목록 템플릿 은 프로젝트의 layouts/_default/list.html
에 있습니다. 다시 말하지만, 예제를 컴파일하는 데 필요한 나머지 파일은 GitHub에서 사용할 수 있습니다. GitHub에서 조각이 서로 어떻게 맞는지 더 잘 볼 수도 있습니다. GitHub 리포지토리는 또한 single.html
템플릿과 함께 제공됩니다. 이름에서 알 수 있듯이 이 템플릿은 하위 페이지가 없지만 그 자체로 단일 콘텐츠로 작동하는 페이지에 사용됩니다.
이제 무대를 설정하고 우리가 할 일을 설명했으므로 시작하겠습니다!
컨텍스트 또는 "점"
모든 것은 점으로 시작됩니다. Hugo 템플릿에서 객체 .
— "점" — 현재 컨텍스트를 나타냅니다. 이것은 무엇을 의미 하는가? Hugo에서 렌더링된 모든 템플릿은 데이터 세트( 컨텍스트 )에 액세스할 수 있습니다. 이것은 처음에 콘텐츠와 일부 메타데이터를 포함하여 현재 렌더링되고 있는 페이지를 나타내는 개체로 설정됩니다. 컨텍스트에는 구성 옵션 및 현재 환경에 대한 정보와 같은 사이트 전체 변수도 포함됩니다. .Title
을 .Hugo.Version
사용 중인 Hugo 버전 — 즉, .
구조.
중요한 것은 이 컨텍스트가 변경되어 위의 `.Title`과 같은 참조가 다른 것을 가리키거나 심지어 무효화될 수 있다는 것입니다. 예를 들어 range
를 사용하여 어떤 종류의 컬렉션을 반복하거나 템플릿을 부분 템플릿과 기본 템플릿으로 분할 할 때 이런 일이 발생합니다. 이에 대해서는 나중에 자세히 살펴보도록 하겠습니다!
Hugo는 Go "템플릿" 패키지를 사용하므로 이 기사에서 Hugo 템플릿을 참조할 때 실제로 Go 템플릿에 대해 이야기하는 것입니다. Hugo는 표준 Go 템플릿에서 사용할 수 없는 많은 템플릿 기능을 추가합니다.
제 생각에는 컨텍스트와 그것을 리바인딩할 수 있는 가능성이 Hugos 최고의 기능 중 하나입니다. 나에게 "점"이 특정 지점에서 템플릿의 주요 초점이 되는 모든 대상을 나타내도록 하고 필요에 따라 다시 바인딩하는 것은 매우 의미가 있습니다. 물론, 엉킨 혼란에 빠질 수도 있지만, 내가 본 다른 정적 사이트 생성기에서 빠르게 누락되기 시작했을 정도로 지금까지는 만족했습니다.
이것으로 우리는 우리 프로젝트의 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으로 제공됩니다.
실제로, 이는 콘텐츠 파일이 각 끝에 세 개의 대시를 포함하는 행으로 구분된 섹션으로 시작함을 의미합니다. 이 섹션은 주요 내용 을 구성하고 여기에서 메타데이터는 key: value
구문을 사용하여 정의됩니다(곧 살펴보겠지만 YAML은 보다 정교한 데이터 구조도 지원합니다). 표제 다음에는 Markdown 마크업 언어를 사용하여 지정된 실제 내용이 나옵니다.
예제를 통해 좀 더 구체적으로 살펴보겠습니다. 다음은 하나의 전면 내용 필드와 하나의 콘텐츠 단락이 있는 매우 간단한 콘텐츠 파일입니다.
--- 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
와 같은 사용자 정의 필드는 .Params.author
를 사용하여 액세스합니다.
(함수, 함수 매개변수 및 페이지 변수와 함께 미리 정의된 필드에 대한 빠른 참조는 Hugo 치트 시트를 참조하십시오!)
템플릿의 콘텐츠 파일에서 주요 콘텐츠에 액세스하는 데 사용되는 .Content
변수는 특별합니다. Hugo에는 Markdown 콘텐츠에 몇 가지 추가 태그를 사용할 수 있는 "단축 코드" 기능 이 있습니다. 당신은 또한 당신 자신을 정의할 수 있습니다. 불행히도 이러한 단축 코드는 .Content
변수를 통해서만 작동합니다. Markdown 필터를 통해 다른 데이터를 실행할 수 있지만 콘텐츠의 단축 코드는 처리하지 않습니다.
정의되지 않은 변수에 대한 참고 사항: .Date
와 같은 미리 정의된 필드에 액세스하는 것은 설정하지 않은 경우에도 항상 작동합니다. 이 경우 빈 값이 반환됩니다. .Params.thisHasNotBeenSet
와 같은 정의되지 않은 사용자 정의 필드에 액세스하는 것도 작동하여 빈 값을 반환합니다. 그러나 .thisDoesNotExist
와 같이 사전 정의되지 않은 최상위 필드에 액세스하면 사이트가 컴파일되지 않습니다.
.Params.author
뿐만 아니라 .Hugo.version
및 .Site.title
이전에 표시된 것처럼 연결 호출을 사용하여 다른 데이터 구조에 중첩된 필드에 액세스할 수 있습니다. 우리는 서론에서 그러한 구조를 정의할 수 있습니다. 콘텐츠 파일의 페이지에서 배너에 대한 일부 속성을 지정 하여 지도 또는 사전을 정의하는 예를 살펴보겠습니다. 다음은 업데이트된 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 및 길이가 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의 관점에서 "목록"에는 일부 문자열만 포함됩니다. "범위" 루프의 각 반복에 대해 컨텍스트는 해당 문자열 중 하나에 바인딩됩니다 . 실제 페이지 개체에 액세스하려면 해당 경로 문자열(현재 값은 .
)을 .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
. 이것은 블로그의 색인 페이지이며 홈 페이지와 마찬가지로 주요 콘텐츠를 표시하고 이 경우 블로그 게시물을 몇 개 더 나열합니다.
이제 나열된 콘텐츠 를 홈 페이지에 약간 다르게 표시하고 싶다고 가정해 보겠습니다. 별도의 템플릿을 보증하기에 충분하지 않지만 템플릿 자체의 조건문으로 할 수 있는 것입니다. 예를 들어 홈 페이지를 렌더링하고 있음을 감지하면 나열된 콘텐츠를 단일 열 목록이 아닌 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>
블로그 색인은 콘텐츠가 다르지만 우리 홈페이지와 같습니다.
...하지만 이제 우리 홈 페이지는 그리드에 추천 콘텐츠를 표시합니다.
부분 템플릿
실제 웹 사이트를 구축할 때 템플릿을 여러 부분으로 나누는 것이 빠르게 유용합니다. 템플릿의 특정 부분을 재사용하거나 거대하고 다루기 힘든 템플릿을 일관된 조각으로 분할하고 싶을 수도 있습니다. 이를 위해 Hugo의 부분 템플릿을 사용하는 것이 좋습니다.
컨텍스트 관점에서 여기에서 중요한 것은 부분 템플릿을 포함할 때 사용할 수 있도록 하려는 컨텍스트를 명시적으로 전달한다는 것입니다. 일반적인 관행은 {{ partial "my/partial.html" . }}
{{ partial "my/partial.html" . }}
. 여기의 점이 렌더링되는 페이지를 참조하는 경우 부분적으로 전달됩니다. 컨텍스트가 다른 것으로 리바운드되면 그것이 전달됩니다.
물론 일반 템플릿과 마찬가지로 부분 템플릿에서 컨텍스트를 다시 바인딩할 수 있습니다. 이 경우 전역 컨텍스트 $
는 렌더링되는 기본 페이지가 아니라 부분에 전달된 원래 컨텍스트를 나타냅니다(전달된 내용이 아닌 한).
부분 템플릿이 특정 데이터 조각에 액세스할 수 있도록 하려는 경우 이 부분만 부분 템플릿에 전달하면 문제가 발생할 수 있습니다. 컨텍스트를 다시 바인딩한 후 페이지 메서드에 액세스하는 것과 관련된 이전 문제를 기억하십니까? 부분 도 마찬가지지만 이 경우 전역 컨텍스트가 도움이 되지 않습니다. 예를 들어 문자열을 부분 템플릿에 전달했다면 부분의 전역 컨텍스트는 해당 문자열을 참조하고 우리는 이겼습니다. 페이지 컨텍스트에 정의된 메서드를 호출할 수 없습니다.
이 문제에 대한 해결책은 부분을 포함할 때 둘 이상의 데이터를 전달하는 데 있습니다. 그러나 부분 호출에는 하나의 인수만 제공할 수 있습니다. 그러나 우리는 이 인수를 복합 데이터 유형, 일반적으로 맵(다른 프로그래밍 언어에서는 사전 또는 해시로 알려짐)으로 만들 수 있습니다.
예를 들어 이 맵에서 Page
키를 현재 페이지 개체로 설정하고 다른 키와 함께 사용자 지정 데이터를 전달할 수 있습니다. 그러면 페이지 개체는 부분에서 .Page
로 사용할 수 있고 다른 하나는 맵의 값은 유사하게 액세스됩니다. 맵은 짝수 개의 인수를 취하는 dict
템플릿 함수를 사용하여 생성되며 키, 값, 키, 값 등으로 번갈아 해석됩니다.
예제 템플릿에서 추천 및 나열된 콘텐츠에 대한 코드를 부분으로 이동해 보겠습니다. 추천 콘텐츠의 경우 추천 페이지 개체를 전달하는 것으로 충분합니다. 그러나 나열된 콘텐츠는 렌더링되는 특정 나열된 콘텐츠 외에 .IsHome
메서드에 대한 액세스 권한이 필요합니다. 앞서 언급했듯이 .IsHome
은 나열된 페이지의 페이지 개체에서도 사용할 수 있지만 정답은 아닙니다. 렌더링되는 기본 페이지 가 홈 페이지인지 알고 싶습니다.
대신 .IsHome
을 호출한 결과에 부울 세트를 전달할 수 있지만, 아마도 partial 은 미래에 다른 페이지 메서드에 액세스해야 할 것 입니다. 메인 페이지 객체와 나열된 페이지 객체를 전달하는 방법을 살펴보겠습니다. 이 예에서 기본 페이지는 $
에 있고 나열된 페이지는 에 .
. 따라서 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>
그러나 나열된 콘텐츠에 대한 부분은 원래 페이지 개체가 이제 .Listed
에서 발견되고 나열된 콘텐츠 조각이 .Page
에서 발견된다는 사실을 반영합니다.
<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
문에 의해 지정된 것과 같은 "블록" 내부에 선언 된 사용자 정의 변수는 해당 블록 내부에서만 사용할 수 있습니다. — 블록 외부에서 참조하려면 블록 외부에서 선언한 다음 내부에서 수정해야 합니다. 필요에 따라 차단합니다.
맞춤 변수에는 이름 앞에 달러 기호( $
)가 붙습니다. 변수를 선언하고 동시에 값을 지정하려면 :=
연산자를 사용하십시오. 변수에 대한 후속 할당은 =
연산자를 사용합니다(콜론 제외). 변수는 선언되기 전에는 할당할 수 없으며 값을 주지 않고 선언할 수 없습니다.
사용자 정의 변수에 대한 한 가지 사용 사례는 적절한 이름의 변수에 일부 중간 결과를 할당하여 긴 함수 호출을 단순화하는 것입니다. 예를 들어, 추천 페이지 개체를 $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에 대한 내 경험을 바탕으로 템플릿에서 좀 더 복잡한 로직을 구현하려고 하는 즉시 사용자 정의 변수를 자유롭게 사용하는 것이 좋습니다. 코드를 간결하게 유지하는 것은 자연스러운 일이지만, 이렇게 하면 쉽게 명확하지 않게 되어 나와 다른 사람들을 혼란스럽게 만들 수 있습니다.
대신 각 단계에 대해 설명적으로 명명된 변수 를 사용하고 한 줄에 두 줄(또는 세 줄, 네 줄 등)을 사용하는 것에 대해 걱정하지 마십시오.
.할퀴다
마지막으로 .Scratch
메커니즘을 살펴보겠습니다. 이전 버전의 Hugo에서는 사용자 정의 변수를 한 번만 할당할 수 있었습니다. 사용자 정의 변수를 재정의할 수 없습니다. 요즘에는 사용자 정의 변수를 재정의할 수 있으므로 .Scratch
는 여전히 용도가 있지만 덜 중요합니다.
간단히 말해서 .Scratch
는 사용자 정의 변수와 같은 고유한 변수를 설정하고 수정할 수 있는 스크래치 영역입니다. 사용자 정의 변수와 달리 .Scratch
는 페이지 컨텍스트에 속하므로 해당 컨텍스트를 예를 들어 부분적으로 전달하면 스크래치 변수가 자동으로 함께 가져옵니다.
Set
및 Get
메서드를 호출하여 .Scratch
에서 변수를 설정하고 검색할 수 있습니다. 예를 들어 복합 데이터 유형을 설정하고 업데이트하는 방법은 이보다 더 많지만 여기에서는 이 두 가지 방법으로 충분합니다. Set
은 두 개의 매개변수 를 사용합니다. 키와 설정하려는 데이터의 값입니다. Get
은 검색하려는 데이터의 키 하나만 사용합니다.
이전에 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>
이렇게 하려면 목록의 부분적인 부분도 약간 수정 .Scratch
listed.html
에서 검색되는 동안 원본 페이지 컨텍스트를 이제 "점"으로 사용할 수 있습니다. 나열된 페이지에 대한 액세스를 단순화하기 위해 맞춤 변수를 사용합니다.
<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>
이런 식으로 일을 하는 것에 대한 한 가지 주장은 일관성입니다. .Scratch
를 사용하면 현재 페이지 객체를 부분적으로 항상 전달하는 습관을 들이고 추가 데이터를 스크래치 변수로 추가할 수 있습니다. 그런 다음 부분을 작성하거나 편집할 때마다 .
페이지 개체입니다. 물론 전달된 맵을 사용하여 자신을 위한 규칙을 설정할 수도 있습니다. 예를 들어 항상 페이지 객체를 따라 .Page
로 전송합니다.
결론
컨텍스트 및 데이터와 관련하여 정적 사이트 생성기는 이점과 제한 사항을 모두 제공합니다. 한편으로 모든 페이지 방문에 대해 실행할 때 너무 비효율적인 작업은 페이지가 컴파일될 때 한 번만 실행될 때 완벽할 수 있습니다. 반면에 정적이 대부분인 사이트에서도 네트워크 요청의 일부에 액세스하는 것이 얼마나 자주 유용할지 알면 놀랄 수 있습니다.
예를 들어 정적 사이트에서 쿼리 문자열 매개변수를 처리 하려면 JavaScript 또는 Netlify의 리디렉션과 같은 독점 솔루션에 의존해야 합니다. 여기서 요점은 동적 사이트에서 정적 사이트로의 이동이 이론상 간단하지만 사고 방식의 전환이 필요하다는 것입니다. 처음에는 옛 습관으로 돌아가기 쉽지만 연습하면 완벽해질 것입니다.
이것으로 Hugo 정적 사이트 생성기의 데이터 관리를 살펴보겠습니다. 비록 우리가 그 기능의 좁은 부분에만 초점을 맞추었지만, 포함되었을 수도 있는 우리가 다루지 않은 것들이 분명히 있습니다. 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.