30개의 프로젝트로 배우는 프론트엔드 with VanillaJS (9-1) To-Do-List

productuidev·2022년 10월 24일
0

FE Study

목록 보기
65/67
post-thumbnail

30개의 프로젝트로 배우는 프론트엔드 with VanillaJS (9-1) To-Do-List

(9) To-Do-List

01) 프로젝트 개요

  • 할일 입력, 할일 목록 추가, 완료한 일 체크, 추가한 할일 수정 후 저장, 할일 삭제 기능
  • 필터링 기능
  • 각 할일 URL (라우터) 구현
  • 추가한 내용 localStorage DB 연동

02) 개발환경 설정 - Rollup.js

Rollup.js

Rollup.js는 코드와 종속관계의 라이브러리를 정적으로 분석하고, 최소한의 것들만 번들링하는 ES6 모듈 플러그인이다. 소프트웨어를 개발할때, 라이브러리나 어플리케이션을 작은 조각으로 세분화하고 나누어 작업하는것은 일반화되어 있다. 3rd-Party 라이브러리를 사용할때는 더욱 그러하다. 하지만, 그 결과로 무수하게 작은 파일들이 생겨나고 이것을 결코 브라우저에게 좋은 소식이 아니다. 결과적으로, 브라우저는 매번 요청을 생성하고 속도는 느려지기 때문이다. 해결책은 코드를 모듈화하고, 모듈 번들러를 이용해 모든것을 하나의 파일로 만드는 것이다. Browserify와 Webpack이 대표적인 예이다. 하지만, 기존의 라이브러리를 번들화 한다고 하여 그 크기가 줄어드는것은 아니다. ES모듈을 통해 utils를 모두 포함하지 않고, 필요한 ajax 함수만 가져온다.

Bundling
번들링은 파일을 하나로 묶는 것을 말한다. 왜 굳이 파일을 하나로 묶어야 할까? 바로 HTTP 통신의 특성 때문입니다. 단발성으로 리소스를 요청하는 HTTP 특성상, 요청할 파일이 많으면 그만큼 요청을 많이 보내야해서 비효율적이다. 그래서 번들링해서 파일을 하나로 묶으면 요청 횟수가 적어지니 효율적이다. 그만큼 프론트엔드에서 번들링은 정말 중요하다. 사용자가 우리의 웹사이트를 방문했을 때, 최대한 빠르게 웹 페이지를 보여줘야하기 때문이다.

Tree Shaking
번들링을 하면서 얻을 수 있는 큰 장점 중 하나는 내 프로젝트가 가져오는 외부 모듈들이 아주 많을텐데, 그 중에 실제로 사용되는 코드들만 번들링 결과물에 포함시켜준다는 점이다.

rollup.config.js
번들러를 사용하다보면 CLI 설정 옵션이 너무 길어지는 상황이 발생합니다. 그리고 한 줄에 모두 작성해야하기 때문에 보기에도 좋지 않습니다. 또 번들링 환경에 따라 다른 결과 파일을 만들어내야할 수도 있습니다. 그런 몇몇 상황들을 충족시키기 위해 자바스크립트 파일로 번들링 옵션을 설정할 수 있도록 기능을 제공한다.

참고

설치

to-do-list 폴더/파일 생성
ㄴ src/js/index.js
ㄴ src/scss/style.scss
ㄴ src/index.html

package.json

npm init -y

rollup.js 관련 플러그인 설치

  "devDependencies": {
    "@rollup/plugin-node-resolve": "^15.0.0",
    "rollup": "^3.2.3",
    "rollup-plugin-eslint": "^7.0.0",
    "rollup-plugin-generate-html-template": "^1.7.0",
    "rollup-plugin-livereload": "^2.0.5",
    "rollup-plugin-node-resolve": "^5.2.0",
    "rollup-plugin-prettier": "^2.2.2",
    "rollup-plugin-scss": "^3.0.0",
    "rollup-plugin-serve": "^2.0.1",
    "rollup-plugin-terser": "^7.0.2",
  }

rollup-plugin-scss sass : scss/sass
rollup-plugin-generate-html-template : index.html에 번들 스크립트 추가하여 생성
rollup-plugin-livereload : watch모드 변경 감지 후 dist 폴더에 빌드
rollup-plugin-serve : dev server
rollup-plugin-terser : minify, uglify

참고

Rollup 플러그인 소개 및 설치
Rollup.js 플러그인 설정
Rollup 사용하기
Rollup Cheat Sheet

config.js 설정

rollup -c (config 설정으로 빌드)
rollup -w (watch mode)

rollup.common.config.js (공통 config)

import htmlTemplate from 'rollup-plugin-generate-html-template';
import scss from 'rollup-plugin-scss';
import { nodeResolve } from '@rollup/plugin-node-resolve';

export default {
  input: 'src/js/index.js',
  output: {
    file: './dist/bundle.js',
    format: 'cjs',
    sourcemap: true,
  },
  plugins: [
    nodeResolve(),
    scss({
      output: './dist/bundle.css',
      insert: true,
      sourceMap: true,
    }),
    htmlTemplate({
      template: 'src/index.html',
      target: 'index.html',
    }),
  ],
};

rollup.dev.config.js (개발 config)

import rollupCommonConfig from "./rollup.common.config.js";
import serve from 'rollup-plugin-serve';
import livereload from "rollup-plugin-livereload";

const config = {...rollupCommonConfig};

config.watch ={
    inclue:'src/**'
};

config.plugins = [
    ...config.plugins,
    serve({
        host:'localhost',
        port:8080,
        open:true,
        contentBase:'dist'
    }),
    livereload('dist')
]

export default config;

rollup.prod.config.js (빌드)

import { terser } from "rollup-plugin-terser"
import rollupCommonConfig from "./rollup.common.config.js"

const config = {...rollupCommonConfig}

config.plugins=[
    ...config.plugins,
    terser()
];

export default config;

index.js 내에 fontawesome 플러그인, style.scss import

import '@fortawesome/fontawesome-free/js/all.min.js';
import '../scss/style.scss';

로컬 실행/빌드 CLI

  "scripts": {
    "start": "rollup -c rollup.dev.config.js -w",
    "build": "rollup -c rollup.prod.config.js"
  }

ESLint, Prettier

코드 정렬을 위한 ESLint, Prettier 설치
.eslintignore, .eslintrc.json, .prettierignore, .prettierrc.json 등 지난 강의 설정방식 동일하게 활용

  "devDependencies": {
    "eslint": "^8.25.0",
    "eslint-config-prettier": "^8.5.0",
    "eslint-plugin-prettier": "^4.2.1",
    "prettier": "2.7.1",
  }

03) HTML과 SASS (Media Query) 적용

HTML

지난 강의와 동일하게 id와 class 분리 (이벤트 바인딩과 스타일링 구분)

  • input-container : 할일을 입력하는 input과 추가하는 button, 전체/예정/완료 필터링 radio button으로 구성
  • todo-container : to-do-list의 todo-item (한 item 내 완료체크, 수정, 저장, 삭제 button)

SASS

SASS 설치
icon은 지난 강의와 동일하게 Font Awesome 패키지 설치

npm i -D sass
npm i -D @fortawesome/fontawesome-free

  • todo-list : transition 효과 (추가 삭제 시 부드럽게)
  • input todo-item (추가한 할일 목록명) : flex 속성, flex-grow (컨테이너 내 div 비율 너비), value 텍스트가 보일 수 있는 너비를 넘을 경우 보이지 않게 overflow-x, white-space 속성 추가, 삭제 시 DOM에서 X축으로 천천히 사라지도록 opacity 속성 추가,
  • 할일 완료 시 목록 투명하게 (opacity), 목록명 line-through 처리
  • Media Query : PC/Mobile 대응

import

index.js 상단에 fontawesome, scss import

import '@fortawesome/fontawesome-free/js/all.min.js';
import '../scss/style.scss';

커스터마이징

기본 제공 HTML과 SASS 커스터마이즈

04-1) 할일 생성 기능

index.js

기본 틀
TodoList Class 내 constructor, assignElement, addEvent 추가
DOMContentLoaded될 때 TodoList 인스턴스 생성
요소 탐색

import '@fortawesome/fontawesome-free/js/all.min.js';
import '../scss/style.scss';

class TodoList {
  constructor() {
    this.assignElement();
    this.addEvent();
  }

  assignElement() {
    this.inputContainerEl = document.getElementById('input-container');
    this.inputAreaEl = this.inputContainerEl.querySelector('#input-area');
    this.todoInputEl = this.inputAreaEl.querySelector('#todo-input');
    this.addBtnEl = this.inputAreaEl.querySelector('#add-btn');
    this.todoContainerEl = document.getElementById('todo-container');
    this.todoListEl = this.todoContainerEl.querySelector('#todo-list');
  }

  addEvent() {}
}

document.addEventListener('DOMContentLoaded', () => {
  const todoList = new TodoList();
});

index.html

todo-list의 todo 부분은 JS를 통해 동적으로 생성(할일이 추가될 때)할 것이므로 해당 부분 삭제

      <div class="todo-container" id="todo-container">
        <div class="todo-list" id="todo-list">
          <!-- <div class="todo">
            <input readonly="" class="todo-item" />
            <button id="complete-btn" class="complete-btn">
              <i class="fas fa-check"></i>
            </button>
            <button id="edit-btn" class="edit-btn">
              <i class="fas fa-edit"></i>
            </button>
            <button id="save-btn" class="save-btn">
              <i class="fas fa-save"></i>
            </button>
            <button id="delete-btn" class="delete-btn">
              <i class="fas fa-trash"></i>
            </button>
          </div> -->
        </div>
      </div>

할일 입력

할일 입력 후 버튼 추가 할일 목록에 입력한 내용 추가되는 기능 구현

  addEvent() {
    this.addBtnEl.addEventListener('click', this.onClickAddBtn.bind(this));
  }
  
  onClickAddBtn() {
    if (this.todoInputEl.value.length === 0) {
      alert('내용을 입력해주세요');
      return;
    }

    this.createTodoElement(this.todoInputEl.value);
  }

createTodoElement : 위에 지운 todo-list의 todo 부분을 할일 입력 시 동적으로 생성
todoContent의 value : createTodoElement(this.todoInputEl.value)의 value
todoContent는 기본값이 readOnly (수정 버튼을 누를 때만 수정 입력할 수 있고 기본은 readOnly)
new DocumentFragment로 할일 목록에 기본 셋팅으로 완료체크, 수정버튼, 저장버튼, 삭제버튼 생성
createButton : 생성할 버튼에 지정해둔 id, class와 fontawesome의 아이콘 class들을 매개변수로 넣기

  createTodoElement(value) {
    const todoDiv = document.createElement('div');
    todoDiv.classList.add('todo');

    const todoContent = document.createElement('input');
    todoContent.value = value;
    todoContent.readOnly = true;
    todoContent.classList.add('todo-item');

    const fragment = new DocumentFragment();
    fragment.appendChild(todoContent);
    fragment.appendChild(
      this.createButton('complete-btn', 'complete-btn', ['fas', 'fa-check']),
    );
    fragment.appendChild(
      this.createButton('edit-btn', 'edit-btn', ['fas', 'fa-edit']),
    );
    fragment.appendChild(
      this.createButton('save-btn', 'save-btn', ['fas', 'fa-save']),
    );
    fragment.appendChild(
      this.createButton('delete-btn', 'delete-btn', ['fas', 'fa-trash']),
    );
    todoDiv.appendChild(fragment);

    this.todoListEl.appendChild(todoDiv); // 추가
    this.todoInputEl.value = ''; // 할일 추가 후 입력한 input 내용 지움
  }

  // 3가지 매개변수
  createButton(btnId, btnClassName, iconClassName) {
    const btn = document.createElement('button');
    const icon = document.createElement('i');
    icon.classList.add(...iconClassName);

    btn.appendChild(icon);
    btn.id = btnId;
    btn.classList.add(btnClassName);

    return btn;
  }

중간 결과


  • To Do List가 가장 기본으로 응용할 수 있어 많은 강의에서 다루고 있는데 이번이 나한테는 3번째 To Do List...(이건 다른 강의 때 react용으로 일기 만들면서 함수형으로, redux 공부하면서 둘다 간단하게 만들었던 적이 있는데, 이 강의가 좀 더 자세하게 구성되있다) 그래서 원래 좀 다른 챕터로 들을까 하여 건너뛰고 들었다가 복습 겸 다시 돌아옴.. 여담으로 패키지 강의가 다양하게 제공한다는 장점이 있지만 그래도 강사 1명의 코드 스타일이나 패턴에 익숙하게 학습하는 게 좀 더 이해하기 쉬운 거 같다.

  • 기능만 구현하려다가 SASS를 만져서 shopify design style을 참고해서 활용.. 반응형에 맞게 조금 커스터마이징. 다소 곁다리 이야기로 공통 컴포넌트를 활용해 부분을 조합해 전체로 구성하더라도 더 디자인해야 하는 경우가 있는데 이건 디자인을 했던 사람은 공감하는 부분이 있다.

profile
필요한 내용을 공부하고 저장합니다.

0개의 댓글