Забудьте об AMD навсегда, больше нет веских причин для использования AMD.

Раньше модули AMD были очень популярны. Многие веб-приложения и веб-сайты были разработаны с использованием модулей AMD. Но в последнее время модули ES явно превзошли устаревшие модули AMD.

В то время наиболее привлекательной особенностью модулей AMD было то, что они, в отличие от модулей CommonJS, могли работать в браузере без дополнительной предварительной обработки. Шаги сборки во время разработки требуют времени и усложняют отладку. Модули ES также не нужно преобразовывать и связывать для выполнения в браузере. По сравнению с AMD модули ES имеют дополнительные полезные функции. Порядок загрузки четко определен, а сообщения об ошибках во время загрузки более информативны. Круговые зависимости — моя любимая функция. Циклические зависимости позволяют быстро исправлять и рефакторить код, сохраняя работоспособность приложения. Динамический импорт на основе промисов позволяет загружать код только тогда, когда он необходим. Модули предварительной загрузки и кэширования делают объединение необязательным, по крайней мере, в корпоративных приложениях. Объединение с Rollup проще, чем объединение модулей AMD с Webpack.

Единственным относительным ограничением модулей ES является то, что они еще не поддерживают импорт HTML-шаблонов. Так что пока текстовые файлы можно загружать с верхним уровнем await.

Как перейти с модулей AMD на модули ES

До недавнего времени я поддерживал несколько приложений с интерфейсом, состоящим из нескольких сотен модулей AMD. Чтобы упростить их разработку, я недавно преобразовал приложения в модули ES. За исключением редких модулей со сложным условным динамическим импортом, преобразование выполняется просто — define() аргументов преобразуются в import операторов, а return в функциях заменяются на export default. На самом деле, основная причина существования export default в модулях ES заключается в возможности легкой миграции с AMD.

Чтобы в значительной степени автоматизировать преобразование, я разработал простой инструмент на основе Nodejs. Приложение зависит от библиотеки парсеров JavaScript acorn (если вы никогда не использовали парсеры JavaScript, вам может быть интересно на всякий случай посмотреть, что они делают https://astexplorer.net/).

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

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

Множество способов определения модуля

Модуль AMD определяется внутри функции define(). Обычно define() получает массив с путями модулей, а функция получает перечисленные модули и возвращает модуль.

define() с массивом зависимостей и функцией

Каждый путь зависимости вместе с соответствующим параметром функции определения становится оператором import. return функции становится export default. define() удаляется.

define() загружает HTML-шаблон

RequireJS позволяет загружать текстовые зависимости с помощью подключаемого модуля text.js. Модули ES поддерживают импорт JSON, но пока не поддерживают импорт HTML. Шаблон HTML можно загрузить в модуль ES с верхним уровнем await. Я подробно описал этот подход в предыдущем посте.

define() с массивом зависимостей и функцией без оператора return

Получившийся модуль ES не имеет экспорта:

определить() с функцией без параметров

Поскольку исходный модуль AMD не имеет зависимостей, в результирующих модулях ES нет imports.

define() с функцией без оператора return

определить() с объектом

Обработка требует()

В отличие от define(), requirejs() не создает модуль, он обычно используется в main.js для загрузки определенных модулей и запуска приложения.

requirejs() с массивом зависимостей и функцией

Он обрабатывается как define() с функцией без оператора return.

Вложенные требования()

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

Тот же результат, если функция requirejs() вложена в функцию define().

Быстрая миграция

Процедура быстрая. Он включает в себя несколько быстрых шагов:

  • Перед миграцией зафиксируйте все незафиксированные изменения в локальном репозитории git. С помощью git вы можете оценить все изменения, внесенные этим инструментом.
  • Откройте командную строку, перейдите в папку, содержащую инструмент, и установите зависимости: npm install
  • Запустите инструмент, выполнив команду, указав папку, содержащую модули AMD. Инструмент преобразует файлы *.js во всех подпапках родительской папки: node main.js path/to/folder/with/js-files
  • Теперь объект paths объекта, переданного в requirejs.config(), должен быть преобразован в карту импорта в файле HTML, обычно index.html, с загрузкой кода JavaScript. Перечисленные в paths URL-адреса папок и модулей должны быть скорректированы, например, с префиксом js/, поскольку в картах импорта URL-адреса относятся к файлу, содержащему карту импорта. Если вы никогда не пользовались импортными картами, читайте пост в точку.
  • Попробуйте запустить преобразованное приложение. Если это не удается, вы можете увидеть причину в консоли. Сначала приложение может не работать из-за неправильно определенных спецификаторов модулей в карте импорта. Но после нескольких корректировок в карте импорта приложение заработает. Помимо форматирования, большинство преобразованных модулей не нуждаются в дополнительной ручной постобработке.
  • Если исходное приложение имело какие-либо импорты на основе requirejs(), проверьте их и скорректируйте автоматически преобразованный код.

Отступ

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

Инструмент для переноса кода JavaScript на базе AMD в модули ES можно загрузить с https://github.com/marianc000/amdToEsm.