Creating Dynamic HTML With Templating
Templating
- 웹 페이지의 패턴을 파악하고 동적으로 제어하기
- express 자체는 템플릿 엔진의 기능을 갖고 있지 않아서, 템플릿 엔진을 따로 설치해야한다.
- 동적인 파일과 정적인 파일의 장단점을 결합한 형태의 새로운 체계다.
- 가장 많이 스이는 것이 ejs와 pug
app.set
먼저 mkdir views
를 해서 views라는 디렉토리 안에 ejs확장자의 파일을 만든다. ejs는 html과 동일한 포맷이다.
-
express에게 template view engine을 알려주는 설정
app.set('view engine', 'ejs')
-
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에는 넣지 않아도 된다.