YESHTML5
nodejs template "PUG" 에 대하여 본문
NodeJS의 템플릿 "PUG"에 관해서 이야기해볼까한다.
nodejs를 사용하다보면 템플릿에 대해서 고민이 한번쯤 들것이다.
static하게 html을 읽을수도 있고, 파일형태로 로드하고 직접 DOM을 표현할수도 있을것이다.
하지만 사용하다보면 php에서 사용하는 include 개념도 쓰고싶고, 템플릿의 장점을 잘 사용했으면 하는것들이, 많이 들때가 있다.
몇가지 찾아보고 테스트를 해봤다.
먼저 PHP와 유사한(?) 문법을 가진 EJS를 사용해봤다.
아무래도 <?php echo "TEST"; ?> 형태로 익숙하다보니 사용을 했으나 사용하면서, 점점 한계를 느꼈다. ( 어쩌면 내공이 부족하거나, 나와 맞지않을수도... )
그다음 찾아본것이 pug다. 원래이름은 JADE인데 소송 이름도용(?) 관련으로 지금은 PUG 로 사용하고있다.
아직은 사용중이라, 특징을 잡는다면, 기본템플릿을 정하고 그 템플릿을 계속해서 쓰고, 일부분만 바꿀수있다.
일반 웹페이지를 볼때, header , contents , footer 에서 정하고 각각 일부분만 override혹은 교체 형태로 사용할수있다. 다음 코드를 보면
layout.pug
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | doctype html html(lang="en") head title yeshtml5.com link(rel='shortcut icon',href='/images/common/favicon.ico',type='image/x-icon') link(rel='shortcut icon',href='/images/common/favicon.ico',type='image/png') link(rel='stylesheet', href='/css/common.css', type='text/css' ) link(rel='stylesheet', href='/css/layout.css', type='text/css' ) link(rel='stylesheet', href='https://fonts.googleapis.com/css?family=Open+Sans:300,400', type='text/css' ) script(src="/js/jquery.js" , type="text/javascript") script(src="/js/vue.js" , type="text/javascript") script(src="/js/axios.js" , type="text/javascript") block scripts body header include inc.navi.html block header main block content footer block footer | cs |
2단계 아래 폴더구조 아래인 test.pug
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 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | //- interface extends ../../layout //- scripts block append scripts script(src="https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js") script(src="https://unpkg.com/imagesloaded@4/imagesloaded.pkgd.min.js") style(type='text/css'). /*=== unsplash =========================================*/ body {background:#ddd;} .info {position:relative;margin-bottom:20px;padding:20px;font-weight:100; box-sizing:border-box;} .info div.page {text-align:center;} .info div.page p.navi em {display:inline-block;margin-right:10px;} .info div.page .search input[type="input"] {display:inline-block;width:200px;padding:5px 15px; background:#111; color:#fff;font-size:16px;border:0;} .info div.page .search button {display:inline-block;margin-left:10px;padding:5px 20px; border:0; background:#111;color:#fff;font-size:16px;} .info div.page .navi {display:block;padding:10px 0; color:#000; font-size:20px;} .info div.arrow {padding:50px auto;} .info div.arrow button {position:absolute;top:50%;transform:translateY(-50%);font-size:30px;font-weight:100;} .info div.arrow button.prev {left:50px;} .info div.arrow button.next {right:50px;} .unsplash {position:relative;display:block;width:100%;margin:0 auto;text-align:center;box-sizing:border-box;} .unsplash .tag {text-align:left;} .unsplash .tag a {display:inline-block;padding:2px 4px;font-size:14px;} .unsplash .tag a:hover {color:#ff0000;text-decoration:underline;} .unsplash dl {position:relative;display:inline-block;width:360px;min-height:300px;margin:10px;padding:10px; box-sizing:border-box;} .unsplash dl dt {margin-bottom:20px;} .unsplash dl dt .user {position:relative;margin-bottom:10px;padding-bottom:10px;border-bottom:1px solid #d7d7d7;text-align:left;} .unsplash dl dt .user > * {display:inline-block;vertical-align:middle;} .unsplash dl dt .user .profile {width:50px;height:50px;border-radius:50%;overflow:hidden;} .unsplash dl dt .user .name { margin-left:20px; } .unsplash dl dt .user .updated { position:absolute;bottom:0;right:0;transform:translateY(-50%); font-size:10px;} .unsplash dl dd {overflow:hidden;} .unsplash dl dd img {max-width:100%;max-height:100%;vertical-align:top;transition:all 0.3s ease-out;} .unsplash dl dd a:hover img {-webkit-filter:contrast(130%); filter:contrast(130%);transform:scale(1.2, 1.2)} .unsplash dl dd.download a {display:block;position:absolute;bottom:15px;right:15px;padding:5px; background:#2b669a; color:#fff; font-size:10px;} /*================================================== Media Query style ==================================================*/ @media only screen and (max-width:1000px) { .unsplash dl {width:96%; margin:10px auto; } } //- content block content div#index div.info.panel div.page p.search input#searchIpt(v-on:keyup.enter="query",v-bind:placeholder="search",v-bind:value="search" type="input" ) button(v-on:click="query") 검색 p.navi em {{page}} | / {{total}} div.arrow button(v-on:click="prev" class="prev") < PREV button(v-on:click="next" class="next") > NEXT div.unsplash dl.panel( v-for="item in items") dt div.user span.profile a(v-bind:href='item.user.portfolio_url' , target="_blank") img(v-bind:src='item.user.profile_image.large',class="flex") span.name {{item.user.username}} span.updated {{item.user.updated_at}} p.tag a(v-on:click="tagLink",v-for="tag in item.tags") {{tag.title}} dd a(v-bind:href='item.links.html',target="_blank") img(v-bind:src='item.urls.regular') dd.download a.panel(v-bind:href='item.links.download',target="_blank") DOWNLOAD <!--[script]-----> script(type='text/javascript'). var index = new Vue({ el: '#index', data: { page: 1, total: 10, per_page: 30, search: 'wallpaper', items: {} }, created: function () { }, updated: function () { if (document.querySelector('.unsplash')) {//imagesLoaded complete -> Masonry imagesLoaded(document.querySelector('.unsplash'), function (instance) { var grid = document.querySelector('.unsplash'); var msnry = new Masonry(grid, { fitWidth: true, itemSelector: 'dl.panel' }); }); } }, methods: { init: function () { index.$data.items = []; }, prev: function () { index.$data.page = index.$data.page - 1; this.ajax(); }, next: function () { index.$data.page = index.$data.page + 1; this.ajax(); }, query: function (event) { index.$data.search = document.querySelector('#searchIpt').value; this.ajax(); }, tagLink: function (event) { index.$data.page = 1; index.$data.search = event.target.innerHTML; this.ajax(); }, ajax: function (event) { this.init(); axios.post('/component/unsplash/' + index.$data.search, { page: index.$data.page, per_page: index.$data.per_page }).then(function (response) { //pagination index.$data.total = Math.ceil(response.data.total / index.$data.per_page); index.$data.items = response.data.results; }).catch(function (error) { console.log(error); }); } } }); /*--[start]--*/ index.ajax(); | cs |
js에서 설정예.
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 | /* */ const express = require('express') const router = express.Router() const path = require('path') const bodyParser = require('body-parser').json(); const request = require('request') /** * @router : unsplash * https://unsplash.com 의 API 가져와서 갤러리, 시간당 200query가 막히면 다른 API 키발급필요 * @document : https://unsplash.com/documentation */ router .get('/', (req, res) => { res.render(path.normalize(__dirname + "/unsplash.pug")) }) .post('/:search/', bodyParser, (req, res) => { const api = "----------" const url = 'https://api.unsplash.com/search/photos' request({ method: "GET", qs: { client_id: api, page: req.body.page || 1, per_page: req.body.per_page || 30, query: req.params.search }, url: url, json: true }, function (err, response, body) { res.json(body) }) }) /*---[exports]-----*/ module.exports = router | cs |
이렇게 사용을 해보니, 장점은 다음과 같다.
1. include개념처럼 붙여쓸수있다.
2. 기본템플릿을 정해서 상속해서 재사용가능하다.
3. 공통적인 요소는 다양하게 include 할수있고, override , 혹은 붙여서 쓸수있다.
4. 변수처리및 서버의 정보를 가져와서 length값만큼 for문을 돌릴수있다.
결론은 한번써보자. 그리고 안맞으면 다른거 찾아보자.
본소스는 RESTFUL API 형태로 unsplash를 만들어 본것이다.
GITHUB에서
https://github.com/yeshtml5/node/tree/master/app/template/component/unsplash 에서 찾아보고 다운로드 할수있다.
끝