Jekyll & Liquid 시작하기 (1)

Updated:

본 내용은 Learn Jekyll & CloudCannon의 Getting started with Jekyll (16 tutorial)의 내용을 공부하여 나의 방식대로 다시 정리한 것이다. Learn Jekyll & CloudCannon 튜토리얼은 liquid를 빠르게 이해할 수 있도록 예제 code와 함께 jekyll serve 명령으로 어떤 동작을 하는지 확인하면서 공부할 수 있다.

여기서는 CloudCannon 튜토리얼을 기반으로 liquid에 관련된 간단한 요약만을 정리한다. 나중에 사용법을 잊어버렸을 때 기억하기 위함이다.

주요 관련 사이트 링크:

1. Jekyll 구동하기

_site/ 폴더에 build 및 localhost:4000으로 server 시작

$ jekyll serve

_site/ 폴더에 build 만 하고 싶을 때

$ jekyll build

jekyll은 아래 표와 같은 build를 제어하는 여러 runtime flags를 가지고 있고 대부분은 _config.yml 파일에 넣어서 정의해 둘 수 있다. 더 자세한 설정 정보는 configuration documentation을 참고하라.

Setting Flags
Site Source
jekyll이 읽어서 처리할 source를 경로로 지정. 디폴트는 현재폴더
-s, –source DIR
Site Destination
jekyll이 build한 결과 파일을 지정. 디폴트는 _site/
-d, –destination DIR
Safe
custom plugins을 사용하지 못하게 하고 symblic links를 무시
–safe
Regeneration
파일이 수정되면 자동으로 site를 재생성
-w, –[no-]watch
Configuration
_config.yml 대신 지정한 파일을 설정파일로 사용.
나중에 지정한 파일이 앞에 지정한 파일보다 우선
–config FILE1[,FILE2,…]
Drafts
draft 포스트를 처리하고 렌더링
–drafts
Environment
build에서 특정 환경값을 사용
JEKYLL_ENV=production
Future
Publish posts or collection documents with a future date.
–future
LSI
Produce an index for related posts.
–lsi
Limit Posts
Limit the number of posts to parse and publish.
–limit_posts NUM
Force polling
Force watch to use polling.
–force_polling
Verbose output
Print verbose output.
-V, –verbose
Silence Output
Silence the normal output from Jekyll during a build.
-q, –quiet
Incremental build
site가 커서 build가 오래걸릴 경우에 사용하면 변경된 포스트와 페이지만 build하기 때문에 build 속도가 개선. 그러나 어떤 경우에 site 생성에 문제 발생
-I, –incremental

2. 파일 구조

jekyll site에서 사용하는 파일 구조에 대해서 살펴 본다.

_config.yml

jekyll site 설정 파일이다. 주로 다음과 같은 목적을 담당한다.

  • site global variables 설정
  • collections 정의 또는 defaults 설정
  • runtime variables 지정

_drafts/

live site에 게시하지 않는 포스트를 작성할 수 있다. 주로 작성 중인 포스트를 쓰는데 사용

_includes/

사이트의 여러 페이지를 만들때 자주 중복되는 코드를 _includes/ 폴더에 정의해 두고 include 해서 쓸 수 있다. 예를 들어 사이트의 header.html, footer.html 을 _includes 폴더에 정의해 두고 여러 페이지를 만들 때 include 할 수 있다.

_layouts/

어떤 포스트를 markdown으로 작성 할 때 그 포스트의 Front matter(머리말)에 layout을 지정하게 된다. layout은 포스트가 jekyll build에 의해 html로 변환되어 _site/ 폴더로 옴겨질 때 그 포스트의 content (내용)를 감싸는 template에 해당한다. 특정 layout 에는 header, footer, navigation 등을 구현하는 코드들이 들어 있다.

_posts/

markdown으로 작성하는 포스트가 위치하는 곳

_data/

jekyll이 site를 build 할 때 이 폴더 안의 YAML, JSON, CSV 형식의 파일 데이터는 다른 여러 페이지에서 liquid 형식으로 참조할 수 있다.

_site/

jekyll이 build하는 모든 결과 파일들과 resouce 파일들이 위치하는 곳

.jekyll-metadata

jekyll이 incremental build에 사용하기 위해 어떤 파일이 변경되었는가 추적하는데 사용하는 파일

Other Files/Folders

site 폴더 내에 있는 Front matter를 가지는 모든 파일들은 jekyll이 적절히 build를 하여 _site/ 폴더에 html 형태로 복사한다. Front matter가 없는 파일(예를 들어 CSS, JavaScript, 이미지, 또는 다른 여러 resouce 파일들)은 변경없이 똑같은 폴더트리를 유지한 채 _site/ 폴더로 복사한다.

3. Liquid 소개

liquid는 사이트의 페이지 처리를 위해 jekyll이 내부적으로 사용하는 templating language이다. liquid를 사용하면 페이지를 만드는데 변수를 사용할 수 있고 페이지 안에서 if 같은 선택로직이나 loop 같은 반복문을 사용할 수 있다.

두 가지 방식의 liquid tag가 있다.

  • 변수 출력: {{ variable }}
  • 로직 구현: {% if statement %}

변수 출력

빈 폴더를 만든다. 이제 이 폴더를 <site root>라고 하자. <site root>ex3-1.html을 만들고 아래 내용을 작성한다. ---로 감싸져 있는 부분은 front matter (머리말) 라고 한다. front matter는 YAML로 정의한다.

---
title: Introduction to Liquid
---
<h1>{{ page.title }}</h1>

터미널을 이용하여 아래와 같이 <site root>에서 jekyll build를 하면 _site/ 폴더가 생성되고, 그 안에 ex3-1.html이 생성되며 그 내용은 “<h1>Introduction to Liquid</h1>” 임을 알 수 있다.

$ jekyll build
...(빌드 메세지 생략)...
$ cat  _site/ex3-1.html
<h1>Introduction to Liquid</h1>

jekyll은 jekyll build 에 의해 <site root> 안에 front matter를 가지는 모든 파일을 적절히 변환하여 _site/ 폴더에 <filename>.html 을 만든다. 그 때 front matter에 정의된 var: valuepage.var 형식으로 접근할 수 있고 {{ page.var }}형식으로 출력할 수 있다. 여기서 page.는 현재 페이지 안에 정의된 것을 가리킨다. (_config.yml에 정의하는 global 변수는 site.으로 참조한다.)

따라서 <site root>/ex3-1.html{{ page.title }}build 시에 Introduction to Liquid로 치환되어 static page <site root>/_site/ex3-1.html로 저장된다.

이제 <site root>/_site/폴더를 삭제하고 <site root>/ex3-1.htmlex3-1.md로 바꾸고 안에 내용을 아래와 같이 변경한다.

---
title: Introduction to Liquid
---
# {{ page.title }}

jekyll build를 해보자. 그러면 아래와 같이 _site/ex3-1.html파일이 생성되고 id속성이 붙여진 것을 제외하면 이전 예제와 동일한 결과인 .html 파일이 생성된다.

$ jekyll build
$ cat _site/ex3-1.html
<h1 id="introduction-to-liquid">Introduction to Liquid</h1>

jekyll은 <site root>안에 있는 모든 front matter를 가지는 파일을 추적하고 markdown 파일은 markdown 변환기에 의해 htmlbuild 한다.

이번에는 <site root>/ex3-1.md의 내용에서 front matter를 지우고 아래와 같이 만들어 둔다.

# {{ page.title }}

그러면 아래와 같이 _site/에는 ex3-1.md만 있고 그 내용은 <site root>/ex3-1.md과 동일하다.

$ jekyll build
$ ls _site/
ex3-1.md

$ cat ex3-1.md
# {{ page.title }}

front matter가 없기 때문에 jekyll은 그냥 그대로 파일을 _site/ 폴더로 복사한다.

필터 (Filters)

필터를 이용하면 변수의 값 출력을 조정할 수 있다. 필터는 파이프(|)를 이용한다. 아래와 같이 <site root>/ex3-2.html 를 작성한다.

---
title: Introduction to Liquid
---
<h1>{{ page.title }}</h1>
<h1>{{ page.title | upcase }}</h1>
<h1>{{ page.title | upcase | truncate: 8 }}</h1>

build 한 후 <site root>/_site/ex3-2.html은 아래와 같다.

<h1>Introduction to Liquid</h1>
<h1>INTRODUCTION TO LIQUID</h1>
<h1>INTRO...</h1>

필터에 대한 더 많은 자료는 Liquid를 참고할 것.

선택(if)

if를 사용하는 형식은 아래와 같다. <site root>/ex3-3.html

---
title: Introduction to Liquid
format: 2
---
{% if page.format == 1 %}
  <h1>{{ page.title }}</h1>
{% elsif page.format == 2 %}
  <h1>{{ page.title | upcase }}</h1>
{% else %}
  <h1>{{ page.title | upcase | truncate: 8 }}</h1>
{% endif %}

build 결과 <site root>/_site/ex3-3.html 아래와 같다.


  <h1>INTRODUCTION TO LIQUID</h1>

주의: 결과의 빈 줄은 아마 jekyll은 build 이후에 해당되는 로직 라인 줄을 삭제하는게 아니라 빈줄(줄넘김)로 남겨 두는 것 같다. 나중에 알게된 건데 빈줄을 제거하는 방법도 있다. Whitespace control 참고

반복(for, array)

아래 코드에서 front matter의 arr은 array이며 그 항목은 “a”, “b”, “c” 이다. for를 이용해서 항목을 하나씩 꺼낼 수 있다.

---
title: Introduction to Liquid
arr:
  - a
  - b
  - c
---
<h1>page.title</h1>
<ul>
{% for item in page.arr %}
  <li>{{ item }}</li>
{% endfor %}
</ul>

build 결과는 다음과 같다.

<h1>page.title</h1>
<ul>

  <li>a</li>

  <li>b</li>

  <li>c</li>

</ul>

주의 : 역시 해당 되는 로직 라인이 3번 반복 되고 마지막 endfor 포함해서 네칸이 빈 줄로 남겨져 있는 것 같다.

4. 머리말 (Front matter)

여기 부분의 CloudCannon 내용은 이미 전 단계와 많이 겹치기 때문에 생략하고, object를 정의하는 것만 작성한다.

Objects

array와 object를 잘 구분하자.

---
fruit:
  - name: apple
    cost: $1
    color: red
  - name: banana
    cost: $2
    color: yellow
  - name: orange
    cost: $1.50
    color: orange
---
<ul>
{% for item in page.fruit %}
  <li>{{ item.name }}, {{ item.cost }}, {{ item.color }}</li>
{% endfor %}
</ul>

build 후 결과.

<ul>

  <li>apple, $1, red</li>

  <li>banana, $2, yellow</li>

  <li>orange, $1.50, orange</li>

</ul>

5. 레이아웃 (layouts)

layout, {{ content }}

<site root>_layouts/ 폴더를 만들고 안에 default.html 파일을 만든 후 아래와 같이 입력한다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  {{ content }}
</body>
</html>

<site root>/index.html 을 만들고 아래와 같이 내용을 입력한다.

---
layout: default
---
<h1>Introduction to layouts</h1>

build 한 후, <site root>/_site/index.html은 다음과 같다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <h1>Introduction to layouts</h1>
</body>
</html>

jekyll은 <site root>/index.htmlbuild할 때 front matter안의 특수한 변수인 layout의 값에 해당하는 layout을 _layouts/ 폴더에서 찾고, front matter 아래쪽의 content(내용)을 layout의 {{ content }}에 치환한 후 <site root>/_site/index.html 로 위치시킨다.

layout 상속

위와 같은 원리로 _layout/ 폴더에 page.html 을 추가하고 내용을 다음과 같이 입력한다.

---
layout: default
---
<h2>start page layout</h2>
{{ content }}

<site root>/index.html의 layout을 아래와 같이 page로 수정한다.

---
layout: page
---
<h1>Introduction to layouts</h1>

build 한 후, <site root>/_site/index.html의 내용은 아래와 같다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <h2>start page layout</h2>
<h1>Introduction to layouts</h1>
</body>
</html>

_layout/page.html의 내용은 _layout/default.html{{ content }}로 주입되고, <site root>/index.html내용은 layout을 page.html로 했기 때문에 page.html{{ content }}로 주입된 것이다.

page.title

_layout/default.html<title> 태그의 내용을 {{ page.title }}로 수정한다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>{{ page.title }}</title>
</head>
<body>
  {{ content }}
</body>
</html>

<site root>/index.html의 front matter에 아래와 같이 title을 추가해 준다.

---
layout: page
title: My Homepage
---
<h1>Introduction to layouts</h1>

build 한 후, <site root>/_site/index.html의 내용은 아래와 같다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>My Homepage</title>
</head>
<body>
  <h2>start page layout</h2>
<h1>Introduction to layouts</h1>
</body>
</html>

jekyll 인터프리터 작업순서

위와 같은 결과가 왜 나오는지 처음에 의아했는데 jekyll 인터프리터 작업순서를 읽으면 어느정도 이해되는 것 같기도? 내가 이해한 대로 순서를 요약하면 다음과 같다.

  1. 모든 파일을 훑으면서 site, page, post, collection 객체의 값을 채운다. 값을 먼저!
  2. front matter가 있는 모든 파일의 liquid 서식 처리: {% %}, {{ variable }} 같은.
  3. front matter가 있는 markdown을 html 로 변환
  4. layout 적용 및 {{ content }} 삽입
  5. 디렉토리 구조에 맞게 각 생성 컨텐츠를 _site/ 로 저장

위 인터프리터 작업순서에 따라 이전 단계의 index.html에서 일어난 일을 뇌내망상으로 생각해 본다.

  1. jekyll은 build가 필요한 파일을 반복적으로 처리한다. 어느순간 <site root>/index.html을 선택한다.
  2. <site root>/index.htmlpage.title 값이 치환될 가능성이 있는 모든 파일에 대해서 훑고 순환추적하다가_layout/default.htmlpage.title 값을 채운다. 그 밖에 모든 변수와 객체값도 채운다.
  3. {{ if }}, {{ for }}, {{ variable }} 등의 liquid 서식 처리를 한다.
  4. layout을 적용하고 {{ content }}삽입을 처리한다.
  5. _site/index.html 로 저장한다.

같은 방식으로 jekyll 인터프리터 작업순서에 잘못된 설정으로 발생하는 문제점을 이해하도록 생각해보자.

Jekyll 3 vs Jekyll 2

현재 사용하고 있는 jekyll version은 3.8.5 이다. jekyll 2와 jekyll 3에 대해 _layout/ 안에 있는 layout 파일들의 front matter에 정의된 변수를 참조 할 때 방식이 다르다. 예를 들어, _layout/page.html의 front matter가 다음과 같다고 가정하자.

---
layout: default
city: San Francisco
---

jekyll 2에서는 아래와 같이 page.으로 참조 했다고 한다.

{{ page.city }}

jekyll 3에서는 layout.으로 참조한다고 한다.

{{ layout.city }}

생각해 보니 jekyll2의 방식은 특정 page에 city 변수가 있을 때 충돌의 위험이 있을 것 같다.

아래와 같이 실제 테스트를 해 보았다. 우선 _layout/page.html을 아래와 같이 수정한다.

---
layout: default
city: San Francisco
---
<h2>start page layout</h2>
{{ content }}
in page: page.city   -> {{ page.city }}
in page: layout.city -> {{ layout.city }}

그리고 <site root>/index.html을 아래와 같이 수정한다.

---
layout: page
title: My Homepage
city: New York
---
<h1>Introduction to layouts</h1>
in index: page.city   -> {{ page.city }}
in index: layout.city -> {{ layout.city }}

build 후, <site root>/_site/index.html은 다음과 같다.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>My Homepage</title>
</head>
<body>
  <h2>start page layout</h2>
<h1>Introduction to layouts</h1>
in index: page.city   -> New York
in index: layout.city -> San Francisco
in page: page.city   -> New York
in page: layout.city -> San Francisco
</body>
</html>

결과를 확인해 보니 생각한 대로 결과가 나왔다. index.htmlbuild 에서 자신이 사용한 layoutcity 변수값을 layout.city 잘 가져왔다.

그런데 여기서 한 가지 의문이 더 생겼는데 _layouts/page.html의 layout은 _layouts/default.html인데 _layouts/page.html 안에서 사용한 layout.city_layouts/default.html의 front matter에 정의된 city 여야 하지 않을까? 즉, 정의가 안되어 있기 때문에 in page: layout.city -> San Francisco처럼 출력이 되는게 아니라 in page: layout.city ->처럼 아무 내용도 출력이 되지 말아야 하는 것 아닌가?

결과를 보니 이런 규칙이 아닌가 추측해 본다.

  • _layouts/에 있는 layout 파일들 안에서 layout.city를 사용할 때 layout.은 자신을 가리킨다.
  • 명시적으로 렌더링되어 _site/ 안에 생성되는 <site root>/index.html 같은 파일에서 layout.city를 사용하면 _layouts/ 폴더에 있는 layout을 가리킨다.

아니면 이런 규칙인가?

  • index.html 처럼 build가 되어 _site/ 에 위치하는 page 관점에서 볼 때, 인터프리터 작업순서 1.에서 처럼 값이 먼저 채워지고 따라서 이 시점에서 index.html에 관련된 layout.<var>, page.<var>는 항상 고정돼 있다. 그리고 나중에 layout 처리가 되니까 문제가 없을 것 같다.

두 번째 규칙이 조금 더 타당해 보이는데? 어쨌든 어떤 동작을 하는지 결과가 나오는지 잘 숙지하고 사용하자.

Leave a comment