Skip to content

dltkdals224/javascript-lotto-personal

Repository files navigation

프로젝트 개요

이름 : javascript-lotto-personal

기간 : 2022년 3월

인원 : 1인 (개인)

배포 : 데모로 대체함


⌨️ 실행 방법

Clone the project

  git clone https://github.com/dltkdals224/javascript-lotto-personal.git

Go to the project directory

  cd javascript-lotto-personal

Install dependencies

  npm install

Start the server

  npm run start-be
  npm run start-fe

🖥 데모

1


2


3


4


🧚🏻‍♂️ 적용 기술 및 채택 근거

MVC 디자인 패턴

소프트웨어 개발에서 사용자 인터페이스, 데이터 및 논리 제어를 구현하는데 널리 사용되는 아키텍처 디자인 패턴 중 하나.
모델(Model), 뷰(View), 그리고 컨트롤러(Controller)로 구성되어, 분리되어 동작함으로써 코드의 재사용성과 유지보수성을 높일 수 있다.

import './fe/css/index.css';
import { Program } from './fe/js/app.js';
new Program();

import Model from './components/model/index.js';
import View from './components/view/index.js';
import Controller from './components/Controller/index.js';
export class Program {
constructor() {
this.Main();
}
Main() {
const model = new Model();
const view = new View(controller);
const controller = new Controller(model, view);
}
}


  • Model

constructor() {
this.purchaseAmount = 0;
this.purchaseCount = 0;
this.confirmedCount = 0;
this.isconfirmedEnd = false;
this.selectedLottoNumbers = [];
this.selectedLottoNumbersList = [];
}

selectPrice(price) {
const EVENT = new CustomEvent('priceSelected', { detail: { price } });
window.dispatchEvent(EVENT);
}
clickPurchaseButton(price) {
this.purchaseAmount = price;
this.purchaseCount = this.purchaseAmount / 5000;
const EVENT = new CustomEvent('purchaseButtonClicked', { detail: { price } });
window.dispatchEvent(EVENT);
}
selectLottoNumber(number) {
if (this.selectedLottoNumbersList.length >= this.purchaseAmount / 1000) {
return;
}
// TO FIX
if (this.selectedLottoNumbers.includes(number)) {
const TARGET_IDX = this.selectedLottoNumbers.indexOf(number);
this.selectedLottoNumbers.splice(TARGET_IDX, 1);
} else if (this.selectedLottoNumbers.length < 6) {
this.selectedLottoNumbers.push(number);
}
const EVENT = new CustomEvent('lottoNumberSelected', {
detail: this.selectedLottoNumbers,
});
window.dispatchEvent(EVENT);
}
clickAutoButton(numbers) {
this.selectedLottoNumbers = numbers;
const EVENT = new CustomEvent('autoButtonClicked', {
detail: this.selectedLottoNumbers,
});
window.dispatchEvent(EVENT);
}
clickApplyButton() {
this.selectedLottoNumbersList.push(this.selectedLottoNumbers);
this.selectedLottoNumbers = [];
const EVENT = new CustomEvent('applyButtonClicked', {
detail: {
isReachAmount: this.selectedLottoNumbersList.length >= this.purchaseAmount / 1000,
selectedLottoNumbersList: this.selectedLottoNumbersList,
},
});
window.dispatchEvent(EVENT);
}
clickConfirmButton() {
this.confirmedCount += 1;
if (this.confirmedCount === this.purchaseCount) {
this.isconfirmedEnd = true;
}
const EVENT = new CustomEvent('confirmButtonClicked', {
detail: {
isPurchaseEnd: this.isconfirmedEnd,
selectedLottoNumbersList: this.selectedLottoNumbersList,
},
});
window.dispatchEvent(EVENT);
this.selectedLottoNumbers = [];
this.selectedLottoNumbersList = [];
}

데이터와 데이터의 값이 바뀌는 이벤트에 대해 CustomEvent를 통한 발행을 처리.

커스텀 이벤트를 사용해 이벤트의 세부 정보를 자유롭게 정의하고 모델에서 발생하는 이벤트의 내용을 명확하게 전달.
이를 통해 모델과 뷰, 그리고 컨트롤러 간의 결합도를 낮출 수 있으며, 뷰와 컨트롤러가 모델의 상태 변화를 쉽게 감지하고 이에 따른 업데이트를 수행할 수 있다.


  • View

initView() {
this.target = document.getElementById('app');
this.target.innerHTML = `
<section id="scrapped-info-section">
<h2>🎱 지난 회차 번호</h2>
<article id="lotto-round-container">
</article>
</section>
<section id="lotto-section">
<h1>🎱 로또 번호 추출기</h1>
<article id="lotto-price-container">
<div>
<span class="font-bold">구입할 금액을 입력해주세요.</span>
<form>
<select id="purchase-amount-select">
<option value="5000">5,000</option>
<option value="10000">10,000</option>
<option value="20000">20,000</option>
<option value="50000">50,000</option>
<option value="auto" selected>직접 입력</option>
</select>
<input id="purchase-price-input" placeholder="금액" type="number"/>
<button id="purchase-btn" class="button">구입</button>
</form>
</div>
</article>
<article id="lotto-number-select-container" class="display-none">
<form id="lotto-number-form">
<div id="lotto-number-wrapper"></div>
<div class="button-wrapper">
<button id="auto-btn" class="button">자동</button>
<button id="apply-btn" class="button">적용</button>
</div>
</form>
</article>
<article id="lotto-number-selected-container" class="display-none">
<div id="confirmed-lotto-number">
<span class="font-bold">
선택번호 확인
<hr/>
</span>
<div id="selected-lotto-number-wrapper">
<div id="selected-lotto-number-div-1" class="selected-div">
<div class="selected-inner-div font-normal">
A
<div id="selected-lottoball-div-1" class="lottoball-broad-wrapper">
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
</div>
<div class="button-wrapper">
<button disabled>수정</button>
<button disabled>삭제</button>
<button disabled>복사</button>
</div>
</div>
<hr style="color:gray; border-style:dashed"/>
</div>
<div id="selected-lotto-number-div-2" class="selected-div">
<div class="selected-inner-div font-normal">
B
<div id="selected-lottoball-div-2" class="lottoball-broad-wrapper">
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
</div>
<div class="button-wrapper">
<button disabled>수정</button>
<button disabled>삭제</button>
<button disabled>복사</button>
</div>
</div>
<hr style="color:gray; border-style:dashed"/>
</div>
<div id="selected-lotto-number-div-3" class="selected-div">
<div class="selected-inner-div font-normal">
C
<div id="selected-lottoball-div-3" class="lottoball-broad-wrapper">
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
</div>
<div class="button-wrapper">
<button disabled>수정</button>
<button disabled>삭제</button>
<button disabled>복사</button>
</div>
</div>
<hr style="color:gray; border-style:dashed"/>
</div>
<div id="selected-lotto-number-div-4" class="selected-div">
<div class="selected-inner-div font-normal">
D
<div id="selected-lottoball-div-4" class="lottoball-broad-wrapper">
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
</div>
<div class="button-wrapper">
<button disabled>수정</button>
<button disabled>삭제</button>
<button disabled>복사</button>
</div>
</div>
<hr style="color:gray; border-style:dashed"/>
</div>
<div id="selected-lotto-number-div-5" class="selected-div">
<div class="selected-inner-div font-normal">
E
<div id="selected-lottoball-div-5" class="lottoball-broad-wrapper">
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
<div class="lottoball-default"></div>
</div>
<div class="button-wrapper">
<button disabled>수정</button>
<button disabled>삭제</button>
<button disabled>복사</button>
</div>
</div>
</div>
</div>
<hr/>
</div>
</article>
<article id="reset-container" class="display-none">
<div class="button-wrapper">
<button id="confirm-btn" class="button" disabled>확정</button>
<button id="reset-btn" class="button">초기화</button>
</div>
</article>
</section>
<section id="purchased-lotto-section" >
<h2>🎱 구매한 로또 번호</h2>
<article id="lotto-number-confirmed-container"></article>
</section>
`;
}

updateInput(price) {
const PRICE_INPUT = document.getElementById('purchase-price-input');
if (isNaN(price)) {
PRICE_INPUT.disabled = false;
PRICE_INPUT.value = '';
}
if (!isNaN(price)) {
PRICE_INPUT.disabled = true;
PRICE_INPUT.value = price.toLocaleString('ko-KR');
}
}
updateInputValue() {
const PRICE_INPUT = document.getElementById('purchase-price-input');
if (PRICE_INPUT.type == 'number') {
PRICE_INPUT.type = 'text';
PRICE_INPUT.value = Number(PRICE_INPUT.value).toLocaleString('ko-KR');
}
if (PRICE_INPUT.type === 'text') {
PRICE_INPUT.type = 'text';
}
}
renderLottoNumberSelectSection() {
const LOTTO_NUMBER_SELECT_SECTION = document.getElementById('lotto-number-select-container');
LOTTO_NUMBER_SELECT_SECTION.classList.remove('display-none');
const LOTTO_NUMBER_SELECTED_SECTION = document.getElementById(
'lotto-number-selected-container'
);
LOTTO_NUMBER_SELECTED_SECTION.classList.remove('display-none');
const RESET_SECTION = document.getElementById('reset-container');
RESET_SECTION.classList.remove('display-none');
}
updateLottoNumbers(lottoArray) {
const NODE_LIST = document.querySelectorAll('.lotto-number');
NODE_LIST.forEach(node => {
const LOTTO_NUMBER = Number(node.id.split('-')[2]);
// TO FIX
node.classList.remove('disabled');
if (lottoArray.length !== 6) {
if (lottoArray.includes(LOTTO_NUMBER)) {
node.classList.add('clicked');
} else {
node.classList.remove('clicked');
}
}
if (lottoArray.length === 6) {
if (lottoArray.includes(LOTTO_NUMBER)) {
node.classList.add('clicked');
} else {
node.classList.add('disabled');
}
}
});
}
reRenderLottoNumberSelectSection(isReachAmount) {
const NODE_LIST = document.querySelectorAll('.lotto-number');
NODE_LIST.forEach(node => {
node.classList.remove('disabled');
node.classList.remove('clicked');
});
if (isReachAmount) {
NODE_LIST.forEach(node => {
node.classList.add('disabled');
});
const AUTO_BTN = document.getElementById('auto-btn');
AUTO_BTN.disabled = true;
const APPLY_BTN = document.getElementById('apply-btn');
APPLY_BTN.disabled = true;
}
}
updateLottoNumberSelectedSection(selectedLottoNumbersList) {
const SELECTED_LOTTOBALL_DIV = document.getElementById(
`selected-lottoball-div-${selectedLottoNumbersList.length}`
);
const ADDED_LOTTO_NUMBERS = [...selectedLottoNumbersList].pop();
ADDED_LOTTO_NUMBERS.sort((a, b) => a - b);
const NUMBER_LIST = document.createElement('div');
NUMBER_LIST.classList.add('lottoball-broad-wrapper');
for (let num of ADDED_LOTTO_NUMBERS) {
const NUMBER = document.createElement('span');
NUMBER.innerText = num;
NUMBER.classList.add(colors[Math.floor(num / 10)]);
NUMBER_LIST.appendChild(NUMBER);
}
SELECTED_LOTTOBALL_DIV.innerHTML = '';
SELECTED_LOTTOBALL_DIV.appendChild(NUMBER_LIST);
if (selectedLottoNumbersList.length === 5) {
const AUTO_BTN = document.getElementById('auto-btn');
AUTO_BTN.disabled = true;
const APPLY_BTN = document.getElementById('apply-btn');
APPLY_BTN.disabled = true;
const CONFIRM_BTN = document.getElementById('confirm-btn');
CONFIRM_BTN.disabled = false;
}
}
controllButtonStatus(isPurchaseEnd) {
if (!isPurchaseEnd) {
const AUTO_BTN = document.getElementById('auto-btn');
AUTO_BTN.disabled = false;
const APPLY_BTN = document.getElementById('apply-btn');
APPLY_BTN.disabled = false;
}
const CONFIRM_BTN = document.getElementById('confirm-btn');
CONFIRM_BTN.disabled = true;
}
reRenderSelectedLottoNumberDiv() {
const ROUND_NUM = 5;
const NUMBER_NUM = 6;
for (let i = 1; i <= ROUND_NUM; i++) {
const SELECTED_LOTTOBALL_DIV = document.getElementById(`selected-lottoball-div-${i}`);
SELECTED_LOTTOBALL_DIV.innerHTML = '';
for (let i = 0; i < NUMBER_NUM; i++) {
const DIV = document.createElement('div');
DIV.classList.add('lottoball-default');
SELECTED_LOTTOBALL_DIV.appendChild(DIV);
}
}
}
updateConfirmedLottoNumberSection(selectedLottoNumbersList) {
const LOTTO_NUMBER_CONFIRMED_CONTAINER = document.getElementById(
'lotto-number-confirmed-container'
);
const ROUND_WRAPPER = document.createElement('div');
selectedLottoNumbersList.forEach(lottoList => {
const NUMBER_LIST = document.createElement('div');
NUMBER_LIST.classList.add('lottoball-broad-wrapper');
lottoList.forEach(num => {
const NUMBER = document.createElement('span');
NUMBER.innerText = num;
NUMBER.classList.add(colors[Math.floor(num / 10)]);
NUMBER_LIST.appendChild(NUMBER);
});
ROUND_WRAPPER.appendChild(NUMBER_LIST);
});
LOTTO_NUMBER_CONFIRMED_CONTAINER.appendChild(ROUND_WRAPPER);

최초의 InitView HTML과 UI 관련 업데이트 함수를 작성.
DOM 조작과 관련한 모든 처리를 View 클래스에서 담당하게 하므로써, 코드를 구성 요소로 분리하여 추상화 수준을 유지할 수 있도록 함.


  • Controller

constructor(model, view) {
this.model = model;
this.view = view;
this.initController();
// eventListener subscription
window.addEventListener('priceSelected', event => {
this.view.updateInput(event.detail.price);
});
window.addEventListener('purchaseButtonClicked', () => {
this.view.updateInputValue();
this.view.renderLottoNumberSelectSection();
});
window.addEventListener('lottoNumberSelected', event => {
this.view.updateLottoNumbers(event.detail);
});
window.addEventListener('autoButtonClicked', event => {
this.view.updateLottoNumbers(event.detail);
});
window.addEventListener('applyButtonClicked', event => {
this.view.reRenderLottoNumberSelectSection(event.detail.isReachAmount);
this.view.updateLottoNumberSelectedSection(event.detail.selectedLottoNumbersList);
});
window.addEventListener('confirmButtonClicked', event => {
this.view.controllButtonStatus(event.detail.isPurchaseEnd);
this.view.reRenderSelectedLottoNumberDiv();
this.view.updateConfirmedLottoNumberSection(event.detail.selectedLottoNumbersList);
});
// eventListener detection
this.detectChangePriceSelect();
this.detectClickPurchaseButton();
this.detectClickAutoButton();
this.detectClickApplyButton();
this.detectClickConfrimButton();
this.detectClickResetButton();

detectChangePriceSelect() {
const AMOUNT_SELECT = document.getElementById('purchase-amount-select');
const PRICE_INPUT = document.getElementById('purchase-price-input');
AMOUNT_SELECT.addEventListener('change', () => {
const SELECTED_PRICE = AMOUNT_SELECT.options[AMOUNT_SELECT.selectedIndex].value;
PRICE_INPUT.type = SELECTED_PRICE === 'auto' ? 'number' : 'text';
this.model.selectPrice(Number(SELECTED_PRICE));
});
}
detectClickPurchaseButton() {
const PURCHASE_BTN = document.getElementById('purchase-btn');
const AMOUNT_SELECT = document.getElementById('purchase-amount-select');
PURCHASE_BTN.addEventListener('click', event => {
event.preventDefault();
const PRICE_INPUT = document.getElementById('purchase-price-input');
const PRICE = Number(PRICE_INPUT.value.replace(/,/g, ''));
if (isValidAmount(PRICE)) {
this.model.clickPurchaseButton(PRICE);
PURCHASE_BTN.disabled = true;
PRICE_INPUT.disabled = true;
AMOUNT_SELECT.disabled = true;
} else {
alert('금액이 잘못 입력되었습니다.');
}
});
}
detectClickAutoButton() {
const AUTO_BTN = document.getElementById('auto-btn');
AUTO_BTN.addEventListener('click', event => {
event.preventDefault();
this.model.clickAutoButton(generateLottoNumber(45, 6));
});
}
detectClickApplyButton() {
const APPLY_BTN = document.getElementById('apply-btn');
APPLY_BTN.addEventListener('click', event => {
event.preventDefault();
if (isValidLottoNumber(this.model.selectedLottoNumbers)) {
this.model.clickApplyButton();
} else {
alert('6개의 숫자를 클릭해주세요.');
}
});
}
detectClickConfrimButton() {
const CONFIRM_BTN = document.getElementById('confirm-btn');
CONFIRM_BTN.addEventListener('click', event => {
event.preventDefault();
this.model.clickConfirmButton();
});
}
detectClickResetButton() {
const RESET_BTN = document.getElementById('reset-btn');
RESET_BTN.addEventListener('click', () => {
location.reload();
});
}
initController() {
this.initializeData();
this.scrapLatestLottoNumber();
this.createLottoNumberField();
}
initializeData() {
this.purchaseAmount = 0;
this.purchaseCount = 0;
this.confirmedCount = 0;
this.isconfirmedEnd = false;
this.selectedLottoNumbers = [];
this.selectedLottoNumbersList = [];
}
scrapLatestLottoNumber() {
if (!getSessionStorageItem('latestLottoNumbers')) {
(async () => {
const res = await getLottoInfo();
this.showScrappedData(res.data);
setSessionStorageItem('latestLottoNumbers', res.data);
})();
} else {
this.showScrappedData(getSessionStorageItem('latestLottoNumbers'));
}
}
showScrappedData(datas) {
const LOTTO_ROUND_CONTAINER = document.getElementById('lotto-round-container');
for (let data of datas) {
const LOTTO_ROUND = document.createElement('div');
// ROUND
const ROUND = document.createElement('strong');
ROUND.innerText = `${data.lottoRound}회차`;
ROUND.classList.add('font-bold');
LOTTO_ROUND.appendChild(ROUND);
// DATE
const DATE = document.createElement('span');
const TRANSLATED_DATE = flow(
split('.'),
target => ` (${target[0]}${target[1]}${target[2]}일)`
)(data.date);
DATE.innerText = TRANSLATED_DATE;
DATE.classList.add('font-lighter');
LOTTO_ROUND.appendChild(DATE);
// LOTTO NUMBERS
const NUMBER_LIST = document.createElement('div');
NUMBER_LIST.classList.add('lottoball-wrapper');
for (let [idx, num] of data.lottoNumber.entries()) {
const NUMBER = document.createElement('span');
NUMBER.innerText = num;
NUMBER.classList.add(colors[Math.floor(num / 10)]);
NUMBER_LIST.appendChild(NUMBER);
if (idx === 6) {
const BONUS_NUMBER_INTERVAL = document.createElement('span');
BONUS_NUMBER_INTERVAL.innerText = '+';
NUMBER_LIST.appendChild(BONUS_NUMBER_INTERVAL);
}
NUMBER_LIST.appendChild(NUMBER);
}
LOTTO_ROUND.appendChild(NUMBER_LIST);
LOTTO_ROUND_CONTAINER.appendChild(LOTTO_ROUND);
}
}
createLottoNumberField() {
const LOTTO_NUMBER_CONTAINER = document.getElementById('lotto-number-wrapper');
const LOTTO_NUMBER_MAX_LENGTH = 45;
for (let number = 1; number <= LOTTO_NUMBER_MAX_LENGTH; number++) {
const LOTTO_NUMBER = document.createElement('div');
LOTTO_NUMBER.id = `lotto-number-${number}`;
LOTTO_NUMBER.classList.add('lotto-number');
LOTTO_NUMBER.innerText = number;
LOTTO_NUMBER.addEventListener('click', event => {
this.model.selectLottoNumber(Number(event.target.innerText));
});
LOTTO_NUMBER_CONTAINER.appendChild(LOTTO_NUMBER);
}
}

Controller의 생성자에서 이벤트 리스너를 등록하고 각 이벤트에 대해 콜백 함수를 정의.
코드가 모듈화되어 의존성을 낮추고, 이벤트 처리 로직이 분리되어 가독성이 향상된다.
코드의 모듈화와 이벤트 리스너 등록의 분리로 코드의 가독성이 높아지도록 작성.


express내 scrapping

const axios = require('axios');
const cheerio = require('cheerio');
const { flow, trim, split, filter } = require('lodash/fp');
const calculateUtil = require('../util/calculate.util');
const getHtml = async round => {
try {
return await axios.get(
`https://search.naver.com/search.naver?where=nexearch&sm=tab_etc&qvt=0&query=${round}%ED%9A%8C%20%EB%A1%9C%EB%98%90%EB%8B%B9%EC%B2%A8%EB%B2%88%ED%98%B8`
);
} catch (error) {
console.error(error);
}
};
const getData = async () => {
const ROUND_ARRAY = calculateUtil.calculateRound();
const HTML_DATA_ARRAY = await Promise.all(ROUND_ARRAY.map(round => getHtml(round)));
const DATA_ARRAY = HTML_DATA_ARRAY.map((htmlData, idx) => {
const $ = cheerio.load(htmlData.data);
const NUMBER_LIST = $(
'#main_pack > div.sc_new.cs_lotto._lotto > div > div.content_area > div > div > div:nth-child(2) > div.win_number_box > div'
).text();
return {
lottoRound: ROUND_ARRAY[idx],
date: calculateUtil.calculateDate(ROUND_ARRAY[idx]),
lottoNumber: flow(
trim,
split(' '),
filter(target => target !== '')
)(NUMBER_LIST),
};
});
return DATA_ARRAY;
};
module.exports = {
getData,
};

node.js 필드에서 axios와 cheerio를 통해 scrapping을 구현.
특정 회차들의 로또 당첨 번호를 html tag를 통해 가져올 수 있도록 함.


함수형 프로그래밍

함수형 프로그래밍기반 함수형 프로그래밍 적용 시도.
적용에 lodash 라이브러리를 사용.

적용 전

const DATA_ARRAY = HTML_DATA_ARRAY.map((htmlData, idx) => {
    const $ = cheerio.load(htmlData.data);

    const DATA = {
      lottoRound: ROUND_ARRAY[idx],
      lottoNumber: $(
        '#main_pack > div.sc_new.cs_lotto._lotto > div > div.content_area > div > div > div:nth-child(2) > div.win_number_box > div'
      )
        .text()
        .split(' ')
        .filter(target => target),
    };

    return DATA;
  });

적용 후

const DATA_ARRAY = HTML_DATA_ARRAY.map((htmlData, idx) => {
    const $ = cheerio.load(htmlData.data);
    const NUMBER_LIST = $(
      '#main_pack > div.sc_new.cs_lotto._lotto > div > div.content_area > div > div > div:nth-child(2) > div.win_number_box > div'
    ).text();

    return {
      lottoRound: ROUND_ARRAY[idx],
      lottoNumber: flow(
        trim,
        split(' '),
        filter(target => target !== '')
      )(NUMBER_LIST),
    };
  });

기타

  • util func

const calculateRound = () => {
const LATEST_NTH_ROUND = 5;
const TODAY_DATE = new Date();
const COMPARE_DATE = new Date(2002, 11, 7);
const A_WEEK = 60 * 60 * 24 * 1000 * 7;
const TIME_GAP = Math.floor((TODAY_DATE.getTime() - COMPARE_DATE.getTime()) / A_WEEK) + 1;
const RETURN_ARRAY = Array.from(
{ length: LATEST_NTH_ROUND },
(_, i) => TIME_GAP - LATEST_NTH_ROUND + i + 1
);
return RETURN_ARRAY.reverse();
};
const calculateDate = round => {
const COMPARE_DATE = new Date(2002, 11, 7);
const DATE = new Date(COMPARE_DATE.setDate(COMPARE_DATE.getDate() + (round - 1) * 7));
return `${DATE.getFullYear()}.${DATE.getMonth() + 1}.${DATE.getDate()}`;
};
module.exports = {
calculateRound,
calculateDate,
};

Date 객체를 사용해 현재 날짜를 scrap하기 위한 최신 회차로 변환하는 util 함수.


🛠 사용 기술

HTML5 CSS3 JavaScript nodedotjs Express


axios NodeMon Lodash



📦 폴더 구조

📂 src
├─ 📂 fe
│  ├─ 📂 css
│  └─ 📂 js
│     ├─ 📂 api
│     │  ├─ client
│     │  └─ lotto
│     ├─ 📂 components
│     │  ├─ Model
│     │  ├─ View
│     │  └─ Controller
│     ├─ 📂 constant
│     ├─ 📂 util
│     │  ├─ checkValid
│     │  ├─ generateLottoNumber
│     │  └─ sessionStorage
│     └─ app.js
├─ 📂 be
│  ├─ 📂 bin
│  ├─ 📂 config
│  ├─ 📂 controllers
│  │  └─ lottoNumberScrap.Controller
│  ├─ 📂 routes
│  │  ├─ lottoNumber.route
│  │  └─ staticPage.route
│  ├─ 📂 util
│  │  └─ calculate.util
│  └─ app.js
└─ index.js

📐 최적화

lodash

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
resolve: {
extensions: ['.js', '.css'],
},
devServer: {
port: 9000,
},
devtool: 'source-map',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env'],
plugins: ['lodash'],
},
},
],
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: './index.html',
}),
],
};

module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: {
node: 'current',
},
},
],
],
plugins: ['lodash'],
ignore: ['./node_modules/lodash'],
};

lodash를 babel을 이용해 번들링하여 모듈 번들러의 크기를 줄임.
babel-loader 가 lodash 모듈에서 사용하지 않는 함수들을 제거 및 번들링할 때 필요한 기능들만을 포함.

이런 최적화 설정을 통해, 모듈 번들러가 최소한의 기능만을 번들링하도록 설정하여 성능이 약간 개선.


🔖 참조

--


🙏🏻 피드백

If you have any feedback, please reach out to us at [email protected]


About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published