Статьи

Нулевой на Гобелене 5.4

На этой неделе у меня было совсем немного времени, чтобы посвятить себя работе над Tapestry 5.4, и я чувствую, что я близок к поворотному моменту. Вы можете следить за прогрессом в ветке 5.4-js-rewrite

Я обсуждал свои планы раньше ; это что-то вроде обновления прогресса.

В основном, этот выпуск будет посвящен JavaScript:

Прототип против 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, и надеюсь, что многие из вас присоединятся ко мне.