NodeJS - template, ejs, static

ejs에 대해서

Posted by Yan on January 26, 2021

Creating Dynamic HTML With Templating

Templating

  • 웹 페이지의 패턴을 파악하고 동적으로 제어하기
  • express 자체는 템플릿 엔진의 기능을 갖고 있지 않아서, 템플릿 엔진을 따로 설치해야한다.
  • 동적인 파일과 정적인 파일의 장단점을 결합한 형태의 새로운 체계다.
  • 가장 많이 스이는 것이 ejs와 pug

app.set

먼저 mkdir views를 해서 views라는 디렉토리 안에 ejs확장자의 파일을 만든다. ejs는 html과 동일한 포맷이다.

  1. express에게 template view engine을 알려주는 설정

    app.set('view engine', 'ejs')

  2. express에게 template 파일이 모여있는 디렉토리를 알려주는 설정

    app.set('views', './views')

res.render

view를 랜더링하고, 클라이언트에게 렌더링 된 HTML string을 전달한다.

  • home.ejs를 만들었다고 가정
1
2
3
4
app.get("/", (req, res) => {
  res.render("home");
  //'./views/home.ejs'라고 쓰지 않는 이유는 default로 views디렉토리 안을 확인하기 때문이다.
});

port를 새로고침하면 home.ejs가 브라우저에 나타남을 알 수 있다

path.join

path.join은 인자들을 받아 순서대로 붙여서 url을 만든다.

1
2
3
const path = require("path");
app.set("views", path.join(__dirname, "/views"));
// localhost:8080/index.js가 위치한 디렉토리명/views
  • ejs파일이 있는 path를 따로 지정해주고 싶다면 path.join을 사용하면 된다.
  • path.join을 사용하면 node로 실행했을때 현재 위치한 디렉토리의 영향을 받지 않는다.

EJS (Embedded Javascript Templating)

  • html 파일에서 자바스크립트 문법을 쓸 수 있는 template
  • index.js에서 변수를 만들고 html에서 ejs문법으로 js 변수를 가져와 사용할 수 있다.
1
2
3
4
5
6
app.get("/rand", (req, res) => {
  const num = Math.floor(Math.random() * 10) + 1;
  res.render("random", { rand: num });
  // random.ejs파일이 localhost:8080/rand에 나타남.
  // num에 랜덤 숫자를 할당했다. 브라우저에서 해당 숫자를 볼 수 있음
});
1
2
3
4
5
6
app.get("/r/:subreddit", (req, res) => {
    const { subreddit } = req.params;
    res.render('subreddit', { subreddit });
    //subbreddit.ejs파일이 localhost:8080/r/params에 나타남.
    //{subreddit}을 params의 값으로 할당했기 때문에, url의 params에 따라서 변수의 값이 달라지게 된다.
}
  • 코드가 한 줄일 때 <%= %> 안에 자바스크립트를 쓴다.
  • 코드가 여러 줄일 때 <% %> 안에 자바스크립트를 쓴다. 단, 모든 줄을 <% %>로 감싸야한다. (javascript가 들어간 모든 줄에 써야 한다.)

예제

data.json을 index.js에서 불러와서 subreddit.ejs에 나타내는 과정

  • index.js
1
2
3
4
5
6
7
app.get("/r/:subreddit", (req, res) => {
  const { subreddit } = req.params;
  const data = redditData[subreddit];
  console.log(data);
  res.render("subreddit", { ...data });
  //{...data}는 subreddit.ejs가 들어가야 할 부분인데, 데이터 객체를 넣고, ejs에서는 객체의 key들로 value를 받을 수 있도록 쓴 것이다.
});
  • data.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
{
    "soccer": {
        "name": "Soccer",
        "subscribers": 800000,
        "description": "The football subreddit. News, results and discussion about the beautiful game.",
        "posts": [
            {
                "title": "Marten de Roon to make pizza for more than 1,000 people in Bergamo if Atalanta win the Champions league.",
                "author": "joeextreme"
            },
            {
                "title": "Stephan Lichtsteiner has retired from professional football",
                "author": "odd person"
            },
            {
                "title": "OFFICIAL: Dani Parejo signs for Villareal.",
                "author": "joeextreme"
            }
        ]
    },
    "chickens": {
        "name": "Chickens",
        "subscribers": 23956,
        "description": "A place to post your photos, video and questions about chickens!",
        "posts": [
            {
                "title": "Naughty chicken hid under a shed for 3 weeks and came home with 14 chicks today!",
                "author": "joeextreme",
                "img": "https://preview.redd.it/sja35au7whd51.jpg?width=640&crop=smart&auto=webp&s=c39f8a99896b55fafc5d0ed882040a963ca54409"
            },
            {
                "title": "Had to kill my first chicken today. Does it get any easier?",
                "author": "sad boi"
            },
            {
                "title": "My five year old chicken set and hatched one baby. I guess she wanted to be a mama one more time.",
                "author": "tammythetiger",
                "img": "https://preview.redd.it/lervkuis3me51.jpg?width=640&crop=smart&auto=webp&s=6a18ab3c4daa80eccf3449217589b922fa443946"
            }
        ]
    },
    "mightyharvest": {
        "name": "Mighty Harvest",
        "subscribers": 44002,
        "description": "Feeding many villages and village idiots for 10s of days.",
        "posts": [
            {
                "title": "My first meyer lemon ripened today. Im so proud of the little guy. Banana for scale",
                "author": "proudmamma",
                "img": "https://preview.redd.it/1bz6we4j54941.jpg?width=640&crop=smart&auto=webp&s=a036ea99299f7737efde9f6c3bfa43893f5eaa00"
            },
            {
                "title": "I think I overestimated the harvest basket size I needed.",
                "author": "grower123",
                "img": "https://preview.redd.it/4h99osd25i351.jpg?width=640&crop=smart&auto=webp&s=d651250a345bbceeba7a66632e8c52a02d71bc73"
            }
        ]
    }
}
  • subreddit.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1><%=name%></h1>
    <h2><%=description%></h2>
    <p><%=subscribers%>Total subscribers</p>
    <hr />
    <% for(let post of posts) { %>
    <article>
      <h3><%= post.title %></h3>
      <p><b><%= post.author %></b></p>
      <% if(post.img) { %>
      <img src="<%= post.img %>" />
      <% } %>
    </article>
    <% } %>
  </body>
</html>

결과물

statics

express.static

  • app.use(express.static('public))
    • public 이라는 이름의 디렉토리에 포함된 css, javascript파일 제공하기
    • 정적 파일이 포함된 디렉토리의 이름을 express.static 미들웨어 함수에 전달하면 파일에서 직접적으로 제공된다.
  • express.static을 여러개 써도 된다.
    app.use(express.static('public')); app.use(express.static('files'));
  • html파일의 link rel에서 href="/app.css"와 같은 형태로 public path를 넣지 않고 적어주면 인식된다. bootstarp파일도 위와 같은 형식으로 넣어주면 된다.
  • app.use(express.static('__dirname, public))
    • 현재 위치한 디렉토리와 관계없이 index.js가 있는 디렉토리를 __dirname이 찾아서 실행시킨다.
    • express.static 함수에 제공되는 경로는 node 프로세스가 실행되는 디렉토리에 대해 상대적이다. Express 앱을 다른 디렉토리에서 실행하는 경우에는 __dirnme과 같이 제공하기 원하는 디렉토리의 절대 경로를 사용하는 것이 더 안전하다.

partial

  • header나 nav, footer같이 페이지 내에서 반복해서 등장하는 부분을 따로 떼어내어 사용할 수 있다.
  • ejs파일에 해당 partial을 위치시키고 싶은 자리에 <%- include ('partial/head')%> (partial이라는 디렉토리의 head.ejs를 가리킴) 와 같은 코드로 써주면 header를 재사용할 수 있다.
  • header만 떼어낸 partial에 html head를 모두 넣고 나머지 partial에 body만 남겨두어도 된다. 같은 맥락에서 footer에 footer 관련한 html코드를 모두 넣고 나머지 partial에는 넣지 않아도 된다.