Umd js что это

Umd js что это

UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

UMD (Universal Module Definition)

This repository formalizes the design and implementation of the Universal Module Definition (UMD) API for JavaScript modules. These are modules which are capable of working everywhere, be it in the client, on the server or elsewhere.

The UMD pattern typically attempts to offer compatibility with the most popular script loaders of the day (e.g RequireJS amongst others). In many cases it uses AMD as a base, with special-casing added to handle CommonJS compatibility.

AMD with simple Node/CommonJS adapter

These are useful for using AMD style while still making modules that can be used in Node and installed via npm without extra dependencies to set up the full AMD API.

This approach does not allow the use of AMD loader plugins, just basic JS module dependencies. It also does not support the callback-style require that is usable in AMD.

The basic pattern for the UMD variations in this repository was derived from the approach @kriskowal used for the Q promise library.

Earlier UMD variations were also of influence, ranging from Kit-Cambridge’s UMD, through to patterns discussed by Addy Osmani, Thomas Davis and Ryan Florence and most recently the UMD patterns proposed by James Burke.

Copyright (c) the UMD contributors

Licensed under the MIT License.

About

UMD (Universal Module Definition) patterns for JavaScript modules that work everywhere.

Источник

О модулях JavaScript, форматах, загрузчиках и сборщиках модулей за 10 минут

Авторизуйтесь

О модулях JavaScript, форматах, загрузчиках и сборщиках модулей за 10 минут

Несмотря на то, что новые языки программирования появляются каждый год, JavaScript остаётся одним из самых распространённых и любимых программистами. И как и любой современный язык, он стремительно развивается, что делает изучение его с нуля очень непростой задачей. В этом материале простым языком рассказывается о том, как в JavaScript устроена работа с модулями. Статья может не только помочь новичкам понять, что происходит, но и освежить знания основ у тех, кто уже работает с JS.

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

Для чего предназначены Webpack и SystemJS? Что значит AMD, UMD или CommonJS? Какое отношение они имеют друг к другу и зачем вообще их использовать?

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

Что такое модуль?

Модуль — это переиспользуемая часть кода, содержащая в себе детали реализации и предоставляющая открытое API, что позволяет легко загрузить её и использовать в другом коде.

Зачем нужны модули?

Технически код можно написать и без использования модулей. Модули — это паттерн, который в разных формах и на разных языках используется разработчиками с 60-х и 70-х годов.

В идеале, модули JavaScript позволяют нам:

Паттерны модулей в ES5

ECMAScript 5 и более ранние версии не были спроектированы с учётом модулей. Со временем разработчики нашли различные возможности симулировать модульную архитектуру на JavaScript.

Прим. перев. Senior-разработчики компании Noveo говорят, что помнят, как это было: как они проходили путь от работы без модулей к первым попыткам написать их самостоятельно, потом использовать чужие наработки… Ну а все системы, перечисленные ниже, они знают не понаслышке. Эх, были времена!

Чтобы дать представление о том, как выглядят такие паттерны, давайте взглянем на два из них: мгновенно вызываемая функция (Immediately Invoked Function Expressions) и выявление модуля (Revealing Module).

Немедленно вызываемая функция (Immediately Invoked Function Expression или IIFE)

Немедленно вызываемая функция (IIFE) — анонимная функция, которая вызывается сразу после объявления. Обратите внимание: функция окружена скобками. В JavaScript строка, начинающаяся со слова function, воспринимается как объявление функции:

Мгновенный вызов объявления функции выдаёт ошибку:

Помещение функции в скобки делает это функциональным выражением:

Функциональное выражение возвращает нам функцию, так что мы можем тут же к ней обратиться:

Мгновенно вызываемые функции позволяют нам:

Однако они не дают нам механизма управления зависимостями.

Паттерн выявления модуля (Revealing Module)

Паттерн выявления модуля схож с IIFE, но здесь мы присваиваем возвращённое значение переменной:

Заметьте, что здесь нет необходимости в скобках, так как слово function находится не в начале строки.

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

Вместо синглтона модуль может выступать и как функция-конструктор:

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

для доступа к внешнему API

Паттерн выявления модуля предоставляет те же преимущества, что и IIFE, но опять же не даёт возможности управлять зависимостями.

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

Форматы модулей

Формат модуля — это синтаксис, который используется для его определения.

До создания ECMAScript 6, или ES2015, в JavaScript не было официального синтаксиса для определения модулей. А значит, опытные разработчики предлагали разные форматы определения.

Вот несколько наиболее известных и широко используемых:

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

Асинхронное определение модуля (AMD)

Формат AMD используется в браузерах и применяет для определения модулей функцию define:

Формат CommonJS

Формат CommonJS применяется в Node.js и использует для определения зависимостей и модулей require и module.exports:

Универсальное определение модуля (UMD)

Формат UMD может быть использован как в браузере, так и в Node.js.

System.registerА

Формат System.register был разработан для поддержки синтаксиса модулей ES6 в ES5:

Формат модулей ES6

В ES6 JavaScript уже поддерживает нативный формат модулей.

Он использует токен export для экспорта публичного API модуля:

и токен import для импорта частей, которые модуль экспортирует:

Мы можем даже присваивать импорту алиас, используя as:

или загружать сразу весь модуль:

Формат также поддерживает экспорт по умолчанию:

который можно импортировать, например, так:

Вы можете экспортировать не только функции, но и всё, что пожелаете:

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

Мы можем использовать формат модулей ES6 уже сегодня, но для этого потребуется компилятор наподобие Babel, который будет переводить наш код в формат ES5, такой, как AMD или CommonJS, перед тем, как код будет запущен в браузере.

Загрузчики модулей

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

Загрузчик модуля запускается в среде исполнения:

Если вы откроете вкладку «Сеть» в консоли разработчика на своём браузере, вы увидите, что многие файлы были загружены по запросу загрузчика модулей.

Вот несколько популярных загрузчиков:

Сборщики модулей

Сборщик модулей заменяет собой загрузчик модулей. Однако в отличие от загрузчика модулей, сборщик модулей запускается при сборке:

Если вы откроете вкладку «Сеть» в консоли разработчика на своём браузере, вы увидите, что загружен только один файл. Таким образом, отпадает необходимость в загрузчике модулей: весь код включён в один пакет.

Пара популярных сборщиков:

Подводим итоги

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

Модуль — это переиспользуемая часть кода, содержащая в себе детали реализации и предоставляющая открытое API, что позволяет легко загрузить её и использовать в другом коде.

Формат модуля — это синтаксис, который используется для определения модулей. В прошлом возникали разные форматы: AMD, CommonJS, UMD и System.register нативный формат модулей появился в ES6.

Загрузчик модуля интерпретирует и загружает модуль, написанный в определённом формате, в время выполнения (в браузере). Распространённые — RequireJS и SystemJS.

Сборщик модуля заменяет загрузчик модулей и создаёт пакет, содержащий весь код, во время сборки. Популярные примеры — Browserify и Webpack.

Вот и всё — теперь у вас достаточно знаний для понимания современной разработки на JavaScript. В следующий раз, когда ваш компилятор TypeScript спросит, какой формат модулей вы хотите использовать, вы уже не будете гадать, что бы это значило. А если такое произойдет — просто перечитайте эту статью.

Отличного дня и программируйте с удовольствием!

За перевод материала выражаем благодарность международной IT-компании Noveo.

Источник

JavaScript Модули

Если вы новичок в JavaScript, то разговоры на тему «module bundlres vs module loaders», «Webpack vs Browserify» и «AMD vs CommonJS», могут быть пугающими.

Понимание системы модулей JavaScript жизненно важно для web разработчика. В этом посте попробую простыми словами (и на примерах кода) объяснить эти модные понятия.

Что такое модуль в JavaScript?

Хорошие авторы делят свои книги на главы и разделы; Хорошие программисты делят свои программы на модули.

Как главы в книге, модули являются просто набором слов (или кода, в зависимости от обстоятельств).

Хорошие модули, однако, максимально автономны в своей функциональности, что позволяет их перетасовывать, удалять или добавлять по мере необходимости, не нарушая систему в целом.

Почему нужно использовать модули в JavaScript?

Использование модулей дает много преимуществ в пользу расширения и независимости кода. Наиболее важным, по моему мнению, является:

Как мы можем включить модули?

Шаблон Модуль

уществует несколько способов, чтобы написать паттерн Модуль. В первом примере я использую анонимное замыкание. Это поможет нам достич цели, обернув весь код в анонимную функцию. (В JavaScript функции это единственный способ создать новую область видимости).

Пример 1: Анонимное замыкание

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

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

Читайте также:  за что погибли мухи

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

Пример 2: Глобальный импорт

Другой популярный подход, используемый библиотекой jQuery, это глобальный импорт. Он подобен вызову анонимной функции, в примере выше, отличается тем, что мы передаем глобальную переменную как параметр.

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

Пример 3: Интерфейс объекта

Как вы можете видеть, этот подход дает нам возможность решить какие переменные/методы мы хотим оставить приватными (такие как myGrades) и какие мы хотим сделать доступными извне через выражение return (такие как average и failing).

Пример 4: Паттерн «раскрывающий модуль»

Этот паттерн похож на предыдущие подходы, за исключением того, что все переменные и методы изначально создаются приватными, пока явно не назначаются публичными:

CommonJS и AMD

Подходы выше имеют одну общую черту: используют одну глобальную переменную для обертки кода в функцию, создавая таким образом пространство имен через замыкание.

Хотя каждый подход эффективен по своему, они имеют свои недостатки.

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

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

Есть два популярных и хорошо реализованных подхода: CommonJS и AMD

CommonJS

С CommonJS каждый js файл хранит модули в своем уникальном контектсе (подобно обертке в замыкании). В этой области видимости мы используем объект module.exports для того, чтобы сделать модуль доступным и require для его импорта.

Когда вы определяете CommonJS модуль, это выглядит примерно так:

Мы получаем два очевидных преимущества, по сравнению с шаблонами, рассмотренными ранее:

Более того, синтаксис становится более компактный.

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

Это прекрасно работает на сервере, но, к сожалению, затрудняет его использование при написании JavaScript для браузера. Чтение модуля из Интернета занимает намного больше времени, чем чтение с диска. Пока скрипт загружает модуль, браузер ждет пока он не завершит загрузку.

AMD (Asynchronous Module Definition)

Загрузка модулей будет выглядеть следующим образом:

Функция define принимает в качестве первого аргумента массив каждой из зависимостей модуля. Эти зависимости загружаются в фоновом режиме (неблокирующим образом), и после загрузки define вызывает колбэк.

Как мы видим, в колбэк передаются зависимости в качестве аргументов, в нашем случае это myModule и myOtherModule что позволяет использовать их в качестве зависимостей.

Например, myModule может выглядеть так:

В отличие от CommonJS, AMD использует браузерный подход (асинхронное поведение).

Помимо асинхронности, еще одним преимуществом AMD является то, что ваши модули могут быть объектами, функциями, конструкторами, строками, JSON и многими другими типами, в то время как CommonJS поддерживает объекты только как модули.

При этом AMD не совместима с файловой системой io и другими серверно-ориентированными функциями, доступными через CommonJS, а синтаксис переноса функций является несколько более подробным по сравнению с простым оператором require.

UMD (Universal Module Definition)

UMD объединяет преимущества AMD и CommonJS. Создает способ использования любого из двух. Как результат, UMD модули могут работать как на сервере, так и на клиенте.

Вот как это работает:

Native JS

Вы все еще здесь? Отлично! У нас есть еще один тип модулей.

Как вы возможно заметили, ни один из модулей выше не является нативным для JavaScript. Вместо этого мы создали способы эмуляции модулей и использованием шаблонов проектирования.

К счастью, умные люди представили встроенные модули с ECMAScript 6 (ES6).

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

Пример того как это работает

В этом примере мы в основном делаем две копии модуля: одну, когда мы ее экспортируем, и одну, когда нам это требуется.

Источник

Модули в JavaScript

Модуль — часть кода, которая инкапсулирует детали реализации и предоставляет открытый API для использования другим кодом.

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

Это привело к появлению сначала паттерна программирования «Модуль», а затем и отдельных форматов: CommonJS, AMD, UMD, и специальных инструментов для работы с ними. Нативная система модулей в JavaScript добавилась в спецификации ECMAScript 6, а разработчики браузеров работают над ее поддержкой.

Шаблон Модуль

Шаблон «Модуль» основывается на немедленно вызываемой функции (IIFE):

Немедленно вызываемая функция образует локальную область видимости, в которой можно объявить необходимые приватные свойства и методы. Результат функции — объект, содержащий публичные свойства и методы.

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

Форматы модулей

CommonJS

С точки зрения структуры модуль CommonJS — это часть JavaScript-кода, которая экспортирует определенные переменные, объекты или функции, делая их доступными для любого зависимого кода.

Модули CommonJS состоят из двух частей: module.exports содержит объекты, которые модуль хочет сделать доступными, а функция require() используется для импорта других модулей.

Пример модуля в формате CommonJS:

Пример кода, использующего модуль:

В основе формата AMD (Asynchronous Module Definition) лежат две функции: define() для определения именованных или безымянных модулей и require() для импорта зависимостей.

Функция define() имеет следующую сигнатуру:

Параметр module_id необязательный, он обычно требуется только при использовании не-AMD инструментов объединения. Когда этот аргумент опущен, модуль называется анонимным. Параметр dependencies представляет собой массив зависимостей, которые требуются определяемому модулю, а третий аргумент ( definition function ) — это функция, которая выполняется для создания экземпляра модуля.

Функция require() используется для импорта модулей:

Также с помощью require() можно динамически импортировать зависимости в модуль:

Существование двух форматов модулей, несовместимых друг с другом, не способствовало развитию экосистемы JavaScript. Для решения этой проблемы был разработан формат UMD (Universal Module Definition). Этот формат позволяет использовать один и тот же модуль и с инструментами AMD, и в средах CommonJS.

Суть подхода UMD заключается в проверке поддержки того или иного формата и объявлении модуля соответствующим образом. Пример такой реализации:

UMD — это скорее подход, а не конкретный формат. Различных реализаций может быть множество.

Модули ECMAScript 2015

В стандарте ECMAScript 2015 появились нативные модули JavaScript. На текущий момент модули ES6 поддерживаются в Safari 10.1 и за флагом в Firefox 54, Chrome 60 и Edge 15.

Синтаксис

Если модуль экспортирует только одно значение, можно использовать экспорт по умолчанию. Например, модуль экспортирует функцию:

Или даже выражение:

Один модуль может экспортировать несколько значений:

Можно перечислить все, что вы хотите экспортировать, в конце модуля:

Импортировать модули также можно несколькими способами:

Импорт из модуля поднимается в начало области видимости:

Модули ES6 выполняются отложено, только когда документ полностью проанализирован.

Код модуля выполняется в строгом режиме.

Загрузчики модулей

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

Популярные загрузчики модулей:

Сборщики модулей

В отличие от загрузчиков модулей, которые работают в браузере и загружают зависимости «на лету», сборщики модулей позволяют заранее подготовить один файл со всеми зависимостями (бандл).

Существует ряд инструментов, позволяющих заранее собирать модули в один файл:

Заключение

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

Модуль — это многократно используемая часть кода, инкапсулирующая детали реализации и предоставляющая открытый API.

Формат модуля — это синтаксис определения и подключения модуля.

Загрузчик модулей загружает модуль определенного формата во время выполнения непосредственно в браузере. Популярные загрузчики — RequireJS и SystemJS.

Сборщик модулей заранее объединяет модули в один файл, который подключается на странице. Примеры сборщиков — Webpack и Browserify.

Источник

Эволюция модульного JavaScript

Скорее всего, когда Брендан Айк проектировал JavaScript, он не представлял, как эволюционирует его проект спустя двадцать лет. На данный момент вышло уже шесть основных спецификаций языка, и работа над его улучшением до сих пор продолжается.

Не будем лукавить: JavaScript никогда не был идеальным языком программирования. Одним из слабых мест в JS была модульность, а точнее её отсутствие. Действительно, зачем в скриптовом языке, который анимирует падающие на странице снежинки и валидирует форму, заботиться об изоляции кода и зависимостях? Ведь всё может прекрасно жить и общаться между собой в одной глобальной области — window.

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

Эта статья появилась в результате общения с участниками TC39 и разработчиками фреймворков, а также чтения исходных кодов, блогов и книг. Мы рассмотрим следующие подходы/форматы: Namespace, Module, Detached Dependency Definitions, Sandbox, Dependency Injection, CommonJS, AMD, UMD, Labeled Modules, YModules и ES2015 Modules. Кроме того, мы восстановим исторический контекст их появления и развития.

Содержание

Используемые термины

Модульность решает следующие задачи: обеспечение поддержки изоляции кода, определение зависимостей между модулями и доставка кода в среду выполнения. Некоторые перечисленные выше подходы решали только одну-две из этих проблем — такие решения мы будем называть «паттернами», а те, где решаются все три задачи, — «модульными системами».

Читайте также:  масло среднецепочечных триглицеридов что это

Конкретную структуру исходного кода с определением импортируемых и экспортируемых сущностей (ко второму типу относятся объекты, функции и т. п.) будем называть «форматом модулей».

Под «обособленным определением зависимостей» (detached dependency definition, DDD) будем понимать такие подходы определения зависимостей, которые можно использовать независимо от модульных систем.

Подробнее о проблемах

Перед погружением в мир модульности давайте подробнее разберёмся в том, какие проблемы перед нами стоят.

Коллизия имён

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

Поддержка большой кодовой базы

Ещё один неудобный момент при использовании JavaScript «из коробки» для построения больших приложений — это необходимость явно указывать подключаемые скрипты с помощью тега script.

Если вы заботитесь о том, чтобы исходный код был удобен для поддержки, вы разбиваете его на независимые части. Таким образом, файлов с кодом может оказаться очень много. При большом количестве файлов ручное управление скриптами (то есть определение подключаемых скриптов через тег script) сильно усложняется: необходимо, во-первых, помнить о подключении нужных скриптов к странице, а во-вторых — распределить последовательность тегов script так, чтобы все зависимости между файлами были разрешены.

Directly Defined Dependencies (1999)

Первой попыткой привнести модульную структуру в JavaScript, а также первой реализацией обособленного определения зависимостей было прямое определение зависимостей в коде (directly defined dependencies). Одним из первых этот паттерн задействовал Эрик Арвидссон (ныне участник TC39) в далёком 1999 году.

Эрик тогда работал в стартапе, где создавалась платформа для запуска GUI-приложений в браузере — WebOS (обратите внимание: речь не про webOS от Palm). WebOS была проприетарной платформой — мне не удалось получить её исходный код. Поэтому рассмотрим реализацию паттерна на примере библиотеки Dojo, которую с 2004 года разрабатывали Алекс Рассел и Дилан Скиманн.

Суть прямого определения зависимостей — загружать код модулей (в терминах Dojo — ресурсы) при явном исполнении функции dojo.require (которая, кроме того, инициализировала загруженный модуль). При таком подходе зависимости определяются по месту требования, прямо в коде.

Давайте переделаем наш пример с использованием Dojo 1.6:

Namespace Pattern (2002)

Один из первых значимых проектов, где была использована данная возможность, — библиотека UI-элементов Bindows. Над ней в 2002 году начал работать уже знакомый нам Эрик Арвидссон. Вместо префиксов в названиях функций и переменных он воспользовался глобальным объектом, свойства которого содержали данные и логику библиотеки. Таким образом, в значительной степени уменьшилось загрязнение глобальной области. Сейчас подобный паттерн для организации кода известен под названием «Пространство имен» (Namespace Pattern).

Переложим эту идею на наш пример.

На сегодняшний день Namespace Pattern — наверное, самый популярный паттерн, который можно встретить в JS. После Bindows подобная логика появилась в Dojo (2005), YUI (2005) и многих других библиотеках и фреймворках. Стоит отметить, что Эрик не считает себя автором этого подхода, но вспомнить, какой проект вдохновил его на использование паттерна, он, к сожалению, не смог.

Module Pattern (2003)

Благодаря Namespace организация кода стала чуть менее беспорядочной, но было очевидно, что этого недостаточно: до сих пор не была решена задача изоляции кода и данных.

Первопроходец в решении указанной задачи — паттерн «Модуль». Идея паттерна — инкапсулировать данные и код с помощью замыкания и сделать их доступными через методы, которые, в свою очередь, доступны извне. Вот базовый пример такого модуля:

В сети первое упоминание этого подхода появилось в 2003 году, когда Ричард Корнфорд привёл пример модуля в группе comp.lang.javascript в качестве иллюстрации использования замыканий. В 2005–2006 году этот подход взяли на вооружение разработчики фреймворка YUI из Yahoo! под руководством Дугласа Крокфорда. Но наибольший импульс к его распространению был дан в 2008 году, когда Дуглас описал паттерн «Модуль» в своей книге JavaScript Good Parts.

Но и это еще не всё. В статье «JavaScript Module Pattern: In-Depth» (вот перевод на Хабрахабре) есть множество разных вариантов реализации модуля. Рекомендую посмотреть.

Template Defined Dependencies (2006)

Шаблонное определение зависимостей — следующий паттерн в семействе обособленного определения. Самый ранний из найденных мной проектов, где задействован этот подход, — Prototype 1.4 (2006), но у меня есть подозрение, что он использовался и в более ранних версиях библиотеки.

Prototype разрабатывался с 2005 года Сэмом Стивенсеном как клиентская часть фреймворка Ruby on Rails. Поскольку Сэм много работал с Ruby, неудивительно, что для менеджмента зависимостей между файлами он выбрал обычную шаблонизацию с помощью erb.

Если попробовать обобщить, можно сказать, что в этом паттерне зависимости определяются с помощью включения в целевой файл специальных меток. Здесь могут использоваться как распространенные механизмы шаблонизации (erb, jinja, smarty), так и специальные инструменты сборки, например borsсhik.

При использовании шаблонизированных зависимостей — в отличие от ранее рассмотренных паттернов обособленного определения — обязателен предварительный этап сборки.

Преобразуем наш пример с использованием описанного стиля. Для этого задействуем borsсhik:

Здесь файл app.tmp.js определяет подключаемые скрипты и их порядок. Если поразмышлять над примером, станет ясно, что данный подход кардинально не меняет жизнь разработчику. Вместо использования тегов script, просто используются другие метки в js-файле. Иными словами, мы по-прежнему можем что-то забыть или перепутать порядок подключаемых скриптов. Поэтому основное назначение этого подхода — обеспечение сборки единого js-файла из множества других.

Comment Defined Dependencies (2006)

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

Приложение, использующее данный подход, должно быть предварительно собрано — как это делалось в 2006 году для сборки MooTools, которую разрабатывал Валерио Пройетти, — или оно должно на этапе исполнения парсить исходный код и затем загружать все определенные с помощью комментариев зависимости. На базе последнего подхода реализована библиотека LazyJS Николаса Бевакуа.

Вот как будет выглядеть наш пример, если переписать его с использованием LazyJS:

Самая известная библиотека, где используется указанный подход, — MooTools. LazyJS была интересным экспериментом, но она появилась после CommonJS и AMD и поэтому особого внимания разработчиков не получила.

Externally Defined Dependencies (2007)

Давайте рассмотрим последний паттерн в семействе DDD. При внешнем определении зависимостей они все определяются вне основного контекста — например, в конфигурационном файле или в коде как объект или массив. При этом существует этап предподготовки, во время которого происходит инициализация приложения с загрузкой всех зависимостей в корректном порядке — на основе имеющейся о них информации.

Самое раннее использование такого подхода, которое я смог найти, датируется 2007 годом. Речь идёт о библиотеке MooTools 1.1.

В самом простом случае реализовать наш пример с использованием этого паттерна можно следующим образом (в качестве образца буду использовать собственную экспериментальную реализацию загрузчика, где задействован нужный паттерн):

Файл deps.json является тем самым внешним контекстом, где определяются все зависимости. При запуске приложения загрузчик получает файл, считывает из него зависимости, которые определяются в виде массива, загружает их и подключает к странице в корректном порядке.

На данный момент этот подход используется в некоторых библиотеках для создания кастомных сборок, например в lodash.

Sandbox Pattern (2009)

Программисты Yahoo!, работавшие над новой модульной системой YUI3, решали проблему использования разных версий библиотеки на одной странице. До YUI3 модульная система во фреймворке реализовывалась комбинацией паттернов Module и Namespace. Очевидно, что при такой схеме корневой объект, содержащий код библиотеки, мог быть только один и, следовательно, использовать несколько версий сразу было затруднительно.

Чтобы решить возникшую проблему, один из разработчиков YUI3 Адам Мур предложил задействовать «Песочницу». Простая реализация модульности с использованием этого паттерна может выглядеть так:

Суть подхода в двух словах — вместо глобального объекта используется глобальный конструктор, а модули в свою очередь могут определяться как его свойства.

«Песочница» послужила интересным решением проблемы модульности, но за пределами YUI3 особого распространения не получила. Если вам хочется узнать больше про Sandbox, рекомендую статью «Javascript Sandbox Pattern», а также официальную документацию YUI про создание новых модулей библиотеки.

Dependency Injection (2009)

В 2004 году Мартин Фаулер для описания нового механизма коммуникации компонентов в Java ввёл понятие «внедрение зависимостей» (dependency injection, DI). Основная суть заключается в том, что все зависимости «приходят» извне компонента. Другими словами, компонент не отвечает за инициализацию своих зависимостей, а лишь использует их.

Пять лет спустя Мишко Хевери, бывший сотрудник Sun и Adobe (где он занимался в том числе разработкой на Java), начал проектировать для своего стартапа JavaScript-фреймворк, где ключевым механизмом взаимосвязей компонентов служило внедрение зависимостей. Идея бизнеса не доказала свою эффективность, но исходный код фреймворка решили выложить на домене стартапа getangular.com. Большинство знает, что было потом: компания Google взяла Мишко и его проект под крыло и сейчас Angular — один из самых известных JavaScript-фреймворков.

Модули в Angular реализуются с помощью механизма DI. Однако модульность — не первичное назначение DI: об этом также явно говорит Мишко в ответе на соответствующий вопрос.

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

Если открыть страницу с примером в браузере, то код магическим образом отработает и мы увидим результат на странице.

Сейчас механизм DI используется во фреймворках Angular 2 и Slot. Также существует большое количество библиотек, упрощающих использование этого подхода в приложениях, не зависящих от каких-либо фреймворков.

CommonJS Modules (2009)

Вместе с браузерными JavaScript-движками ещё до появления Node.js разрабатывались платформы для серверной разработки, использующие JavaScript как основной язык. Серверные решения ввиду отсутствия соответствующих спецификаций не предоставляли унифицированного API для работы с операционной системой и внешним окружением (файловой системой, сетью, переменными окружения и т. д.), тем самым создавая проблемы с распространением кода. Например, скрипты, написанные для старичка Netscape Enterprise Server, не работали в Rhino и наоборот.

Читайте также:  что нельзя наливать в термос

В 2009 году наступил переломный момент — сотрудник Mozilla Кевин Дангур опубликовал пост о проблемах с серверным JavaScript. В посте он призвал всех заинтересованных присоединиться к неофициальному комитету для обсуждения и разработки серверного JavaScript API. Проект, над которым началалась работа, был назван ServerJS; но спустя его переименовали в CommonJS.

Работа закипела. Наибольшее внимание разработчиков получила спецификация формата модулей в JavaScript — CommonJS Modules (иногда её называют CJS или просто CommonJS), которая в конечном счёте была реализована в Node.js.

Для демонстрации CommonJS-модуля давайте адаптируем наш модуль следующим образом:

Babel также расширяет require при транспиляции модуля с дефолтным экспортом в формате ES2015 Modules (об этой модульной системе поговорим в конце статьи):

Babel преобразует подобный экспорт в CommonJS-модуль, где дефолтное значение экспортируется с помощью соответствующего свойства. То есть, упрощённо, получается следующий код:

На сегодняшний день CommonJS — самый распространённый формат модулей. Этот формат используется не только в Node.JS — ещё его можно использовать при разработке клиентских веб-приложений, собирая все модули в единый файл с помощью Browserify или webpack.

AMD (2009)

Когда разработка спецификации CommonJS шла полным ходом, в рассылке время от времени возникали бурные обсуждения о добавлении в спецификацию возможности асинхронной загрузки модулей. Она позволила бы ускорить загрузку веб-приложений, состоящих из большого количества файлов, и избавиться от необходимости сборки для доставки кода пользователям.

Коллега Кевина по работе в Mozilla Джеймс Бёрк был одним из самых активных сторонников асинхронной загрузки во всех обсуждениях. Джеймс мог выступать экспертом, поскольку был автором асинхронной модульной системы во фреймворке Dojo 1.7. Кроме того, именно он с 2009 года разрабатывал загрузчик require.js.

Основная идея, которую пытался донести Джеймс, — что загрузка модулей не должна осуществляться синхронно (то есть последовательно, один модуль за другим); нужно использовать возможности браузера для параллельной загрузки скриптов. Для реализации всех требований впоследствии он предложил собственный формат модулей, который был назван AMD (Asynchronous Module Definition).

Если переписать наш пример в соответствии с практиками AMD, то мы получим следующий код:

В 2011 году наступил переломный момент всех дискуссий: Джеймс объявил о создании отдельной рассылки для координации работ по AMD, поскольку консенсус с группой CommonJS за всё это время достигнут не был.

По личным наблюдениям могу сказать, что AMD всё ещё используется при разработке приложений, однако тенденция перехода к распространению клиентского кода через npm и отказ от bower уводят разработчиков от AMD всё дальше и дальше.

UMD (2011)

На самом деле явное противостояние форматов модулей началось ещё до того, как AMD отделился от CommonJS Modules. Уже тогда в лагере AMD было много разработчиков, которым нравился минимальный порог вхождения для начала работы с модульным кодом. Количество приверженцев CommonJS Modules из-за роста популярности Node.js и появления Browserify тоже росло очень быстро.

Фактически у нас было два стандарта, которые не могли ужиться друг с другом. AMD-модули без модификации кода нельзя было задействовать в средах, реализующих спецификацию CommonJS Modules (Node.js), а модули CommonJS не удавалось использовать с инструментами, поддерживающими AMD: RequireJS, curl.js. Да, впоследствии появилась возможность использовать RequireJS для работы с CommonJS-модулями, но такое положение дел всё равно никого не устраивало. Именно для решения проблемы переносимости кода между разными системами модульности и был разработан паттерн UMD — Universal Module Definition.

Найти настоящего автора паттерна оказалось довольно сложно — пришлось провести небольшое расследование. Сначала я обратился к автору репозитория паттернов UMD на GitHub Эдди Османи. Он вывел меня на Джеймса Бёрка и Криса Коваля, а они, в свою очередь, сослались на репозиторий реализации промисов Q.

Со дня своего появления библиотека Q могла работать в разных окружениях: в браузере (при подключении модуля через тег script) и на сервере в Node.js и Narwhal (через CommonJS Modules). Кроме того, Джеймс Бёрк через некоторое время добавил в проект Q поддержку AMD. Затем Эдди Османи систематизировал все похожие шаблоны в одном общем репозитории, который получил название UMD. Результат подобной адаптации кода для разных систем модульности мы сейчас и называем UMD.

Давайте в качестве примера переделаем наш игрушечный модуль для одновременной работы в окружениях CommonJS и AMD:

В сердце такой реализации паттерна находится самовызывающаяся функция, аргумент которой принимает разные значения в зависимости от окружения. В качестве аргумента передаётся функция следующего вида:

Это если код используется как CommonJS-модуль. Если же код используется как AMD-модуль, в качестве аргумента передаётся функция define. Адаптация кода под различные окружения происходит как раз благодаря такой подмене.

Сейчас большинство разработчиков, когда им надо обеспечить возможность использования библиотеки и в браузере, и в Node.js, пользуются именно форматом UMD. Экспорт в UMD применяется в разных популярных библиотеках, например в moment.js и lodash.

Labeled Modules (2012)

В 2010 году в комитете ТС39 началась работа над полноценной модульной системой в JavaScript. На тот момент система называлась ES6 Modules. К 2012 году уже стало примерно ясно, каким будет ее окончательный вид. Один из участников комитета, Себастьян Маркбейдж (на данный момент также ведущий разработчик React), по собственной инициативе подготовил транзитивный формат модулей. Предполагалось, что можно было бы задействовать его в средах, работающих даже с ES3, а впоследствии легко адаптировать приложение, использующее этот формат, под новый стандарт модулей. Формат получил название Labeled Modules.

Основная идея — применение меток (labels). Поскольку ключевые слова import и export зарезервированы в языке, метки их использовать не могли и были придуманы соответствующие синонимы: для определения экспорта — exports, для импорта — require.

Как обычно, давайте переделаем наш пример, чтобы показать формат в действии.

Пример конфига для сборки приложения, использующего Labeled Modules, можно посмотреть тут.

Как мы видим, получилось довольно элегантно. Но поскольку в 2012 году уже царствовали форматы CommonJS и AMD, конкурировать с ними было тяжело. В итоге, даже когда поддержка Labeled Modules появилась в первом webpack, особого распространения среди JS-разработчиков она всё равно не получила.

YModules (2013)

Как вы наверное догадались по названию Яндекс тоже не сидел сложа руки и сделал свою собственную модульную систему ;). Зачем нам надо было создавать что-то свое, когда можно было воспользоваться существующими форматами CommonJS или AMD? Дело в том, что хотя они и предоставляют возможность определения зависимостей и должный уровень изоляции кода, но для решения наших возникших задач ни CommonJS, ни AMD не подходили.

Было два основных дополнительных требования. Для реализации асинхронных API (например API Яндекс.Карт) нужно было, чтобы JS-модули с асинхронной природой использовались как можно более прозрачно. Кроме того, требовалась возможность использовать модули с уровнями переопределения БЭМ, иначе говоря — доопределять модули.

В 2013 году команды Карт и БЭМа окончательно выработали спецификацию новой системы, которую впоследствии воплотил в жизнь Дмитрий Филатов dfilatov.

Вот реализация нашего примера с помощью YModules:

Указанная особенность позволяет осуществлять «провайд» из блоков асинхронного кода, тем самым скрывая асинхронную природу модуля от внешнего мира. Например, если мы добавим в greeting.js какую-либо асинхронную работу (в данном случае — setTimeout ), то весь код, где используется этот модуль, останется без изменений:

Как было сказано выше, ещё одной отличительной чертой YModules является возможность работы с уровнями переопределения. Давайте рассмотрим её немного подробнее.

Если выполнить этот пример, метод sayHello в результате доопределения модуля greeting изменится на новый, и текст выводимого сообщения преобразуется к верхнему регистру. Всё благодаря тому, что в YModules при повторной декларации модуля его предыдущая версия будет содержаться в его же последнем аргументе (например, module как в примере выше).

На данный момент YModules используется в проектах Яндекса. Ещё это основная модульная система клиентского фреймворка i-bem.js.

ES2015 Modules (2015)

Комитет разработки ECMAScript (TC39), конечно, наблюдал за всем, что творилось в мире JavaScript. Стало очевидно, что пришло время для серьёзных изменений в языке.

В 2010 году над нативной модульной системой начал трудиться директор по стратегическому развитию Mozilla Дэйв Хёрман. Работа над спецификацией продолжалась в течение пяти лет, причём Дейв параллельно занимался другими задачами. За это время он успел побывать в роли архитектора и ведущего разработчика в проектах asm.js, emscripten, servo.

И вот, в 2015 году была выпущена спецификация ES2015, которая, в свою очередь, содержала в себе окончательную версию спецификации модулей. Давайте по традиции адаптируем наш пример:

Из-за того, что мы имеем дело с новыми ключевыми словами языка, а также в виду того, что спецификация Module Loader API, отвечающая за поддержку загрузки модулей в различных средах, еще не готова, мы не можем просто так взять и начать использовать новую систему модульности.

Впрочем, ES2015 всё равно применяется в большом количестве проектов. Чтобы начать использовать новый стандарт в мире, где пока ещё правит ES5, можно воспользоваться транспиляцией при помощи Babel: это довольно распространённая практика.

Итого

Существуют и другие подходы к организации модульности в JS. Некоторые из них могут переплетаться друг с другом, образуя причудливые формы, другие были созданы под конкретные проекты, а какие-то создавались в качестве транзитивного формата. Описать их все — очень непростая задача, поэтому в статье рассмотрены только более-менее популярные подходы и форматы. Тем не менее, думаю, статья помогла вам систематизировать знания о модульности, узнать о чём-то новом и о тех людях, которые стояли за упомянутыми технологиями.

Не могу не порекомендовать замечательную статью «Путь JavaScript модуля» Михаила Давыдова, где модульность рассматривается вкупе с загрузчиками и бандлерами, а также приводятся плюсы и минусы всех подходов к организации модульной структуры приложения.

Источник

Образовательный портал