유튜브 클론코딩 복습노트-3

Hyuno Choi·2021년 7월 23일
0
post-thumbnail

2021년 7월 24일

지금까지는 사용자의 요청에 단순히 res.send()로 문자열을 보냈습니다. 이제부터는 페이지 구성을 위해 HTML을 전송해보겠습니다.

우리가 보는 웹 페이지 안에는 반복되는 HTML 구조가 많습니다. 예를 들어 네비게이터나 푸터는 어느 페이지를 가도 보이기 때문에 순수한 HTML로 페이지를 구현한다면 HTML 간에 반복되는 코드의 양이 많아집니다.

붕어빵 틀로 붕어빵 모양을 만들고 팥이나 슈크림을 채워넣는 것과 같이 HTML도 반복되는 부분을 미리 틀로 만들어두고 페이지별로 필요한 세부 사항만 첨가할 수 있습니다. 이번 프로젝트에서는 pug를 사용해 그러한 작업을 해보겠습니다.

pug 사용 준비🐶

  1. pug도 NPM에 등록된 모듈입니다. $ npm i pug 명령어로 설치합니다.
  1. 그리고 Express에게 우리가 pug를 사용한다는 사실을 알려줘야 합니다. server.js 파일에서 정의한 익스프레스 어플리케이션인 app 에 설정합니다.
app.set("view engine", "pug"); // here
app.use(morgan("dev"));
app.use("/", globalRouter);
app.use("/videos", videoRouter);
app.use("/users", userRouter);

맨 윗 줄과 같이 app.set() 을 통해 뷰 엔진을 pug로 설정합니다.

  1. 이제 pug 파일을 모아놓을 수 있는 디렉터리를 생성합니다. src 폴더 내부터 views 폴더를 생성해주세요.
  1. 마지막으로 해줘야 할 세팅이 하나 더 있습니다. Express에게 뷰 파일이 /src/views 폴더 안에 있다는 것을 알려줘야 합니다. 이 설정을 해주지 않으면 Expess는 view 파일을 프로젝트 폴더 최상위에서만 찾게 됩니다.
app.set("view engine", "pug");
app.set("views", process.cwd() + "/src/views"); // here
app.use(logger);
app.use("/", globalRouter);
app.use("/users", userRouter);
app.use("/videos", videoRouter);

두 번째 줄과 같이 views 폴더의 위치를 Express에게 알려줍니다. process.cwd()는 현재 작업 디렉토리를 반환합니다. 즉, 프로젝트 폴더 최상위 경로에 /src/views 를 더한 경로를 views 폴더의 위치라고 설정합니다.

pug 파일 생성

첫 pug 파일🎉

위에서 생성했던 views 폴더 내부에 base.pug 파일을 생성합니다. 이 파일은 이제부터 프로젝트에서 사용할 HTML 파일의 템플릿이 됩니다. base.pug 파일에 다음 내용을 작성합니다.

doctype html
html(lang="ko")
    head
        title Home | Wetube
    body
        header
            h1 Home
        main
            p I'm first pug file!

보이는 대로 pug 문법은 HTML보다 간편하게 만들어져 있습니다. HTML 문법을 알고 있다면 pug 문법은 무척 배우기 쉽습니다.

  1. HTML에서 쓰이는 <> 와 닫는 태그 </> 가 없습니다.
  2. 태그 이름을 적고 한 칸을 띄고 태그 안에 들어갈 내용을 적습니다.
  3. 태그 블럭을 파이썬처럼 들여쓰기로 구분합니다(스페이스와 탭 둘 다 사용 가능하나 섞어 쓰면 안 됩니다.).
  4. 태그 속성(attribute)은 태그 뒤에 괄호를 열고 씁니다.

pug 파일 렌더링

이제 만들어진 pug 파일을 렌더링해보겠습니다. 임시로 controllers/videoController.js 파일로 들어가서 trending 컨트롤러를 수정합니다.

export const trending = (req, res) => res.render("base");

send()render() 로 바꿔주고 pug파일명을 확장자는 빼고 인자로 넘겨줍니다. trending 은 루트 URL을 담당하는 컨트롤러이므로 브라우저로 서버에 접속하면 바로 pug 파일이 HTML로 렌더링 된 것을 볼 수 있습니다.

그렇지만 pug 파일을 이렇게 쓰려고 다운받은 것은 아닙니다. 지금의 base.pug 파일은 어떤 페이지에서 렌더링하더라도 동일한 모습이기 때문입니다. 이제 pug 파일의 확장성에 대해 알아보겠습니다.

pug의 확장성

include

우선 footer 를 만들어보겠습니다. footernav 같은 경우 거의 모든 페이지에서 사용하는 필수 부품입니다. 따라서 한 페이지에 만들어두기보다는 부품으로 만들었다가 필요할 때 끼워 사용하는 것이 효과적입니다.

우선, views 폴더 안에 partials 폴더를 만듭니다. 그리고 그 안에 footer.pug 파일을 생성합니다.

footer.pug 파일을 간단하게 작성해줍니다.

footer &copy; #{new Date().getFullYear()} Wetube

pug 파일에서 #{} 를 사용하여 자바스크립트 코드를 사용할 수 있습니다. new Date().getFullTear() 을 통해 footer 의 연도를 자동으로 업데이트 해줍니다.

이제 base.pug 파일에서 footer.pug 파일을 불러오겠습니다.

doctype html
html(lang="ko")
    head
        title Home | Wetube
    body
        header
            h1 Home
        main
            p I'm first pug file!
    	include partials/footer

footer 가 들어갈 자리에 include partials/footer.pug 를 적어줍니다. pug 파일의 위치는 views 폴더가 기준입니다.

이제 브라우저에서 확인해보면 footer가 제자리에 들어간 것을 볼 수 있습니다.

이렇게 pug 파일은 include 를 통해 다른 pug 파일을 사용할 수 있습니다.

이제 연습으로 edit,home, watch pug 파일을 views 폴더 안에 만들어줍니다. 그리고 모든 파일에

extends base

를 적습니다. base.pug 템플릿을 확장해서 사용한다는 의미입니다.

그리고 videoController.js 에서 각각의 파일을 렌더링합니다.

  • trending 컨트롤러는 기존의 base 대신 home을,

  • see 컨트롤러는 watch를,

  • edit 컨트롤러는 edit을 렌더링해줍니다.

export const trending = (req, res) => res.render("home");
export const see = (req, res) => res.render("watch");
export const edit = (req, res) => res.render("edit");

이제 브라우저에서 각각의 URL로 들어가보면 모두 base.pug 가 렌더링되는 것을 볼 수 있습니다.

block

지금은 모든 페이지가 base.pug 파일을 똑같이 렌더링합니다. block 으로 공간을 지정해주면 해당 공간을 각자 파일에 맞게 꾸밀 수 있게 됩니다.

base.pugmain 태그 안쪽을 다음과 같이 content 라는 이름의 block 으로 지정해줍니다.

doctype html
html(lang="ko")
    head
        title Home | Wetube
    body
        header
            h1 Home
        main
            block content
    	include partials/footer.pug

이제 content 라는 공간은 다른 파일에서 마음대로 꾸밀 수 있습니다. 이번에는 각각의 파일 별로 다른 h1 을 지정해주도록 하겠습니다.

home.pug 의 예입니다.

extends base

block content
	h1 Welcome to the home!

content 블럭에 채울 내용을 적고 싶으면 block content 라고 똑같이 써준 뒤, 들여쓰기 후에 원하는 내용을 적으면 됩니다. 그러면 base.pug 파일의 content 블럭 안에 해당 내용이 들어가 HTML이 렌더됩니다.

JS 변수 사용

doctype html
html(lang="ko")
    head
        title Home | Wetube
    body
        header
            h1 Home
        main
            block content
    	include partials/footer.pug

base.pug 파일의 내용을 다시 한 번 보면 headerh1 부분은 Home으로 고정되어 있습니다. 이 역시 block 으로 만들어서 페이지 별로 다른 내용이 들어가게 할 수 있지만 미리 지정할 수 없는 값도 있습니다. 예를 들어, 유튜브에서 각 동영상의 제목을 표시하는 경우 크리에이터들이 만든 동영상의 제목을 개발자가 일일히 입력해줄 수는 없을 것입니다.

이럴 때 footer 의 연도를 표시할 때 썼던 #{} 를 활용하여 자바스크립트 변수를 사용할 수 있습니다.

doctype html
html(lang="ko")
    head
        title Home | Wetube
    body
        header
            h1=pageTitle
        main
            block content
    	include partials/footer.pug

h1 부분을 다음과 같이 수정합니다. 이제 변수로 처리한 부분에는 자바스크립트의 pageTitle 이라는 변수가 들어가 HTML로 렌더링될 것입니다.

= 을 사용하는 것과 #{} 을 사용하는 것의 차이는 다른 문자열의 포함 여부입니다. 변수만 사용한다면 = 을 사용하여 변수값을 할당할 수 있고, 다른 문자열과 변수를 섞어 사용해야 한다면 #{} 를 써야 합니다.

이제 저 변수를 pug 파일에 전달해줘야 합니다. videoContoller.js 파일에서 각각의 컨트롤러에 pug 파일과 함께 변수를 담은 객체도 함께 전달해줍니다.

export const trending = (req, res) =>
  res.render("home", {
    pageTitle: "Home",
  });
export const see = (req, res) =>
  res.render("watch", {
    pageTitle: "Watch videos",
  });
export const edit = (req, res) =>
  res.render("edit", {
    pageTitle: "Edit video",
  });

다음과 같이 pageTitle 변수를 객체로 전달합니다.

이제 브라우저에서 각각의 경로로 들어가보면 headerh1 이 페이지마다 다르게 나오는 것을 볼 수 있습니다.

localhost4000:/

localhost4000:/videos/1

localhost4000:/videos/1/edit

pug 조건문

HTML 요소 중 특정 조건에 따라 다르게 보여주고 싶은 것이 있을 수 있습니다. 예를 들어, 로그인 버튼은 로그인 상태가 아닐 때 보여줘야 하고, 로그아웃 버튼은 로그인 상태일 때 보여줘야 합니다. 이런 기능을 pug에서 조건문으로 구현할 수 있습니다.

우선 pug 조건문을 연습하기 위해 가상의 유저와 로그인 상태를 videoController.js 상에 만들겠습니다.

const fakeUser = {
  name: "Unknown",
  isLogin: false,
};

가짜 유저 객체를 파일 맨 윗 줄에 만듭니다. 그리고 home.pug 파일에 pug 조건문을 만듭니다.

extends base

block content
    h1 Welcome to the home!
    
    if fakeUser.isLogin
    		h1 Hello #{fakeUser.name}
        button Logout
    else 
        button Login

조건문 끝에 콜론이 붙지 않는다는 것만 제외하면 파이썬의 조건문이랑 굉장히 유사합니다. pug에서는

  • if
  • else if
  • else
  • unless

가 조건문에서 쓰입니다. unless 의 경우 if !조건 과 같은 의미입니다.

이제 유저 객체를 videoController.jstrending 컨트롤러에 전달합니다.

export const trending = (req, res) => {
  res.render("home", {
    pageTitle: "Home",
    fakeUser,
  });
};

이제 fakeUserisLogin 플래그에 따라 화면에 표시되는 내용이 달라지게 됩니다. isLogintrue 일 때는 로그아웃 버튼과 "Hello Unkown"이라는 문장이, false일 때는 로그인 버튼이 보이게 됩니다.

isLogin: false

isLogin: true

이제 연습에 사용했던 유저 객체와 로그인 관련 코드는 지우고, pug에서 반복문을 사용하는 방법에 대해 알아보겠습니다.

pug 반복문

화면에 100개의 비디오 썸네일과 제목을 만들어야 한다면 100개의 div 를 만드는 것보다는 비디오 객체를 전달받아 div 를 만드는 작업을 반복문에게 맡기는 것이 훨씬 편할 것입니다.

이번에는 pug 반복문을 연습해보겠습니다. 연습을 위해 videoController.js 파일 상단에 더미 비디오 객체 배열을 만듭니다.

const videos = [
  {
    title: "First video",
    rating: 5,
    comments: 43,
    createdAt: "2 minuites ago",
    views: 89,
    id: 1,
  },
  {
    title: "Second video",
    rating: 5,
    comments: 43,
    createdAt: "2 minuites ago",
    views: 89,
    id: 2,
  },
  {
    title: "Third video",
    rating: 5,
    comments: 43,
    createdAt: "2 minuites ago",
    views: 89,
    id: 3,
  },
];

그리고 home.pug 파일에 다음과 같이 반복문을 만듭니다.

extends base

block content
    h1 Welcome to the home!

    each video in videos
        div
            h4=video.title
            ul
                li #{video.rating}/5.
                li #{video.comments} comments.
                li Posted #{video.createdAt}.
                li #{video.views} views.
    else
        h4 Sorry, no video.

pug 반복문은 each in 형태로 사용합니다. home.pug 가 전달받은 videos 객체 배열에서 각각의 비디오 객체를 꺼내 정보를 나열합니다.

pug 반복문에서는 else 를 사용할 수 있습니다. 만약 videos 가 빈 배열이라면 else 로 넘어가게 됩니다.

이제 아까와 같이 videos 리스트를 trending 컨트롤러에 전달합니다.

export const trending = (req, res) => {
  res.render("home", {
    pageTitle: "Home",
    videos,
  });
};

이제 홈페이지에 들어가보면 각각의 비디오 정보가 잘 출력되는 모습을 볼 수 있습니다.

pug mixin

pug로 코드 재사용성을 높일 수 있는 방법은 아직 더 있습니다. 예를 들어, 위에서 만든 비디오 반복문이 그렇습니다. 유튜브를 들어가보면 메인 화면에도 비디오 리스트가 있고, 비디오를 볼 때도 오른쪽 끝에 비디오 리스트가 있습니다. 즉, 비디오 리스트를 만들어주는 코드는 여러 페이지에서 필요로 합니다.

비디오 리스트를 만드는 코드를 재사용하기 위해 우선 views 폴더 안에 mixins 폴더를 만듭니다. 그리고 그 안에 video.pug 파일을 생성합니다.

mixin 을 만드는 것은 함수를 만드는 것과 유사합니다.

mixin video(video)
  div
      h4=video.title
      ul
          li #{video.rating}/5.
          li #{video.comments} comments.
          li Posted #{video.createdAt}.
          li #{video.views} views.

mixin 이름(매개변수)를 적습니다. 그리고 mixin 블럭 안에 home.pug 에서 비디오를 만들었던 틀을 복사합니다.

이제 home.pug 에서 mixin 을 불러옵니다.

extends base
include mixins/video

block content
    h1 Welcome to the home!

    each video in videos
        +video(video)
    else
        h4 Sorry, no video.

mixin 을 불러올 때도 include 를 사용합니다. 파일 경로는 views 폴더가 기준입니다. 불러온 mixin+이름(인자) 형태로 사용합니다. 브라우저를 통해 접속하면 이전과 동일한 결과를 볼 수 있습니다.

보너스 - 개발 과정을 조금 더 아름답게🎨

프로젝트를 진행하다보면 큰 틀부터 만들기 시작해서 세부 사항을 다듬어나가고, 스타일링은 거의 마지막 단계에 이르러 시작하게 됩니다. 그러나 그 마지막 단계까지 못생긴 HTML을 보고 있는 것은 고통이기도 합니다.

MVP.css 는 그런 고통을 조금 덜어줄 수 있습니다. MVP.css는 HTML 태그마다 심플한 스타일을 지정해놓은 CSS 파일입니다. 사용방법도 무척 간단합니다. base.pug 파일의 head 블럭 안에 밑의 코드를 붙여넣으면 됩니다.

link(rel="stylesheet" href="https://unpkg.com/mvp.css")

훌륭하다고는 할 수 없지만 그래도 이전보다 훨씬 보기 좋아졌습니다.

다음 링크에서 MVP.css에 대한 자세한 내용을 확인할 수 있습니다. https://andybrewer.github.io/mvp/

지금까지 Express의 pug를 사용한 HTML 템플릿 만들기의 기초에 대해 살펴보았습니다. 다음에는 유저 정보와 동영상을 저장할 데이터베이스의 기초에 대해 살펴보겠습니다.


<참고 문서>

profile
프론트엔드 웹 개발자를 목표로 하고 있습니다.

0개의 댓글