9 нояб. 2008 г.

Пакетирование js- и сss-ресурсов. Управление подключением ресурсов с помощью XML/XSL.

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

Я расскажу о способе, который использую в своем фрэймворке «Конфигуратор», однако, если вашим проектам не чужды понятия XML и XSL, вы легко можете адаптировать предлагаемый способ к своим нуждам.

Если вкратце, то я предлагаю разбивать все js- и css-ресурсы проекта на пакеты, исходя из их функциональности. Внутри пакета собирать входящие в него файлы и минимизировать их. В режиме разработки отдавать исходники как они есть, а в «боевом» — собранные по пакетам и минимизированные (для уменьшения количества http-запросов и размера файлов). Кроме этого, за каждую функциональность проекта должен отвечать отдельный xsl (который непосредственно и знает о необходимых этой функциональности ресурсах). Причем все подключения, сборки пакетов, и их минимизации должны происходить при как можно меньшем количестве телодвижений со стороны разработчика.

Рассмотрим на примере двух пакетов — global (содержащего базовую функциональность проекта) и zforms (содержащий функциональность для форм). Пакет global должен подключаться везде, а пакет zforms — только на страницах форм.

Xml-описание пакетов (должно быть доступно везде внутри проекта):

<package name="global" version="1">
  <css media="screen">
    <file name="common-screen.css" dev-mode="development" />
    <file name="main-screen.css" dev-mode="development" />
    <file name="main-colors-screen.css" dev-mode="development" />
    <file name="main-screen-package.css" dev-mode="production" />
  </css>
  <css media="screen" cc="if IE">
    <file name="common-screen-ie.css" dev-mode="development" />
    <file name="main-screen-ie.css" dev-mode="development" />
    <file name="main-screen-ie-package.css" dev-mode="production" />
  </css>
  <js>
    <file name="Common.js" dev-mode="development" />
    <file name="Common_Ajax.js" dev-mode="development" />
    <file name="Common-package.js" dev-mode="production" />
  </js>
</package>

<package name="zforms" version="2.43">
  <css media="screen">
    <file name="ZForms-screen.css" dev-mode="development" />
    <file name="ZForms-screen-package.css" dev-mode="production" />
  </css>
  <css media="screen" cc="if IE">
    <file name="ZForms-screen-ie.css" dev-mode="development" />
    <file name="ZForms-screen-ie-package.css" dev-mode="production" />
  </css>
  <js>
    <file name="ZForms.js" />
  </js>
</package>

Рассмотрим подробнее из чего состоит пакет. Во-первых, у пакета есть имя (@name), и версия (@version). Во-вторых, пакет делится на блоки по типу ресурсов: css и js. Каждый такой блок может содержать несколько файлов с @dev-mode = ’development’ (которые будут подключаться в режиме разработки) и один файл с @dev-mode = ’production’ (подключаемый в «боевом» режиме). Если же используемые файлы уже собраны (например вы используете какую-то библиотеку), то @dev-mode указывать не нужно. Для примера, именно так подлючается ZForms.js.

Кроме того, блоки пакета могут содержать необязательные атрибуты:

  • для css-блоков:
    • @media (screen/print/handheld)
    • @cc (для указания conditional comments, если стили блока нужно подключать только для IE)
    • @version (версия блока)
  • для js-блоков
    • @cc (аналогично css-блоку)
    • @version (версия блока)
    • @pack, true/false (использовать ли алгоритм base-62 для сжатия production-версии)

Суммарная версия файла будет складываться из версии пакета и версии блока. Версия будет добавлена к имени файла в качестве параметра — ?v={номер_версии}. Добавление этого параметра позволяет избежать кэширования браузерами предыдущей версии файла.

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

В общем шаблоне для всех страниц:

<xsl:template match="&document;" mode="cdocument:head">
  <head>
    ...
    <xsl:apply-templates select="." mode="cdocument:include-packages">
    ...
  </head>
</xsl:template>

<xsl:template match="&document;" mode="cdocument:include-packages">
  <xsl:call-template name="cinclude:package">
    <xsl:with-param name="name" select="'global'" />
  </xsl:call-template>
</xsl:template>

В шаблоне для форм перекрываем:

<xsl:template match="&document;" mode="cdocument:include-packages">
  <xsl:apply-imports /> <!-- подключение остальных пакетов -->
  <xsl:call-template name="cinclude:package">
    <xsl:with-param name="name" select="'zforms'" />
  </xsl:call-template>
</xsl:template>

В итоге, получаем, что за каждый функционал отвечает отдельный xsl, который вызывает шаблон для подключения своих ресурсов и, что самое важное, вызывает через apply-imports шаблоны для подключения всех остальных ресурсов текущей страницы, причем не только из пакета global, но и из других возможных независимых пакетов (при этом ничего не зная о них).

Все файлы с @dev-mode = ’production’ можно и нужно создавать автоматически при выкладке в «боевой» режим, распарсив xml-описание пакетов.

Текущее состояние dev-mode определяется глобальной переменной $cglobal:dev-mode, которая, в свою очередь, берет его из конфига проекта.

1 комментарий:

123 комментирует...

It is very interesting for me to read the article. Thank you for it. I like such topics and anything connected to this matter. I would like to read a bit more soon.
Alex
Cell phone blockers