diff --git a/.eslintrc.json b/.eslintrc.json index 056e053914..41c9082b5b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,8 @@ "env": { "es6": true, "node": true, - "jest": true + "jest": true, + "browser": true }, "parserOptions": { "ecmaVersion": "latest", @@ -74,6 +75,7 @@ "caseInsensitive": true } } - ] + ], + "func-names": "off" } } diff --git a/README.md b/README.md index 581261bd6a..286225bc56 100644 --- a/README.md +++ b/README.md @@ -2,37 +2,90 @@

level1 - 행운의 로또

자바스크립트로 구현 하는 로또 어플리케이션

-### 실행 - -```bash - npm run start-step1 -``` +## STEP1 -### 테스트 +### 실행 및 테스트 ```bash -npm run test + npm run start-step1 //실행 + npm run test //테스트 ``` ### 실행 화면 -실행화면 +실행화면 ### 테스트 실행 화면 테스트 실행화면 -### 도메인 로직 +### 파일 구조 + +``` +📦src +┣ 📂constant : 상수 관리 +┃ ┣ 📜Messages.js : 입력, 입력힌트, 출력, 에러 메세지 관리 +┃ ┗ 📜Options.js : 로또 가격, 로또 범위 최소, 최대 숫자, 상금, 게임 결과 등 관리 +┣ 📂controller +┃ ┗ 📜LottoController.js : 로또 게임 진행 +┣ 📂domain +┃ ┣ 📜Lotto.js +┃ ┗ 📜LottoMachine.js +┣ 📂util +┃ ┣ 📂random +┃ ┃ ┗ 📜Random.js +┃ ┣ 📂readLine +┃ ┃ ┗ 📜readLineAsync.js +┃ ┗ 📂validation +┃ ┃ ┣ 📜LottoNumbersValidator.js +┃ ┃ ┣ 📜PurchaseAmountValidator.js +┃ ┃ ┗ 📜Validation.js +┣ 📂view +┃ ┣ 📜InputView.js +┃ ┗ 📜OutputView.js +┣ 📜App.js +┣ 📜step1-index.js +┗ 📜step2-index.js + +📦**tests** ┣ 📜.gitkeep ┣ 📜Lotto.test.js ┣ 📜LottoMachine.test.js ┗ 📜Random.test.js + +``` + +
+ +## STEP2 + +### 배포 링크 + +[로또 페이지](https://00kang.github.io/javascript-lotto/) + +### 실행 + +```bash +npm run start-step2 +``` + +### 실행 화면 + +실행화면 +실행화면 +실행화면 +실행화면 ### 파일 구조 ``` 📦src - ┣ 📂constant : 상수 관리 - ┃ ┣ 📜Messages.js : 입력, 입력힌트, 출력, 에러 메세지 관리 - ┃ ┗ 📜Options.js : 로또 가격, 로또 범위 최소, 최대 숫자, 상금, 게임 결과 등 관리 + ┣ 📂constant + ┃ ┣ 📜Messages.js + ┃ ┗ 📜Options.js ┣ 📂controller - ┃ ┗ 📜LottoController.js : 로또 게임 진행 + ┃ ┣ 📜LottoController.js + ┃ ┗ 📜step2-LottoController.js + ┣ 📂css + ┃ ┣ 📜reset.css + ┃ ┣ 📜style.css + ┃ ┗ 📜theme.css ┣ 📂domain ┃ ┣ 📜Lotto.js ┃ ┗ 📜LottoMachine.js @@ -42,8 +95,10 @@ npm run test ┃ ┣ 📂readLine ┃ ┃ ┗ 📜readLineAsync.js ┃ ┗ 📂validation + ┃ ┃ ┣ 📜BonusNumberValidator.js ┃ ┃ ┣ 📜LottoNumbersValidator.js ┃ ┃ ┣ 📜PurchaseAmountValidator.js + ┃ ┃ ┣ 📜RestartValidator.js ┃ ┃ ┗ 📜Validation.js ┣ 📂view ┃ ┣ 📜InputView.js @@ -51,11 +106,4 @@ npm run test ┣ 📜App.js ┣ 📜step1-index.js ┗ 📜step2-index.js - -📦__tests__ - ┣ 📜.gitkeep - ┣ 📜Lotto.test.js - ┣ 📜LottoMachine.test.js - ┗ 📜Random.test.js - ``` diff --git a/index.html b/index.html index e083bc1cbb..8a3816ab52 100644 --- a/index.html +++ b/index.html @@ -1,16 +1,109 @@ - + 🎱 행운의 로또 - Document + -
-

🎱 행운의 로또

+
+

🎱 행운의 로또

+
+ +
+
+
🎱 내 번호 당첨 확인 🎱
+ +
+ +
+ + +
+
+
+ + + + +
+
+ + + + + + - diff --git a/lotto-step2-1.png b/lotto-step2-1.png new file mode 100644 index 0000000000..51773c64ec Binary files /dev/null and b/lotto-step2-1.png differ diff --git a/lotto-step2-2.png b/lotto-step2-2.png new file mode 100644 index 0000000000..557301918b Binary files /dev/null and b/lotto-step2-2.png differ diff --git a/lotto-step2-3.png b/lotto-step2-3.png new file mode 100644 index 0000000000..413209c57d Binary files /dev/null and b/lotto-step2-3.png differ diff --git a/lotto-step2-4.png b/lotto-step2-4.png new file mode 100644 index 0000000000..35142f26bb Binary files /dev/null and b/lotto-step2-4.png differ diff --git a/package.json b/package.json index 8d4c72328f..d39859ca63 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test": "jest --watch --no-cache", "build-step1": "webpack --config step1.config.js", "start-step1": "npm run build-step1 && node dist/step1-bundle.js", + "build-step2": "webpack --mode production --config step2.config.js", "start-step2": "webpack serve --open --config step2.config.js" }, "devDependencies": { diff --git a/src/constant/Messages.js b/src/constant/Messages.js index cc3aef2ca0..2c1d4fc169 100644 --- a/src/constant/Messages.js +++ b/src/constant/Messages.js @@ -7,6 +7,7 @@ export const VARIABLE_ALIAS = { export const ERROR_MESSAGES = { prefix: '[ERROR] ', + isNotNumber: (name) => `${name}(은)는 숫자만 가능합니다.`, isNotInteger: (name) => `${name}(은)는 정수 값이어야 합니다.`, isNotAtLeast: (name, threshold) => `${name}(은)는 ${threshold} 이상이어야 합니다.`, hasNotLength: (name, length) => `${name}의 길이는 ${length}이어야 합니다.`, diff --git a/src/controller/step2-LottoController.js b/src/controller/step2-LottoController.js new file mode 100644 index 0000000000..7bb8e8bb63 --- /dev/null +++ b/src/controller/step2-LottoController.js @@ -0,0 +1,217 @@ +/* eslint-disable no-param-reassign */ +import { OUTPUT_MESSAGES } from '../constant/Messages.js'; +import LottoMachine from '../domain/LottoMachine.js'; +import BonusNumberValidator from '../util/validation/BonusNumberValidator.js'; +import LottoNumbersValidator from '../util/validation/LottoNumbersValidator.js'; +import PurchaseAmountValidator from '../util/validation/PurchaseAmountValidator.js'; + +class LottoController { + #lottoMachine; + + constructor() { + this.#lottoMachine = new LottoMachine(); + } + + async validateInput({ input, validateFunction, elementId }) { + const trimmedInput = typeof input === 'string' ? input.trim() : input; + const errorElement = document.getElementById(elementId); + + return this.applyValidation({ input: trimmedInput, validateFunction, errorElement }); + } + + // eslint-disable-next-line max-lines-per-function + async applyValidation({ input, validateFunction, errorElement }) { + try { + const result = validateFunction(input) ?? input; + errorElement.textContent = ''; + return result; + } catch (error) { + console.error(error); + errorElement.textContent = error.message; + return null; + } + } + + // 구입 금액 입력 + async validatePurchaseAmount(purchaseAmount) { + const validatedPurchaseAmount = await this.validateInput({ + input: purchaseAmount, + validateFunction: (input) => PurchaseAmountValidator.validate(input), + elementId: 'purchaseError' + }); + + return validatedPurchaseAmount; + } + + // 로또 발행 갯수 계산 및 출력 + getIssueQuantity(purchaseAmount) { + return this.#lottoMachine.calculateIssueQuantity(purchaseAmount); + } + + updateIssueQuantityText(issueQuantity) { + const lottoMainTitle = document.getElementById('lottoMainTitle'); + lottoMainTitle.textContent = `총 ${OUTPUT_MESSAGES.issueQuantity(issueQuantity)}`; + } + + // 로또 발행 갯수에 맞게 로또 발행 + generateLottos(issueQuantity) { + return this.#lottoMachine.issueLottos(issueQuantity); + } + + updateLottoNumbers(lottos) { + const lottoNumbersList = lottos.map((lotto) => lotto.getNumbers()); + this.clearLottoBox(); + + lottoNumbersList.forEach((lottoNumbers) => { + const lottoTicket = this.createLottoTicket(lottoNumbers); + this.addLottoTicketToBox(lottoTicket); + }); + } + + clearLottoBox() { + const lottoBox = document.getElementById('lottoBox'); + while (lottoBox.firstChild) { + lottoBox.firstChild.remove(); + } + } + + createLottoTicket(lottoNumbers) { + const lottoTicket = this.createLottoTicketDiv(); + lottoNumbers.forEach((number, index) => { + const numberDiv = this.createNumberDiv({ number, index, length: lottoNumbers.length }); + lottoTicket.appendChild(numberDiv); + }); + + return lottoTicket; + } + + createLottoTicketDiv() { + const lottoTicket = document.createElement('div'); + lottoTicket.className = 'lottoTicket'; + lottoTicket.textContent = `🎟️ `; + + return lottoTicket; + } + + createNumberDiv({ number, index, length }) { + const numberDiv = document.createElement('div'); + numberDiv.className = 'lottoTicketNumber'; + numberDiv.textContent = index < length - 1 ? `${number}, ` : number; + + return numberDiv; + } + + addLottoTicketToBox(lottoTicket) { + const lottoBox = document.getElementById('lottoBox'); + lottoBox.appendChild(lottoTicket); + } + + async purchaseLottos(purchaseAmount) { + const validatedPurchaseAmount = await this.validatePurchaseAmount(purchaseAmount); + if (validatedPurchaseAmount === null) { + return null; + } + + const lottos = this.issueAndGenerateLottos(validatedPurchaseAmount); + return lottos; + } + + issueAndGenerateLottos(purchaseAmount) { + const issueQuantity = this.getIssueQuantity(purchaseAmount); + const lottos = this.generateLottos(issueQuantity); + this.updateDisplayAfterPurchase(issueQuantity, lottos); + + return lottos; + } + + updateDisplayAfterPurchase(issueQuantity, lottos) { + this.showLottoSection(); + this.updateIssueQuantityText(issueQuantity); + this.updateLottoNumbers(lottos); + this.showWinningAndBonusSection(); + } + + showLottoSection() { + const lottoSection = document.getElementById('lottoSection'); + lottoSection.style.display = 'block'; + } + + showWinningAndBonusSection() { + const winningAndBonusSection = document.getElementById('winningAndBonusSection'); + winningAndBonusSection.style.display = 'block'; + } + + // 당첨 번호 입력 + getWinningNumbers() { + return Array.from(document.querySelectorAll('#winningInputContainer .inputRectangle')) + .map((input) => Number(input.value)) + .filter((number) => number !== ''); + } + + // eslint-disable-next-line max-lines-per-function + async validateWinningNumbers() { + const winningNumbers = this.getWinningNumbers(); + + const validatedWinningNumbers = await this.validateInput({ + input: winningNumbers, + validateFunction: (input) => { + LottoNumbersValidator.validate(input); + return input; + }, + elementId: 'winningAndBonusError' + }); + + return validatedWinningNumbers; + } + + // 보너스 번호 입력 + getBonusNumber() { + return Number(document.querySelector('#bonusInputContainer .inputRectangle').value); + } + + // eslint-disable-next-line max-lines-per-function + async validateBonusNumber(winningNumbers) { + const bonusNumber = this.getBonusNumber(); + + const validatedBonusNumber = await this.validateInput({ + input: bonusNumber, + validateFunction: (number) => { + BonusNumberValidator.validate(number, winningNumbers); + return number; + }, + elementId: 'winningAndBonusError' + }); + + return validatedBonusNumber; + } + + // 게임 실행 + async executeGame({ lottos, winningNumbers, bonusNumber }) { + // 로또 순위 결정 + const winningResult = this.#lottoMachine.determineLottoRanks({ + lottos, + winningNumbers, + bonusNumber + }); + // 수익률 계산 + const profitRate = this.#lottoMachine.calculateProfitRate(winningResult); + + this.displayWinningResult(winningResult, profitRate); + } + + // eslint-disable-next-line max-lines-per-function + displayWinningResult(winningResult, profitRate) { + const resultTable = document.getElementById('resultTable'); + const profitResult = document.getElementById('profitResult'); + + for (let i = 5; i >= 1; i -= 1) { + const row = resultTable.rows[6 - i]; + const cell3 = row.cells[2]; + cell3.innerHTML = winningResult[i.toString()]; + } + + profitResult.textContent = `당신의 ${OUTPUT_MESSAGES.profitRate(profitRate)}`; + } +} + +export default LottoController; diff --git a/src/css/modal.css b/src/css/modal.css new file mode 100644 index 0000000000..279bbc449d --- /dev/null +++ b/src/css/modal.css @@ -0,0 +1,77 @@ +#modalContainer { + position: fixed; + z-index: 1; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; +} + +#modalWrapper { + position: relative; + width: 350px; + height: 500px; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + gap: 33px; + background-color: var(--white); + border: 1px solid var(--gray); + border-radius: 4px; +} + +#modalTitle { + top: 40px; +} + +#resultTable { + width: 318px; + height: 247px; + text-align: center; + border-top: 1px solid var(--gray); +} + +#resultTable tr { + font-size: 15px; + border-bottom: 1px solid var(--gray); +} + +#resultTable td { + font-size: 15px; + text-align: center; + vertical-align: middle; +} + +#profitResult { + font-size: 15px; +} + +#resetButton { + width: 318px; + height: 36px; + padding: 0px 6px; + border: none; + border-radius: 4px; + background-color: var(--blue); + color: var(--white); + text-align: center; + cursor: pointer; +} + +#modalCloseButton { + width: 16px; + height: 16px; + position: absolute; + top: 16px; + right: 16px; + background-color: transparent; + border: none; + text-align: center; + cursor: pointer; + font-size: 16px; +} diff --git a/src/css/reset.css b/src/css/reset.css new file mode 100644 index 0000000000..a3f76817f3 --- /dev/null +++ b/src/css/reset.css @@ -0,0 +1,129 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, +body, +div, +span, +applet, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +pre, +a, +abbr, +acronym, +address, +big, +cite, +code, +del, +dfn, +em, +img, +ins, +kbd, +q, +s, +samp, +small, +strike, +strong, +sub, +sup, +tt, +var, +b, +u, +i, +center, +dl, +dt, +dd, +ol, +ul, +li, +fieldset, +form, +label, +legend, +table, +caption, +tbody, +tfoot, +thead, +tr, +th, +td, +article, +aside, +canvas, +details, +embed, +figure, +figcaption, +footer, +header, +hgroup, +menu, +nav, +output, +ruby, +section, +summary, +time, +mark, +audio, +video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +menu, +nav, +section { + display: block; +} +body { + line-height: 1; +} +ol, +ul { + list-style: none; +} +blockquote, +q { + quotes: none; +} +blockquote:before, +blockquote:after, +q:before, +q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/src/css/style.css b/src/css/style.css new file mode 100644 index 0000000000..10e2af43e1 --- /dev/null +++ b/src/css/style.css @@ -0,0 +1,187 @@ +@import './reset.css'; +@import './theme.css'; + +/* header */ +header { + width: 100%; + height: 64px; + background-color: var(--blue); +} + +header .title { + position: absolute; + top: 14px; + left: 130px; + color: var(--white); +} + +/* main */ +main { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: calc(100vh - 64px - 64px); + background-color: var(--light-gray); +} + +section { + position: relative; + width: 414px; + height: 727px; + background-color: var(--white); + border: 1px solid var(--gray); + + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; +} + +.section-div { + width: 100%; +} + +#mainTitleSection { + top: 30px; + width: 382px; + margin-top: 40px; + text-align: center; +} + +#purchaseInputSection { + width: 382px; + display: flex; + flex-direction: column; + gap: 4px; +} + +#purchaseInputSection div { + display: flex; + flex-direction: row; + gap: 16px; +} + +#purchaseInputField { + width: 310px; + height: 36px; + padding: 0px 8px; + border: 1px solid var(--gray); + border-radius: 4px; +} + +#purchaseSubmitButton { + width: 56px; + height: 36px; + padding: 0px 6px; + border: none; + border-radius: 4px; + background-color: var(--blue); + color: var(--white); + text-align: center; + cursor: pointer; +} + +#lottoSection { + width: 382px; + display: flex; + flex-direction: column; +} + +#lottoMainTitle { + margin-bottom: 8px; +} + +#lottoBox { + height: 276px; + display: flex; + flex-direction: column; + gap: 4px; + overflow-y: scroll; +} + +.lottoTicket { + min-height: 36px; + max-height: 36px; + display: flex; + align-items: center; + justify-content: flex-start; + flex-wrap: nowrap; + gap: 10px; + font-size: 15px; +} + +.lottoTicketNumber { + width: 24px; + text-align: center; +} + +#winningAndBonusSection { + width: 382px; + display: flex; + flex-direction: column; +} + +#winningAndBonusTitle { + white-space: nowrap; + margin-bottom: 8px; +} + +#winningAndBonusInputSection { + display: flex; + flex-direction: row; + align-items: flex-start; + justify-content: space-between; + margin-bottom: 4px; +} + +.winningAndBonusContainer { + display: flex; + flex-direction: column; + gap: 8px; +} + +#winningInputContainer { + display: flex; + flex-direction: row; + gap: 8px; +} + +.inputRectangle { + width: 34px; + height: 36px; + border-radius: 4px; + border: 1px solid var(--gray); + text-align: center; +} + +#winningAndBonusSubmitButton { + width: 100%; + height: 36px; + padding: 0px 6px; + margin-top: 10px; + border: none; + border-radius: 4px; + background-color: var(--blue); + color: var(--white); + text-align: center; + cursor: pointer; +} + +#bonusInputContainer { + align-items: flex-end; +} + +footer { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 64px; + border-top: 1px solid #4e5ba633; + background-color: var(--light-gray); +} + +footer .caption { + color: var(--blue); +} diff --git a/src/css/theme.css b/src/css/theme.css new file mode 100644 index 0000000000..c4018ef6e5 --- /dev/null +++ b/src/css/theme.css @@ -0,0 +1,62 @@ +/* Fonts */ +.title { + font-family: Roboto; + font-size: 24px; + font-weight: 800; + line-height: 36px; + text-align: left; +} + +.subtitle { + font-family: Roboto; + font-size: 20px; + font-weight: 600; + line-height: 24px; + text-align: left; +} + +.body { + font-family: Roboto; + font-size: 15px; + font-weight: 400; + line-height: 24px; + text-align: left; +} + +.placeholder { + font-family: Roboto; + font-size: 15px; + font-weight: 400; + line-height: 24px; + text-align: left; +} + +.caption { + font-family: Roboto; + font-size: 14px; + font-weight: 700; + line-height: 16px; + text-align: left; +} + +.error-message { + font-family: Roboto; + font-size: 12px; + font-weight: 400; + line-height: 16px; + text-align: left; + color: red; + min-height: 12px; + flex-wrap: nowrap; + white-space: nowrap; +} + +/* Colors */ +:root { + --blue: #4e5ba6; + --white: #ffffff; + --light-gray: #fcfcfd; + --gray: #b4b4b4; + --dark-gray: #8b8b8b; + --black: #000000; +} diff --git a/src/step2-index.js b/src/step2-index.js index f1527d9448..8f84381961 100644 --- a/src/step2-index.js +++ b/src/step2-index.js @@ -1,4 +1,58 @@ +/* eslint-disable max-lines-per-function */ /** * step 2의 시작점이 되는 파일입니다. * 노드 환경에서 사용하는 readline 등을 불러올 경우 정상적으로 빌드할 수 없습니다. */ + +import './css/theme.css'; +import './css/style.css'; +import './css/modal.css'; + +import LottoController from './controller/step2-LottoController'; + +const lottoController = new LottoController(); + +document.addEventListener('DOMContentLoaded', function () { + const purchaseForm = document.getElementById('purchaseInputSection'); + const winningAndBonusForm = document.getElementById('winningAndBonusSection'); + const modalContainer = document.getElementById('modalContainer'); + const modalCloseButton = document.getElementById('modalCloseButton'); + const resetButton = document.getElementById('resetButton'); + + let lottos = []; + + purchaseForm.addEventListener('submit', async function (event) { + event.preventDefault(); + const purchaseAmount = event.target.elements.purchaseInputField.value; + lottos = await lottoController.purchaseLottos(purchaseAmount); + }); + + winningAndBonusForm.addEventListener('submit', async function (event) { + event.preventDefault(); + const winningNumbers = await lottoController.validateWinningNumbers(); + if (winningNumbers === null) { + return; + } + + const bonusNumber = await lottoController.validateBonusNumber(winningNumbers); + if (bonusNumber === null) { + return; + } + + await lottoController.executeGame({ lottos, winningNumbers, bonusNumber }); + modalContainer.style.display = 'flex'; + }); + + modalContainer.addEventListener('click', function () { + modalContainer.style.display = 'none'; + }); + + modalCloseButton.addEventListener('click', function () { + modalContainer.style.display = 'none'; + }); + + resetButton.addEventListener('click', function () { + modalContainer.style.display = 'none'; + window.location.reload(); + }); +}); diff --git a/src/util/validation/PurchaseAmountValidator.js b/src/util/validation/PurchaseAmountValidator.js index 221e0a3f6b..1db8f8e3fb 100644 --- a/src/util/validation/PurchaseAmountValidator.js +++ b/src/util/validation/PurchaseAmountValidator.js @@ -6,8 +6,21 @@ class PurchaseAmountValidator { static name = VARIABLE_ALIAS.purchaseAmount; static validate(purchaseAmount) { - this.validateIsInteger(purchaseAmount); - this.validateIsAtLeast(purchaseAmount, OPTIONS.LOTTO.price); + this.validateIsNumber(purchaseAmount); + const numberInput = this.convertToNumber(purchaseAmount); + this.validateIsInteger(numberInput); + this.validateIsAtLeast(numberInput, OPTIONS.LOTTO.price); + return numberInput; + } + + static convertToNumber(purchaseAmount) { + return parseInt(purchaseAmount, 10); + } + + static validateIsNumber(value) { + if (!/^\d+$/.test(value)) { + throw new Error(`${ERROR_MESSAGES.prefix}${ERROR_MESSAGES.isNotNumber(this.name)}`); + } } static validateIsInteger(value) { diff --git a/step2.config.js b/step2.config.js index 9b9ffd9318..94ac187fe2 100644 --- a/step2.config.js +++ b/step2.config.js @@ -1,19 +1,20 @@ -const path = require("path"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const path = require('path'); + +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { - mode: "development", + mode: 'development', devServer: { - port: 9000, + port: 9000 }, - entry: "./src/step2-index.js", + entry: './src/step2-index.js', output: { - filename: "step2-bundle.js", - path: path.resolve(__dirname, "dist"), + filename: 'step2-bundle.js', + path: path.resolve(__dirname, 'dist') }, resolve: { - extensions: [".js", ".mjs", ".css"], + extensions: ['.js', '.mjs', '.css'] }, module: { rules: [ @@ -22,24 +23,24 @@ module.exports = { exclude: /node_modules/, use: [ { - loader: "babel-loader", + loader: 'babel-loader', options: { - presets: ["@babel/preset-env"], - }, - }, - ], + presets: ['@babel/preset-env'] + } + } + ] }, { test: /\.css$/, - use: ["style-loader", "css-loader"], - }, - ], + use: ['style-loader', 'css-loader'] + } + ] }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ - template: "./index.html", - }), + template: './index.html' + }) ], - devtool: "inline-source-map", + devtool: 'inline-source-map' };