diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..49e0fc6b4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/node_modules
+/package-lock.json
\ No newline at end of file
diff --git a/README.md b/README.md
index b884f6170..5c8e76b91 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,2 @@
-# Репозиторий для тестового задания на должность верстальщика
+# Form
+## Is available here: [form](https://master--musical-cocada-cdfd84.netlify.app/)
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..05068883a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,42 @@
+{
+ "name": "test-form",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "start": "set NODE_ENV=development&&webpack serve",
+ "dev": "set NODE_ENV=development&&webpack",
+ "build": "set NODE_ENV=production&&webpack",
+ "clean": "rd /s /q dist"
+ },
+ "browserslist": ">0.25%, not dead",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/18AN/test-form.git"
+ },
+ "author": "",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/18AN/test-form/issues"
+ },
+ "homepage": "https://github.com/18AN/test-form#readme",
+ "devDependencies": {
+ "@babel/core": "^7.18.9",
+ "@babel/preset-env": "^7.18.9",
+ "babel-loader": "^8.2.5",
+ "css-loader": "^6.7.1",
+ "html-loader": "^4.1.0",
+ "html-webpack-plugin": "^5.5.0",
+ "image-webpack-loader": "^8.1.0",
+ "mini-css-extract-plugin": "^2.6.1",
+ "sass": "^1.53.0",
+ "sass-loader": "^13.0.2",
+ "style-loader": "^3.3.1",
+ "webpack": "^5.73.0",
+ "webpack-cli": "^4.10.0",
+ "webpack-dev-server": "^4.9.3"
+ },
+ "dependencies": {
+ "@babel/polyfill": "^7.12.1"
+ }
+}
diff --git a/src/assets/Arrow-black.svg b/src/assets/Arrow-black.svg
new file mode 100644
index 000000000..665abfc14
--- /dev/null
+++ b/src/assets/Arrow-black.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/Arrow-blue.svg b/src/assets/Arrow-blue.svg
new file mode 100644
index 000000000..78fcccd13
--- /dev/null
+++ b/src/assets/Arrow-blue.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/fonts/PTSans-Regular.woff b/src/fonts/PTSans-Regular.woff
new file mode 100644
index 000000000..b0d0ea9f4
Binary files /dev/null and b/src/fonts/PTSans-Regular.woff differ
diff --git a/src/fonts/WorkSans-Light.woff b/src/fonts/WorkSans-Light.woff
new file mode 100644
index 000000000..11aaeda5e
Binary files /dev/null and b/src/fonts/WorkSans-Light.woff differ
diff --git a/src/fonts/WorkSans-Medium.woff b/src/fonts/WorkSans-Medium.woff
new file mode 100644
index 000000000..02876d1ed
Binary files /dev/null and b/src/fonts/WorkSans-Medium.woff differ
diff --git a/src/fonts/WorkSans-Regular.woff b/src/fonts/WorkSans-Regular.woff
new file mode 100644
index 000000000..896af6ea3
Binary files /dev/null and b/src/fonts/WorkSans-Regular.woff differ
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 000000000..2340d77f7
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+ Test Form
+
+
+
+
+
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 000000000..d8e179e0a
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,45 @@
+"use strict"
+
+import './index.html';
+import './index.scss';
+import {showPopup,hidePopup, formValidate} from './modules/functions';
+import {svg,button,popup,form,date,nationality} from './modules/consts';
+
+//Making svg animation start right after all styles loaded
+document.addEventListener('DOMContentLoaded', function(){
+ svg.classList.add('hide');
+})
+
+window.addEventListener('load', function(){
+ svg.classList.remove('hide');
+})
+
+//Removing appearing animation from button after it ends
+setTimeout(() => {
+ button.classList.add('opacity');
+ button.classList.remove('appear','twelfth');
+}, 6500);
+
+popup.addEventListener('click', hidePopup);
+
+form.addEventListener('submit', formSend);
+function formSend(e){
+ e.preventDefault();
+ let valid = formValidate();
+
+ if(valid === 0){
+ let formData = new FormData(form);
+ formData.append('nationality', nationality);
+ formData.append('date', date);
+ fetch('https://jsonplaceholder.typicode.com/posts', {
+ method: 'POST',
+ body: JSON.stringify(formData),
+ headers: {
+ 'Content-type': 'application/json; charset=UTF-8',
+ },
+ })
+ .then(()=>showPopup())
+ .then(()=>form.reset())
+ .catch((error)=>console.log(error))
+ }
+}
\ No newline at end of file
diff --git a/src/index.scss b/src/index.scss
new file mode 100644
index 000000000..5548b3cf1
--- /dev/null
+++ b/src/index.scss
@@ -0,0 +1,14 @@
+@import 'normalize/normalize.scss';
+@import 'style/fonts.scss';
+@import 'style/var.scss';
+@import 'style/general.scss';
+@import 'style/header.scss';
+@import 'style/main.scss';
+@import 'style/form.scss';
+@import 'style/item.scss';
+@import 'style/select.scss';
+@import 'style/options.scss';
+@import 'style/popup.scss';
+@import 'style/error.scss';
+@import 'style/animations.scss';
+@import 'style/media.scss';
diff --git a/src/modules/consts.js b/src/modules/consts.js
new file mode 100644
index 000000000..bdcac59d7
--- /dev/null
+++ b/src/modules/consts.js
@@ -0,0 +1,14 @@
+import { dateAssemble } from "./functions";
+
+export const form = document.getElementById('form');
+export const nationality = document.getElementById('nationality').textContent;
+export const day = document.querySelector('.day');
+export const mounth = document.querySelector('.mounth');
+export const year = document.querySelector('.year');
+export const popup = document.querySelector('.popup');
+export const wrapper = document.querySelector('.wrapper');
+export const image = document.getElementById('valid');
+export const emailWarning = document.getElementById('email-warn');
+export const button = document.querySelector('.form__button');
+export const date = dateAssemble(day,mounth,year);
+export const svg = document.getElementById('svg');
\ No newline at end of file
diff --git a/src/modules/functions.js b/src/modules/functions.js
new file mode 100644
index 000000000..34303e8f5
--- /dev/null
+++ b/src/modules/functions.js
@@ -0,0 +1,104 @@
+"use strict"
+import { button, emailWarning, image, popup, wrapper } from "./consts";
+import { passwordRegEx, emailRegEx, mismatch, uncorrect, wrongEmail } from "./strings";
+
+function addError(input){
+ input.parentElement.classList.add('error');
+}
+
+function removeError(input){
+ input.parentElement.classList.remove('error');
+}
+
+function testEmail(input){
+ return !emailRegEx.test(input.value);
+}
+function testPassword(input){
+ return !passwordRegEx.test(input.value);
+}
+function compare(array){
+ let r =(array[0] === array[1]);
+ return !r ;
+}
+export function dateAssemble(...array){
+ let date = '';
+ array.forEach(function(item){
+ date = date + item.textContent + '.';
+ })
+ return date;
+}
+export function showPopup(){
+ popup.classList.add('show');
+ wrapper.classList.add('hide');
+}
+
+export function hidePopup(){
+ popup.classList.remove('show');
+ wrapper.classList.remove('hide');
+}
+
+function showImage(){
+ image.classList.add('block');
+}
+
+function hideImage(){
+ image.classList.remove('block');
+}
+
+function shake(){
+ button.classList.add('shake');
+ setTimeout(()=>{removeShake()},500);
+
+}
+function removeShake(){
+ button.classList.remove('shake');
+}
+
+export function formValidate(){
+ let error = 0;
+ let formCheck = document.querySelectorAll('.check');
+ let passwords = [];
+ let passwordElements = [];
+
+ for (let i = 0; i < formCheck.length; i++){
+ let input = formCheck[i];
+ removeError(input);
+
+ if(input.id === 'email'){
+ if(testEmail(input)){
+ hideImage();
+ emailWarning.textContent = wrongEmail;
+ addError(input);
+ error++;
+ }else{
+ showImage();
+ emailWarning.textContent='';
+ }
+ }
+ else{
+ passwordElements.push(input);
+ passwords.push(input.value);
+ if(testPassword(input)){
+ input.closest('.item').lastElementChild.textContent = uncorrect;
+ addError(input);
+ error++
+ }else{
+ input.closest('.item').lastElementChild.textContent='';
+ if(passwords.length === 2){
+ if(compare(passwords)){
+ passwordElements.forEach(input => {
+ input.closest('.item').lastElementChild.textContent = mismatch;
+ addError(input);
+ error++
+ });
+ }
+ }
+ }
+ }
+
+ }
+ if(error!==0){
+ shake();
+ }
+ return error;
+}
\ No newline at end of file
diff --git a/src/modules/strings.js b/src/modules/strings.js
new file mode 100644
index 000000000..98c06b634
--- /dev/null
+++ b/src/modules/strings.js
@@ -0,0 +1,5 @@
+export const uncorrect = 'Password must contain at least 8 characters, uppercase and lowercase letters, and numbers';
+export const wrongEmail = 'wrong e-mail address';
+export const mismatch = 'passwords do not match';
+export const passwordRegEx = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;
+export const emailRegEx = /^\w+@\w+\.\w+$/;
diff --git a/src/normalize/normalize.scss b/src/normalize/normalize.scss
new file mode 100644
index 000000000..c604a77c4
--- /dev/null
+++ b/src/normalize/normalize.scss
@@ -0,0 +1,59 @@
+/* Указываем box sizing */
+*,
+*::before,
+*::after {
+ box-sizing: border-box;
+}
+
+/* Убираем внутренние отступы */
+ul[class],
+ol[class],
+input,
+button {
+ padding: 0;
+}
+
+/* Убираем внешние отступы */
+body,
+h1,
+h2,
+h3,
+h4,
+p,
+ul[class],
+ol[class],
+li,
+figure,
+figcaption,
+blockquote,
+dl,
+dd {
+ margin: 0;
+}
+
+/* Выставляем основные настройки по-умолчанию для body */
+body {
+ min-height: 100vh;
+ scroll-behavior: smooth;
+ text-rendering: optimizeSpeed;
+ line-height: 1.5;
+}
+
+/* Элементы a, у которых нет класса, сбрасываем до дефолтных стилей */
+a:not([class]) {
+ text-decoration-skip-ink: auto;
+}
+
+/* Упрощаем работу с изображениями */
+img {
+ max-width: 100%;
+ display: block;
+}
+
+/* Наследуем шрифты для инпутов и кнопок */
+input,
+button,
+textarea,
+select {
+ font: inherit;
+}
\ No newline at end of file
diff --git a/src/style/animations.scss b/src/style/animations.scss
new file mode 100644
index 000000000..254116a73
--- /dev/null
+++ b/src/style/animations.scss
@@ -0,0 +1,87 @@
+@keyframes shake {
+ 0%{
+ transform: translateX(0em);
+ }
+ 25%{
+ transform: translateX(-1em);
+ }
+ 50%{
+ transform: translateX(1em);
+ }
+ 75%{
+ transform: translateX(-1em);
+ }
+ 100%{
+ transform: translateX(0em);
+ }
+}
+
+
+@keyframes appear{
+ 0%{
+ transform: translateY(1em);
+ opacity: 0;
+ }
+ 100%{
+ transform: translateY(0em);
+ opacity: 1;
+ }
+}
+
+.appear{
+ animation-name: appear;
+ animation-duration: 1000ms;
+ animation-fill-mode: forwards;
+}
+
+.first{
+ animation-delay: 0ms;
+}
+.second{
+ animation-delay: 500ms;
+}
+.third{
+ animation-delay: 1000ms;
+}
+.fourth{
+ animation-delay: 1500ms;
+}
+.fifth{
+ animation-delay: 2000ms;
+}
+.sixth{
+ animation-delay: 2500ms;
+}
+.seventh{
+ animation-delay: 3000ms;
+}
+.eighth{
+ animation-delay: 3500ms;
+}
+.ninth{
+ animation-delay: 4000ms;
+}
+.tenth{
+ animation-delay: 4500ms;
+}
+.eleventh{
+ animation-delay: 5000ms;
+}
+.twelfth{
+ animation-delay: 5500ms;
+}
+.shake{
+ animation-name: shake;
+ animation-duration: 500ms;
+}
+
+.opacity{
+ opacity: 1;
+}
+
+.svg{
+ position: absolute;
+ right: 0;
+ bottom: -0.4em;
+ z-index: 0;
+}
\ No newline at end of file
diff --git a/src/style/error.scss b/src/style/error.scss
new file mode 100644
index 000000000..c6af9c69e
--- /dev/null
+++ b/src/style/error.scss
@@ -0,0 +1,12 @@
+.error{
+ border-bottom: 0.05em solid $red;
+ &>.item__input{
+ color: $lightRed;
+ }
+}
+.warning{
+ font-size: 0.85em;
+ max-width: 14em;
+ line-height: 1.16em;
+ color: $lightRed;
+}
\ No newline at end of file
diff --git a/src/style/fonts.scss b/src/style/fonts.scss
new file mode 100644
index 000000000..98453cf92
--- /dev/null
+++ b/src/style/fonts.scss
@@ -0,0 +1,20 @@
+@font-face {
+ font-family: 'WorkSans-Regular';
+ src: url('fonts/WorkSans-Regular.woff') format('woff');
+ font-display: swap;
+}
+@font-face {
+ font-family: 'WorkSans-Medium';
+ src: url('fonts/WorkSans-Medium.woff') format('woff');
+ font-display: swap;
+}
+@font-face {
+ font-family: 'PTSans-Regular';
+ src: url('fonts/PTSans-Regular.woff') format('woff');
+ font-display: swap;
+}
+@font-face {
+ font-family: 'WorkSans-Light';
+ src: url('fonts/WorkSans-Light.woff') format('woff');
+ font-display: swap;
+}
\ No newline at end of file
diff --git a/src/style/form.scss b/src/style/form.scss
new file mode 100644
index 000000000..8b53deaee
--- /dev/null
+++ b/src/style/form.scss
@@ -0,0 +1,50 @@
+.form{
+ min-height: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ font-size: 1em;
+ font-family: 'WorkSans-Regular','Arial', sans-serif;
+}
+
+.form__header{
+ font-family: 'WorkSans-Medium','Arial',sans-serif;
+ font-size: 1.57em;
+ line-height: 1.18em;
+ opacity: 0;
+}
+
+.form__hint{
+ font-family: 'WorkSans-Light', 'Arial', sans-serif;
+ line-height: 1.14em;
+ margin-bottom: 1.3em;
+ opacity: 0;
+}
+
+.form__container{
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+ min-width: 16.75em;
+ align-items: center;
+}
+
+.form__button{
+ font-family: 'PTSans-Regular', 'Arial', sans-serif;
+ font-size: 0.87em;
+ line-height: 1.125em;
+ padding: 0.75em 2.5em;
+ border: none;
+ background-color: $blue;
+ cursor: pointer;
+ color: $white;
+ letter-spacing: .02em;
+ opacity: 0;
+}
+
+.form__link{
+ font-size: 0.92em;
+ font-family: 'WorkSans-Light','Arial', sans-serif;
+ line-height: 1.07;
+ opacity: 0;
+}
\ No newline at end of file
diff --git a/src/style/general.scss b/src/style/general.scss
new file mode 100644
index 000000000..89aaec9dc
--- /dev/null
+++ b/src/style/general.scss
@@ -0,0 +1,36 @@
+html{
+ font-size: 100%;
+}
+
+body{
+ font-family: 'WorkSans-Regular', 'Arial', sans-serif;
+ background-color: $background;
+ padding-top: 5vh;
+}
+input{
+ background-color: transparent;
+}
+a{
+ font-size: 0.92em;
+ font-family: 'WorkSans-Medium', 'Arial', sans-serif;
+ color: $blue;
+}
+
+.container{
+ display: flex;
+ max-width: 41.56em;
+ min-width: 21.66em;
+ min-height: 30em;
+ margin: 0 auto;
+ background-color: $white;
+}
+
+.wrapper{
+ position: relative;
+ min-width: 100%;
+ display: flex;
+ min-height: 100%;
+ z-index: 1;
+}
+
+
diff --git a/src/style/header.scss b/src/style/header.scss
new file mode 100644
index 000000000..038f0703e
--- /dev/null
+++ b/src/style/header.scss
@@ -0,0 +1,15 @@
+.header{
+ display: flex;
+ justify-content: end;
+ font-size: 3.06em;
+ font-family: 'PTSans-Regular', 'Arial', sans-serif;
+ width: 3.58em;
+ padding-bottom: 0.44em;
+ background-color: $blue;
+ color: $white;
+ z-index: 1;
+ & > div{
+ writing-mode: vertical-lr;
+ transform: rotate(180deg);
+ }
+}
\ No newline at end of file
diff --git a/src/style/item.scss b/src/style/item.scss
new file mode 100644
index 000000000..1b337ba70
--- /dev/null
+++ b/src/style/item.scss
@@ -0,0 +1,26 @@
+.item{
+ min-width: 47.7%;
+ margin-bottom: 1.03em;
+ opacity: 0;
+ &__title{
+ font-size: 0.85em;
+ font-family: 'WorkSans-Light','Arial', sans-serif;
+ line-height: 1.16em;
+ color: $gray;
+ margin-bottom: 0.4em;
+ }
+ &__container{
+ display: flex;
+ align-items: center;
+ min-width: 16.75em;
+ justify-content: space-between;
+ border-bottom: 0.05em solid $border;
+ }
+ &__input{
+ outline: none;
+ padding-bottom: 0.3em;
+ line-height: 1.14em;
+ min-width: 12em;
+ border: none;
+ }
+}
\ No newline at end of file
diff --git a/src/style/main.scss b/src/style/main.scss
new file mode 100644
index 000000000..7a0f162ff
--- /dev/null
+++ b/src/style/main.scss
@@ -0,0 +1,7 @@
+.main{
+ width: 100%;
+ font-size: 0.875em;
+ padding: 2.4em 1.31em 2.03em;
+ background-color: transparent;
+ z-index: 1;
+}
\ No newline at end of file
diff --git a/src/style/media.scss b/src/style/media.scss
new file mode 100644
index 000000000..7bcbb1030
--- /dev/null
+++ b/src/style/media.scss
@@ -0,0 +1,11 @@
+@media (max-width: 767px) {
+ body{
+ padding-top: 0;
+ }
+}
+
+@media (max-width: 480px) {
+ .container{
+ min-height: 100vh;
+ }
+}
\ No newline at end of file
diff --git a/src/style/options.scss b/src/style/options.scss
new file mode 100644
index 000000000..bcf9d4873
--- /dev/null
+++ b/src/style/options.scss
@@ -0,0 +1,39 @@
+.options{
+ display: flex;
+ &__radio{
+ display: none;
+ }
+}
+
+.options__radio:checked + .options__label::after{
+ transform: scale(1);
+}
+
+.options__label{
+ display: inline-flex;
+ padding-bottom: 0.3em;
+ position: relative;
+ min-width: 5em;
+ line-height: 1.14em;
+ &::before{
+ content: '';
+ flex: 0 0 12px;
+ height: 12px;
+ background-color: transparent;
+ border-radius: 50%;
+ border: 0.7px solid $blue;
+ align-self: center;
+ margin-right: 0.428em;
+ }
+ &::after{
+ content: '';
+ position: absolute;
+ width: 6px;
+ height: 6px;
+ background-color: $blue;
+ border-radius: 50%;
+ top: 5px;
+ left: 3px;
+ transform: scale(0);
+ }
+}
\ No newline at end of file
diff --git a/src/style/popup.scss b/src/style/popup.scss
new file mode 100644
index 000000000..d1c01dc4e
--- /dev/null
+++ b/src/style/popup.scss
@@ -0,0 +1,67 @@
+.popup{
+ display: none;
+ min-width: 100%;
+ min-height: 100%;
+}
+
+.popup__header{
+ display: flex;
+ justify-content: end;
+ font-size: 3.06em;
+ font-family: 'PTSans-Regular','Arial', sans-serif;
+ width: 3.58em;
+ padding-bottom: 0.44em;
+ background-color: $blue;
+ color: $white;
+ & > div{
+ writing-mode: vertical-lr;
+ transform: rotate(180deg);
+ }
+}
+
+.popup-main{
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ width: 100%;
+ font-size: 0.875em;
+ padding: 2.4em 1.31em 2.03em;
+ background-color: $white;
+}
+
+.popup-main__container{
+ padding-left: 3.07em;
+ padding-top: 12.71em;
+ min-width: 16.75em;
+}
+
+.popup-main__header{
+ font-family: "WorkSans-Medium",'Arial', sans-serif;
+ font-size: 1.57em;
+ line-height: 1.18em;
+}
+
+.popup-main__text{
+ margin-top: 0.78em;
+ line-height: 1.14em;
+ font-family: 'WorkSans-Light','Arial', sans-serif;
+}
+
+.popup-main__link{
+ font-size: 0.92em;
+ font-family: 'WorkSans-Light','Arial', sans-serif;
+ line-height: 1.07;
+ opacity: 1;
+ margin-bottom: 0.7em;
+}
+.show{
+ display: flex;
+}
+
+.hide{
+ display: none;
+}
+
+.block{
+ display: block;
+}
\ No newline at end of file
diff --git a/src/style/select.scss b/src/style/select.scss
new file mode 100644
index 000000000..3bcee2422
--- /dev/null
+++ b/src/style/select.scss
@@ -0,0 +1,39 @@
+.select{
+ display: flex;
+ min-width: 16.75em;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 0.05em $border solid;
+ &__item{
+ width: 12em;
+ line-height: 1.14em;
+ padding-bottom: 0.3em;
+ }
+}
+
+.date{
+ display: flex;
+ min-width: 16.75em;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ &__container{
+ padding-bottom: 0.3em;
+ line-height: 1.14em;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ border-bottom: 0.05em $border solid;
+ }
+}
+
+.day{
+ min-width: 4em;
+}
+
+.mounth{
+ min-width: 6.5em;
+}
+
+.year{
+ min-width: 4.83em;
+}
diff --git a/src/style/var.scss b/src/style/var.scss
new file mode 100644
index 000000000..78e531ca5
--- /dev/null
+++ b/src/style/var.scss
@@ -0,0 +1,8 @@
+$background: #F2F2F2;
+$blue: #5A61ED;
+$white: #FFFFFF;
+$gray: #7C7C7C;
+$border: #F2F2F2;
+$lightRed: #FF2222;
+$red: #FF2828;
+$animation: #F9FAFF;
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 000000000..7c25d0cc4
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,105 @@
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const path = require('path');
+
+const mode = process.env.NODE_ENV || 'development';
+const devMode = mode === 'development';
+const target = devMode ? 'web' : 'browserslist';
+const devtool = devMode ? 'source-map' : undefined;
+
+
+module.exports = {
+ mode,
+ target,
+ devtool,
+ devServer:{
+ port: 7000,
+ open: true,
+ hot: true,
+ },
+ entry: ["@babel/polyfill", path.resolve(__dirname, 'src', 'index.js')],
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ clean: true,
+ filename: '[name].[contenthash].js',
+ assetModuleFilename: 'asset/[name][ext]',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: path.resolve(__dirname, 'src', 'index.html')
+ }),
+ new MiniCssExtractPlugin({
+ filename: '[name].[contenthash].css'
+ }),
+ ],
+ module: {
+ rules: [
+ //Loading HTML
+ {
+ test: /\.html$/i,
+ loader: 'html-loader',
+ },
+ //Loadding CSS
+ {
+ test: /\.(c|sa|sc)ss$/i,
+ use: [
+ devMode ? "style-loader" : MiniCssExtractPlugin.loader,
+ "css-loader",
+ "sass-loader"
+ ],
+ },
+ //Loading fonts
+ {
+ test: /\.woff2?$/i,
+ type: 'asset/resource',
+ generator: {
+ filename: "fonts/[name][ext]"
+ }
+ },
+ //Loading images
+ {
+ test: /\.(jpe?g|svg|png|gif)?$/i,
+ use: [
+ {
+ loader: 'image-webpack-loader',
+ options: {
+ mozjpeg: {
+ progressive: true,
+ },
+ // optipng.enabled: false will disable optipng
+ optipng: {
+ enabled: false,
+ },
+ pngquant: {
+ quality: [0.65, 0.90],
+ speed: 4
+ },
+ gifsicle: {
+ interlaced: false,
+ },
+ // the webp option will enable WEBP
+ webp: {
+ quality: 75
+ }
+ }
+ }
+ ],
+ type: 'asset/resource',
+ generator: {
+ filename: "assets/[name][ext]"
+ }
+ },
+ //Loading js(Babel)
+ {
+ test: /\.m?js$/i,
+ exclude: /(node_modules|bower_components)/,
+ use: {
+ loader: 'babel-loader',
+ options: {
+ presets: ['@babel/preset-env']
+ }
+ }
+ },
+ ]
+ }
+}
\ No newline at end of file