🌿Thymeleaf
Thymeleaf는 View를 제작하여 화면을 출력하는 템플릿 엔진으로, 주로 웹 애플리케이션의 백엔드에서 서버 측 HTML 렌더링을 위해 사용된다.
Thymeleaf는 JSP, EL, JSTL을 합쳐 놓은 느낌이다. 이를 이용하면 JSP를 사용하지 않고 Thymeleaf를 사용하여 화면을 만들 수 있다.
Thymeleaf 외에도 Freemarker, Mustache, Groovy 등을 사용할 수 있다.
프로젝트 설정
application.properties
# 서버 포트
server.port=8090
# JSP
spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp
# JDBC + MyBatis
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=hr
spring.datasource.password=java1234
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.max-lifetime=2000000
spring.datasource.hikari.connection-timeout=30000
# Thymeleaf
# -기본값
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.mode=HTML
spring.thymeleaf.check-template=true
spring.thymeleaf.check-template-location=true
# -기본값(true) > 개발중(false)
spring.thymeleaf.cache=false
Thymeleaf를 기본적으로 사용하고 있기 때문에 enabled 속성이 true이다.
원한다면 HTML이 아닌 것으로도 웹 페이지를 만들 수 있지만, mode는 무조건 HTML을 사용하도록 한다.
cache는 기본값이 true지만, 개발 중에는 false로 설정해 두는 게 좋다.
cache가 켜져 있으면 페이지가 켜져 있을 때 수정한 내용이 캐시 안에 있는 걸 재사용하므로 수정 작업의 변화가 늦어서 개발하는 데 용이하다.
Thymeleaf Plugin for Eclipse
Thymeleaf Plugin for Eclipse를 설치한다.
Add Thymeleaf Nature
Add Thymeleaf Nature를 선택하고 나면 thymeleaf 인텔리센스가 작동하는 프로젝트가 된다.
파일 추가
com.test.thymeleaf.controller
- TestController.java
com.test.thymeleaf.mapper
- TestMapper.java (I)
com.test.thymeleaf.repository
- TestDAO.java (C)
com.test.thymeleaf.domain
- TestDTO.java
src/main/resources > templates(=views)
- m1.html
- m2.html
- m3.html
- m4.html
- m5.html
- m6.html
src/main/resources > "com" > "test" > "thymeleaf" > "mapper"
- TestMapper.xml
src/main/resources
- messages.properties
- messages_en.properties
- messages_ja.properties
전체 코드
ThymeleafApplication.java
package com.test.thymeleaf;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan(basePackages = "com.test.thymeleaf.mapper")
public class ThymeleafApplication {
public static void main(String[] args) {
SpringApplication.run(ThymeleafApplication.class, args);
}
}
TestController.java
package com.test.thymeleaf.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import com.test.thymeleaf.mapper.TestMapper;
import com.test.thymeleaf.repository.TestDAO;
@Controller
public class TestController {
@Autowired
private TestMapper mapper; //DB
@Autowired
private TestDAO dao; //HashMap
@GetMapping(value="/m1.do")
public String m1(Model model) {
model.addAttribute("num", 100); //request
model.addAttribute("name", "Isaac");
return "m1";
}
@GetMapping(value="/m2.do")
public String m2(Model model) {
//객체 반환(map/obj)
model.addAttribute("map", dao.get());
model.addAttribute("dto", mapper.get());
return "m2";
}
@GetMapping(value="/m3.do")
public String m3(Model model) {
//스프링 메시지 (Spring Message)
//src/main/resources > "messages.properties"
//src/main/resources > "messages_en.properties"
//src/main/resources > "messages_ja.properties"
return "m3";
}
@GetMapping(value="/m4.do")
public String m4(Model model) {
model.addAttribute("a", 10);
model.addAttribute("b", 5);
return "m4";
}
@GetMapping(value="/m5.do")
public String m5(Model model) {
//HTML 속성 조작
model.addAttribute("size", 50);
model.addAttribute("name", "Isaac");
model.addAttribute("color", "cornflowerblue");
return "m5";
}
@GetMapping(value="/m6.do")
public String m6(Model model) {
//콘텐츠 조작
String txt1 = "Isaac입니다.";
String txt2 = "<b>Isaac</b>입니다.";
String name = "Isaac";
model.addAttribute("txt1", txt1);
model.addAttribute("txt2", txt2);
model.addAttribute("name", name);
return "m6";
}
}
m1.html
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
</head>
<body>
<h1>Thymeleaf</h1>
<h2>변수 표현식</h2>
<div>${num}</div>
<div th:text="${num}"></div>
<div th:text="${num}">기존 데이터</div>
<div th:text="'제가 가지고 있는 숫자는 ' + ${num} + '입니다.'"></div>
<div>제 이름은 <span th:text="${name}"></span>이고, 제가 가지고 있는 숫자는 <span th:text="${num}"></span>입니다.</div>
</body>
</html>
html파일의 상단에서 namespace로 thymeleaf.org를 등록해 주어야 한다.
m2.html
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
</head>
<body>
<h1>Thymeleaf <small>객체 출력</small></h1>
<h2>Map</h2>
<div th:text="${map}"></div>
<ul>
<li th:text="${map.kor}"></li>
<li th:text="${map.eng}"></li>
<li th:text="${map.math}"></li>
</ul>
<h2>DTO</h2>
<div th:text="${dto}"></div>
<ul>
<li th:text="${dto.title}">
<li th:text="${dto.author}">
<li th:text="${dto.discount}">
</ul>
<!-- 선택 변수 표현식 -->
<h2>Map</h2>
<ul th:object="${map}">
<li th:text="*{kor}"></li>
<li th:text="*{eng}"></li>
<li th:text="*{math}"></li>
</ul>
<h2>DTO</h2>
<ul th:object="${dto}">
<li th:text="*{title}">
<li th:text="*{author}">
<li th:text="*{discount}">
</ul>
</body>
</html>
m3.html
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
</head>
<body>
<h1>스프링 메시지</h1>
<h2 th:text="#{language}"></h2>
<h3 th:text="#{item.title}"></h3>
<ul>
<li th:text="#{item.name}"></li>
<li th:text="#{item.color}"></li>
<li th:text="#{item.option}"></li>
</ul>
<div th:text="#{item.desc('제품명', '색상')}"></div>
<div th:text="#{item.desc(#{item.name}, #{item.color})}"></div>
</body>
</html>
m4.html
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
</head>
<body>
<h1>연산자</h1>
<div>10 + 3 = 13</div>
<div><span th:text="${a}"></span> + <span th:text="${b}"></span> = <span th:text="${a + b}"></span></div>
<div th:text="${a + ' + ' + b + ' = ' + (a + b)}"></div>
<div th:text="${a} + ' + ' + ${b} + ' = ' + ${a + b}"></div>
<!-- 문자열 연산자 > Literal Substitions -->
<div th:text="|${a} + ${b} + ${a + b}|"></div>
<hr>
<div th:text="${a} + ${b}"></div>
<div th:text="${a} - ${b}"></div>
<div th:text="${a} * ${b}"></div>
<div th:text="${a} / ${b}"></div>
<div th:text="${a} % ${b}"></div>
<hr>
<div th:text="${a} > ${b}"></div>
<div th:text="${a} >= ${b}"></div>
<div th:text="${a} < ${b}"></div>
<div th:text="${a} <= ${b}"></div>
<div th:text="${a} == ${b}"></div>
<div th:text="${a} != ${b}"></div>
<hr>
<div th:text="true and true"></div>
<div th:text="true or true"></div>
<div th:text="not true"></div>
<hr>
<div th:text="${a} > 0 ? '양수' : '양수아님'"></div>
<div th:text="${a} > 0 ? '양수'"></div>
<div th:text="${a} < 0 ? '음수'"></div>
<div th:text="${a} > 0 ?: '양수'"></div>
<div th:text="${a} < 0 ?: '음수'"></div>
</body>
</html>
m5.html
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
<style>
.one { color: cornflowerblue; }
.two { background: gold; }
.three { text-decoration: underline; }
</style>
</head>
<body>
<h1>HTML Attributes</h1>
<input type="text" name="age" size="${size}">
<br>
<input th:type="text" th:name="age" th:size="${size}">
<br>
<input type="text" name="age" th:size="${size}">
<br>
<input type="text" th:value="${name}" name="name" th:size="${size}">
<hr>
<div class="one">Box</div>
<div th:class="one">Box</div>
<div class="one" th:class="two">Box</div> <!-- thymeleaf가 서식을 덮어쓰기 한다. -->
<div class="one" th:attrappend="class=' two'">Box</div> <!-- 기존의 서식에서 누적한다. -->
<div class="one" th:attrprepend ="class='two '">Box</div>
<input type="text" value="이름: ">
<input type="text" th:value="'이름: ' + ${name}">
<input type="text" th:value="|이름: ${name}|">
<input type="text" value="이름: " th:attrappend="value=${name}">
<input type="text" value=" 이름" th:attrprepend="value=${name} ">
<hr>
<div class="one" th:classappend="two">Box</div>
<hr>
<input type="checkbox" name="cb" checked>
<input type="checkbox" name="cb" th:checked="true">
<input type="checkbox" name="cb" th:checked="false">
<input type="checkbox" name="cb" th:checked="${size > 0}">
<input type="checkbox" name="cb" th:checked="${size < 0}">
<hr>
<div th:style="'background-color: ' + ${color}">Box</div>
<div th:style="|background-color: ${color}|">Box</div>
</body>
</html>
m6.html
<!DOCTYPE html>
<html xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
<link rel="stylesheet" href="https://me2.do/5BvBFJ57">
</head>
<body>
<h1>Content</h1>
<div th:text="${txt1}"></div>
<div th:text="${txt2}"></div>
<div th:utext="${txt1}"></div>
<div th:utext="${txt2}"></div>
<hr>
<div th:text=${txt1}></div>
<div th:inline="text">[[${txt1}]]</div>
<div>[[${txt1}]]</div>
<div>[[${txt2}]]</div>
<div>[(${txt2})]</div>
</body>
</html>
TestMapper.java (I)
package com.test.thymeleaf;
import com.test.thymeleaf.domain.TestDTO;
public interface TestMapper {
TestDTO get();
}
ThymeleafApplication에서 MapperScan을 해 주었다.
TestDAO.java (C)
package com.test.thymeleaf.repository;
import java.util.HashMap;
import org.springframework.stereotype.Repository;
@Repository
public class TestDAO {
public HashMap<String,Integer> get() {
HashMap<String,Integer> map = new HashMap<String,Integer>();
map.put("kor", 100);
map.put("eng", 90);
map.put("math", 80);
return map;
}
}
TestDTO.java
package com.test.thymeleaf.domain;
import lombok.Data;
@Data
public class TestDTO {
private String seq;
private String title;
private String author;
private String discount;
}
TestMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.thymeleaf.mapper.TestMapper">
<select id="get" resultType="com.test.thymeleaf.domain.TestDTO">
select title, author, discount from tblBook
where seq = 1
</select>
</mapper>
messages.properties
language=한국어
item.title=컴퓨터용품
item.name=마우스
item.color=검정
item.option=왼손 사용자
item.desc=제품명은 {0}입니다. 색상은 {1}입니다.
파일을 마우스 오른쪽 클릭하여 properties에서 encoding 속성을 UTF-8로 변경한다.
messages_en.properties
language=english
item.title=Computer Product
item.name=Mouse
item.color=Black
item.option=Left Hand
item.desc=Product name is {0}, Color is {1}.
messages_ja.properties
language=日本語
item.title=上品
item.name=マウス
item.color=黒
item.option=左利き用
item.desc=製品名は{0}です。 色は{1}です。
🌿Thymeleaf Expression
Thymeleaf 표현식은 EL/JSTL과 굉장히 유사하다.
Thymeleaf에는 다양한 표현식이 있으며, 다음에 설명하는 표현식 외에도 여러 표현식이 존재한다.
반드시 th접두어를 붙인 태그와 함께 사용하며, 속성을 통해서 PCDATA를 바인딩한다.
EL/JSTL에 대해서는 위 글을 참고한다.
그러나 EL과 표현이 같다고 해서 기능까지 같다고 생각하면 안 된다.
변수 표현식 (Variable Expressions)
상수 반환
${}
@GetMapping(value="/m1.do")
public String m1(Model model) {
model.addAttribute("num", 100); //request
model.addAttribute("name", "Isaac");
return "m1";
}
<h1>Thymeleaf</h1>
<h2>변수 표현식</h2>
<div>${num}</div>
<div th:text="${num}"></div>
<div th:text="${num}">기존 데이터</div>
<div th:text="'제가 가지고 있는 숫자는 ' + ${num} + '입니다.'"></div>
<div>제 이름은 <span th:text="${name}"></span>이고, 제가 가지고 있는 숫자는 <span th:text="${num}"></span>입니다.</div>
thymeleaf 안에서 상수(text)는 홑따옴표('') 안에 작성한다.
가장 많이 사용하는 기능이다.
데이터 바인딩
before) <div th:text="${}"></div>
after) <div>100</div>
컨트롤러에서 전달받은 데이터를 접근한다.
해당 HTML 태그에 데이터를 데이터바인딩하여 PCDATA로 출력한다.
또한 기존의 PCDATA가 있을 경우 덮어쓰기를 한다.
객체 반환 (map/obj)
@Autowired
private TestMapper mapper; //DB
@Autowired
private TestDAO dao; //HashMap
@GetMapping(value="/m2.do")
public String m2(Model model) {
//객체 반환(map/obj)
model.addAttribute("map", dao.get());
model.addAttribute("dto", mapper.get());
return "m2";
}
<h1>Thymeleaf <small>객체 출력</small></h1>
<h2>Map</h2>
<div th:text="${map}"></div>
<ul>
<li th:text="${map.kor}"></li>
<li th:text="${map.eng}"></li>
<li th:text="${map.math}"></li>
</ul>
<h2>DTO</h2>
<div th:text="${dto}"></div>
<ul>
<li th:text="${dto.title}">
<li th:text="${dto.author}">
<li th:text="${dto.discount}">
</ul>
getter를 생략하고 key로 호출할 수 있다.
선택 변수 표현식 (Selection Variable Expressions)
*{}
<!-- 선택 변수 표현식 -->
<h2>Map</h2>
<ul th:object="${map}">
<li th:text="*{kor}"></li>
<li th:text="*{eng}"></li>
<li th:text="*{math}"></li>
</ul>
<h2>DTO</h2>
<ul th:object="${dto}">
<li th:text="*{title}">
<li th:text="*{author}">
<li th:text="*{discount}">
</ul>
'*'은 위에 있는 상위 객체의 멤버를 가져오라는 뜻이다.
메시지 표현식 (Message Expressions)
#{}
@GetMapping(value="/m3.do")
public String m3(Model model) {
//스프링 메시지 (Spring Message)
//src/main/resources > "messages.properties"
//src/main/resources > "messages_en.properties"
//src/main/resources > "messages_ja.properties"
return "m3";
}
<h1>스프링 메시지</h1>
<h2 th:text="#{language}"></h2>
<h3 th:text="#{item.title}"></h3>
<ul>
<li th:text="#{item.name}"></li>
<li th:text="#{item.color}"></li>
<li th:text="#{item.option}"></li>
</ul>
<div th:text="#{item.desc('제품명', '색상')}"></div>
<div th:text="#{item.desc(#{item.name}, #{item.color})}"></div>
src/main/resources 아래에 "messages.properties" 파일을 생성한다.
이미 스프링이 파일 안의 내용을 알고 있기 때문에 불러올 때에는 바로 바인딩을 해주면 된다.
en, ja 등은 예약어이다. language를 호출하는 건 브라우저가 직접 설정한다. 브라우저의 설정이 영어일 경우 language가 english가 출력되며, 일본어일 경우 日本語가 출력된다.
즉, 브라우저가 어떤 설정을 하고 있는지를 서버로 보내서 웹 페이지는 한 장이지만 접속한 국가에 따라서 언어에 맞는 페이지가 생성된다.
스프링 메시지 (Spring Message)
스프링 메시지는 프로젝트 내에서 자주 사용되는 반복적인 문자열 혹은 다국어를 지원하기 위해 사용하는 기술이다.
여러 페이지에서 동시다발적으로 출력되는 문자열은 DB에 저장하는 경우가 많지만, DB에 저장하지 않아도 되는 반복적인 상수 정보(회사 주소, 전화번호)의 경우에 스프링 메시지를 사용할 수 있다.
링크 주소 표현식 (Link URL Expressions)
@{}
조각 표현식 (Fragment Expressions)
~{}
문자열 연산자 (Literal Substitions)
@GetMapping(value="/m4.do")
public String m4(Model model) {
model.addAttribute("a", 10);
model.addAttribute("b", 5);
return "m4";
}
<h1>연산자</h1>
<div>10 + 3 = 13</div>
<div><span th:text="${a}"></span> + <span th:text="${b}"></span> = <span th:text="${a + b}"></span></div>
<div th:text="${a + ' + ' + b + ' = ' + (a + b)}"></div>
<div th:text="${a} + ' + ' + ${b} + ' = ' + ${a + b}"></div>
<!-- 문자열 연산자 > Literal Substitions -->
<div th:text="|${a} + ${b} + ${a + b}|"></div>
<hr>
<div th:text="${a} + ${b}"></div>
<div th:text="${a} - ${b}"></div>
<div th:text="${a} * ${b}"></div>
<div th:text="${a} / ${b}"></div>
<div th:text="${a} % ${b}"></div>
<hr>
<div th:text="${a} > ${b}"></div>
<div th:text="${a} >= ${b}"></div>
<div th:text="${a} < ${b}"></div>
<div th:text="${a} <= ${b}"></div>
<div th:text="${a} == ${b}"></div>
<div th:text="${a} != ${b}"></div>
<hr>
<div th:text="true and true"></div>
<div th:text="true or true"></div>
<div th:text="not true"></div>
<hr>
<div th:text="${a} > 0 ? '양수' : '양수아님'"></div>
<div th:text="${a} > 0 ? '양수'"></div>
<div th:text="${a} < 0 ? '음수'"></div>
<div th:text="${a} > 0 ?: '양수'"></div>
<div th:text="${a} < 0 ?: '음수'"></div>
🍃HTML 속성 조작
th:HTML속성명 = 값
HTML 속성을 조작할 때, 태그에 'th:'을 적게 되면 HTML 속성이 있기 때문에 값을 넣기만 하면 된다.
@GetMapping(value="/m5.do")
public String m5(Model model) {
//HTML 속성 조작
model.addAttribute("size", 50);
model.addAttribute("name", "Isaac");
model.addAttribute("color", "cornflowerblue");
return "m5";
}
<input type="text" name="age" size="${size}">
<br>
<input th:type="text" th:name="age" th:size="${size}">
<br>
<input type="text" name="age" th:size="${size}">
<br>
<input type="text" th:value="${name}" name="name" th:size="${size}">
${} 표현식은 th속성에서만 사용 가능하다.
그래서 th속성에 넣은 ${size}는 인식하지만, th속성에 없는 위의 input 박스의 경우 인식하지 못한다.
<div class="one">Box</div>
<div th:class="one">Box</div>
<div class="one" th:class="two">Box</div> <!-- thymeleaf가 서식을 덮어쓰기 한다. -->
<div class="one" th:attrappend="class=' two'">Box</div> <!-- 기존의 서식에서 누적한다. -->
<div class="one" th:attrprepend ="class='two '">Box</div>
<input type="text" value="이름: ">
<input type="text" th:value="'이름: ' + ${name}">
<input type="text" th:value="|이름: ${name}|">
<input type="text" value="이름: " th:attrappend="value=${name}">
<input type="text" value=" 이름" th:attrprepend="value=${name} ">
<hr>
<div class="one" th:classappend="two">Box</div>
<hr>
<input type="checkbox" name="cb" checked>
<input type="checkbox" name="cb" th:checked="true">
<input type="checkbox" name="cb" th:checked="false">
<input type="checkbox" name="cb" th:checked="${size > 0}">
<input type="checkbox" name="cb" th:checked="${size < 0}">
<hr>
<div th:style="'background-color: ' + ${color}">Box</div>
<div th:style="|background-color: ${color}|">Box</div>
attrappend와 attrprepend를 이용하여 속성을 합쳐줄 수 있다.
클래스 작업을 할 때에는 classappend를 전용으로 사용한다.
checkbox 속성은 boolean 값을 이용해서 체크를 하거나, 해제할 수 있다.
🍃콘텐츠 조작
PCDATA 조작, 하위 태그 조작을 해 보도록 하자.
이는 innerText(textContent), innerHTML 기능을 조작하는 것과 비슷하다.
- th:text
- escaped text
- 문자열에 이스케이프를 적용한다.
- '<' -> '<'
- '>' -> '>'
- 태그를 적용하지 못하도록 한다.
- innerText
- th:utext
- unescaped text
- 문자열에 이스케이프를 적용하지 않는다.
- 태그가 적용 가능하도록 한다.
- innetHTML
- 인라인 출력
- th:inline="text" > 기본값
- th:inline="javascript"
- th:inline + [[]] > escaped text
- th:inline + [()] > unescaped text
용법은 똑같으며, text인지, utext인지 기능상의 차이만 있을 뿐이다.
추가로 인라인을 출력하는 기능도 함께 확인하도록 한다.
@GetMapping(value="/m6.do")
public String m6(Model model) {
//콘텐츠 조작
String txt1 = "Isaac입니다.";
String txt2 = "<b>Isaac</b>입니다.";
String name = "Isaac";
model.addAttribute("txt1", txt1);
model.addAttribute("txt2", txt2);
model.addAttribute("name", name);
return "m6";
}
<h1>Content</h1>
<div th:text="${txt1}"></div>
<div th:text="${txt2}"></div>
<div th:utext="${txt1}"></div>
<div th:utext="${txt2}"></div>
<hr>
<div th:text=${txt1}></div>
<div th:inline="text">[[${txt1}]]</div>
<div>[[${txt1}]]</div>
<div>[[${txt2}]]</div>
<div>[(${txt2})]</div>
PCDATA를 조작하는 모든 방법
<div>이름: Isaac</div>
<div>이름: <span th:text="${name}"></span></div>
<div th:text="'이름: ' + ${name}"></div>
<div th:text="|이름: ${name}|"></div>
<div th:inline="text">이름: [[${name}]]</div>
<div>이름: [[${name}]]</div>
Thymeleaf 주석
<!--/* [[]] [()] */-->
[[]], [()]는 thymeleaf 주석으로 달아야 인식하지 않기 때문에 기존 HTML 주석에 넣으면 에러가 발생하므로 주의하도록 한다.