ToolsWebpack

Теги

полныйконфиг - полная версия конфига webpack

Написание базового приложения

Этот скрипт реализует функционал отправки сообщения в JSON-формате

Post.js

export default class Post {  
   constructor(title, img) {  
      this.title = title;  
      this.img = img;  
      this.date = new Date();  
   }  
  
   toString() {  
      return JSON.stringify({  
         title: this.title,  
         date: this.date.toJSON(),  
         img: this.img,  
      });  
   }  
}

Этот скрипт инициализирует новую отправку сообщения и вывод в консоль

index.js

const post = new Post("Webpack Post Title");  
  
console.log("post to string", post.toString);

Уже этот скрипт не связан с работой самого сайта - он считает клики на странице и позволяет нам их вывести в нужный момент

Analytics.js

function createAnalytics() {  
   let counter = 0;  
   let isDestroyed = false;  
  
   const listener = () => counter++;  
  
   document.addEventListener("click", listener);  
  
   return {  
      destroy() {  
         document.addEventListener("click", listener);  
         isDestroyed = true;  
      },  
      getClicks() {  
         if (isDestroyed) {  
            return "Analytics is destroyed";  
         }  
         return counter;  
      },   
    };  
}  
  
window.analytics = createAnalytics();

Тут уже в правильном порядке подключаем скрипты и создаём основу сайта

index.html

<!DOCTYPE html>  
<html lang="en">  
  
<head>  
   <meta charset="UTF-8">  
   <meta http-equiv="X-UA-Compatible" content="IE=edge">  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>Webpack</title>  
   <script src="analytics.js"></script>  
</head>  
  
<body>  
   <div class="container">  
      <h1>WP Course</h1>  
   </div>  
  
   <script src="Post.js"></script>  
   <script src="index.js"></script>  
</body>  
  
</html>

А теперь попробуем понять, что с этим приложением не так:

  • Нам нужно подключать очень много скриптов в наш index.html
  • Нам нужно обязательно учитывать последовательность подключенных скриптов к странице (потому что в неправильном порядке вылезет ошибка)

Инициализация приложения

Инициализируем node, через который и установим в дальнейшем Webpack

npm init

Установка Webpack

Устанавливаем webpack для разработки (-D)

npm install -D webpack webpack-cli
  • webpack - это сам основной функционал webpack
  • webpack-cli - это его команды в консоли

Базовая настройка Webpack

Это минимальный конфиг для запуска webpack

полныйконфиг webpack.config.js

// Это модуль, который хранит в себе путь до нашего проекта  
const path = require("path");  
  
// WP принимает в себя те опции, которые мы сюда вставим и по ним будет собирать наш проект  
module.exports = {  
   // Указываем начальный файл нашего проекта, в который и будет всё импортироваться  
   entry: "./src/index.js",  
   // Параметры вывода webpack  
   output: {  
      // Имя выводимого файла  
      filename: "bundle.js",  
      // тут уже указываем: путь до проекта и имя папки, в которую будут компилироваться файлы  
      path: path.resolve(__dirname, "dist"), //__dirname - системная переменная, которая указывает на текущее положение  
   },  
};

Команда для единоразового вызова компиляции webpack

webpack

И теперь после подключения выходного файла к index.html webpack скомпилирует файл со всеми экспортами и импортами. Первыми в выходном файле всегда идут иммитации экспортов и импортов и сами exports/imports, которые мы делали. Уже только потом идёт сам код.

index.html

<script src="bundle.js"></script>

Паттерны

Но в прошлом варианте у нас выпадал файл Analytics.js, так как он не был никак связан через импорты с основной точкой входа. Чтобы исправить ситуацию, можно назначить несколько точек входа (определить несколько чанков) и задать паттерн для имени выводимых файлов

webpack.config.js

module.exports = {  
   mode: "development",  
   entry: {  
      // так же может быть несколько точек входа в приложение  
      main: "./src/index.js", // основной чанк  
      analytics: "./src/analytics.js", // побочный чанк  
   },  
   output: {  
		// Тут уже задаётся паттерн [name]
		filename: "[name].bundle.js",  
		path: path.resolve(__dirname, "dist"),  
   },  
};

И так же нужно будет немного подправить импорты скриптов в HTML-файл

Однако мы можем столкнуться с той проблемой, что мы обновили скрипт, а он со своим именем уже захэшировался у пользователя в браузере и уже не обновляется - это может привести к неожиданным поломкам, поэтому стоит добавить ещё один паттерн, который будет основываться на внутреннем содержимом файла

[contenthash] - будет давать имя, основываясь на его хэше

webpack.config.js

output: {  
   filename: "[name].[contenthash].js",  
   path: path.resolve(__dirname, "dist"),  
},

И теперь при каждом обновлении мы будем получать новый файл

Плагины

Для примера установим плагин, который позволяет вебпаку компилировать не только JS-файлы, но и HTML со всеми нужными входными данными

Установка плагина:

npm install -D html-webpack-plugin

Подключение плагина: webpack.config.js

const path = require("path");  
 
// подключение плагина в вебпак  
const HTMLWebpackPlugin = require("html-webpack-plugin"); 
  
module.exports = {  
   mode: "development",  
   entry: {  
      // так же может быть несколько точек входа в приложение  
      main: "./src/index.js", // основной чанк  
      analytics: "./src/analytics.js", // побочный чанк  
   },  
   output: {  
      filename: "[name].[contenthash].js",  
      path: path.resolve(__dirname, "dist"),  
   },  
   // Здесь мы задаём список плагинов, которые мы подключаем в вебпак  
   plugins: [  
      new HTMLWebpackPlugin() // инициализируем плагин в вебпаке  
   ]  
};

Как можно увидеть, сам плагин генерирует новый index.html из имеющегося в src и подставляет все нужные импорты скриптов, которые в свою очередь компилируются со своим хешем

Так же мы можем настраивать внутренности тегов в HTML

webpack.config.js

plugins: [  
   new HTMLWebpackPlugin({  
      title: 'webpack valery' // дали тайтл 
   })
]

Работа с HTML

Так же мы можем указать плагину, который компилирует HTML, какой файл будет являться для него темплейтом, который будет являться отображением сайта. Свойство template определяет, на примере какого файла генерировать основной HTML. Так же в основной HTML будут вложены все нужные импорты

webpack.config.js

const path = require("path");  
const HTMLWebpackPlugin = require("html-webpack-plugin");  
  
module.exports = {  
   mode: "development",  
   entry: {  
      main: "./src/index.js",  
      analytics: "./src/analytics.js",  
   },  
   output: {  
      filename: "[name].[contenthash].js",  
      path: path.resolve(__dirname, "dist"),  
   },  
   plugins: [  
      new HTMLWebpackPlugin({  
         template: "./src/index.html", // можем указать основной HTML
      })  
   ]
};

Оригинальный HTML в src (без каких-либо импортов)

<!DOCTYPE html>  
<html lang="en">  
<head>  
   <meta charset="UTF-8">  
   <meta http-equiv="X-UA-Compatible" content="IE=edge">  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>Webpack</title>  
</head>  
<body>  
   <div class="container">  
      <h1>WP Course</h1>  
   </div>  
</body>  
</html>

То , что сгенерировал webpack (вебпак сам добавил импорты на актуальные скрипты)

<!DOCTYPE html>  
<html lang="en">  
<head>  
   <meta charset="UTF-8">  
   <meta http-equiv="X-UA-Compatible" content="IE=edge">  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>Webpack</title>  
<script defer src="main.a4fbcc6c859c6c9cd2a2.js"></script><script defer src="analytics.b0d68796ad2563de4d6c.js"></script></head>  
<body>  
   <div class="container">  
      <h1>WP Course</h1>  
   </div>  
</body>  
</html>

Очистка папки проекта

Устанавливаем плагин, который чистит проект от неиспользуемых файлов

npm i -D clean-webpack-plugin 

Подключаем его

webpack.config.js

const path = require("path");  
const HTMLWebpackPlugin = require("html-webpack-plugin");  
 
// подключаем плагин-очиститель
const {CleanWebpackPlugin} = require("clean-webpack-plugin");  
  
module.exports = {  
   mode: "development",  
   entry: {  
      main: "./src/index.js",  
      analytics: "./src/analytics.js",  
   },  
   output: {  
      filename: "[name].[contenthash].js",  
      path: path.resolve(__dirname, "dist"),  
   },  
   plugins: [  
      new HTMLWebpackPlugin({  
         template: "./src/index.html",  
      }),  
      // Новый плагин
      new CleanWebpackPlugin(), // инициализируем плагин
   ]  
};

И теперь в папке проекта чистятся неиспользуемые файлы

Сборка проекта

Так же мы можем задать свои собственные консольные команды, которые мы можем забиндить под короткие алиасы. Конкретно в файле package.json мы можем в свойстве "scripts" задать свои алисасы и им присвоить консольную команду

package.json

"scripts": {  
	"dev": "webpack --mode development",  
	"build": "webpack --mode production"  
},

И через команду npm run мы вызываем запуск определённого скрипта (тут - компиляция в development режиме)

npm run dev

автоматическаякомпиляция Так же мы можем задать автоматическую компиляцию изменений в файлах

"scripts": {  
  "dev": "webpack --mode development",  
  "build": "webpack --mode production",  
  // создаём команду, которая будет постоянно смотреть и компилировать файлы
  "watch": "webpack --mode development --watch"  
},

Контекст

Конкретно свойство context позволяет нам указать самостоятельно, от какой точки будет идти ориентирование в проекте. По умолчанию вебпак ориентируется от начальной папки нашего проекта. Если мы добавим контекст, то все пути, нам нужно будет прописывать относительно этого контекста. Это удобно, так как почти все пути до файлов мы прописываем внутри той же папки src

webpack.config.js

const path = require("path");  
const HTMLWebpackPlugin = require("html-webpack-plugin");  
const {CleanWebpackPlugin} = require("clean-webpack-plugin");  
  
module.exports = {  
   // говорит, где находятся исходники  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   output: {  
      filename: "[name].[contenthash].js",  
      path: path.resolve(__dirname, "dist"),  
   },  
   plugins: [  
      new HTMLWebpackPlugin({  
         template: "./index.html",  
      }),  
      new CleanWebpackPlugin(),  
   ]  
};

CSS-лоадеры

Лоадеры - это сущноси, которые добавляют дополнительный функционал вебпаку, который позволяет работать с другими видами файлов

Устанавливаем первым делом два лоадера

  • css-loader позволяет импортировать стили в JS
  • style-loader добавляет стили в секцию HEAD в HTML
npm i -D style-loader css-loader 

webpack.config.js

module.exports = {  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   output: {  
      filename: "[name].[contenthash].js",  
      path: path.resolve(__dirname, "dist"),  
   },  
   plugins: [  
      new HTMLWebpackPlugin({  
         template: "./index.html",  
      }),  
      new CleanWebpackPlugin(),  
   ],  
   
   // Задаём лоадеры  
   module: {  
      rules: [  
         {  
			// Тут задаётся паттерн поиска файла 
            // если нам попадаются файлы с таким расширением  
            test: /\.css$/,  
            // то нам нужно использовать такие лоадеры  
            // лоадеры срабатывают справа-налево            
            // первый лоадер позволяет импортировать стили, второй добавляет стили в секцию HEAD в HTML            
            use: ['style-loader', 'css-loader'],  
         }  
      ],  
   }  
   
};

index.html

import Post from './Post';  
import './styles/style.css';  // подключение стилей
  
const post = new Post("Webpack Post Title");  
  
console.log("post to string", post.toString);

style.css

.container {  
    padding-top: 2rem;  
    max-width: 1000px;  
    margin: 0 auto;  
}  
  
h1 {  
    text-align: center;  
    color: red;  
    font-weight: 700;  
    font-size: 60px;  
}

Работа с JSON

WP позволяет нам подключать json-файлы без дополнительных запросов обычным импортом

// Подключение JSON-файла
import json from './assets/json.json';  
console.log('json: ', json);

Работа с файлами

Во-первых, нужно установить новый лоадер, который обрабатывает файлы

npm install file-loader -g

Во-вторых, нужно описать для него правила. В правилах мы укажем варианты расширений изображений, которые могут встречаться в нашем проекте

webpack.config.js

module: {  
   rules: [  
      {  
         test: /\.css$/,  
         use: ['style-loader', 'css-loader'],  
      },  
      // Описываем новый лоадер для WP
      {  
		// при работе с файлами расширений:
         test: /\.(png|svg|jpg|gif)$/,  
         // использовать:
         use: ['file-loader']  
      }  
   ],  
}

Использование в JS

import WebpackLogo from './assets/webpack-logo.png';
  
const post = new Post("Webpack Post Title", WebpackLogo);

Использование в CSS (создаст в dist отдельный файл с изображением)

.logo {  
    background-image: url("../assets/webpack-logo.png");  
    background-size: cover;  
    height: 200px;  
    width: 200px;  
    margin: 0 auto;  
}
<div class="logo"></div>

Работа со шрифтами

Дополнение конфига для работы со шрифтами: webpack.config.js

rules: [  
   {  
      test: /\.css$/,  
      use: ['style-loader', 'css-loader']  
   },  
   {  
      test: /\.(png|jpe?g|gif)$/i,  
      use: ['file-loader'],  
   },  
   // Тут нужно прописать правила работы file-loader со шрифтами
   {  
	   // типы шрифтов
      test: /\.(ttf|eot|woff|woff2)$/,  
      use: ['file-loader']  
   }  
],

Подключаем стили с нашего компьютера font.css

@font-face {  
    font-family: 'Roboto';  
    src: url('../assets/fonts/Roboto-Regular.ttf') format('truetype');  
}

Импортим файл в наш основной. Импорты работают почти так же как и в WP style.css

@import "font.css";  
  
body {  
    font-family: 'Roboto', sans-serif;  
}

Подключение CSS-библиотек

Попробуем установить библиотеку для css. Это нормализатор стилей под разные браузеры.

npm install normalize.css

Подключаем модуль так же через импорт. ~ в начале имени модуля говорит нам о том, что модуль нужно искать в папке: node_modules

@import "~normalize.css";

Защита от публикации пакета

Стандартно наш пакет имеет основную входную точку, которая определена в package.json, однако, чтобы защититься от публикации нам нужно заменить одну строчку

package.json

{  
  "name": "webpack",  
  "version": "1.0.0",  
  "description": "",  
  "main": "index.js",  // Это свойство нужно для публичных пакетов
  "scripts": {  
    "dev": "webpack --mode development",  
    "build": "webpack --mode production",  
    "watch": "webpack --mode development --watch"  
  },

А сейчас наш пакет будет защищён от публикации

{  
  "name": "webpack",  
  "version": "1.0.0",  
  "description": "",  
  "private": true,  // Это свойство делает наш пакет приватным
  "scripts": {  
    "dev": "webpack --mode development",  
    "build": "webpack --mode production",  
    "watch": "webpack --mode development --watch"  
  },

Работа с XML-файлами

Установка лоадера XML

npm install -D xml-loader

Добавление правил на XML

webpack.config.js

module: {  
   rules: [  
      {  
         test: /\.css$/,  
         use: ['style-loader', 'css-loader']  
      },  
      {  
         test: /\.(png|jpe?g|gif)$/i,  
         use: ['file-loader'],  
      },  
      {  
         test: /\.(ttf|eot|woff|woff2)$/,  
         use: ['file-loader']  
      }, 
      // настройки для xml-loader 
      {  
         test: /\.xml$/,  
         use: ['xml-loader']  
      }  
   ],  
}

Вывод в консоль и подключение

index.js

import json from './assets/json.json';  
import xml from './assets/data.xml';
 
console.log('json: ', json);  
console.log('XML: ', xml);

Работа с CSV-файлами

papaparse нужен для работы с парсингом файлов csv-loader лоадер, который умеет обрабатывать csv формат файлов

npm i -d papaparse
npm i -D csv-loader

webpack.config.js

{  
   test: /\.csv$/,  
   use: ['csv-loader'],  
}

Дополнительные настройки

Свойство resolve позволяет нам определить для Webpack, что ему нужно искать по умолчанию. То есть, если вложенное свойство extensions будет пустое, то мы должны будем всегда прописывать в import расширения файлов. Если мы добавим расширения в массив, то WP будет искать файл с подходящим расширением, даже если в импорте мы его не укажем

module.exports = {  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   output: {  
      filename: "[name].[contenthash].js",  
      path: path.resolve(__dirname, "public"),  
   },  
   resolve: {  
      // Тут мы должны сказать WP, какие расширения нужно понимать по умолчанию  
      extensions: ['.js', '.json', '.png'],  
   },  
   devServer: {  
      static: {  
         directory: path.join(__dirname, 'public'),  
      },  
      compress: true,  
      port: 9000,  
      open: true  
   },
// ....

С настройками выше наши импорты будут работать без указания расширения

import Post from './Post';  // .js
import json from './assets/json';  // .json
import WebpackLogo from './assets/webpack-logo'; // .png

Так же присутствует свойство alias, которое позволяет задать псевдоним для путей в наших импортах

resolve: {  
   extensions: ['.js', '.jsx', '.ts', '.tsx'],  
   alias: {  
      '@': path.resolve(__dirname, "src"),  
      '@components': path.resolve(__dirname, 'src/components'),  
      '@utilities': path.resolve(__dirname, 'src/utilities'),
      '@modules': path.resolve(__dirname, 'src/modules')  
   }  
},
// и теперь можно задать относительный путь
import sayHi from './models/script'; 
// или через алиасы сгенерировать абсолютный путь
import sayHic from '@models/script';
// @ заменит src - так же сделает абсолютный путь
import sayHic from '@/models/script';

Подключение JS-библиотек

npm i -S jquery

index.js

import * as $ from 'jquery';
$('pre').html(post.toString());

export default class Post {  
   constructor(title, img) {  
      this.title = title;  
      this.img = img;  
      this.date = new Date();  
   }  
  
   toString() {  
      return JSON.stringify({  
         title: this.title,  
         date: this.date.toJSON(),  
         img: this.img,  
      }, null, 2); // так же можно передать сюда параметры формата
   }  
}

Оптимизация

Представим, что у нас есть два файла, которые импортируют в себя jquery. Мы столкнёмся с проблемой, что оба этих файла будут в себя отдельно импортировать библиотеку. Это приведёт к дополнительным прибавкам к весу файлов

index.js

import * as $ from 'jquery';
$('pre').html(post.toString());

analytics.js

import * as $ from 'jquery';  
  
function createAnalytics() {  
   let counter = 0;  
   let isDestroyed = false;  
  
   const listener = () => counter++;  
  
   $(document).on("click", listener);  
  
   return {  
      destroy() {  
         $(document).off("click", listener);  
         isDestroyed = true;  
      },  
  
      getClicks() {  
         if (isDestroyed) {  
            return "Analytics is destroyed";  
         }  
         return counter;  
      },  
   };  
}  
  
window.analytics = createAnalytics();

Поэтому в WP есть свойство, которое позволяет настроить оптимизацию работы проекта. В нём мы можем объединять общие импорты в отдельные чанки, которые будут служить своего образа библиотеками (у нас будет один js, который будет хранить jquery)

module.exports = {  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   // Параметр оптимизации
   optimization: {  
      splitChunks: {  
         chunks: 'all'  
      }   
	},
// ....

Webpack-dev-server

devServer вебпака собирает наш проект так же как и при обычной сборке, но складывает все собранные файлы в оперативную память, что позволяет ему не обновляя страницу обновлять все модули и отображать изменения. Поэтому сервер WP используется только в режиме разработки. Чтобы получить конечный билд, нужно запустить production сборку вебпака.

Если не будет работать, то вместо -D нужно попробовать -g

npm i webpack-dev-server -D

Создаём новую команду для запуска

package.json

"scripts": {  
  "dev": "webpack --mode development",  
  "build": "webpack --mode production",  
  "watch": "webpack --mode development --watch", 
  // Команда стартует сервер вебпака
  "start": "webpack serve"  
},

Тут уже достаточно для запуска будет вписать такую команду:

npm start

Чтобы остановить работу процесса, достаточно нажать ctrl+c

webpack.config.js

module.exports = {  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   output: {  
      filename: "[name].[contenthash].js",  
      path: path.resolve(__dirname, "public"),  
   },  
   // Сюда вставляем активацию работы плагина devServer 
   devServer: {  
      static: {  
         directory: path.join(__dirname, 'public'),  
      },  
      compress: true,  // Если нужно компрессия файла
      port: 9000,  // Определяем порт
      open: true // Автоматически запускает страницу в браузере
   },  
   plugins: [  
      new HTMLWebpackPlugin({  
         template: "./index.html",  
      }),  
      new CleanWebpackPlugin(),  
   ],  
   module: {  
      rules: [  
         {  
            test: /\.css$/,  
            use: ['style-loader', 'css-loader']  
         },  
         {  
            test: /\.(png|jpe?g|gif)$/i,  
            use: ['file-loader'],  
         },  
         {  
            test: /\.(ttf|eot|woff|woff2)$/,  
            use: ['file-loader']  
         },  
         {  
            test: /\.xml$/,  
            use: ['xml-loader']  
         }  
      ],  
   }  
};

Копирования статических файлов

Так же мы можем указать нашему WP куда копировать статические файлы (которые мы, например, подключили только в HTML)

npm i -D copy-webpack-plugin
<head>  
   <meta charset="UTF-8">  
   <meta http-equiv="X-UA-Compatible" content="IE=edge">  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>Webpack</title>  
   <link rel="stylesheet" href="styles/style.css">  
   <!--добавляем фавиконку-->
   <link rel="icon" href="favicon.ico" type="image/icon">  
</head>

webpack.config.js

// подключаем плагин
const CopyWebpackPlugin = require("copy-webpack-plugin");
 
// ....
 
plugins: [  
   new HTMLWebpackPlugin({  
      template: "./index.html",  
   }),  
   new CleanWebpackPlugin(),  
   // И теперь этот плагин перенесёт фотографию в выводимую папку
   new CopyWebpackPlugin({  
      patterns: [  
         {  
            from: path.resolve(__dirname, 'src/favicon.ico'),  
            to: path.resolve(__dirname, 'dist')  
         }  
      ],  
   }),  
],
 
// ....

Как итог, можно увидеть фавиконку, которую мы напрямую подключили в HTML

Сжатие CSS, HTML, JS

Минификация CSS Данный плагин будет выводить отдельные CSS файлы и нормально их компилировать

npm install --save-dev mini-css-extract-plugin

Производим небольшие настройки и добавляем плагин в конфиг

webpack.config.js

const MiniCSSExtractPlugin = require("mini-css-extract-plugin");
 
// ....
 
plugins: [  
   new HTMLWebpackPlugin({  
      template: "./index.html",  
   }),  
   new CleanWebpackPlugin(),  
   new CopyWebpackPlugin({  
      patterns: [  
         {  
            from: path.resolve(__dirname, 'src/favicon.ico'),  
            to: path.resolve(__dirname, 'dist')  
         }  
      ],  
   }),  
   // Добавляем плагин, который будет минифицировать CSS
   new MiniCSSExtractPlugin({  
      // копируем из output путь и меняем расширение на '.css'
      filename: "[name].[contenthash].css",   
}),  
],  
module: {  
   rules: [  
      {  
         test: /\.css$/, 
         // Меняем 'style-loader' на лоадер минификатора 
         use: [MiniCSSExtractPlugin.loader, 'css-loader']  
      },  
      {  
         test: /\.(png|jpe?g|gif)$/i,  
         use: ['file-loader'],  
      },  
      {  
         test: /\.(ttf|eot|woff|woff2)$/,  
         use: ['file-loader']  
      },  
      {  
         test: /\.xml$/,  
         use: ['xml-loader']  
      },  
      {  
         test: /\.csv$/,  
         use: ['csv-loader'],  
      }  
   ],  
}

Убираем из подключений CSS, так как теперь он будет подключаться самостоятельно

<head>  
   <meta charset="UTF-8">  
   <meta http-equiv="X-UA-Compatible" content="IE=edge">  
   <meta name="viewport" content="width=device-width, initial-scale=1.0">  
   <title>Webpack</title>  
   <!--добавляем фавиконку-->  
   <link rel="icon" href="favicon.ico" type="image/icon">  
</head>

Вот так выглядит выходной HTML-файл

Тут мы дополнили

  1. как сам плагин, потому что в него можно передать объект с параметрами
  2. так и реализовали проверку режима для наших компонентов и если мы находимся в режиме разработки, то у нас будет активна смена модулей без перезагрузки страницы

webpack.config.js

// Эта переменная будет хранить в себе значение состояния, в котором находится сайт во время разработки  
const isDev = process.env.NODE_ENV === "development";
 
// ...
 
devServer: {  
   static: {  
      directory: path.join(__dirname, 'dist'),  
   },  
   compress: true,  
   port: 9000,  
   open: true,
   // перезагружает страницу если находимся в режиме разработчика    
   hot: isDev, 
},  
 
module: {  
   rules: [  
      {  
         test: /\.css$/,  
        // Так же можно более детально настроить плагин, так как первым параметром он позволяет в себя положить объект с самим лоадером и его опциями
         use: [{  
            loader: MiniCSSExtractPlugin.loader,  
            options: { 
		// hot module reload - меняет сущности без перезагрузки
	    // Если находимся в режиме разработчика, то будет активно  
               hmr: isDev,  
               reloadAll: true  
            }  
         }, 'css-loader']  
      },
      
// ....

Чтобы проверить работу смены окружений, нужно воспользоваться пакетом для их смены:

npm i -G cross-env

Строчками cross-env NODE_ENV=development мы определяем среду нашей разработки (продакшен или разработка). Этот код в скриптах позволит нам всегда контролировать среду, в которой мы находимся и определять работу некоторых наших функций вебпака

package.json

"scripts": {  
  "dev": "cross-env NODE_ENV=development webpack --mode development",  
  "build": "cross-env NODE_ENV=production webpack --mode production",  
  "watch": "cross-env NODE_ENV=development webpack --mode development --watch",  
  "start": "cross-env NODE_ENV=development webpack serve"  
},

Сам плагин для минификации CSS:

npm install css-minimizer-webpack-plugin --save-dev
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");  
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");   
  
// Эта переменная будет хранить в себе значение состояния, в котором находится сайт во время разработки  
const isDev = process.env.NODE_ENV === "development";  
const isProd = process.env.NODE_ENV === "production";  
  
const optimization = () => {  
   const config = {  
      splitChunks: {  
         chunks: 'all'  
      }   }  
 
   // Если режим продакшена
   if (isProd) {  
	   // то нужно добавить свойство minimizer с плагином минификации
      config.minimizer = [   
         new CssMinimizerPlugin() // сам минификатор CSS
      ]  
   }  
  
   return config;  
}
 
// ....
 
module.exports = {  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   // Тут нужно вложить функцию, которая сгенерирует наш объект оптимизации
   optimization: optimization(),
 
// ....

Минификация JS

npm install terser-webpack-plugin --save-dev

webpack.config.js

// Подключаем минификатор JS
const TerserPlugin = require("terser-webpack-plugin");  
  
// Эта переменная будет хранить в себе значение состояния, в котором находится сайт во время разработки  
const isDev = process.env.NODE_ENV === "development";  
const isProd = process.env.NODE_ENV === "production";  
  
const optimization = () => {  
   const config = {  
      splitChunks: {  
         chunks: 'all'  
      }   }  
  
   if (isProd) {  
      config.minimizer = [  
         new TerserPlugin(), // Подключаем минификатор JS  
         new CssMinimizerPlugin(),  
      ]  
   }  
  
   return config;  
}

Минификация HTML Уже этот код будет минифицировать HTML код при работе в режиме продакшена

webpack.config.js

// Если работаем в продакшн режиме...
const isProd = process.env.NODE_ENV === "production";
 
// ....
 
plugins: [  
   new HTMLWebpackPlugin({  
	   template: "./index.html",  
	   // Тут уже настраиваем минификацию
	   minify: {  
		   // ...то сжимаем все пробелы в коде
	      collapseWhitespace: isProd  
	   }  
	}),
 
// ...

Компиляция Less и Sass

Устанавливаем сам less, sass и их его лоадеры в наш проект

npm install less less-loader --save-dev
npm install sass-loader sass webpack --save-dev

Далее подключаем правило, которое будет компилировать less и sass

полныйконфиг wenpack.config.js

const path = require("path");  
const HTMLWebpackPlugin = require("html-webpack-plugin");  
const {CleanWebpackPlugin} = require("clean-webpack-plugin");  
const CopyWebpackPlugin = require("copy-webpack-plugin");  
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");  
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");  
const TerserPlugin = require("terser-webpack-plugin");  
  
// Эта переменная будет хранить в себе значение состояния, в котором находится сайт во время разработки  
const isDev = process.env.NODE_ENV === "development";  
const isProd = process.env.NODE_ENV === "production";  
  
const optimization = () => {  
   const config = {  
      splitChunks: {  
         chunks: 'all'  
      }   }  
  
   if (isProd) {  
      config.minimizer = [  
         new TerserPlugin(),  
         new CssMinimizerPlugin(),  
      ]  
   }  
  
   return config;  
}  
  
// эта функция будет генерировать наименование файла  
const filename = ext => isDev ? `[name].${ext}` : `[name].[hash].${ext}`;  
  
module.exports = {  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   optimization: optimization(),  
   output: {  
      filename: filename('js'),  
      path: path.resolve(__dirname, "dist"),  
   },  
   resolve: {  
      extensions: ['.js', '.jsx', '.ts', '.tsx'],  
      alias: {  
         '@': path.resolve(__dirname, "src"),  
         '@components': path.resolve(__dirname, 'src/components'),  
         '@utilities': path.resolve(__dirname, 'src/utilities')  
      }  
   },  
   devServer: {  
      static: {  
         directory: path.join(__dirname, 'dist'),  
      },  
      compress: true,  
      port: 9000,  
      open: true,  
      hot: isDev, // перезагружает страницу если находимся в режиме разработчика  
   },  
   plugins: [  
      new HTMLWebpackPlugin({  
         template: "./index.html",  
         minify: {  
            collapseWhitespace: isProd  
         }  
      }),  
      new CleanWebpackPlugin(),  
      new CopyWebpackPlugin({  
         patterns: [  
            {  
               from: path.resolve(__dirname, 'src/favicon.ico'),  
               to: path.resolve(__dirname, 'dist')  
            }  
         ],  
      }),  
      new MiniCSSExtractPlugin({  
         // копируем из output  
         filename: filename('css'),  
      }),  
   ],  
   module: {  
      rules: [  
         {  
            test: /\.css$/,  
            use: [MiniCSSExtractPlugin.loader, 'css-loader']  
         },
         // Тут подключаем SASS/SCSS  
         {  
            test: /\.s[ac]ss$/i,  
            use: [  
               "style-loader", // или MiniCSSExtractPlugin.loader
               "css-loader",  
               "sass-loader",  
            ],  
         },  
         // А тут подключаем LESS
         {  
            test: /\.less$/i,  
            use: [  
               // compiles Less to CSS  
               "style-loader", // или MiniCSSExtractPlugin.loader 
               "css-loader",  
               "less-loader",  
            ],  
         },  
         {  
            test: /\.(png|jpe?g|gif)$/i,  
            use: ['file-loader'],  
         },  
         {  
            test: /\.(ttf|eot|woff|woff2)$/,  
            use: ['file-loader']  
         },  
         {  
            test: /\.xml$/,  
            use: ['xml-loader']  
         },  
         {  
            test: /\.csv$/,  
            use: ['csv-loader'],  
         }  
      ],  
   }  
};

Стили Less

@border: 1px solid #ccc;  
  
.box {  
  padding: 1rem;  
  border-radius: 5px;  
  margin-top: 1rem;  
  border: @border;  
  
  h2 {  
    text-align: center;  
    color: darkblue;  
  }  
}

Стили SCSS

$border: 1px solid #ccc;  
  
.card {  
  padding: 1rem;  
  border-radius: 5px;  
  margin-top: 1rem;  
  border: $border;  
  
  h2 {  
    text-align: center;  
    color: darkred;  
  }  
}

подключаем стили в файл скрипта index.js

import './styles/style.css';  
import './styles/less.less';  
import './styles/scss.scss';

Оптимизация

Чтобы сократить повторяющийся код, можно вынести его в отдельные функции, в которые будем вкладывать изменения

полныйконфиг webpack.config.js

const path = require("path");  
const HTMLWebpackPlugin = require("html-webpack-plugin");  
const {CleanWebpackPlugin} = require("clean-webpack-plugin");  
const CopyWebpackPlugin = require("copy-webpack-plugin");  
const MiniCSSExtractPlugin = require("mini-css-extract-plugin");  
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");  
const TerserPlugin = require("terser-webpack-plugin");  
  
// Эта переменная будет хранить в себе значение состояния, в котором находится сайт во время разработки  
const isDev = process.env.NODE_ENV === "development";  
const isProd = process.env.NODE_ENV === "production";  
  
const optimization = () => {  
   const config = {  
      splitChunks: {  
         chunks: 'all'  
      }   }  
  
   if (isProd) {  
      config.minimizer = [  
         new TerserPlugin(),  
         new CssMinimizerPlugin(),  
      ]  
   }  
  
   return config;  
}  
 
// Эта функция будет возвращать значение конфига в свойство, которое хранит сведения о лоадерах для определённых файлах
const cssLoaders = (extra) => {  
   const loader = [  
      MiniCSSExtractPlugin.loader,  
      'css-loader',  
   ];  
 
	// если дополнение есть, то пушим его в массив
   if(extra) loader.push(extra);  
  
   return loader;  
}  
  
// эта функция будет генерировать наименование файла  
const filename = ext => isDev ? `[name].${ext}` : `[name].[hash].${ext}`;  
  
module.exports = {  
   context: path.resolve(__dirname, "src"),  
   mode: "development",  
   entry: {  
      main: "./index.js",  
      analytics: "./analytics.js",  
   },  
   optimization: optimization(),  
   output: {  
      filename: filename('js'),  
      path: path.resolve(__dirname, "dist"),  
   },  
   resolve: {  
      extensions: ['.js', '.jsx', '.ts', '.tsx'],  
      alias: {  
         '@': path.resolve(__dirname, "src"),  
         '@components': path.resolve(__dirname, 'src/components'),  
         '@utilities': path.resolve(__dirname, 'src/utilities')  
      }  
   },  
   devServer: {  
      static: {  
         directory: path.join(__dirname, 'dist'),  
      },  
      compress: true,  
      port: 9000,  
      open: true,  
      hot: isDev, // перезагружает страницу если находимся в режиме разработчика  
   },  
   plugins: [  
      new HTMLWebpackPlugin({  
         template: "./index.html",  
         minify: {  
            collapseWhitespace: isProd  
         }  
      }),  
      new CleanWebpackPlugin(),  
      new CopyWebpackPlugin({  
         patterns: [  
            {  
               from: path.resolve(__dirname, 'src/favicon.ico'),  
               to: path.resolve(__dirname, 'dist')  
            }  
         ],  
      }),  
      new MiniCSSExtractPlugin({  
         // копируем из output  
         filename: filename('css'),  
      }),  
   ],  
   module: {  
      rules: [  
         {  
            test: /\.css$/,  
            // тут уже мы просто вызваем функцию, которая будет вставлять нужный конфиг
            use: cssLoaders(),  
         },  
         {  
            test: /\.s[ac]ss$/i,  
            // тут уже мы просто вызваем функцию, которая будет вставлять нужный конфиг и передавать параметр
            use: cssLoaders('sass-loader'),  
         },  
         {  
            test: /\.less$/i,  
            // тут уже мы просто вызваем функцию, которая будет вставлять нужный конфиг и передавать параметр
            use: cssLoaders('less-loader'),  
         },  
         {  
            test: /\.(png|jpe?g|gif)$/i,  
            use: ['file-loader'],  
         },  
         {  
            test: /\.(ttf|eot|woff|woff2)$/,  
            use: ['file-loader']  
         },  
         {  
            test: /\.xml$/,  
            use: ['xml-loader']  
         },  
         {  
            test: /\.csv$/,  
            use: ['csv-loader'],  
         }  
      ],  
   }  
};

Babel

Устанавливаем нужные компоненты babel

npm install -D babel-loader @babel/core @babel/preset-env webpack

Так же нужно установить полифилы, которые будут переводить современные функции (async/await, get/set) под старые стандарты

npm install --save @babel/polyfill

Ну и добавим новые правила для активации работы babel внутри webpack

webpack.config.js

// ....
 
module.exports = {  
   context: path.resolve(__dirname, 'src'),  
   mode: 'development',  
   entry: {  
	   // тут как точку входа определяем массив, первым значением которого будут являться полифилы babel 
      main: ['@babel/polyfill', './index.js'],  
      analytics: './analytics.js',  
   },
 
// ....
 
{ 
	// обрабатываем js-файлы
	test: /\.m?js$/, 
	// исключаем нод-модули из компиляции
	exclude: /node_modules/, 
	use: { 
		// используем лоадер babel
		loader: "babel-loader", 
		options: { 
			// в опциях определяем пресеты
			presets: ['@babel/preset-env'] 
		} 
	} 
}

package.json

{  
  "name": "webpack",
  // компилировать так, чтобы не поддерживали только 0,25% браузеров, а все остальные - поддерживались  
  "browserList": ">0.25%, not dead",  
  "version": "1.0.0",  
  "description": "",
// ...

Добавление плагинов для Babel

npm install --save-dev @babel/plugin-proposal-class-properties
{  
   test: /\.m?js$/,  
   exclude: /node_modules/,  
   use: {  
      loader: 'babel-loader',  
      options: {  
         presets: ['@babel/preset-env'],  
         plugins: [  
            '@babel/plugin-proposal-class-properties',  
         ]  
      },  
   },  
},
class Util {  
   static id = Date.now();  
}  
  
console.log(Util.id); // выведет дату

Приведённый в примере плагин уже входит в preset-env

Компиляция TypeScript

Для начала компиляции нужно установить пресет на ТС

npm install --save-dev @babel/preset-typescript

Дальше нужно подправить конфиг

// ...
 
module.exports = {  
   context: path.resolve(__dirname, 'src'),  
   mode: 'development',  
   entry: {  
      main: ['@babel/polyfill', './index.js'],  
      // тут в качестве второй точки входа поставить ts (так как этот файл мы переименовали в ts из js)
      analytics: './analytics.ts',  
   },
 
// ...
 
// Это уже правила для компиляции ts
{  
   test: /\.ts$/,  
   exclude: /node_modules/,  
   use: {  
      loader: 'babel-loader',  
      options: {  
         presets: [  
            '@babel/preset-env',  
            // добавляем пресет на ts
            '@babel/preset-typescript',  
         ],  
      },  
   },  
},
 
// ...

Это сама аналитика, переделанная в TS

import * as $ from 'jquery';  
  
function createAnalytics(): Object {  
   let counter = 0;  
   let isDestroyed: boolean = false;  
  
   const listener = (): number => counter++;  
  
   $(document).on('click', listener);  
  
   return {  
      destroy() {  
         $(document).off('click', listener);  
         isDestroyed = true;  
      },  
  
      getClicks() {  
         if (isDestroyed) {  
            return 'Analytics is destroyed';  
         }  
         return counter;  
      },  
   };  
}  
  
window['analytics'] = createAnalytics();

Так же как и в прошлых случаях, тут можно оптимизировать конфиг

// Это опции под типы пресетов babel  
const babelOptions = (ext) => {  
   const options = {  
      presets: [  
         '@babel/preset-env',  
      ],  
   }  
   if (ext) options.presets.push(ext);  
   return options; 
}
 
// ...
 
{  
   test: /\.m?js$/,  
   exclude: /node_modules/,  
   use: {  
      loader: 'babel-loader',  
      options: babelOptions(),  
   },  
},  
{  
   test: /\.ts$/,  
   exclude: /node_modules/,  
   use: {  
      loader: 'babel-loader',  
      options: babelOptions('@babel/preset-typescript'),  
	},  
},

Компиляция React JSX

npm install --save-dev @babel/preset-react
npm i react react-dom

webpack.config.js

entry: { 
	// точку входа в вебпаке нужно поменять на '.jsx'
   main: ['@babel/polyfill', './index.jsx'],  
   analytics: './analytics.ts',  
},
 
// ....
// Это правила для работы и компиляции JSX
{  
   test: /\.jsx$/,  
   exclude: /node_modules/,  
   use: {  
      loader: 'babel-loader',  
      // Устанавлвиаем пресет
      options: babelOptions('@babel/preset-react'),  
   },  
},

index.jsx

import './babel';  
 
import './styles/style.css';  
import './styles/less.less';  
import './styles/scss.scss';  
 
import React, { Component } from 'react';  
import ReactDOM from 'react-dom/client';  
  
 
class App extends Component {  
   render() {  
      return (  
         <div className='container'>  
            <h1>WP Course</h1>  
            <hr />  
            <div className='logo' />  
            <hr />  
            <hr />  
            <pre />  
            <hr />  
            <div className='box'>  
               <h2>Less</h2>  
            </div>  
            <hr />  
            <div className='card'>  
               <h2>SCSS</h2>  
            </div>  
         </div>  
      );  
   }  
}  
  
const root = ReactDOM.createRoot(document.getElementById('app'));  
root.render(  
   <React.StrictMode>  
      <App />  
   </React.StrictMode>,  
);

Devtool

Так же в WP можно настроить режимы компиляции по огромной таблице значений

В конфиг WP нужно вписать свойство devtool, которому по условию можно назначить определённый тип компайла карт

webpack.config.js

module.exports = {  
   context: path.resolve(__dirname, 'src'),  
   mode: 'development',  
   // тут уже в режиме разработчика будем генерировать карты
   devtool: isDev ? 'source-map' : 'eval-cheap-source-map',

И теперь карты показывают, в каком файле были созданы стили и на какой строке они располагаются (конкретно позволяет работать с файлами специфических расширений из браузера)

И так же показывает исходник в самом браузере

ESLint

Устанавливаем сам eslint и babel-парсер для еслинта

npm i -D eslint-loader
 
npm i -D babel-eslint

Добавим функцию, которая будет добавлять дополнительные лоадеры для JS и закинем eslint-loader в компиляцию JS

webpack.config.js

// будет добавлять указанные лоадеры  
const jsLoaders = ext => {  
   const loaders = [  
      {  
         loader: ['babel-loader'],  
         options: babelOptions(),  
      },  
   ];  
  
   if (ext) loaders.push(ext);  
  
   return loaders;  
};
 
// ....
 
{  
   test: /\.js$/,  
   exclude: /node_modules/,  
   use: jsLoaders('eslint-loader'),  
},

babel.js - создадим для примера одну неиспользуемую переменную

const unused = 10;

.eslintrc - создаём в корне проекта

{  
	// Назначим парсер под бэйбель
  "parser": "babel-eslint",  
  // Скажем, чтобы выпадали варнинги, если будут обнаружены неиспользуемые переменные
  "rules": {  
    "no-unused-vars": "warn"  
  },  
  // активируем поддержку ES6
  "env": {  
    "es6": true,  
    "browser": true  
  },  
  "extends": [  
    "eslint:recommended"  
  ]  
}

Динамические импорты

Библиотека простых функций

npm i -D lodash

Динамические импорты позволяют нам вставить библиотеку в любом участке кода и сразу же её использовать

babel.js

import('lodash').then(_ => {  
   console.log('Lodash: ', _.random(10, 11, true));  
});

Анализ финальной сборки

npm i webpack-bundle-analyzer -D
// ....
 
// импортируем функцию анализирования конфига
const {BundleAnalyzerPlugin} = require('webpack-bundle-analyzer');
 
// Это список наших плагинов, который будет зависеть от 
const plugins = () => {  
   const base = [  
      new HTMLWebpackPlugin({  
         template: './index.html',  
         minify: {  
            collapseWhitespace: isProd,  
         },  
      }),  
      new CleanWebpackPlugin(),  
      new CopyWebpackPlugin({  
         patterns: [  
            {  
               from: path.resolve(__dirname, 'src/favicon.ico'),  
               to: path.resolve(__dirname, 'dist'),  
            },  
         ],  
      }),  
      new MiniCSSExtractPlugin({  
         // копируем из output  
         filename: filename('css'),  
      }),  
   ];  
  
   if (isProd) base.push(new BundleAnalyzerPlugin());  
  
   return base;  
};
 
// ....
 
module.exports = {  
   plugins: plugins(),
 
// ....

И теперь тут можно увидеть, сколько занимают места разные библиотеки в нашем проекте

Либо можно записать выполнение этого плагина через скрипт

package.json

"scripts": {    
  "stats": "webpack --json > stats.json && webpack-bundle-analyzer stats.json"

Полный конфиг сборки

Папка проекта:

Конфиг:

const path = require('path');  
const HTMLWebpackPlugin = require('html-webpack-plugin');  
const { CleanWebpackPlugin } = require('clean-webpack-plugin');  
const CopyWebpackPlugin = require('copy-webpack-plugin');  
const MiniCSSExtractPlugin = require('mini-css-extract-plugin');  
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');  
const TerserPlugin = require('terser-webpack-plugin');  
  
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');  
  
// Эта переменная будет хранить в себе значение состояния, в котором находится сайт во время разработки  
const isDev = process.env.NODE_ENV === 'development';  
const isProd = process.env.NODE_ENV === 'production';  
  
// эта функция определяет под каждый тип разработки свою минификацию файлов  
const optimization = () => {  
   const config = {  
      splitChunks: {  
         chunks: 'all',  
      },  
   };  
  
   if (isProd) {  
      config.minimizer = [new TerserPlugin(), new CssMinimizerPlugin()];  
   }  
  
   return config;  
};  
  
// эта функция генерирует опции лоадеров  
const cssLoaders = extra => {  
   const loader = [MiniCSSExtractPlugin.loader, 'css-loader'];  
  
   if (extra) loader.push(extra);  
  
   return loader;  
};  
  
// Это опции под типы пресетов babel  
const babelOptions = ext => {  
   const options = {  
      presets: ['@babel/preset-env'],  
   };  
  
   if (ext) options.presets.push(ext);  
  
   return options;  
};  
  
// будет добавлять указанные лоадеры  
// const jsLoaders = ext => {  
//     const loaders = [  
//        {  
//           loader: ['babel-loader'],  
//           options: babelOptions(),  
//        },  
//     ];  
//  
//     if (isDev) loaders.push(ext);  
//  
//     return loaders;  
// };  
  
// эта функция будет генерировать наименование файла  
const filename = ext => (isDev ? `[name].${ext}` : `[name].[hash].${ext}`);  
  
const plugins = () => {  
   const base = [  
      new HTMLWebpackPlugin({  
         template: './index.html',  
         minify: {  
            collapseWhitespace: isProd,  
         },  
      }),  
      new CleanWebpackPlugin(),  
      new CopyWebpackPlugin({  
         patterns: [  
            {  
               from: path.resolve(__dirname, 'src/favicon.ico'),  
               to: path.resolve(__dirname, 'dist'),  
            },  
         ],  
      }),  
      new MiniCSSExtractPlugin({  
         // копируем из output  
         filename: filename('css'),  
      }),  
   ];  
  
   if (isProd) base.push(new BundleAnalyzerPlugin());  
  
   return base;  
};  
  
module.exports = {  
   context: path.resolve(__dirname, 'src'),  
   mode: 'development',  
   devtool: isDev ? 'source-map' : 'eval-cheap-source-map',  
   entry: {  
      main: ['@babel/polyfill', './index.jsx'],  
      analytics: './analytics.ts',  
   },  
   optimization: optimization(),  
   output: {  
      filename: filename('js'),  
      path: path.resolve(__dirname, 'dist'),  
   },  
   resolve: {  
      extensions: ['.js', '.jsx', '.ts', '.tsx'],  
      alias: {  
         '@': path.resolve(__dirname, 'src'),  
         '@components': path.resolve(__dirname, 'src/components'),  
         '@utilities': path.resolve(__dirname, 'src/utilities'),  
      },  
   },  
   devServer: {  
      static: {  
         directory: path.join(__dirname, 'dist'),  
      },  
      compress: true,  
      port: 9000,  
      open: true,  
      hot: isDev, // перезагружает страницу если находимся в режиме разработчика  
   },  
   plugins: plugins(),  
   module: {  
      rules: [  
         {  
            test: /\.js$/,  
            exclude: /node_modules/,  
            use: {  
               loader: 'babel-loader',  
               options: babelOptions(),  
            },  
         },  
         {  
            test: /\.ts$/,  
            exclude: /node_modules/,  
            use: {  
               loader: 'babel-loader',  
               options: babelOptions('@babel/preset-typescript'),  
            },  
         },  
         {  
            test: /\.jsx$/,  
            exclude: /node_modules/,  
            use: {  
               loader: 'babel-loader',  
               options: babelOptions('@babel/preset-react'),  
            },  
         },  
         {  
            test: /\.css$/,  
            use: cssLoaders(),  
         },  
         {  
            test: /\.s[ac]ss$/i,  
            use: cssLoaders('sass-loader'),  
         },  
         {  
            test: /\.less$/i,  
            use: cssLoaders('less-loader'),  
         },  
         {  
            test: /\.(png|jpe?g|gif)$/i,  
            use: ['file-loader'],  
         },  
         {  
            test: /\.(ttf|eot|woff|woff2)$/,  
            use: ['file-loader'],  
         },  
         {  
            test: /\.xml$/,  
            use: ['xml-loader'],  
         },  
         {  
            test: /\.csv$/,  
            use: ['csv-loader'],  
         },  
      ],  
   },  
};