В этой статье я затрону тему подключения ресурсов, а именно, как можно управлять подключением нужных 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, которая, в свою очередь, берет его из конфига проекта.