Node.js | EJS 템플릿 엔진

npm으로 EJS 설치

이전 HTML 파일을 읽어 들여 표시하려고 했으나, 생각보다 귀찮은 작업이었다. 파일을 읽어들이는데 비동기 메소드를 사용하지 않으면 안되고, 약간의 데이터를 HTML로 주고 받아 표시하는 것도 일일이 replace으로 코드를 바꿔야만 했다. 더 쉬운 방법은 없는가? 라고 생각했을 것이다.

물론 있다. 그것은 “템플릿 엔진 ‘을 사용하는 것이다. Node.js는 다양한 템플릿 엔진을 사용할 수 있다. 가장 일반적으로 이용되고 있는 것은 “EJS"라는 것이다.

그럼 EJS을 설치해 보자. Node.js에는 “npm"라는 패키지 관리 프로그램이 준비되어 있다. 이것을 사용하면 명령어 한방에 필요한 라이브러리를 온라인을 통해 다운로드 설치할 수 있다.

명령 프롬프트 또는 터미널을 시작하고, 다음과 같이 실행한다. 이걸로 EJS가 자동으로 설치된다.

npm install ejs

이 ’npm “라는 패키지 관리 프로그램은 Node.js를 이용하는 경우에는 필수이다. 필요한 것이 있으면 대부분이 npm으로 설치를 하기 때문에, ’npm install"는 여기에서 기억하도록 한다.

또한, EJS 자체는 npm을 사용하지 않아도 얻을 수 있다. Github에 공개되어 있으며, 거기에서 파일을 다운로드할 수 있다.

https://github.com/visionmedia/ejs

템플릿에서 사용할 수 있는 특수 태그

EJS는 템플릿이 되는 HTML 코드에 특수 태그를 사용하여 필요한 정보를 포함할 수 있다. 준비되어 있는 태그는 다음 두 가지이다.

<%= 값 %>

작성한 값을 그 자리에 써 낸다. 스크립트 측에서 준비한 변수 등을 표시하는데 사용한다. HTML 태그 등이 포함되어 있는 경우, 그들은 이스케이프 처리된다.

<%- 값 %>

마찬가지로 값을 그 자리에 써 낸다. 다만, 이곳은 HTML 관련 태그는 이스케이프 처리되지 않고 그대로 쓰여진다.

<% 스크립트 %>

스크립트를 작성하고, 그것을 렌더링할 때 실행한다. 이것은 HTML에 <script> 태그로 작성된 스크립트와는 다르다. <script> 태그는 클라이언트(브라우저)에 보내져 실행되지만, 이 <% %>으로 작성된 태그는 서버 사이드(Node.js 내)에서 실행되어 그 결과가 클라이언트로 보내진다.

이 태그는 닫기 부분은 모두 공통적 %>로 되어 있지만, 이것을 -%>와 같이 마이너스를 붙여 작성하면 값을 출력하고, 후에 줄바꿈할 수 있다.

실제로 이러한 태그를 어떻게 사용하는지는 해보지 않으면 모른다. 그럼, 간단한 예제를 만들어 보자. 아래 에 예제를 올려 두었다. 이것을 “hello.ejs"라는 파일 이름으로, Node.js 스크립트 파일과 동일한 위치에 저장한다.

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title><%=title %></title>
    <style>
    body { font-size:12pt; color:#006666; }
    h1 { font-size:18pt; background-color:#AAFFFF; }
    pre { background-color:#EEEEEE; }
    </style>
</head>

<body>
    <header>
        <h1 id="h1"><%=title %></h1>
    </header>
    <div role="main">
        <p><%-content %></p>
    </div>
</body>
 
</html>

이것이 EJS 템플릿 파일이다. 템플릿 파일은 일반적으로 “. ejs"확장명으로 만든다. 여기에서는 HTML에 다음과 같은 태그를 포함하고 있다.

<%=title %>
<%-content %>

이것들은 각각 title, content라는 변수를 출력하게 된다. 그것은 스크립트 측에서 이러한 변수를 준비하고, 템플릿 측에 전달할 수 있다면 되는 거다. <%= %><%- %> 템플릿의 가장 기본이되는 것이라고해도 좋을 것이다. (덧붙여 content 출력은 <%= 대신 <%-을 사용하고 있다. 이것은 좀 더 나중에 이유를 알 수 있다)

EJS에 의해 템플릿 표시

그럼 준비한 템플릿 파일을 로드하여 Web 페이지를 표시해 보자. 여기에는 몇 가지 작업이 필요하다.

  1. 파일 가져 오기. EJS 자체에 파일을 로드 기능은 없다. 로드는 fs 객체를 이용하게 된다. (다만, 이번에는 비동기가 아니라 동기화 메소드를 사용하여 이전 readFile보다 간단한 스크립트을 작성할 수 있다.)

  2. 가져온 템플릿 데이터의 렌더링. 그것으로 템플릿에 있는 <%= %> 태그 등의 특수 태그가 실제로 출력되는 텍스트로 변환된다.

  3. 생성된 데이터를 응답에 작성하여 완료한다.

결국, EJS는 템플릿 엔진이 수행해 주는 것는 2번 렌더링 부분뿐 나머지는 보통의 Node.js 기능을 사용하여 처리해야 한다는 것이다.

그럼, 방금 만든 hello.ejs을 렌더링하고 표시하는 스크립트를 만들어 보도록 하자. 아래에 샘플을 올려두기에, 이를 작성하고 움직여 보자. 제목과 내용 부분에는 스크립트 측에서 준비해 놓은 텍스트를 끼워 넣어 표시되는 것을 알 수 있을 것이다. 동작을 확인한 후 스크립트의 포인트를 확인한다.

var http = require('http');
var fs = require('fs');
var ejs = require('ejs');
 
var hello = fs.readFileSync('./hello.ejs', 'utf8');
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(req, res) {
    var hello2 = ejs.render(hello, {
        title:"제목입니다.",
        content:"이것은 샘플에서 만든 템플릿입니다.",
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(hello2);
    res.end();
}

ejs 읽어오기

var ejs = require('ejs');

먼저 EJS 라이브러리를 로드한다. 이것은 require 함수에서 “ejs"를 인수로 지정한다. 여기에서 얻은 ejs 개체를 사용하여 템플릿 렌더링을 수행한다.

동기화 방식으로 파일 로드

var hello = fs.readFileSync('./hello.ejs', 'utf8');

fs 개체를 사용하여, 템플릿 파일을 로드한다. 여기에서는 readFile 대신 “readFileSync"라는 메소드를 사용하고 있다. 기본적으로 readFile과 동일하지만, 비동기가 아닌 동기화에서 가져온다. 즉,이 메소드를 호출하여 파일을 로드하고, 그것이 완료될 때까지 계속 진행한다는 것이다.

동기화이기 때문에, 읽기 끝나면 호출하는 “콜백 함수"는 없다. 끝나면 그대로 이어 스크립트를 실행하기 때문에 콜백는 필요없다.

“하지만, 파일로드 시간이 걸릴 수도 있기 때문 비동기하지 않으면 다른 처리가 멈춰 버린다……” 라고 생각하는 사람이 있을 것이다. 그렇다. readFileSync 실행 중에는 다른 프로세스가 있었다고 하더라도 정지중이다. 하지만, 이를 호출하는 곳을 살펴보자. http.createServer에서 서버를 만들기 전에 파일로드를 끝 마치고 있는 것을 알 수 있다.

템플릿이라고 하는 것은, 요청이 있을 때마다 사용하는 것이라면, 최초에 글로벌 변수에 로드해 놓고 사용하는 것이 절대적으로 편한다.

템플릿을 렌더링하기

var hello2 = ejs.render(hello, {
    title:"타이틀입니다",
    content:"이것은 샘플에서 만든 템플릿입니다.",
});

템플릿 렌더링을 하는 것이 ejs 개체 “render"메소드이다. 이것은 정리하면 다음과 같이 된다.

ejs.render(템플릿 데이터, 옵션);
  • 첫번째 인수 : 렌더링 대상 데이터 (=가져온 템플릿 문자열)을 지정한다.
  • 두번째 인수 : 템플릿에 전달하는 변수 등의 정보를 연관 배열에 모은 것을 지정한다.

포인트는 두번째 인수이다. 방금 템플릿에 title과 content라는 변수를 출력하도록 태그를 마련한 것을 기억해 내자. 이러한 변수가 연관 배열에 포함되어있는 것을 알 수 있을 것이다. 이와 같이, 두번째 인수에 변수의 값을 제공하고, render하여 템플릿 측에 있는 그 변수에 값이 대입되는 것이다.

헤더 정보에 ‘Content-Type’: ’text/html’을 설정

res.writeHead(200, {'Content-Type': 'text/html'});

마지막으로 잊지 말아야 것이, writeHead에 의해 헤더를 출력한다. Content-Type에 “text/html"을 설정한다. 그러면 응답에 기록되는 내용이 HTML임을 알 수 있다.

템플릿 부분 결합

템플릿의 기본적인 사용법은 이제 알았다. 하지만 이것만으로는 템플릿로서의 기능은 충분하지 않다. 페이지를 표시하기 위해 템플릿을 렌더링하여 작성해 한다. 그 뿐이다.

템플릿 결합

예를 들어, 여러 페이지가 있는 사이트에는 전체의 구성을 기록한 템플릿을 제공하고 여기에 필요한 컨텐츠용의 템플릿을 끼워 넣어 표시하거나 한다(사실 이 싸이트도 그런 형태로 만들고 있다). 이러한 “전체 템플릿 안에 컨텐츠의 템플릿을 끼워 넣어 표시한다"라고 하는 것은 어떻게 할 수 있을까? 해보도록 하자.

기반이 되는 템플릿에는 앞에서 만든 hello.ejs을 그대로 사용한다. 이 템플릿에는 콘텐츠를 표시하는 부분에 <%- content %>라는 태그를 준비해 두었다. 것은 실제로 표시하는 내용의 템플릿을 준비해 두고, 그것을 여기에 끼워 넣어 출력하면, 콘텐츠만을 여러가지로 변경하여 페이지를 만들 수 있게 된다.

그럼 콘텐츠용의 템플릿으로 “content1.ejs"라는 파일을 만들어 보자. 내용은 아래의 예제로 올려 두었다. 작성 후에 Node.js 스크립트 파일과 같은 위치에 저장하도록 한다.

content1.ejs

<p>예제로 만든 탬플릿입니다.</p>
<p>다른 파일로 준비한 것을 읽어와서 사용합니다.</p>
<p><%= message %></p>

이 파일에는 콘텐츠로 표시할 내용을 HTML로 작성되어 있다. 또한 <%= message %>와 같이 템플릿용의 태그도 준비해 두었다.

이 content1.ejs을 앞전에 hello.ejs의 <%- content %>에 끼워 넣어 표시해 본다. 스크립트를 아래에 올려 두었고 작성 다시 변경하여 시도해 보도록 한다.

sampleapp.js

var http = require('http');
var fs = require('fs');
var ejs = require('ejs');
 
var hello = fs.readFileSync('./hello.ejs', 'utf8');
var content1 = fs.readFileSync('./content1.ejs', 'utf8');
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(req, res) {
    var hello2 = ejs.render(hello, {
        title: "제목입니다.",
        content: ejs.render(content1,{
            message:"텍스트 메세지"
        })
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(hello2);
    res.end();
}

여기에서는 다음과 같은 순서로 표시를 작성한다.

  1. 서버를 만들기 전에, 미리 2개의 템플릿을 변수에 로드 둔다.

  2. 먼저 컨텐츠 템플릿(content1.ejs)를 render으로 렌더링한다.

  3. 페이지 전체의 템플릿 (hello.ejs)를 render 렌더링한다. 이때 content1.ejs을 렌더링한 데이터를 옵션의 값으로 반환한다.

  4. 렌더링된 데이터를 적어 낸다.

여러 템플릿을 조합하여 사용하는 경우의 기본은, “안에 있는 것을 먼저 렌더링하고, 그 결과를 옵션으로 지정하여 외부 렌더링을 한다"는 것이다.

어떤 템플릿 안에 다른 템플릿을 끼워 넣는 경우, “렌더링 끝난” 데이터를 끼워 넣는 것이 기본이다. 끼워 넣은 후에 렌더링하다고 생각하면, 끼워 넣은 템플릿 안에 태그가 렌더링 되지 않고 표시되어 버리거나 하기 때문에 조심해야 한다.

배열 데이터 반복 출력

EJS 템플릿에서는 <% %> 태그를 사용하여 JavaScript 스크립트를 실행시킬 수 있다. 이는 서버에서 렌더링할 시에 실행되기 때문에, 브라우저에 실제로 표시되는 페이지에는 나타나지 않는다. 스크립트의 실행 결과가 표시될 뿐이다.

<% %>에 의한 스크립트를 작성하는 경우, 주의해야 할 점이 몇 가지 있다. 정리하면 아래와 같다.

  1. <% %> 내에 뭔가를 작성하여도 표시되지 않는다. 그 경우는 일단 태그를 빠져 나와서, <%= %> 태그로 값을 출력해야 한다. <% %> 태그 안에 <%= %> 태그를 사용할 수 없다.

  2. JavaScript 스크립트이지만, 일반적인 JavaScript 기능에는 사용할 수 없는 것도 있다. 예를 들어, Date 및 Math 등의 개체는 사용할 수 있지만, alert 같이 브라우저 의존 기능은 사용할 수 없다. 또한 document 등 DOM을 조작하는 기능도 사용할 수 없다.

  3. 스크립트에서 렌더링할 때에 전달되는 변수의 값은 그대로 <% %> 안에서 변수로 사용할 수 있다.

그러면, 실제로 간단한 예제를 만들어 보자. 먼저 content1.ejs의 내용을 아래와 같이 변경해 보도록 한다.

content1.ejs

<p>예제로 만든 탬플릿입니다.</p>
<p>배열 데이터를 반환하여 목록으로 표시한다.</p>
<p><ol>
<% data.forEach(function(val){ %>
    <li><%= val %></li>
<% }) %>
</ol></p>

sampleapp.js

var http = require('http');
var fs = require('fs');
var ejs = require('ejs');
 
var hello = fs.readFileSync('./hello.ejs', 'utf8');
var content1 = fs.readFileSync('./content1.ejs', 'utf8');
 
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
 
// 요청 처리
function doRequest(req, res) {
    var hello2 = ejs.render(hello, {
        title: "제목입니다.",
        content: ejs.render(content1, {
            data: [
                "이것은 최초의 데이터입니다.",
                "다음 데이터이다.",
                "제일 마지막의 데이터이다."
            ]
        })
    });
    res.writeHead(200, {'Content-Type': 'text/html'});
    res.write(hello2);
    res.end();
}

여기에서는 data의 내용을 꺼내어 표시하는 처리를 <% %> 이용하여 작성하고 있다.

이어서 Node.js 스크립트를 아래에 올린 것처럼 수정한다. 그리고 실제로 액세스해 본다. 배열에 포함된 데이터가 목록으로 표시된다.

여기에서는 render 메소드의 인수에 “data"값을 포함하고 있다. 이것은 보면 알 수 있듯이 텍스트의 배열이 설정되어 있다. 이 배열의 값을 순서대로 얻어서 표시하는 것이다. 출력되는 부분을 보면,

<% data.forEach(function(val){ %>
    <li><%= val %></li>
<% }) %>

이렇게 되어 있다. <% %> 태그를 사용하여 반복 처리를 작성하고 있다. “forEach"라고 하는 낯선 메소드가 사용되고 있는데, 이것은 배열(Array 객체)에 있는 메소드에서, 배열에서 요소를 차례로 얻어와서 인수의 함수를 실행하는 것이다. 얻어온 값은 인수의 함수에 인수로 전달된다. 움직임을 잘 모르는 사람은 다음과 같이 작성을 변경하여도 된다.

<% for (var i = 0;i < data.length;i++){ %>
    <li><%= data[i] %></li>
<% } %>

구문의 값을 내보낼 부분은 <%= %> 태그로 되어 있다. <% %>를 사용하는 경우에는 이와 같이 “처리 부분만 <% %>에 쓰고, 그 출력은 <%= %>에 작성"하는 것과 같이, 처리 및 출력을 제대로 분리하여 생각할 필요가 있다라는 것을 잊지 않도록 하자.




최종 수정 : 2018-07-16