Webpack

Насколько далеко можно зайти с конфигурацией Webpack по умолчанию?

Я выступал на конференции GDG Devfest, где говорил об использовании webpack в разработке современных приложений. Изначально я планировал посвятить большую часть речи использованию webpack с фронтенд фреймворками/библиотеками, такими как ReactJS, Vue, Angular и т. д., но сначала решил провести небольшое исследование, чтобы узнать мнение людей о webpack. К моему удивлению, большинство заявило, что webpack годен к использованию только с фреймворками, хотя это совсем не так. Другие до сих пор утверждают, что установить webpack слишком сложно. В результате я решил уделить больше внимания использованию webpack с Vanilla JS и конфигурации Webpack по умолчанию.

Но сначала:

Что такое webpack?

Проще говоря, webpack — это инструмент, объединяющий множество модулей Javascript в единый модуль, который перенаправляется в браузер.

Если рассуждать точнее, то webpack — это бандлер, который ищет модули Javascript с зависимостями (в основном, файлы Javascript, для которых нужен код из других файлов Javascript), объединяет их вместе в единый файл или файлы Javascript без зависимостей для использования в браузере.

История Webpack

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

  • Google Web Toolkit — фреймворк от Google, конвертирующий Java в Javascript. Одна из его главных особенностей — разделение кода (code splitting).
  • Modules Webmake — библиотека, из которой произошел webpack. По сути, это инструмент, с помощью которого можно организовать файлы Javascript для использования в браузере, так же как и для NodeJS.
  • IIFE (Immediately Invoked Function Expression) — немедленно вызываемая функция. Это функция Javascript, которая вызывается во время ее создания.

Immediately Invoked Function Expression

Разберем эту тему отдельным пунктом. Один из примеров IIFE:

Функция запускается сразу при ее размещении в теге Script, который загружается браузером. Это схоже с присоединением функции к window.onload, однако с некоторыми преимуществами.

Из-за особенностей работы замыканий в Javascript все переменные, объявленные в IIFE, находятся в этой функции. Это означает, что можно избежать таких проблем, как коллизия пространства имен в базе кода, в то же время обладая доступом к функциям, предоставляемым IIFE.

Почему Webpack?

Какие из современных проблем можно решить с помощью webpack?

В первую очередь, проблема с тегами script. Я работаю с кодовой базой, каждая HTML-страница которой обладает минимум 30-ю тегами script, расположенными в определенном порядке. Некоторые не считают это проблемой, однако браузеру необходимо сделать запрос на каждый файл, что требует больше времени для загрузки. Помимо этого, тегами script тяжело управлять, поскольку при их перегруппировке можно сломать все приложение (я уже пробовал 😂).

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

В некоторых организациях принято размещать переменные в области видимости одной функции, но не стоит всегда полагаться на такой подход (или на this). В конце концов, это может вызвать трудности при разделении ответственности.

В-третьих, я уже говорил о том, что webpack возник из modules_webmake. Поскольку с помощью webpack можно организовывать файлы так же, как и в NodeJS (используя CommonJS), дополнительным преимуществом является написание хорошо масштабируемого модульного кода (спросите тех, кто использует фронтенд фреймворки).

CommonJS

Это система модулей в JavaScript, которая используется в NodeJS.

С помощью Webpack можно беспрепятственно использовать в браузере как этот модуль, так и модульную систему ES (Webpack отлично справляется с обработкой). В результате получаем модульный и поддерживаемый код, в котором один файл JS обрабатывает одну функцию (Single Responsibility Principle — Принцип единственной ответственности).

ES Modules (ESM)

Еще одна модульная система, реализованная в текущих браузерах, но, к сожалению, с некоторыми ограничениями. С помощью Webpack также можно беспрепятственно использовать этот модуль (поскольку webpack конвертирует его в конце), однако, судя по моему опыту, с помощью ESM можно создать более читабельные базы кода.

Как работает Webpack?

Ранее я говорил, что Webpack — настоящий волшебник, но я соврал. Проще говоря:

  • Webpack использует единую точку входа (entry point), которая представляет собой файл JS, для поиска инструкций import (ESM или CJS).
  • Затем он пересекает импортированный файл, ищет другие операторы import, создавая график зависимости в процессе.

Рассмотрим пример:

Два файла index.js и helpers.js выполняют различные функции, но я импортирую и использую функцию в helpers.js в файле index.js. ./src/index.js — это точка входа (entry point) Webpack по умолчанию, из которой строится график зависимости, как показано ниже:

С чего начать

Чтобы разобраться в том, как работает webpack, создадим простое приложение TODO с базовыми функциями add и delete, а в качестве бандлера будем использовать конфигурацию Webpack по умолчанию (без конфигурационного файла webpack). Приложение будет выглядеть следующим образом:

Для начала создаем директорию нового проекта и две папки (dist и src). Точка входа (entry point) Webpack по умолчанию ./src/index.js выводит связанный файл JS в ./dist/main.js (поэтому нам понадобятся две папки).

В папке dist можно создать файл index.html. При использовании webpack в этом нет необходимости, поскольку файл можно разместить в любом месте в рамках директории проекта. Достаточно сделать ссылку на main.js. В результате структура проекта выглядит следующим образом:

В папке src создаем файл index.html, в котором будут реализованы функции приложения TO-DO. Но сначала заполним файл index.html следующим кодом:

<html>
  <head>
    <title>Todo App</title>
  </head>
  <body>
    <div class="container">
      <p>
        <label for="new-task">Add Item</label>
        <input id="new-task" type="text">
        <button id="addTask">Add</button>
      </p>
      
      <h3>Todo</h3>
      <ul id="tasks">
      </ul>
    </div>
    <script src="main.js"></script>
  </body>
</html>

Теперь добавим функции. Разбиваем две функции (Add и Delete) на файлы, а затем импортируем их в index.js . В папке src создаем два файла: addTask.js и deleteTask.js . Теперь структура проекта выглядит так:

Теперь добавляем необходимую логику. Для начала реализуем deleteTask.js, поскольку у него нет зависимостей. Вставляем следующий код в файл deleteTask.js:

const deleteTask = function(e) {
  console.log("Delete Task...", e);
  //Remove the parent list item from the ul
  var listItem = e.target.parentNode;
  var ul = listItem.parentNode;
ul.removeChild(listItem);
};
export default deleteTask;

В этом файле мы создаем функцию deleteTask, а затем экспортируем ее в качестве экспорта по умолчанию.

Теперь реализуем функцию addTask, добавив следующий код в файл addTask.js:

import deleteTask from "./deleteTask";
const createNewTaskElement = function(taskString) {
  const listItem = document.createElement("li");
  const label = document.createElement("label");
  const deleteButton = document.createElement("button");
deleteButton.innerText = "Delete";
  deleteButton.className = "delete";
  deleteButton.addEventListener("click", deleteTask);
label.innerText = taskString;
listItem.appendChild(label);
  listItem.appendChild(deleteButton);
return listItem;
};
const addTask = function(e) {
  const taskList = document.getElementById("tasks");
  const task = document.getElementById("new-task");
  if (task.value !== "") {
    const newTaskItem = createNewTaskElement(task.value);
    taskList.appendChild(newTaskItem);
    task.value = "";
  }
};
export default addTask;

Сначала импортируем файл deleteTask.js. Если в импорте не указаны какие-либо расширения, то webpack по умолчанию автоматически помечает его как файл .js. Также у нас есть функция, создающая элемент списка, содержащий введенную в форму задачу. Стоит отметить, что функция delete присоединяется к обработчику “клика” кнопки delete. Затем создаем функцию addTask и экспортируем ее.

Теперь нужно импортировать функцию addTask в index.js. Вставьте следующий код в файл index.js:

import addTask from './addTask';
const addTaskButton = document.getElementById("addTask");
addTaskButton.addEventListener("click", addTask);

Все достаточно просто: мы импортируем функцию addTask и присоединяем ее к обработчику “клика” для addTaskButton

Наконец, чтобы получить файл main.js нужно запустить Webpack. Убедитесь, что у вас установлен NodeJS, а затем выполните глобальную установку webpack с помощью следующей команды:

npm install -g webpack OR sudo npm install -g webpack

После завершения установки запустите следующую команду:

webpack

В терминале появится предупреждение:

Это предупреждение о том, что не указана опция mode. Можно оставить все, как есть, и запустить код, однако если вам не нравится предупреждение, то запустите Webpack следующим образом:

webpack --mode=development

Готово. 

Надеюсь, эта статья помогла вам разобраться в базовых особенностях Webpack.

Перевод статьи Samuel Omole: How to build modern applications with WEBPACK