[Spring boot] HTMX + Thymeleaf ์กฐํฉ์ผ๋ก ์น ํ์ด์ง ๊ฐ๋ฐ ์์ํ๊ธฐ
์ง๋ ํฌ์คํธ์์ ์ฐ๋ฆฌ๋ HTMX๋ผ๋ HTML ํ์ฅ ๊ฐ๋ ์ ๋ํด ์์๋ดค์ต๋๋ค. ์ด๋ฅผ ๋ฐํ์ผ๋ก ์ด๋ฒ ํฌ์คํธ์์๋ Spring๊ณผ Spring์์ ์ง์ํ๋ ํ ํ๋ฆฟ ์์ง์ธ ํ์๋ฆฌํ(Thymeleaf) ์กฐํฉ์ผ๋ก ๊ฐ๋จํ ํ์ด์ง๋ฅผ ๋ง๋ค์ด๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
์ด๊ธฐ ๊ตฌ์ฑ
Spring Initializer๋ฅผ ์ด์ฉํ์ฌ ๋จผ์ ์ ํ๋ฆฌ์ผ์ด์ ํ๋ก์ ํธ ๊ตฌ์ฑ์ ํด๋ณด๊ฒ ์ต๋๋ค. ์ฌ์ฉํ ํจํค์ง๋ ์๋์ ๊ฐ์ต๋๋ค.
- Spring boot 3.2.0
- Spring boot starter web
- Spring boot starter thymeleaf
build.gradle ํน์ pom.xml์์ ํด๋น ํจํค์ง๋ฅผ ์ถ๊ฐํ ๋ค ์๋์ ๊ฐ์ด ํ์๋ฆฌํ ๊ธฐ๋ณธ ์ค์ ์ ์งํํฉ๋๋ค.
๊ฐ๋ฐํ ์ฝ๋๊ฐ ์ ์ ์ฉ๋์๋์ง ๋ณด๊ธฐ ์ํด ์บ์๋ฅผ ๋นํ์ฑํ ํฉ๋๋ค. ์บ์๋ฅผ ํ์ฑํ ํ๋ ๊ฒฝ์ฐ ๋ธ๋ผ์ฐ์ ์์ ๊ธฐ์กด์ ์๋ฒ๋ก ๊ฐ์ ธ์จ ๊ฒฐ๊ณผ๋ฌผ์ ๋ฏธ๋ฆฌ ๋ก๋ํ์ฌ ๋ณด์ฌ์ฃผ๋ฏ๋ก ๋ฐ๋ก ๋ณด์ฌ์ง์ง ์๋ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ์น ํ์ด์ง ํ์ผ์ ๋ชจ๋ resources/templates ํด๋์ ๋ฃ์ด์ ๋ณด๋๋ก ํ ๊ฒ์ด๋ฉฐ ์ ์ ํ์ผ(js, css ํ์ผ)์ static ํด๋์ ๋ฃ์ด์ ๋ณด๋ ๊ฒ์ผ๋ก ์ค์ ํ๊ฒ ์ต๋๋ค.
์ด์ templates/index.html ํ์ผ์ ๋ง๋ค๊ณ , ์ ์ฝ๋๋ฅผ ์ ๋ ฅํด๋ณธ ๋ค์ ์คํ๋ง ๋ถํธ ์๋ฒ๋ฅผ ์คํํด๋ณด๊ฒ ์ต๋๋ค.
์์ ๊ฐ์ด It works ! ๋ผ๋ ํ๋ฉด์ด ๋ณด์ด๋ฉด thymeleaf๊ฐ ํฌํจ๋ Spring boot ํ๋ก์ ํธ๊ฐ ์ ์์ฑ๋ ๊ฒ์ ๋๋ค. ์ด์ ๋ค์์ผ๋ก HTMX๋ฅผ ๊ตฌ์ฑํด๋ณด๊ฒ ์ต๋๋ค.
HTMX๋ฅผ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ 1
HTMX ํํ์ด์ง์์ ๊ณต์์ ์ผ๋ก ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ HTML ํ์ผ์ htmx ์คํฌ๋ฆฝํธ ํ์ผ์ ์ถ๊ฐํ๋ ๊ฒ์ ๋๋ค.
script ํ๊ทธ๋ฅผ ์ด์ฉํ์ฌ htmx ์คํฌ๋ฆฝํธ๋ฅผ ์ถ๊ฐํ๋ฉด ์ฑ๊ณต์ ์ผ๋ก ์ ์ฉ์ด ์๋ฃ๋์์ผ๋ฉฐ ํ ์คํธ๋ฅผ ์ํด ์ํ API๋ฅผ ํ ๊ฐ ๋ง๋ค๊ณ , ํ๋ฉด์ ๋ฒํผ์ ์ถ๊ฐํ์ฌ ๋ฒํผ์ ํด๋ฆญํ์ ๋, It works ! ํ๋ฉด์ Hello htmx ! ๋ผ๋ ๋ฌธ๊ตฌ๋ก ๋ฐ๊พธ์ด๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ ํ๋ฉด์ ๊ตฌ์ฑํด๋ณด๊ฒ ์ต๋๋ค. It works ! ๋ฉ์์ง๋ฅผ DOM์ผ๋ก ๊ฐ์ธ๊ณ , ๊ทธ DOM์ด ๋ฐ๋ ์ ์๋๋ก ๊ตฌํํ๊ธฐ ์ํด ID๋ฅผ ์ง์ ํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ , ๋ฐ๋ก ํ๋จ์ ๋ฒํผ์ ๋ง๋ค์ด ๋ฒํผ ํด๋ฆญ์ It works ! ๋ฉ์์ง๊ฐ ๋ฐ๋ ์ ์๋๋ก hx-get์ผ๋ก GET /hello ํธ์ถ์ outerHTML ํจ์๋ก ์ปดํฌ๋ํธ๊ฐ ๋ฐ๋๋๋ก ํ๊ณ hx-target์ผ๋ก ๋ฐ๊ฟ DOM์ ID๋ฅผ ์ง์ ํด์ค๋๋ค.
๋ค์์ผ๋ก /hello API๋ฅผ ๊ตฌํํฉ๋๋ค. HTMX๋ ์ ์ธ์ ๊ตฌ๋ฌธ์ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ Hello htmx๋ผ๋ ๋จ์ ํ ์คํธ๊ฐ ์๋ HTML ์ฝ๋๋ฅผ ๋ฐํํด์ค์ผ ํ๋ฉฐ ์ด ๋๋ Controller ์ด๋ ธํ ์ด์ ์ด ์๋ RestController ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ์๋ํ๋ฉด View๋ฅผ ๋ฐํํ๋ ๊ฒ์ด ์๋๊ณ , HTML ์ฝ๋๋ฅผ ๋ฐํํด์ค์ผ ํ๊ธฐ ๋๋ฌธ์ด์ฃ .
์ด์ ์๋ฒ๋ฅผ ์คํํ๋ฉด Click Me ๋ผ๋ ๋ฒํผ์ด ๋ง๋ค์ด์ง๊ณ ์ด ๋ฒํผ์ ํด๋ฆญํด๋ณด๋ฉด...
์ด๋ ๊ฒ ์์ ๊ฐ์ด Hello htmx ! ๋ฌธ๊ตฌ๋ก ๋ฐ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ HTMX๋ Javascript์ ๊ฐ์ ๋ช ๋ น ๊ตฌ๋ฌธ ์์ด๋ ์ ์ธ์ HTML ์ฝ๋์ attribute๋ง์ ์ด์ฉํ์ฌ ์ฝ๊ฒ ์นํ์ด์ง๋ฅผ ๊ฐ๋ฐํ ์ ์์ต๋๋ค.
HTMX ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ธ๋ถ CDN ๋ฑ์ ๋ฏธ๋ฆฌ ์ ์ฅ๋ ๋ฆฌ์์ค๋ฅผ ๋ถ๋ฌ์ฌ ์๋ ์์ง๋ง Spring์ ๊ฒฝ์ฐ๋ Maven์์ npm ํจํค์ง๋ฅผ ์ผ๋ถ ์ง์ํด์ฃผ๊ธฐ ๋๋ฌธ์ ํด๋น ํจํค์ง ์ ์ฅ์์์ ์ง์ ๋ฐ์ HTMX๋ฅผ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ๋ ์์ต๋๋ค.
HTMX๋ฅผ ํ๋ก์ ํธ์ ์ ์ฉํ๋ ๋ฐฉ๋ฒ 2
1์์๋ ํ ํ๋ฆฟ ์์ง ์์ด ์์ HTML ํ์ผ์์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ์ง๋ง ์ด๋ฒ์๋ ํ ํ๋ฆฟ ์์ง์ธ ํ์๋ฆฌํ(thymeleaf)๋ฅผ ์ด์ฉํ์ฌ Maven์์ ์ํ๋ HTMX ๋ฒ์ ์ ๋ง์ถฐ ์๋์ผ๋ก ์ ์ ํ์ผ์ ์ ์ฉ๋ ์ ์๋๋ก ์ ์ฉํด๋ณด๊ฒ ์ต๋๋ค.
๋จผ์ ์์์ ์ฌ์ฉํ๋ web.resources.static-locations๋ฅผ ๊ธฐ์กด์ผ๋ก ๋กค๋ฐฑํ๊ณ , thymeleaf์ prefix๋ฅผ templates ํ์๋ก ๋ณ๊ฒฝํฉ๋๋ค. ์ด์ templates ํ์์ ์๋ html ํ์ผ์ ์์ HTML ํ์ผ์ด ์๋ ํ ํ๋ฆฟ ์์ง์ผ๋ก ์ฝ์ด์ HTML๋ก ๋ณํํ ๊ฒ์ ๋๋ค.
๊ทธ๋ฐ ๋ค์ ์์ ๊ฐ์ด webjars htmx์ webjars-locator๋ฅผ ์ถ๊ฐํด์ค๋๋ค. webjars๋ ์น ๊ฐ๋ฐ์ ํ์ํ ์๋ฐ์คํฌ๋ฆฝํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ jar๋ก ํจํค์งํ์ฌ Maven ๋ฑ์ ์ ์ฅ์์ ์ฌ์ฉํ ์ ์๋๋ก ๋ง๋ ๋ ํฌ์งํฐ๋ฆฌ์ด๋ฉฐ webjars-locator๋ ์ ์ ๊ฒฝ๋ก ์ง์ ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฒ์ ์ ์ง์ ํ์ง ์๊ณ , ๋์ ์ผ๋ก ์ปดํ์ผ์ ์ ์ฉํด์ฃผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
์ด์ index.html์ ํ ํ๋ฆฟ ์์ง์ ๋ง๊ฒ ๋ณํํด์ค๋๋ค. ๋์ ๋๊ฒ ๋ฐ๋ ๊ฒ์ ๊ธฐ์กด์ script ํ๊ทธ์ src ์์ฑ์ด ์๋ th:src๋ฅผ ์ฌ์ฉํ๋ค๋ ์ ์ ๋๋ค. ์ฃผ์ํ์์ด ํ ํ๋ฆฟ ์์ง์์ ์ง์ํ๋ ํํ์(expression)์ ์ฌ์ฉํ์ฌ์ผ ํฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด HTMX ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ฒ์ ์ด ๋ฐ๋์ด๋ ์์๋ก index.html ํ์ผ์์ ๋ณ๊ฒฝํ ํ์๊ฐ ์๊ฒ ๋ฉ๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก ์ ๋์ํ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ์ด์ฒ๋ผ HTMX๋ ํ ํ๋ฆฟ ์์ง์ ์ด์ฉํ์ง ์๊ณ ๋ ์ผ๋ฐ์ ์ธ MVC ๊ฐ๋ฐ ๋ฐฉ์๋๋ก ์ฌ์ฉํ ์๋ ์๊ณ , ๋ฐ๋๋ก ํ ํ๋ฆฟ ์์ง๊ณผ ํจ๊ป HTMX๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค.
Tailwind CSS ์ ์ฉํด๋ณด๊ธฐ
ํ์ด์ง๋ฅผ ๋ง๋ค๊ณ ๋๋ CSS ์ ์ฉ์ด ์ฌ์ ํ ๋์ ์ ๋๋ค. webjars์์ bootstrap ๋ฑ ๋ค์ํ CSS๋ฅผ ์ง์ํด์ฃผ๊ณค ์์ง๋ง ์ข ๋ ๋๋ง์ ์คํ์ผ์ ๋ง๋ค์ด๋ณด๊ธฐ ์ํด tailwind CSS๋ฅผ ์ ์ฉํด๋ณด๋ ๊ฒ๋ ์ข์ต๋๋ค.
HTMX + Thymeleaf ์กฐํฉ์ผ๋ก tailwind CSS ์กฐํฉ์ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Node.js๋ฅผ ์ฌ์ฉํ์ฌ tailwind ๋ํ๋์๋ฅผ ์ค์นํ๊ณ ์งํํด์ผ ํฉ๋๋ค. ํด๋น ํ์ผ ์์ฑ์ ์ํด ๋ณ๋์ ํ๋ก์ ํธ๋ฅผ ๋ด๋ถ์ ๋ชจ๋์ฒ๋ผ ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.
main ํ์์ frontend๋ผ๋ ํด๋๋ฅผ ๋ง๋ค๊ณ , ํฐ๋ฏธ๋์ ์คํํฉ๋๋ค.
$ npm init
์ ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ์ฌ ์ด๊ธฐ Node.js ํ๋ก์ ํธ๋ฅผ ์์ฑํฉ๋๋ค.
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Neon K.I.D",
"license": "MIT"
}
๊ทธ๋ฌ๋ฉด ์์ ๊ฐ์ด package.json ํ์ผ์ด ๋ง๋ค์ด์ง๋๋ฐ, ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ tailwindcss๋ฅผ ๋ํ๋์๋ก ์ถ๊ฐํ ๊ฒ์ ๋๋ค.
$ yarn add tailwindcss --devโ
yarn ํน์ npm ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ์ฌ tailwindcss๋ฅผ ๋ํ๋์๋ก ์ถ๊ฐํด์ค๋๋ค.
๊ทธ๋ฌ๋ฉด ์์ ๊ฐ์ด devDependencies์ tailwindcss๊ฐ ์ถ๊ฐ๋ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ , scripts ํ์์ build ๋ช ๋ น์ด๋ฅผ ์ถ๊ฐํด์ค๋๋ค. npm, yarn ๋ฑ์ผ๋ก build ๋ช ๋ น์ด๋ฅผ ์ ๋ ฅํ์ ๋ ์ต์ข CSS ๊ฒฐ๊ณผ๋ฌผ์ด Spring MVC์ ์ ์ ํ์ผ์ ์ ์ฅ๋๋๋ก ํ๊ธฐ ์ํจ์ ๋๋ค.
$ tailwindcss init
tailwindcss init ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ์ฌ tailwind.config.js ํ์ผ์ ์์ฑํฉ๋๋ค. ๊ทธ๋ฐ ๋ค์ content์ ์๋์ ๊ฐ์ด ์ฝ๋๋ฅผ ์ถ๊ฐํด์ค๋๋ค.
๊ทธ๋ฌ๋ฉด tailwindcss๊ฐ ์ ์ฉ๋ ๊ฒฝ๋ก๊ฐ thymeleaf ํ ํ๋ฆฟ ์์ง์ ๋ง์ถฐ ๊ตฌ์ฑ๋ฉ๋๋ค.
์ฌ์ฉํ CSS ์ปดํฌ๋ํธ๋ฅผ ๋์ดํด์ค๋๋ค.
$ yarn buildโ
์ด์ build ๋ช ๋ น์ด๋ฅผ ์ด์ฉํ๋ฉด resources/static/css/main.css ํ์ผ์ด ์์ฑ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
index.html์ ํ ํ๋ฆฟ ์์ง ํํ์์ ์ฌ์ฉํ์ฌ link ํ๊ทธ๋ฅผ ์ด์ฉํด ์์ฑ๋ CSS๋ฅผ ์ ์ฉํ๋ฉด Tailwind CSS๋ ์ ๋ง๊ฒ ์ฌ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค.
์ด๋ฅผ Spring boot ์ ์ฒด ํ๋ก์ ํธ ๋น๋์ ๊ฐ์ด ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด ์๋์ ๋งํฌ๋ฅผ ํตํด Gradle์ node ํ๊ฒฝ์ ๊ตฌ์ฑํ๊ณ , ๋น๋ํด ๋ณผ ์ ์์ต๋๋ค.
๋ง์น๋ฉฐ...
์์ฃผ ๊ฐ๋จํ๊ฒ ํ์๋ฆฌํ์ HTMX๋ก ํ๋ก์ ํธ๋ฅผ ์์ํ ์ ์๋ ๋ฐฉ๋ฒ์ ๋ํด ์์๋ดค์ต๋๋ค. Spring ํ๋ก์ ํธ๋ก ์งํํ๊ณ ์๋ค๋ฉด ์ด๋ ์ ๋ ๋ฐฑ์๋ ์๋ฒ๋ฅผ ๋ง๋ค ์ ์๋ ์ญ๋์ด ๋์๋ ๋ถ๋ค์ผํ ๋ HTMX๋ฅผ ์ ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ํด์๋ง ๊ฐ๋จํ ์๋ค๋ฉด ์ฝ๊ฒ ๋ฐฑ์คํผ์ค์ ์นํ์ด์ง๋ฅผ ๊ฐ๋ฐํด ๋ณผ ์ ์์ ๊ฒ์ ๋๋ค.
๊ฐ์ธ์ ์ผ๋ก HTMX๋ฅผ ์ฌ์ฉํด ๋ณธ ๊ฒฐ๊ณผ ๋ณต์กํ SPA๋ฅผ ์ด์ฉํ์ ๋๋ณด๋ค ๊ฐ๋จํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋๋ ๋ฐ ๊ฐ๋ฐ์ ์๊ฐ์ ๋น์ฉ์ 1.5๋ฐฐ ์ ๋ ๊ฐ์์ํฌ ์ ์์์ผ๋ฉฐ ํนํ webpack ๋ฑ์ ๋น๋ ๋๊ตฌ ์์ด ์คํํ ์ ์๊ธฐ ๋๋ฌธ์ CI/CD๋ก ๊ฒ์ฆ์ด๋ ๋ฐฐํฌ๋ฅผ ํ๋ ๋ฐ ์์ด์๋ ์๊ฐ ๋น์ฉ์ด ๊ฝค ๋จ์ถ๋ ์ ์์์ต๋๋ค. ๋ง์ฝ, webpack, vite ๋ฑ์ ๋น๋ ๋๊ตฌ๋ก ์น ํ์ด์ง๋ฅผ ๋น๋ํ๋ ๋ฐ ๋๋ฌด ๋ง์ ์๊ฐ์ด ์๋ชจ๋๊ณ , ๊ฒ๋ค๊ฐ ๊ทธ ์ ํ๋ฆฌ์ผ์ด์ ์ด ๋ฐฑ์คํผ์ค ๋ด์ง ๊ฐ๋จํ ์นํ์ด์ง๋ผ๋ฉด HTMX๋ ์ต๊ณ ์ ์ ํ์ด๋ผ๊ณ ๋ณผ ์ ์์ด ๋ณด์ ๋๋ค.