Когда кода становится очень много, когда его пишут разные люди в разных концах света, встаёт вопрос о разбиении его на файлы. К сожалению (а может и к счастью) JS не имеет встроенных средств инклуда и уж тем более автолоада. Поэтому остро встаёт проблема организации исходников так, чтобы:
• Они грузились в правильном порядке и подключалось только то, что нужно.
• Не конфликтовали между собой.
• На продакшене всё грузились одним-двумя файлами, а не сотней.
• Не было проблем с отладкой во время разработки.
Давайте рассмотрим наиболее яркие решения этой проблемы...
Обычно эта техника велосипедится каждым разработчиком на свой лад. Где-нибудь в укромном месте заводится специальный файлик, где перечисляется все необходимые скрипты. Например:
# js/common.php
<?php return array(
'libs/jquery.js',
'libs/jquery-cookie.js',
);
# js/main.php
<?php return array(
include( 'common.php' ),
'app/main.js',
);
При разработке каждый такой скрипт подключается отдельным тегом <script/>, а при выкладке на продакшен, исходники сливаются в один файл.
+ Плюсы
+ Предельно простая реализация.
+ Мы точно знаем что и в каком порядке грузится.
+ Нет никаких ограничений на содержимое подключаемых файлов.
+ Может работать с различными типами ресурсов (js, css, шаблоны).
− Минусы
− Сложно поддерживать эти списки в актуальном состоянии, что приводит к загрузке лишнего кода или наоборот недозагрузке необходимого, но редко используемого.
− Вынуждает регистрировать каждый файл, а при переносе или переименовывании файла искать его упоминания и изменять их. Это рутина, которую разработчики стараются избегать, что приводит к излишней группировке большого числа модулей в небольшом числе файлов, а также бардаку с их расположением.
− Нельзя просто скопировать пачку файлов из другого проекта – нужно поподключать их в нужные места.
− Требуется серверный сборщик на нужном языке.
!!! CommonJS
Подход [CommonJS\http://www.commonjs.org/specs/] предполагает объявление зависимостей в самих модулях и отсутствие захламления глобальной области видимости. Каждый модуль исполняется в своей песочнице, где есть несколько предопределённых модулей: exports, require, module.
# js/libs/jquery.cjs.js
// jQuery content
exports.jQuery= jQuery
# js/libs/jquery-cookie.cjs.js
var $= require( '../libs/jquery.cjs' ).jQuery
// jQuery-cookie content
# js/modules/user.cjs.js
var $= require( '../libs/jquery.cjs' ).jQuery
exports.User= function( ){
// User content
}
# js/app/main.cjs.js
var $= require( '../libs/jquery.cjs' ).jQuery
var User= require( '../modules/user.cjs' ).User
require( '../libs/jquery-cookie.cjs' )
alert( new User( $.cookie.get( 'session-id' ) ) )
Как видно, если подключается библиотека, не рассчитанная на подключение через CommonJS API, то её код приходится модифицировать. Заметьте, тут jQuery-cookie ничего не экспортирует, а патчит jQuery (от которого зависит), поэтому в приложении мы вызываем require, чтобы патчинг состоялся.
+ Плюсы
+ Не засоряется глобальная область видимости.
+ Зависимости всегда грузятся до того, как они понадобятся.
− Минусы
− Прежде чем воспользоваться каким-либо модулем, его нужно сначала импортировать с помощью require. А когда он больше не используется – нужно не забыть и удалить соответствующий require. Это рутина и потенциальное место ошибок загрузки (лишней или недостающей).
− Чтобы воспользоваться модулями в консоли, их опять же нужно проимпортировать, что нивелирует основное преимущество консоли – быстрая отладка без лишних телодвижений.
− Необходимо вносить изменения в код библиотек, не совместимых с CommonJS.
− Скрипты должны исполняться в отдельных «песочницах», что усложняет отладку и даёт пенальти в производительности.
− Статический анализ кода для поиска зависимостей либо очень сложный, либо не надёжный.
− Не умеет подключать необходимые для скриптов стили и шаблоны.
− Много инфраструктурного кода (подключение моделей, задание их имён).
± Спорные моменты
± В разных скриптах один и тот же модуль может называться по разному. Это приводит к путанице при разработке (тут одно имя, там другое и боже упаси перепутать) и усложнению рефакторинга (перенос кода между скриптами, переименовывание модуля). А если ввести соглашение «везде называть одинаково», то это будет ничем не лучше глобальных переменных (разумеется с использованием пространств имён, как например $.cookie), а только хуже (из-за рутины с импортами).
± Удобно динамически синхронно подгружать скрипты. Но это приводит к тормозам: при разработке из-за большого числа модулей (загрузка которых идёт последовательно), а на продакшене из-за долгой загрузки по сети собранного пакета.
!!! AMD
[Asynchronous Module Definition\https://github.com/amdjs/amdjs-api/wiki/AMD] не требует изоляции модулей, но тем не менее поощряет её. Определение модуля заключается в вызове функции define или require с передачей им опционального списка зависимостей и коллбэк-функции, которая будет вызвана когда зависимости будут подгружены. В качестве зависимостей могут быть любые скрипты, в том числе даже и jsonp.
# js/libs/jquery.js
// jQuery content
# js/libs/jquery-cookie.js
// jQuery-cookie content
# js/modules/user.amd.js
define( [ "../libs/jquery" ], function( ){
// User content
})
# js/app/main.amd.js
require([ "../modules/user.amd", "../libs/jquery", "../libs/jquery-cookie" ],
function( User ){
alert( new User( $.cookie.get( 'session-id' ) ) )
})
+ Плюсы
+ Благодаря асинхронной архитектуре зависимости грузятся параллельно.
+ Модуль инициализируется строго после загрузки зависимостей.
− Минусы
− Опять же, необходимость вручную следить за всеми зависимостями, со всеми вытекающими отсюда последствиями.
− Опять же, неудобства с консолью.
− Опять же, статический анализ зависимостей сложен или ненадёжен. Разве что используя тяжёлую артиллерию – один из серверных JS движков.
− Опять же, работает исключительно с JS и знать не знает про сопутствующие файлы.
− Очень громоздкое и вариативное определение модуля. Особенно это чувствуется, когда есть много зависимостей (параметры коллбэка сопоставляются с зависимостями по порядковому номеру).
± Спорные моменты
± Опять же, возможность локального переименовывания модуля – фича скорее вредная, чем полезная.
!!! JAM
Javascript Autoloadable Modules – конвенция в рамках архитектуры [PMS\https://github.com/nin-jin/hyoo.ru/] (Package/Module/Source), идея которой заключается в том, чтобы организовывать исходники следующим образом:
• Все файлы (не только скрипты, но и сопутствующие им стили, картинки, шаблоны и прочее) лежат на одном уровне иерархии и объединены в модули.
• Имена файлов произвольные. Файлов одного типа может быть произвольное количество.
• Модули объединены в пакеты. На продакшене, все модули грузятся объединёнными попакетно. При разработке все файлы подключаются по отдельности.
• Зависимости между модулями определяются статическим анализом содержащихся в них исходников, основываясь на расширении файлов.
• Все файлы подключаемого модуля входят в сборку пакета, даже если сам модуль из другого пакета.
Конкретно JAM соглашение заключается в следующем:
• Все модули регистрируются глобально через добавление поля в this. При этом имя задаётся вида «$foo_bar».
• Если в jam-файле встречается комбинация вида «$foo_bar», то считается, что его модуль зависит от модуля «bar» в пакете «foo».
• JAM-файлы должны иметь расширение «jam.js», чтобы не путать их с обычными JS-файлами, зависимости внутри которых не анализируются.
• Все модули пакета зависят от главного модуля пакета, который имеет то же имя, что и сам пакет.
Правила достаточно тривиальны. Проще понять их на простом примере:
# jq/jq/jq.js
// jQuery content
# jq/jq/jq.jam.js
this.$jq= jQuery
# jq/cookie/jq_cookie.js
// jQuery-cookie content
# jq/cookie/jq_cookie.jam.js
this.$jq_cookie= jQuery.cookie
# myApp/myApp/myApp.jam.js
this.$myApp= {}
# myApp/User/myApp_User.jam.js
$myApp.User= function(){
// User content
}
# myApp/startup/myApp_startup.jam.js
alert( new $myApp.User( $jq_cookie.get( 'session-id' ) ) )
Дополнительные зависимости, которые автоматика (пока;) не в состоянии распознать, можно указывать в отдельном файле в формате, meta-tree:
# jq/cookie-plus/jquery_cookie-plus.js
// jQuery-cookie-plus content
# jq/cookie-plus/jquery_cookie-plus.meta.tree
include module =jq/cookie
+ Плюсы
+ Нет никаких ограничений на содержимое подключаемых js-файлов (только на jam).
+ Зависимости в основном определяются автоматически. Дополнительно вручную задаются в отдельном файле для модуля целиком, а не для каждого файла по отдельности.
+ Отсутствие какой-либо рантайм библиотеки (все зависимости подключаются статически).
+ При разработке все файлы подключаются отдельно и грузятся параллельно – это быстро и удобно в отладке.
+ В ссылки на файлы добавлется метка времени модификации, что избавляет от необходимости чистить кэш браузера и от лишних http-запросов.
+ Системное решение. Вместе со скриптами подгружаются и стили, и шаблоны. А вместе с шаблонами, необходимые скрипты и стили (в том числе скрипты, стили и подшаблоны из других модулей).
+ Единое именование модулей везде и отсутствие необходимости каждый файл где-либо прописывать позволяет легко рефакторить код и в том числе переносить между проектами.
− Минусы
− Теоретически возможно неправильное распознание зависимости в JAM-файлах (например, в закоментированном коде или строковой константе), но на практике, зная правила их поиска, допустить эти ошибки сложно.
− В JAM нельзя напрямую использовать сторонний код, апи которого использует знак доллара. В примере выше, эта проблема обходится созданием алиаса, удовлетворяющим принципам JAM. Ну либо отказом от JAM в пользу «meta.tree».
± Спорные моменты
± Требуется серверный сборщик. Впрочем, запускается он как утилита, которая формирует все необходимые файлы в модуле «-mix», что позволяет легко интегрировать его в любую систему.
± Все модули доступны глобально, что потенциально конфликтноопасно. Однако, для встраивания на сторонние сайты, собирается специальная изолированная версия – «library.js».
!!! Попробуйте PMS
Чтобы было легко поиграться, я написал простенький тестовый [пример\http://nin-jin.github.com/pms-demo/] ([исходники\https://github.com/nin-jin/PMS-demo]). Скрипт «index.php» сначала запускает компиляцию пакета «demo», а потом выдаёт браузеру две статические хтмл-ки, демонстрирующие подключение ресурсов при разработке и на продакшене.
Источник -
http://hyoo.ru/?article=%D0%9C%D0%BE%D0%B4%D1%83%D0%BB%D0%B8+%D0%B2+JavaScript;author=Nin+Jin
Комментариев нет:
Отправить комментарий