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 @@
-
🎱 행운의 로또
+
+
+
+
+ 🎱 내 번호 당첨 확인 🎱
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🏆 당첨 통계 🏆
+
+
+ 일치 갯수 |
+ 당첨금 |
+ 당첨 갯수 |
+
+
+ 3개 |
+ 5,000 |
+ |
+
+
+ 4개 |
+ 50,000 |
+ |
+
+
+ 5개 |
+ 1,500,000 |
+ |
+
+
+ 5개+보너스볼 |
+ 30,000,000 |
+ |
+
+
+ 6개 |
+ 2,000,000,000 |
+ |
+
+
+
+
+
-
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'
};