Примечание. Этот пост вдохновлен этим превосходным 30-минутным видео Тайлера МакГинниса и представляет собой его резюме, и я рекомендую вам его посмотреть!

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

Модули и зачем они нужны

В модуль входят 3 основных компонента:

  1. Импорт: зависимости, необходимые для кода в модуле.
  2. Код: собственный код модуля.
  3. Экспорт: какие части себя модуль делает видимыми и используемыми для других модулей.

Есть 5 основных причин, по которым мы разбиваем код на модули:

  1. Возможность повторного использования. Один модуль можно использовать в нескольких местах кода.
  2. Возможность комбинирования. Модули можно соединять вместе несколькими способами для достижения более продвинутой функциональности.
  3. Использование: модули можно легко установить из репозиториев, таких как NPM, что позволяет быстро добавлять функциональные возможности в свой код.
  4. Изоляция: модуль содержит определенные, изолированные, что упрощает тестирование. Кроме того, это позволяет нескольким людям работать над разными модулями одновременно.
  5. Организация: с большими базами кода легче управлять, если код разделен на логические модули.

Мы собираемся использовать игрушечный проект и продемонстрировать на нем основные подходы, которые были приняты для управления модулями в Javascript в хронологическом порядке. «Проект» устроен следующим образом: файл data.js импортирует массив чисел. m1.js и m2.js содержат функции, которые полагаются на эти данные (мы просто распечатаем их, чтобы понять суть). Все это хранится в файле index.html.

Подход 0: импорт файлов без модулей

В традиционном Javascript мы использовали для импорта внешних файлов Javascript без каких-либо попыток создания модулей. Это показано ниже на трех файлах.

Во-первых, у нас есть наш основной файл index.html с функцией, определенной внутри, которая называется indexHTMLFunction.

Далее наши файлы javascript выглядят следующим образом:

m1Function и m2Functi on теперь доступны для использования в файле index.html, а также имеют доступ к данным. Нам удалось разбить наш код на несколько файлов, что кажется правильным шагом в направлении разделения нашего кода на модули. Однако у этого элементарного подхода есть одна ключевая проблема: на самом деле мы не создаем модули. Это как если бы мы просили компилятор просто скопировать / вставить код из файлов Javascript в index.html, что в большинстве случаев является смертным грехом. Это можно быстро проверить, открыв index.html в браузере и взглянув на объект окна.

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

Подход 1: выражения немедленного вызова функций (IIFE):

Следующим шагом на пути к нашему элементарному подходу является использование немедленно вызываемых функциональных выражений для экспорта материалов в объект, который мы определяем. Затем этот объект будет содержать все наши экспортные данные, тем самым предотвращая затопление оконного объекта. Чтобы перейти на этот подход, мы должны сделать две вещи:

  1. Создайте объект для хранения всех экспортируемых вещей. Мы назовем это APP.
  2. Оберните весь код импортированных модулей в IIFE.

Отредактированный код будет следующим:

Давайте посмотрим на результаты:

Кажется, после небольшого рефакторинга мы решили нашу проблему. Однако в этом методе все еще есть некоторые ключевые проблемы:

  1. Весь наш импорт помещается в один объект. Существует высокий риск конфликта имен.
  2. Мы должны быть осторожны с порядком. Сначала необходимо объявить объект APP.
  3. Мы должны завернуть все, что мы импортируем, в IIFE, что утомительно.

Подход 2: CommonJS

«То, что я здесь описываю, не является технической проблемой. Это вопрос того, чтобы люди собрались вместе и приняли решение сделать шаг вперед и вместе начать создавать что-то большее и крутое ». - Кевин Дангур, создатель CommonJS

CommonJS был впервые предложен в 2009 году для решения проблемы модуля Javascript. Это был набор спецификаций, которые развивались в течение нескольких лет и стали чрезвычайно популярными благодаря Node, который принял его парадигму как часть Node Package Manager. CommonJS решил все три проблемы IIFE, предоставив основу для использования модулей:

  1. Модули можно легко импортировать с помощью ключевого слова require.
  2. У каждого модуля есть объект module.exports, в котором он может указать свой экспорт, который, в свою очередь, может быть требоваться ’d другими модулями.
  3. Для этого подхода не требуется глобального пространства имен или объекта, что решает проблему конфликтов имен.

А как насчет браузеров?

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

Что это обозначает? Все приостанавливается при выполнении инструкции require.

Каковы его последствия? CommonJS нельзя использовать в браузерах. Загрузка удаленного модуля приостановит поток браузера, заморозив пользовательский интерфейс.

Однако есть несколько решений, которые позволяют нам использовать спецификацию CommonJS в браузерах, таких как Webpack и RequireJS. Эти решения выходят за рамки данной статьи.

Подход 3: модули ES6

Спецификация ES6 официально представила новую систему управления модулями в Javascript, которая работает аналогично CommonJS, но работает «из коробки» в браузерах и не блокируется при импорте. Модули ES6 позволяют использовать ключевые слова с подходящими названиями import и export для определения именно этих вещей в каждом модуле. Вот последняя версия нашего игрушечного примера, теперь в ES6:

Следует отметить два важных момента:

  1. Вы должны указать тип как модуль в теге скрипта, чтобы это работало в браузерах.
  2. Вам нужно будет обслуживать index.html с сервера (например, http-сервера python), иначе вы получите CORS при использовании этого подхода в браузере.

Теперь у нас есть простая в использовании и надежная реализация модуля на Javascript, которая работает везде. Вы заметите очень четкое соответствие между кодом и тремя компонентами модуля, которые мы описали в начале. Модули ES6 также предлагают некоторые другие полезные функции, такие как экспорт по умолчанию и псевдонимы, которые вам обязательно стоит проверить, если вы с ними не знакомы.

Последние тенденции и подведение итогов

Очень редко можно увидеть простой импорт скриптов и IIFE в современном коде Javascript. CommonJS по-прежнему довольно популярен среди сообщества NodeJS. Последние версии Node упростили использование модулей ES6, и возможно, что сообщество Node перейдет на модули ES6 в будущем. С другой стороны, экосистема React использует исключительно модули ES6.

Подводя итог, можно сказать, что модули являются неотъемлемой частью хороших практик разработки программного обеспечения. С современным Javascript вы должны стремиться использовать модули CommonJS или ES6 в зависимости от среды и сообщества инструментов, с которыми вы работаете.