На этой неделе у меня было совсем немного времени, чтобы посвятить себя работе над Tapestry 5.4, и я чувствую, что я близок к поворотному моменту. Вы можете следить за прогрессом в ветке 5.4-js-rewrite
Я обсуждал свои планы раньше ; это что-то вроде обновления прогресса.
В основном, этот выпуск будет посвящен JavaScript:
- Устранение жесткой зависимости от прототипа
- Создание слоя абстракции, позволяющего использовать вместо него jQuery
- Представляем модули AMD ( определение асинхронного модуля ) (используя RequireJS )
- Переход на более декларативный стиль JavaScript; используя атрибуты данных HTML5
Прототип против JQuery
Гобелен исторически использовал Prototype как то, что я называю своей основой . Фонд отвечает за выбор и создание элементов, присоединение обработчиков событий, запуск событий и инкапсуляцию запросов Ajax.
Уже возможно иметь и Prototype, и jQuery на одной странице; Я делаю это регулярно. Для этого также есть проект, гобелен-jquery . Тем не менее, есть несколько затрат на наличие нескольких базовых структур:
Прежде всего это размер ; в каждом фреймворке много кода (160Kb для Prototype, 260Kb для jQuery — намного меньше после минимизации и сжатия) и огромное перекрытие. Это включает в себя тот факт, что обе платформы имеют версию механизма выбора Sizzle CSS .
Есть также некоторые случайные столкновения; У меня была неприятная проблема, когда некоторый Bootstrap JavaScript запускал событие «скрыть», когда модальное диалоговое окно было закрыто. После долгого отслеживания и удаления волос я обнаружил, что jQuery будет рассматривать функцию, прикрепленную к элементу, как обработчик события для события с соответствующим именем. Событие «hide», вызванное jQuery, нашло метод hide (), добавленный Prototype, и мой модальный диалог просто подмигнул, а не анимация, как я хотел.
Наконец, это не просто мир. есть также YUI, ExtJS, MooTools … в течение последних нескольких лет, каждый раз, когда я упоминал о своем желании создать слой абстракции, я получал вопрос «будет ли он поддерживать X?» и «X» был какой-то новой структурой, фаворитом этого человека, о которой я раньше не слышал.
Поэтому уровень абстракции будет важен для обеспечения быстрой загрузки JavaScript и минимизации накладных расходов на выполнение сети и JavaScript.
Если вы разработчик приложений, ничто не помешает вам напрямую использовать нативную платформу по вашему выбору. Во многих случаях это будет наилучший подход, и он обеспечит большую эффективность, доступ к более полному API и доступ (в случае jQuery) к огромному сообществу плагинов.
Если вы создаете повторно используемые компоненты, лучше всего попытаться использовать SPI (интерфейс поставщика услуг; уровень абстракции). Это позволяет широко использовать вашу библиотеку, не привязывая пользователей к какой-либо конкретной фундаментальной структуре.
Модули против библиотек
Tapestry 5.3 и более ранние версии предоставляют отличную поддержку JavaScript в виде библиотеки. Концепция JavaScriptStack позволяет объединять любое количество отдельных библиотек JavaScript вместе с поддержкой файлов таблиц стилей CSS. Более того, Tapestry может создать одну виртуальную библиотеку JavaScript, объединяя все библиотеки стека вместе. Tapestry делает это во время выполнения и условно … поэтому во время разработки вы можете работать со многими небольшими файлами JavaScript, а в процессе работы они превращаются в один объединенный минимизированный файл JavaScript.
Однако у этого подхода JavaScript есть свои проблемы. Логика инициализации страницы очень зависит от стеков (включая основы), присутствующих и загружаемых в определенном порядке. Никакая логика инициализации страницы не может быть выполнена, пока все библиотеки не будут загружены. Если у вас есть несколько стеков в производстве, то все стеки должны быть загружены до выполнения любой логики.
Все это влияет на время загрузки страницы, особенно на время, воспринимаемое конечным пользователем. Слишком много происходит последовательно, и слишком много происходит в целом.
В Tapestry 5.4 фокус смещается с библиотек на модули. В AMD модуль — это функция JavaScript, которая выражает зависимость от любого количества других модулей и экспортирует значение в другие модули. Экспортируемое значение может быть отдельной функцией или объектом, содержащим несколько функций (или других значений).
Вот снимок одного из модулей, который обеспечивает поддержку компонента Zone :
define ["core/spi", "core/events", "core/ajax", "core/console", "_"], (spi, events, ajax, console, _) -> spi.onDocument events.zone.update, (event) -> this.trigger events.zone.willUpdate content = event.memo.content unless content is undefined this.update content this.trigger events.zone.didUpdate spi.onDocument events.zone.refresh, (event) -> parameters = this.getAttribute "data-zone-parameters" parameters = if parameters is null then null else JSON.parse(parameters) ajax event.memo.url, parameters: _.extend { "t:zoneid": this.element.id }, parameters, event.memo.parameters onsuccess: (reply) => this.trigger events.zone.update, content: reply.responseJSON?.content deferredZoneUpdate = (id, url) -> _.defer -> zone = spi id if zone is null console.error "Could not locate element '#{id}' to update." return zone.trigger events.zone.refresh, { url } return { deferredZoneUpdate }
Функция define () используется для определения модуля; Первый параметр — это массив имен модулей. Второй параметр — это функция, вызываемая после загрузки всех зависимостей; параметры этой функции — экспорт названных модулей.
Эта функция выполняет некоторую инициализацию, присоединяя обработчики событий к объекту документа; он также определяет и экспортирует одну именованную функцию («deferredZoneUpdate»), которая может использоваться другими модулями.
Да, у меня есть модуль с именем «_», который называется
Underscore.js . Я, вероятно, добавлю «$» для jQuery. Почему бы не иметь короткие имена?
Библиотека RequireJS знает, как загружать отдельные модули и обрабатывать зависимости этих модулей от других. А еще лучше, он знает, как сделать это параллельно . Фактически, в 5.4 единственным JavaScript, загруженным с использованием традиционного тега <script>, является RequireJS; все остальные библиотеки и модули загружаются через него.
В конце дня будет меньше загружаться JavaScript, а загружаемый JavaScript будет загружаться параллельно. Я все еще работаю над некоторыми деталями о том, как библиотеки модулей могут быть объединены в стеки.
Декларативный JavaScript
Даже в более ранних формах Tapestry поддержка JavaScript была мощной … но неуклюжей. Чтобы сделать что-то нестандартное и значимое на стороне клиента, вы должны были:
- Напишите библиотеку JavaScript для вашей функциональности
- Monkey-patch для функции в пространстве имен T5.initializers.
- Упакуйте ваши данные инициализации как JSONObject … как правило, идентификаторы клиентских элементов, URL-адреса и т. Д.
- Ваш код компонента Java вызывает JavaScriptSupport.addInitializerCall (String, JSONObject), чтобы связать все это вместе.
Tapestry упаковывает все эти инициализации addInitializerCall () в один большой блок, который выполняется в нижней части страницы (и выполняет нечто подобное для Ajax-ориентированных частичных обновлений страницы).
Уф! Для этого необходимо, чтобы элементы имели уникальные идентификаторы (что может быть проблемой при обновлении Ajax на странице). Кроме того, типичным поведением является создание объектов контроллера и присоединение обработчиков событий непосредственно к элементам; это прекрасно работает для небольших страниц, но если на странице присутствуют сотни (или тысячи) элементов, это обременяет среду JavaScript: множество объектов.
Для этого подхода нет конкретного названия, кроме, возможно, «грубого». Давайте назовем это активной инициализацией.
Более современный подход более пассивен . В этом стиле дополнительное поведение определяется в первую очередь в терминах событий, которые может вызвать элемент, и обработчика верхнего уровня для этого события. События могут быть связаны с DOM, такими как «щелчок», «изменение» или «фокус», или для конкретного приложения, которые срабатывают непосредственно на элементе. Обработчик верхнего уровня, часто присоединяемый к документу, обрабатывает событие, когда он всплывает от элемента к верхнему уровню; он часто различает, какие элементы ему интересны, используя селектор CSS, который включает ссылки на атрибут данных.
Например, в модуле core / forms мы должны отслеживать клики на кнопках отправки и изображения как часть формы (это связано с поддержкой отправки формы Ajax).
spi.onDocument "click", "input[type=submit], input[type=image]", -> setSubmittingHidden (spi this.element.form), this
Этот небольшой кусочек кода присоединяет к документу обработчик события «щелчка» для любого элемента submit или изображения в любом месте на странице … или когда-либо добавляемого на страницу позже через Ajax. В более старых версиях Tapestry мог быть установлен отдельный обработчик событий для каждого такого элемента, но в 5.4 достаточно одного обработчика событий.
Внутри обработчика событий у нас есть доступ к самому элементу, включая атрибуты данных. Это означает, что то, что могло быть сделано с использованием инициализации страницы в Tapestry 5.3, в Tapestry 5.4 может быть обработчиком событий уровня документа и атрибутами данных для конкретного элемента; нет JSONObject, нет addInitializerCall (). Меньше работы по инициализации страницы, меньше страниц, меньше объектов во время выполнения.
Обратная сторона, однако, заключается в том, что стоимость запуска дополнительных событий и стоимость всех дополнительных событий всплывают трудно. Тем не менее, в подходе 5.3 все время вводится много дополнительной памяти, в то время как в подходе 5.4 в худшем случае должны вводиться некоторые предельные издержки, когда пользователь активно взаимодействует.
Есть еще одно преимущество; присоединяя свои собственные обработчики событий к конкретным элементам, вы получаете возможность дополнять или заменять поведение в определенных случаях. Это своего рода версия наследования, в которой поведение элемента фактически частично определяется элементами, в которых он содержится. Отчасти сумасшедший … и вроде как CSS.
Совместимость
Итак, вы написали необычное приложение на Tapestry 5.3 и думаете о том, чтобы обновить его до 5.4, когда оно будет готово … к чему вы должны быть готовы?
На стороне сервера Tapestry 5.4 представляет ряд новых сервисов и API, но не сильно меняет то, что уже присутствовало.
Ряд интерфейсов и сервисов, которые ранее считались устаревшими, были полностью удалены; даже некоторые зависимости, такие как
Javassist .
Все встроенные компоненты Tapestry будут работать через SPI; они будут работать по существу одинаково, независимо от того, выберете ли вы работу с использованием Prototype, jQuery или обоих.
Внешний вид меняется от встроенного CSS в Tapestry до Twitter Bootstrap. Все старые классы CSS с префиксом «t-» теперь бессмысленны.
С другой стороны, что если вы создали какой-то сложный код на стороне клиента? Хорошо, если он основан на Prototype напрямую, это будет хорошо; просто держите Prototype в миксе и начинайте миграцию ваших компонентов для использования SPI.
Дьявол в деталях — это все о пространствах имен Tapestry и T5. Гобеленовый объект — самый старый код; пространство имен T5 было введено в 5.2 в попытке немного лучше организовать на стороне клиента.
Пока не ясно, сколько из этих библиотек останется. В идеале они не будут присутствовать вообще, если не включен явный режим совместимости (возможно, включен по умолчанию). При этом конфликт между старым активным / грубым подходом и новым модульным и пассивным / декларативным подходом трудно преодолеть.
Фактически, цель этого поста состоит в том, чтобы частично стимулировать дискуссию о том, какой уровень совместимости требуется и является реалистичным.
Вывод
Я полностью рассказал о том, где уже находится Tapestry 5.4 и куда он направляется. Несмотря на то, что будущее за веб-приложениями зависит от клиента, гибридные приложения по-прежнему играют важную роль: частично ориентированные на страницы, частично на Ajax — и приятно иметь единый инструмент, объединяющий оба подхода.
Кроме того, даже для приложения Tapestry, которое представляет собой одну «страницу», то, что Tapestry приносит на стол, все еще очень полезно:
- перезарядка в прямом эфире
- Стеки JavaScript (с минификацией, сжатием Gzip и кэшированием на стороне клиента)
- будущая поддержка CoffeeScript-to-JavaScript
- лучшие в своем классе сообщения об исключениях
- потрясающе высокая производительность
Все эти преимущества столь же неотразимы в одностраничном приложении, как и в традиционном приложении, если не больше. Я с нетерпением жду создания еще более впечатляющих приложений в версии 5.4, чем в 5.3, и надеюсь, что многие из вас присоединятся ко мне.