Использование Phing для сборки web приложений

13 октября, 2008
phing logo

В этой статье я хочу рассказать об использовании Phing при разработке небольшого web приложения. Почему небольшого? Все очень просто. С одной стороны в качестве примера будет использован реальный build файл, а не упрощенный вариант из учебника. А с другой – этот build файл достаточно короткий, и о нем можно было рассказать в рамках статьи ;) .

Примечание. Phing – это система сборки проектов, основанная на Apache Ant. Я предполагаю, что вы знаете как её установить и настроить. Если нет – читайте «Программирование на PHP. Избавляемся от рутинных операций с помощью Phing».

Итак, приступим.

Недавно я писал о генераторе форм для фреймворка CodeIgniter. Как вы понимаете приложение небольшое, но, тем не менее, перед переносом его на сервер нужно сделать массу рутинных операций.

1) Subversion в каждой папке приложения создает скрытую папку “.svn”, что значительно усложняет «ручное» копирование файлов. Т.е. каждую папку придется копировать отдельно.

2) JavaScript и CSS файлы желательно сжать.

3) Кроме того, много маленьких файлов загружаются дольше, чем один большой. Т.к. в данном приложении используется 5 js-файлов, то разумно будет объединить их в один. С другой стороны, во время разработки удобнее работать именно с 5-тью маленькими файлами.

4) Раз мы объединяем несколько js-файлов в один, то необходимо изменить теги <script> в заголовках страниц.

5) Практически все современные браузеры поддерживают gzip сжатие. Поэтому сделаем архивную версию объединенного JavaScript файла (для браузеров, которые поддерживают gzip, будем отдавать архивную версию, всем остальным – обычную).

Как видите, работы не много. Но представьте, вы подготовили приложения для размещения на сервере, и нашли ошибку или решили что-то изменить. Исправить сжатые js файлы конечно можно, но, поверьте, это далеко не самое увлекательное занятие :-) . Т.е. вам придется внести исправления в исходный вариант и выполнить все шаги с 1 по 5 заново.

Естественно такую работу можно автоматизировать и Phing отлично подойдет в этой ситуации.

Рассмотрим размещение файлов проекта.

css/
	ie6styles.css
	styles.css
images/
	add.png
	ci_logo.jpg
	.......
lib/
	jquery/
		jquery.js
	tabs/
		jquery-ui-personalized-1.5.2.js
	tooltip/
		jquery.dimensions.js
		jquery.tooltip.js
	mainscripts.js
index.html
.htaccess
build.xml

Здесь index.html – единственная страница приложения, build.xml – файл с задачами для Phing, .htaccess – определяет в каком случае отправлять архивную версию js файла (подробнее здесь).

Переходим к build файлу. Т.к. он довольно объемный я буду приводить его по частям. Прежде всего, создаем проект и несколько свойств.

  1. <project name="CIFormBuilder" default="deploy" basedir=".">
  2.    
  3.     <property name="DEPLOY_DIR" value="deploy" />
  4.     <property name="IMG_DIR" value="images" />
  5.     <property name="CSS_DIR" value="css" />
  6.     <property name="LIB_DIR" value="lib" />
  7.    
  8.     <property name="GLOBAL_LIB_DIR" value="E:/www/libs" />
  9.    
  10.     ………….
  11.    
  12. </project>

Имя (name) проекта выбираем произвольно. В атрибуте default указываем название задачи, которая будет выполняться по умолчанию. basedir – корневая папка проекта.

С помощью свойств (property) мы объявили константы, которые в данном случае содержат имена папок.

Отдельно хочу остановиться на GLOBAL_LIB_DIR. Это свойство содержит имя папки, в которой находятся PHP скрипты для сжатия файлов (подробнее о них ниже).

Как вы, наверное, догадались, все файлы проекта, предназначенные для размещения на сервере, мы скопируем в папку deploy. Поэтому, прежде всего, создаем её.

  1. <target name="prepare">
  2.     <echo msg="Removing old files…" />
  3.     <delete dir="${DEPLOY_DIR}" includeemptydirs="true" verbose="true" failonerror="true" />
  4.     <echo msg="Creating new dir…" />
  5.     <mkdir dir="${DEPLOY_DIR}" />
  6. </target>

Эта задача сначала пытается удалить папку deploy (операция delete) вместе со всем содержимым, а затем с помощью mkdir создает её заново. Таким образом, можно быть уверенным, что проект не будет содержать старых файлов.

Примечание. Операция echo выводит произвольное сообщение. К сожалению, русские буквы отображаются не правильно.

Теперь копируем в эту папку все файлы кроме js и css. И сразу же исправляем теги <script> в index.html.

  1. <target name="copyfiles" depends="prepare">
  2.     <echo msg="Copying images…" />
  3.     <copy todir="${DEPLOY_DIR}/${IMG_DIR}">
  4.         <fileset dir="${IMG_DIR}">
  5.             <include name="*.gif" />
  6.             <include name="*.png" />
  7.             <include name="*.jpg" />
  8.         </fileset>
  9.     </copy>
  10.     <echo msg="Copying main files…" />
  11.     <copy todir="${DEPLOY_DIR}">
  12.         <fileset dir=".">
  13.             <include name="index.html" />
  14.             <include name=".htaccess" />
  15.         </fileset>
  16.         <filterchain>
  17.             <replaceregexp>
  18.                 <regexp pattern="<script.*\/script>\r\n" replace="" />
  19.                 <regexp pattern="(<\/head>)"
  20.                     replace="<script type="text/javascript" src="lib/global.js"></script>\1" />
  21.                 <regexp pattern="\.css" replace="-min.css" />
  22.             </replaceregexp>
  23.         </filterchain>
  24.     </copy>
  25. </target>

Разберем эту задачу подробнее. В первой строке мы задали название и указали, что эта задача зависит от prepare. Т.е. при вызове copyfiles всегда предварительно будет выполняться prepare.

В строках 3-9 мы копируем файлы картинок. Для этого используется операция copy, а список файлов, которые нужно скопировать, создается с помощью fileset.

После этого копируем index.html и .htaccess. Здесь наибольший интерес представляет операция filterchain. Она создает цепочку фильтров, через которые пропускаются файлы. В данном случае фильтр только один. В нем мы используем операцию замены по регулярному выражению replaceregexp.

Первое правило (строка 18) удаляет все теги <script>. Второе – находит тег </head> и вставляет перед ним тег скрипт со ссылкой на объединенный js файл (global.js). Третье – добавляет к именам css файлов суффикс -min (сжатые версии файлов).

Таким образом, в папку deploy будет записан исправленный index.html.

Теперь займемся сжатием файлов.

В стандартную поставку Phing нужные нам задачи не входят. Поэтому нам понадобятся две библиотеки: JSMin и CSSMin. Размещаем их в папке, которую мы задали в свойстве GLOBAL_LIB_DIR.

Напрямую работать с библиотеками Phing не может. Поэтому нужно создать соответствующие операции (tasks). Каждая такая операция представляет собой PHP класс. Описать его в рамках одной статьи не реально, да и необходимости в этом нет. Для бибилиотеки JsMin я нашел готовое решение и в течении 15 минут переписал его для использования с CSSMin (в конце статьи приведена ссылка на архив с этими файлами).

Т.е. вам нужно просто распаковать этот архив в папку #PHING_DIR#/tasks.

Для того, чтобы Phing узнал о том, что вы создали операцию необходимо в build файле использовать taskdef. Выглядит это так:

  1. <taskdef name="stubjsmin" classname="phing.tasks.my.stubJsMinTask" />
  2. <taskdef name="stubcssmin" classname="phing.tasks.my.stubCssMinTask" />

Правила следующие. Имя должно совпадать с именем класса, но без окончания task. Имя класса (classname) задает имя класса с учетом пути к нему (в качестве разделителя папок используется точка).

Теперь напишем задачу, которая будет сжимать js файлы. Сначала создадим набор файлов (fileset).

  1. <fileset id="srcjs" dir="${LIB_DIR}">
  2.     <include name="jquery/jquery.js" />
  3.     <include name="tabs/jquery-ui-personalized-1.5.2.js" />
  4.     <include name="tooltip/jquery.dimensions.js" />
  5.     <include name="tooltip/jquery.tooltip.js" />
  6.     <include name="mainscripts.js" />
  7. </fileset>

Теперь сжимаем файлы.

  1. <target name="minifyjs">
  2.     <echo msg="Minifying JavaScript files…" />
  3.     <stubjsmin jsminpath="${GLOBAL_LIB_DIR}/jsmin/jsmin-1.1.1.php"
  4.         targetdir="${DEPLOY_DIR}/${LIB_DIR}">
  5.         <fileset refid="srcjs" />
  6.     </stubjsmin>
  7. </target>

Здесь нужно отметить два момента.

Первый – использование операции stubjsmin. В её параметрах мы указываем размещение библиотеки JSmin и путь к папке, в которой будут размещены сжатые файлы. При записи сжатых файлов будет сохранена структура папок, в которых они размещены.

Второй – использование fileset внутри stubjsmin. Имя набора файлов указывается с помощью параметра refid.

Сжатие CSS файлов выполняется точно также.

  1. <target name="minifycss">
  2.     <echo msg="Minifying CSS files…" />
  3.     <stubcssmin cssminpath="${GLOBAL_LIB_DIR}/cssmin/cssmin_v.1.0.php"
  4.         targetdir="${DEPLOY_DIR}/${CSS_DIR}">
  5.         <fileset dir="${CSS_DIR}" />
  6.     </stubcssmin>
  7. </target>

Теперь объединяем JavaScript файлы в один и удаляем сжатые версии js файлов.

  1. <target name="combinejs" depends="minifyjs">
  2.     <echo msg="Creating single js file…" />
  3.     <append destFile="${DEPLOY_DIR}/${LIB_DIR}/global.js">
  4.         <filelist dir="${DEPLOY_DIR}/${LIB_DIR}"
  5.             files="jquery/jquery-min.js, tabs/jquery-ui-personalized-1.5.2-min.js, tooltip/jquery.dimensions-min.js, tooltip/jquery.tooltip-min.js, mainscripts-min.js" />
  6.     </append>
  7.     <echo msg="Deleting minified js files…" />
  8.     <delete includeemptydirs="true">
  9.         <fileset dir="${DEPLOY_DIR}/${LIB_DIR}">
  10.             <include name="**" />
  11.             <exclude name="global.js" />
  12.         </fileset>
  13.     </delete>
  14. </target>

Операций всего две.

1) Объединение файлов (append). Файл, в который будет записан результат, задается с помощью параметра destFile, а файлы-источники – с помощью filelist. Это важный момент, т.к. нам нужно объединять файлы в определенном порядке (сначала библиотеки, а потом функции, их использующие). А filelist в отличие от fileset сохраняет не только имена файлов, но и порядок в котором они перечислены.

2) Удаление (delete) ненужных файлов. Здесь все просто. Удаляем все файлы и папки кроме global.js (строка 11).

Теперь нам нужно заархивировать (gzip) файл global.js.

Тут я столкнулся с одним непонятным моментом. В принципе, существует операция tar, в параметрах которой можно указать метод сжатия – gzip.

  1. <tar destfile="archive_name.gz" basedir="." compression="gzip" />

Но почему-то браузеры не понимают созданные таким образом архивы.

В общем, разбираться было лень, и я решил задачу «в лоб». Использовал архиватор 7-zip. Для выполнения системных команд в Phing предусмотрена операция exec.

  1. <target name="gzipjs" depends="combinejs">
  2.     <exec command="& quot;c:\program files\7-zip\7z.exe& quot; a -tgzip global.js.gz global.js"
  3.         dir="${DEPLOY_DIR}/${LIB_DIR}" />
  4. </target>

Обратите внимание. Если путь к файлу содержит пробелы, то его нужно взять в кавычки, которые задаются с помощью эскейп последовательности &quot;. Параметр dir указывает рабочую папку программы.

И завершающий штрих. Задача deploy. Сама по себе она ничего не делает, но в её параметре depends перечислены задачи, которые должны быть выполнены при её вызове.

  1. <target name="deploy" depends="copyfiles, gzipjs, minifycss">
  2. </target>

Таким образом, если выполнить команду

e:\projectfolder> phing

в корневой папке проекта, то будут последовательно запущены все перечисленные в этой статье задачи.
Примечание. Если имя build файла не указано явно, то Phing ищет build.xml. Тоже самое касается задачи. По умолчанию выполняется та, которая указана в параметре default (в данном случае – это deploy).

Скачать

Архив с stubJsMinTask и stubCssMinTask.

Если возникли вопросы или замечания, не стесняемся, комментарии открыты ;) .

До встречи!

Понравилась статья? Подписывайтесь на продолжение rss link !

Или на мой твиттер twitter link

]]>

Добавьте эту страницу в google.com bobrdobr.ru del.icio.us technorati.com linkstore.ru news2.ru rumarkz.ru memori.ru moemesto.ru

]]>

Опубликовано в Phing, Web разработка Комментарии (7) »

]]>

Комментарии (7)

Вы можете отслеживать обсуждение записи с помощью RSS 2.0 rss link

Вы также можете оставить комментарий, или трекбек с Вашего сайта.

]]>
  1. den

    А откуда берется ${DEPLOY_DIR}? Как ее пропустить внутрь XML файла?

  2. den

    Другими словами, можно ли ее задавать динамически?

  3. ${DEPLOY_DIR} это внутренняя переменная
    Задается в теге property:

    Вместо имени (${DEPLOY_DIR}) подставляется значение (deploy). Т.е. если в данном примере нужно будет изменить имя папки deploy, то достаточно будет изменить только одну строку.

  4. Наверное, правильнее считать ${DEPLOY_DIR} константой, а не переменной.

  5. У меня не читается XML из-за символа < в атрибуте pattern=" required]. Помогите разобраться, пожалуйста. Спасибо.

]]>

Оставить комментарий

* - обязательные для заполнения поля

]]>