Автоматизируем клиентскую оптимизацию

    Предыстория

    Как известно, перед тем, как выложить сайт в нет, мы его разрабатываем. И делаем мы это, как ни странно, на машине разработчика. И давно замечено, что javascript, а в некоторых случаях и css удобнее при разработке держать в нескольких файлах.Проблема в том, что, согласно принципам, описанным в статье Best Practices for Speeding Up Your Web Site (перевод доступен на сайте webo.in), для ускорения загрузки сайта нам нужно произвести следующие манипуляции над javascript и css файлами:
    1. Слить весь javascript в один файл, причем, желательно так, чтобы сохранился нужный порядок — т.е., скажем, библиотека jQuery — была ближе к началу, а функции и объекты, которые ее используют — после нее.
    2. Слить весь css в один файл
    3. Сжать эти большие файлы с помощью какой-нибудь утилиты вроде yui-compressor (за исключением css-файлов, название которых начинается, скажем, с префикса ie_, которые содержат data:URL, и поэтому критично относятся к переходам со строки на строку, так что их для собственного спокойствия лучше не сжимать)
    4. Расположить их в таком порядке — css-файл как можно ближе к открывающему тэгу head, а js-файл — как можно ближе к закрывающему тэгу body.
    5. Выставить HTTP-заголовок expires на подольше, чтобы браузер пользователя их закешировал. Ну а для того, чтобы при следующем билде у пользователя обновился js и css надо этим файлам дать какое-нибудь уникальное имя.
    6. Перед отдачей файлов клиенту сжимать их с помощью gzip

    К чему это я?

    Пункты 5 и 6 уже подробно расписаны в других местах.
    Я же хочу рассмотреть в этой статье вопрос автоматизации пунктов 1,2,3,4. А точнее, я хочу предложить инструмент, с помощью которого одним (ну, максимум — двумя-тремя :) нажатием кнопки можно выполнить пункты 1, 2, 3, 4 настоящего списка и получить готовые к заливке на сервер javascript и css файлы.

    Инструментарий

    • Apache Ant в качестве сборщика. Выбор пал на него за скорость работы, доступность, кроссплатформенность, а также то соображение, что получившийся скрипт легко включить в более общий скрипт выкладывания проекта на production, который будет выполнять какой-нибудь CI-tool, скажем, teamcity.
    • YUI Compressor в качестве утилиты сжатия js и css файлов. Взят за кроссплатформенность, адекватность, хорошую скорость работы
    • JSLint4Java (порт JSLint на java) в качестве валидатора javascript. Мы же не хотим выкладывать нерабочий код на продакшен, верно? Кстати, фраза, написанная на офф. сайте, «JSLint may hurt your feelings» очень даже справедлива :)

    Алгоритм работы скрипта

    1. Скачиваем и распаковываем JSLint4Java и YUI Compressor, кладем их в папочку tools внутри проекта. Правильнее, конечно, ложить ее куда-нибудь в место, определенное системной переменной, что-нибудь вроде $TOOLS_LOCATION, но в демонстрационном скрипте и так сойдет, а уж вы для себя поправьте скрипт, как вам нужно.
    2. Натравливаем JSLint4Java на все js-файлы. Если JSLint находит какую-нибудь ошибку, выводим ее на экран и останавливаем выполнение скрипта.
    3. Сливаем все js-файлы, за исключением тех, в имени которых есть фраза test, в один файл с уникальным именем, при этом сохраняем порядок следования файлов, который мы где-нибудь в другом месте определим. В качестве уникального имени давайте возьмем такую конструкцию: main.hh.dd.MM.yy.js, где hh, dd, MM, yy, соответственно, текущие час, день, месяц, год.
    4. Сливаем все css-файлы, за исключением тех, имя которых начинается с ie_ в один файл с уникальным именем (имя такое же, как и в предыдущем пункте, только расширение сменится на `css`). Порядок следования в данном случае не важен.
    5. Натравливаем на получившиеся файлы YUI Compressor. Если при сжатии произошла ошибка, выводим на экран ошибку и останавливаем выполнение скрипта.
    6. В html-темплейте, который подключает все файлы стилей, удаляем все тэги link, кроме тех, в src которых прописаны файлы ie_ и тех, которые содержат правила стилей, а не подключают внешний css-файл при помощи атрибута src.
    7. В том же темплейте удаляем все тэги скрипт, кроме тех, которые содержат javascript-код (а не подключают внешний файл скрипта через атрибут src).
    8. В том же темплейте прописываем получившийся css-файл как можно ближе к открывающему тэгу head.
    9. В том же темплейте прописываем получившийся js-файл как можно ближе к закрывающему тэгу body или открывающему тэгу script получившийся js-файл… Такое странное поведение нужно вот для чего: предположим, что мы в js-файле прописали какие-то библиотечные функции, а прямо в html-файле инициализируем прямо в server-side коде js-объекты какими-то данными. Вот для этого-то нам и нужно сохранить script-тэг, а также подключить получившийся js-файл до него.
    10. Выкладываем получившиеся js, css, html файлы в какую-нибудь директорию.

    Пример реализации

    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project name="production-build" default="build" basedir=".">
    3.   <!-- место, куда будем складывать свежескачанные yui-compressor и jslint4java -->
    4.   <property name="tools.location" value="tools/"/>
    5.   <!-- какую версию yui compressor'а и откуда качать, а также, какое имя будет у получившегося jar-файла -->
    6.   <property name="yuicompressor-version" value="2.4"/>
    7.   <property name="yuicompressor-zip" value="yuicompressor-${yuicompressor-version}.zip"/>
    8.   <property name="yuicompressor-unzip-dir" value="yuicompressor-${yuicompressor-version}"/>
    9.   <property name="yuicompressor-location" value="http://www.julienlecomte.net/yuicompressor/"/>
    10.   <property name="yuicompressor-jar" value="yuicompressor-${yuicompressor-version}.jar"/>
    11.   <!-- какую версию jslint4java и откуда будем качать, а также, какое имя будет у получившегося jar-файла -->
    12.   <property name="jslint-version" value="1.2.1"/>
    13.   <property name="jslint-location" value="http://jslint4java.googlecode.com/files/"/>
    14.   <property name="jslint-zip" value="jslint4java-${jslint-version}.zip"/>
    15.   <property name="jslint-jar" value="jslint4java-${jslint-version}+rhino.jar"/>
    16.   <property name="jslint-unzip-dir" value="jslint4java-${jslint-version}"/>
    17.   <!-- откуда мы будем брать js-файлы, css-файлы, html-темплейт -->
    18.   <property environment="env"/>
    19.   <property name="js.src" value="js/"/>
    20.   <property name="css.src" value="css/"/>
    21.   <property name="template.name" value="index.html"/>
    22.   <property name="template" value="${template.name}"/>
    23.  
    24.  
    25.   <!-- и куда мы будем их все складывать -->
    26.   <property name="output.dir" value="build/"/>
    27.   <property name="js.out" value="${output.dir}/js/"/>
    28.   <property name="css.out" value="${output.dir}/css/"/>
    29.   <property name="template.out" value="${output.dir}/${template.name}"/>
    30.  
    31.   <!-- порядок конкатенации js-файлов. Указанные файлы будут расположены в начале общего js-файла в указанном порядке -->
    32.   <!-- все оставшиеся файлы будут присоединены в конец файла -->
    33.   <property name="js-required-file-order" value="jquery-1.2.6.js, some_object.js"/>
    34.  
    35.   <!-- эта задача всегда выполнится первой -->
    36.   <target name="init">
    37.     <tstamp>
    38.       <!-- запомним в качестве переменной текущее время в формате mm-hh-MM-dd-yyyy -->
    39.       <format property="build-time" pattern="mm-hh-MM-dd-yyyy"/>
    40.     </tstamp>
    41.     <!-- создаем директорию, содержащую yui compressor и jslint4java -->
    42.     <mkdir dir="${tools.location}"/>
    43.   </target>
    44.  
    45.   <target name="prepare-tools" depends="init">
    46.     <!-- скачаем и распакуем jslint и yui compressor -->
    47.     <antcall target="prepare-yuicompressor"/>
    48.     <antcall target="prepare-jslint"/>
    49.   </target>
    50.  
    51.   <!-- скачаем и подготовим к работе jslint -->
    52.   <target name="prepare-jslint" depends="check-if-jslint-exists" unless="jslint.exist">
    53.     <get src="${jslint-location}${jslint-zip}" dest="${tools.location}${jslint-zip}" verbose="true"/>
    54.     <unzip src="${tools.location}${jslint-zip}" dest="${tools.location}" />
    55.     <copy file="${tools.location}${jslint-unzip-dir}/${jslint-jar}" todir="${tools.location}"/>
    56.     <delete dir="${tools.location}${jslint-unzip-dir}"/>
    57.     <delete file="${tools.location}${jslint-zip}"/>
    58.   </target>
    59.  
    60.   <!-- удостоверимся, что jslint скачан и готов к работе - эта задача выполняется непосредственно перед проверкой js-файлов -->
    61.   <target name="check-if-jslint-exists">
    62.     <condition property="jslint.exist">
    63.       <and>
    64.         <available file="${tools.location}${jslint-jar}"/>
    65.       </and>
    66.     </condition>
    67.   </target>
    68.  
    69.   <!-- скачаем и подготовим к работе jslint -->
    70.   <target name="prepare-yuicompressor" depends="check-if-yuicompressor-exists" unless="yuicompressor.exist">
    71.     <get src="${yuicompressor-location}${yuicompressor-zip}" dest="${tools.location}${yuicompressor-zip}" verbose="true"/>
    72.     <unzip src="${tools.location}${yuicompressor-zip}" dest="${tools.location}" />
    73.     <copy file="${tools.location}${yuicompressor-unzip-dir}/build/${yuicompressor-jar}" todir="${tools.location}"/>
    74.     <delete dir="${tools.location}${yuicompressor-unzip-dir}"/>
    75.     <delete file="${tools.location}${yuicompressor-zip}"/>
    76.   </target>
    77.  
    78.   <!-- удостоверимся, что jslint скачан и готов к работе - эта задача выполняется непосредственно перед сжатием js/css-файлов -->
    79.   <target name="check-if-yuicompressor-exists">
    80.     <condition property="yuicompressor.exist">
    81.       <and>
    82.         <available file="${tools.location}${yuicompressor-jar}"/>
    83.       </and>
    84.     </condition>
    85.   </target>
    86.  
    87.   <!-- валидируем javascript -->
    88.   <target name="validate-javascript" depends="prepare-tools">
    89.     <apply executable="java" parallel="false" failonerror="false">
    90.       <fileset dir="${js.src}">
    91.         <include name="**/*.js"/>
    92.         <!-- файлы библиотек тестировать не нужно, их и другие люди уже оттестировали -->
    93.         <exclude name="**/jquery-1.2.6.js"/>
    94.       </fileset>
    95.       <arg value="-jar" />
    96.       <arg file="${tools.location}${jslint-jar}" />
    97.       <arg value="--bitwise" />
    98.       <arg value="--browser" />
    99.       <arg value="--undef" />
    100.       <arg value="--widget" />
    101.       <srcfile />
    102.     </apply>
    103.   </target>
    104.  
    105.   <!-- сжимаем js/css-файлы -->
    106.   <target name="compress" depends="prepare-tools, concatenate-files">
    107.     <apply executable="java" parallel="false" failonerror="true" dest="${js.out}" verbose="true" force="true">
    108.       <fileset dir="${js.out}" includes="*.js"/>
    109.       <arg line="-jar"/>
    110.       <arg path="${tools.location}${yuicompressor-jar}"/>
    111.       <arg line="--line-break 8000"/>
    112.       <arg line="-o"/>
    113.       <targetfile/>
    114.       <srcfile/>
    115.       <mapper type="glob" from="*.js" to="*.js"/>
    116.     </apply>
    117.     <apply executable="java" parallel="false" failonerror="true" dest="${css.out}" verbose="true" force="true">
    118.       <fileset dir="${css.out}" includes="*.css" excludes="ie_*.css"/>
    119.       <arg line="-jar"/>
    120.       <arg path="${tools.location}${yuicompressor-jar}"/>
    121.       <arg line="--line-break 0"/>
    122.       <srcfile/>
    123.       <arg line="-o"/>
    124.       <mapper type="glob" from="*.css" to="*.css"/>
    125.       <targetfile/>
    126.     </apply>
    127.   </target>
    128.  
    129.   <!-- удаляем все старые css и js файлы в темплейте, а затем вставляем ссылки на наши сжатые файлы -->
    130.   <target name="update-tags" depends="prepare-tools">
    131.     <copy file="${template}" tofile="${template.out}" overwrite="true"/>
    132.  
    133.     <replaceregexp file="${template.out}" match="<script\s+type="text/javascript"\s+src="[A-Za-z0-9._\-/]*"></script>" flags="igm" replace=""/>
    134.  
    135.     <replaceregexp file="${template.out}" match="(<script*|</body>)" flags="im" replace="<script type="text/javascript" src="js/main-${build-time}.js"></script>${line.separator}\1"/>
    136.  
    137.     <replaceregexp file="${template.out}" match="<link[^>]*href="css/[^i>]{1}[^e>]{1}[^_>]{1}[^>]*/>" flags="igm" replace=""/>
    138.  
    139.     <replaceregexp file="${template.out}" match="</head>" flags="im" replace="<link rel="stylesheet" href="css/main-${build-time}.css" type="text/css" /></head>"/>
    140.   </target>
    141.  
    142.   <!-- конкатенация файлов -->
    143.   <target name="concatenate-files" depends="update-tags">
    144.     <concat destfile="${js.out}/main-${build-time}.js" fixlastline="true">
    145.       <filelist dir="${js.src}"
    146.         files="${js-required-file-order}"/>
    147.       <fileset dir="${js.src}"
    148.         includes="**/*.js"
    149.         excludes="${js-required-file-order}"/>
    150.     </concat>
    151.  
    152.     <copy todir="${css.out}">
    153.       <fileset dir="${css.src}" includes="**/ie_*.css"/>
    154.     </copy>
    155.     <concat destfile="${css.out}/main-${build-time}.css" fixlastline="true">
    156.       <fileset dir="${css.src}"
    157.         includes="**/*.css"
    158.         excludes="**/ie_*.css"/>
    159.     </concat>
    160.   </target>
    161.   
    162.   <!-- вызываем цели в нужном порядке -->
    163.   <target name="build">
    164.     <mkdir dir="${output.dir}"/>
    165.     <antcall target="validate-javascript"/>
    166.     <antcall target="compress"/>
    167.   </target>
    168. </project>
    * This source code was highlighted with Source Code Highlighter.
    На всякий случай, пример: ant1

    TODO

    1. Не слишком красивым является указание порядка конкатенации js-файлов прямо в скрипте. Гораздо лучше было бы, если бы мы писали прямо в html что-нибудь вроде <% javascript: { first: jquery, last: initialize } %>. При development build'е скрипт бы заменял эту строку на несколько script-tag'ов (удобно для debug-a), а на продакшене сливал бы файлы в один в указанном порядке.
    2. Скачивание файлов при помощи ant-а не сказать, чтобы очень удобное — а что, если выйдет новая версия или изменится ссылка на скачивание? Гораздо удобнее пользоваться maven'ом для таких случаев.
    3. Дополняйте :)
    P.S. кросспост в моем блоге.
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 74

      0
      товарищи из www.getrpo.com также подобным занимаются
        0
        Ну, это public domain, так что, надеюсь, кому-нибудь это поможет сэкономить 749 (ну у них и цены %) баксов на сервер.
        +2
        Ну вы, батенька, и маниак :) преклоняюсь :)
        Спасибо, на выходных попробую попробывать это у себя в живую…

        Сейчас дальше незначительной автоматизации «минифицирования» js и css с помощью .bat с yui-js-compressor я не зашел.
        Кстати когда то уже в комменте писал про это, кому интересно: vant.kiev.ua/compressor.zip (1.6 Мб)
          0
          Ну, тут на самом деле ничего сложного — сложнее всего было продумать алгоритм :)

          Ваш вариант тоже представляет собой вполне себе работоспособное решение, единственное что, некросплатформенное (для нас это критично), да и для работы с java-проектом сам бог велел использовать ant :)
          0
          Спасибо. Мне было полезно это узнать.
            +1
            Сложная, громоздкая, берущая на себя много ответственности система. Вы представляете, каким маниаком должен быть человек, чтобы ею пользоваться? Как мне поступить, когда я вернусь к этому конфигу через месяц? Я в своих-то сырцах через две недели уже начинаю плохо ориантироваться.

            Также такая сложность значительно усложняет интеграцию решения.

            Описанная в этой статье теория может быть полезна в очень большом проекте без выхода библиотеки за рамки самого проекта.

            Имхо.
              +2
              1) Из-за того, что этот скрипт — ознакомительный, т.е. ставит перед собой цель «показать, представить пример того, как можно автоматизировать указанный процесс», он довольно громоздкий. На практике его можно очень и очень сильно упростить и сделать гибче.

              2) А зачем вообще заморачиваться на автоматизацию выкладки проекта, если он маленький и над ним работает один человек? Вот у нас, к примеру, работает по 5-8 человек над проектом, и новые рабочие билды собираются teamcity с обязательной прогонкой через все тесты перед выкладыванием. И в наших условиях собирать это все руками — смерти подобно. Гораздо проще создать свод правил о том, как именовать файлы, куда их класть и все в таком духе, и потом создать скрипты для развертывания приложения для development, test и production окружения.
                +1
                Если разбивать предложенный вами вариант на подсистемы, то постепенно получится фреймворк. А то, что представили вы — это как Остап бендер:
                «Ослепительные перспективы развернулись перед васюкинскими любителями. Пределы комнаты расширились. Гнилые стены коннозаводского гнезда рухнули, и вместо них в голубое небо ушел стеклянный тридцатитрехэтажный дворец шахматной мысли. В каждом его зале, в каждой комнате и даже в проносящихся пулей лифтах сидели вдумчивые люди и играли в шахматы на инкрустированных малахитом досках».
                  0
                  Любопытно. Вы действительно считаете, что это чрезмерно связанная и усложненная система? Я написал этот скрипт (точнее, тот, что мы используем сейчас) за два часа, и никаких проблем с ним не было на всех проектах.

                  Я думаю, что на самом деле тут все супер-просто, и для сравнения приведу вам описание скрипта, который производит полное развертывания проекта для production окружения (в описании некоторых шагов я могу ошибаться, так как занимаюсь далеко не всем):

                  1. с помощью maven производится скачивания всех зависимостей (разработка ведется на j2ee).
                  2.с помощью того же maven'а проект собирается, все файлы кладутся туда, куда надо.
                  3.специально написанный скрипт прогоняет последовательно юнит, интеграционные тесты.
                  4.прогоняются скрипты, завязанные с базой данных — что конкретно делается, не скажу, не моя зоня ответственности, но, по логике, экспортируется нужная схема, данные, если нужно преобразуются.
                  5.прогоняется вышеуказанный модификация обсуждаемого скрипта, только она еще и выкладывает все ресурсы на кластер и проставляет заголовки expires.
                  6..оттестированное приложение располагается на application server'ах
                  7.application сервера с новым билдом соединяются с frontend сервером
                  8.все, пользователь видит новый билд

                  Если на каком-то шаге произошла ошибка, остается работать старый билд, а текст ошибки отправляется ответственному человеку на мыло.

                  И приблизительно за каждым из этих шагов стоит свой скрипт. Вот это уже фреймворк, и у нас он сделан, как фреймворк. А обсуждаемый скрипт — лишь его часть.
                +2
                >Я в своих-то сырцах через две недели уже начинаю плохо ориантироваться.
                Плохо :(
                0
                ага, а потом разбирайся например в таком
                  0
                  ага, а что Packer вытворяет? :)
                    0
                    То, что представлено на конечном сайте может быть сколь угодно уродским. Красивым и лаконичным структура проекта должна быть на девелопмент-версии. Попробуйте разобраться в JS на gmail.com
                      0
                      Видимо, вы невнимательно читали.
                      На дев-машине все остается по-прежнему.
                      А на продакшен все выкладывается примерно в том виде, что вы указали.
                      А что, вы хотите дебажить js-код прямо на продакшене? Так это неправильно, отлов багов должен происходить на машине тестера/разработчика. То, что лежит на сервере, должно быть оптимизировано для быстрой загрузки, для чего сжатие и делается.
                        0
                        Это был ответ не тебе, а ident )
                          0
                          простите? может быть, что-то с системой комментирования.
                          0
                          но оракл распространяет этот oraclemaps.js как javascript-библиотеку для создания карт

                          и порой приходится в ней рыться, много там ошибок ещё
                            0
                            видимо, оракл не читает хабрахабр ;)
                        –5
                        Что вы предпочитаете… экономить свои нервы… или же делать клиентскую оптимизацию?, которую я думаю даже пользователи не заметят, а уж заказчик тем более. На дворе 21 век… гигабайты оперативной памяти… На мой взгляд нецелесообразно.
                          +1
                          Гм.
                          Простите, а вы имеете представление о том, для чего делается клиентская оптимизация и какой эффект она дает?
                          Если вкратце, то даже начального уровня клиентская оптимизация позволяет ускорить с точки зрения пользователя работу сайта (в первую очередь — скорость появления первой «картинки») в несколько раз. Как вы, наверное, представляете, это может вылиться в серьезную экономию денег, а также помочь удержать на сайте новых пользователей и привлечь новых. Почитайте статью сколько живых денег принесет ускорение загрузки сайта.

                          Гигабайты ОЗУ — это здорово, но вот гигабайтные каналы далеко не у всех, и это я даже не вспоминаю всякие наладонники и GPRS.
                            0
                            Как вы собираетесь сократить трафик склеиванием нескольких файлов? (просто меня больше всего затронул этот фактор). Если опустить все пункты которые влияют на входящий трафик, то по сути вся остальная оптимизация должна переваливаться на плечи разработчиков браузеров.

                            P.S. С сегодняшним кешированием в браузерах даже несколько лишних килобайт не трагедия.

                            P.S.S. Да, думаю многие люди могут осуждать меня, «надо экономить, быстрая загрузка, экономия памяти», а в итоге это все лишь затрудняет дальнейшую разработку. Просто нужно все делать грамотно, не перегибая палку. В итоге, экономите на ресурсах компьютера, растрачиваете свои физические ресурсы, растрачиваете ресурсы пользователя, сокращаете время разработки. Все равно придется чем то жертвовать.
                              +5
                              Видимо, вы где-то промахнулись в своих расчетах.
                              Давайте посчитаем, что дает уменьшение количества файлов:

                              1) Не надо посылать новый запрос, а значит, экономим на request/response header'ах, что уменьшает трафик. не смейтесь, для мелких файлов (1-2 кб) совокупный размер хедеров вполне сравним с размерами самого файла.
                              2) Не надо ждать загрузки файлов (а ведь она складывается из ping+request_time+ping+download_time). а это порядочный выигрыш во времени, а не в трафике. Вы еще считаете?
                              3) Уменьшение и сжатие файлов дает уменьшение размера страницы с ~800 KB до 140 KB. Теперь умножаем на, скажем, 100 000 уникальных посетителей в месяц, получаем неплохую экономию трафика для сервера, не так ли?

                              С учетом всего вышеперечисленного, на канале ~100 MB/S с использованием клиентской оптимизации сайт появляется на экране компьютера мгновенно после клика на ссылку, без нее — где-то через 5-6 секунд. Разница, по моему, громадная.
                                +1
                                Я бы еще добавил, что не стоит забывать, сколько одновременных соединений поддерживают современные браузеры, да и сам Windows, если не серверная версия, по дефолту поддерживает 10 одновременных полуоткрытых соединений. Если его не патчить, то любые сайты с большим количество ресурсов открываются достаточно долго.
                                  0
                                  Да, справедливое уточнение.
                            0
                            У вас много сайтов, которые открываются (становятся доступными для чтения, прокрутки и кликанья) меньше чем за 1с с момента ввода адреса или щелчка по ссылке? Нет? Может у вас медленный интернет или слабый компьютер, а может просто много вебмастеров-чайников с таким же пождходом к разработке сайта.
                              +1
                              (с горечью глядя на vkontakte.ru) иногда все же все решает канал…
                                +1
                                Ну, кстати, конкретно этот случай (vkontakte.ru) не показателен.

                                Мне кажется, его успех заключается в первую очередь потому, что «Content is the King» — там контент, там удобно, там куча людей.

                                Ну и, во вторых, его делали очень грамотные люди — я знаком с несколькими разработчиками, и они свое дело знают туго.
                            0
                            Возможно немного не в тему, но раз вы занимались клиентской оптимизацией… то вдруг.
                            Бьюсь над проблемой отжирания памяти страницей.
                            Смотрел IEWatche'м, данных (страница+скрипты+стили+картинки) приходит на 200Кб. Смотрел sIEve и JS Memory leaks detector'ом — утечек нет. Искал потенциальные источники утечек — не нашёл.
                            Тем не менее, после загрузки отъедает +30Мб оперативки, а после выгрузки 10Мб не отдает. Т.е. пять раз на страницу зашел, 50Мб не вернутся до закрытия браузера.
                            Есть идеи куда копать? Может существует утилита, показывающая распределение памяти по DOM/JS-объектам?
                            P.S.: извините, наболело :( Уже у всех спрашиваю.
                              +1
                              JS Memory leaks == Drip? тогда фиг знает. Но могу посмотреть
                                0
                                Не могу дать посмотреть, технические сложности — доступа не дадут, сохраняться не умеет. Только не надо про кривые руки :)
                                Поэтому ищу способ/инструмент решения проблемы.
                                  +1
                                  профилирование — на каждый чих вставлять задержку и смотреть, что память отъедает и не возвращает. Более интеллектуального, к сожалению, нет.
                                    0
                                    И вот этим (а также тупым комментированием addHandler'ов) я и занимаюсь второй день. Нереально грустно.
                                  0
                                  И Drip'ом тоже смотрел.
                                  +1
                                  Боюсь, что быстрого автоматического способа не знаю. Ниже sunnybear указал тот способ, что я хотел предложить.
                                    0
                                    Вот мне иногда кажется, что должна существовать утилита, показывающая число живых объектов JS и DOM. Те же sIEve и Drip умеют подсчитывать число ссылок JS-объектов на DOM-элементы.
                                    Кажется, такую даже написать не очень сложно (в JS не так много типов, рефлексия в самом сердце).
                                    Но вот гугл молчит. Коллега пробовал написать — не получилось (не помню почему).
                                      +1
                                      Вот и я не знаю, хоть я и не гугл :)
                                      Возможно, выходом будет переписать все навешивания addEventHandler через какой-нибудь framework вроде jQuery или YUI. И удалять элементы с помощью них же. Они гарантируют, что убирают все ссылки на dom element'ы из javascript и наоборот.
                                        +1
                                        Firebug — не оно? И под IE есть
                                          +1
                                          Не совсем. Можно посмотреть: какие файлы и каких размеров скачались, полазить по DOM, подебажить JS, но не палит утечки и не показывает у кого сколько ссылок. Причем, лучше занимаемый объем объектов по этим ссылкам и возможность агрегации (чтобы по всем узлам не бегать).
                                          Либо я не умею пользоватье файрбагом, что вполне допустимо :)
                                            0
                                            Пардон за стиль изложения — с утра и до сих пор дебажу.
                                      +1
                                      кстати, попробуйте вставить перед каждым удалением элемента что-нибудь вроде метода purge от Дугласа Крокфорда:

                                      function purge(d) {
                                          var a = d.attributes, i, l, n;
                                          if (a) {
                                              l = a.length;
                                              for (i = 0; i < l; i += 1) {
                                                  n = a[i].name;
                                                  if (typeof d[n] === 'function') {
                                                      d[n] = null;
                                                  }
                                              }
                                          }
                                          a = d.childNodes;
                                          if (a) {
                                              l = a.length;
                                              for (i = 0; i < l; i += 1) {
                                                  purge(d.childNodes[i]);
                                              }
                                          }
                                      }
                                      


                                        0
                                        Пробовал. И даже учитывающую особенности ASP.NET Ajax функцию (которая корректно диспозит, а я корректно отписываюсь) использую. Вот здесь пример этой функции.
                                          +1
                                          и перед использованием innerHTML на все уже существующие элементы внутри контейнера (которые удалятся) прогоняете?
                                            0
                                            Да. Я читал про утечки — как исконно IE-шные, так и добавленные (PDF) ASP.NET Ajax'ом, — и такие моменты тоже выискивал. Впрочем, есть повод для радости — понизили приоритет задачи :) Фффух. Буду подробнее изучать.
                                      0
                                      с мнением jslint не все согласны, кстати
                                        0
                                        Ну его можно по разному настраивать.
                                        Ну и кроме того, всегда полезно послушать критику jslint относительно своего кода :)
                                        Лично я тоже не следую абсолютно всем рекомендациям в strict-режиме, очень уж это накладно.
                                      • UFO just landed and posted this here
                                        • UFO just landed and posted this here
                                            +3
                                            Ну, в моем случае выбор пал на ант потому, что мы программируем на java, и наши проекты собираются maven + ant. Выгоднее и для оптимизации использовать уже используемую технологию.

                                            Ну а насчет использования xml+regex — то, что я не люблю эти технологии не означает, что я не могу ими пользоваться. Хотя вы не поверите — писал и плакал, писал и плакал :)
                                          • UFO just landed and posted this here
                                              0
                                              упс :)
                                              комментарии я навешивал уже после того, как написал скрипт, халтурил, пользовался Ctrl-V -> Ctrl-C, и вот результат :)
                                              +1
                                              все намного проще

                                              code.google.com/p/minify/
                                                0
                                                Штука неплохая, но обладает рядом не очень удобных (для меня, по крайней мере) особенностей:

                                                1) Он каждый раз при отдаче файлов заново проводит всю обработку. Не верите? Вот цитата из фака, пункт первый:
                                                With Minify you will ideally serve fewer requests, but Minify can be slower than your HTTPd serving flat files

                                                2) Он работает под апач. У нас, к примеру, апач на последних проектах вообще не используется (раньше использовался как frontend-сервер в связки nginx+jboss+apache, сейчас перешли на nginx+jboss).

                                                3) Он работает на php5. Проблема в том, что многие компании (и мы в том числе) вообще не используем php. И ставить его ради таких случаев — не слишком разумно.

                                                Но вообще — большое спасибо за ссылку, думаю, я покопаюсь в исходниках и найду что-нибудь полезное для своего скрипта.
                                                +1
                                                Клиентская оптимизация это очень нужная вещь. Но эта реализация отпугивает своей сложностью. Да и человек, который будет возможно через год после вас работать с этим проектом, просто не сможет найти исходников.

                                                Я, начиная с сайта своей скромной студии, использую довольно простое решение. На сервере лежит маленький PHP скрипт, который сам выставляет заголовки, склеивает все нужные файлы, удаляет лишние символы (несколько str_replace и preg_replace) и при большом размере файла использует gzip.

                                                У такой реализации, имхо, много плюсов. Все исходники лежат в открытом виде на сервере и каждый желающий может их посмотреть, если возникнут какие-то глюки при оптимизации скрипта это будет заметно ещё во время разработки и решить будет проще, да и можно самому определить какие символы вырезать (я, например, не использую псевдоклассы, поэтому вырезаю все пробелы из css). Экономия трафика отличная, оптимизация экономит 10%, а gzip сжимает оставшееся в 3-4 раза :) Если не нравится то, что такая реализация выполняется при каждом запросе, то можно кешировать результат, хотя нагрузка минимальна. Такой php скрипт, кстати. получается не больше чем в 30 строк. В общем, советую всем :)
                                                  0
                                                  1) То, что исходники потеряются — бред. Для хранения исходников предназначен репозиторий системы управлениями версий (те же subversion и git). И место для исходников — рабочее место программиста и тот же репозиторий. Ну, еще бэкап репозитория. Все. Сервер, на котором исходники могут посмотреть все желающие — бред. Сервер не для этого предназначен. А на девелоперской машине все исходники остаются в прежнем виде.

                                                  2) Моя реализация работает быстрее. Вот почему: этот скрипт один раз за билд (т.е. развертывание приложение на сервере) склеил все, сжал и заботливо положил в папочку к nginx. А тот его сжал с помощью gzip и положил рядышком два варианта — сжатый и сжатый. И когда клиент просит контент, он смотрит, поддерживает ли клиент gzip, и если да, отдает ему пожатый, если нет — отдает ему непожатый (gzip'ом). И потом это все добро еще и кеширующий прокси закешировал. Кроме того, бьюсь об заклад, что ant-скрипт отработает в разы быстрее аналогичного php-скрипта (в данном случае под словом аналогичный я понимаю «имеющий один и тот же алгоритм»).

                                                  А в вашей реализации на каждый запрос клиента будет отрабатывать php-скрипт (+compressor), а это нагрузка. Маленькая, но все же. А что, если посетителей будет очень много? Возможно, отдача flat-файлов как раз и даст ту необходимую экономию ресурсов, которая позволит серверу не захлебнуться под валом запросов.
                                                    +1
                                                    Впрочем, простите, я слишком резко ответил.

                                                    Ваш вариант тоже вполне себе жизнеспособное и обладающее рядом плюсов (как минимум, нет столько xml-a :) решение.

                                                    Видимо, мне очень дорого свое детище, аж на людей начал кидаться :)
                                                    +1
                                                    Есть ещё такая полезная штука js-builder. Склеивает файлы в нужном порядке. Проект хранится в xml-ке. Есть консольный и гуёвый вариант.
                                                    Легко прикручивается к проектам на пару с яха копрессором.
                                                      0
                                                      Да, мне js-builder тоже понравился. Он как-раз делает все, что нужно.
                                                      К сожалению, он обладает одним, но очень существенным недостатком. Он только под win-платформу.
                                                      А у нас в команде половина разработчиков сидит под *nix, сервер (где это развертывание должно происходить) под *nix, я вообще белая ворона — у меня мак.
                                                        0
                                                        Там исходники открыты, может быть попробуйте собрать под Mac и nix используя mono-project?
                                                          +1
                                                          В принципе, можно попробовать. Посмотрю, поверчу.
                                                      0
                                                      Сливаем все css-файлы, за исключением тех, имя которых начинается с ie_ в один файл с уникальным именем (имя такое же, как и в предыдущем пункте, только расширение сменится на `css`). Порядок следования в данном случае не важен.


                                                      Как это порядок не важен? А если, например, один и тот же класс (правило и т.д.) переопределяется или дополняется по-ходу проета?

                                                        0
                                                        Простите, но если по ходу проекта один и тот же класс переопределяется, то после сливания останется только последний вариант. При чем в данном случае неважно, какой именно вариант затерся, а какой нет — в любом случае на клиенте получится кака.

                                                        Такие css файлы просто напросто нельзя сливать. И получается только два варианта:
                                                        1) не переопределять классы по ходу проекта.
                                                        2) не сливать эти файлы.
                                                          +1
                                                          Наверно, и я, и вы просто не правильно выразились, отсюда и непонимание.
                                                          Я имел ввиду следующее: что будет если в результирующем файле поменять местами код reset.css и того файла, в котором эти стили переопределяются, после сброса?
                                                            0
                                                            Да, вы абсолютно правы, css тоже надо сливать с соблюдением порядка. Я версткой редко занимаюсь и по мелочи, поэтому сразу не представил такой вариант :)

                                                            Это легко реализовать, по аналогии с js-файлами. Сейчас я занят, но как освобожусь, доработаю пример.
                                                      • UFO just landed and posted this here
                                                          0
                                                          Ага, уверен. Мы все-все сливаем в один файл.
                                                          У нас на последнем проекте вес исходников js-кода порядка 1 мегабайта (правда, та же jQuery не сжатая, и плагины к нему тоже не сжатые, да и код у нас хорошо задокументирован).
                                                          После склеивания и сжатия получается один файл размером в 98 KB. По моему, нормально. Грузится очень быстро, и функционал для всех страничек готов.
                                                          А если бить js на core-файл и кучку специализированных для каждой странички файликов, то будет все медленнее в итоге грузиться, уже не из-за трафика и размеров файлов, а из-за того, что запросы-то все равно проходят, и специализированный файл будет грузиться как минимум (ping + request_process_time + ping + download_time). Я подробнее уже вот тут отвечал.

                                                          Итого — выгоднее клеить все. Правда, есть еще одно правило:
                                                          1) Если объем сжатого js-кода больше 100 KB, то правильнее бить его на части, чтобы браузер мог грузить его в два потока. Или больше частей.
                                                          2) Если объем сжатого js-кода больше 500 KB, то надо его переписывать, т.к. такое количество js-кода просто убъет интерпретатор javascript (т.е. вкладка начнет тормозить сразу после загрузки кода, а через минуту работы подвесит весь браузер) во всех браузерах, кроме chrome.
                                                          • UFO just landed and posted this here
                                                              0
                                                              1) По поводу работы скрипта на слабой машине: все ок. Наши скрипты написаны следующим образом: есть объект manager, который следит за тем, в на каком этапе бизнес-процесса находится пользователь (грубо говоря, на какой страничке). И есть куча других объектов, которые умеют что-то делать. И на каждой странице сначала инициализируется менеджер, и вызывает initialize у тех объектов, которые нужны пользователю на этой странице. А у всех остальных объектов вызывается метод shutdown, который их удаляет из памяти. Таким образом, расход памяти минимален для каждой страницы.

                                                              2) Нет, никакой задержки незаметно.

                                                              Вся фишка в специфике работы алгоритмов сжатия. Специфический для каждой страницы скрипт, будучи прибавлен к результирующему файлу и вместе с ним сжатый, увеличивает размер получающегося файла на несколько сотен байт максимум. А если бы он gzip-пился и жался отдельно, то его размер был бы 2-3 KB.

                                                              Так что в итоге выгоднее все грузить одним файлом.

                                                              • UFO just landed and posted this here
                                                                  0
                                                                  засекалось, отчего же.
                                                                  на 1Гб машине Pentium 3 533 (меньше не достали :) win XP sp2 на ie6 отрабатывает за 120 мс.
                                                        • UFO just landed and posted this here
                                                            0
                                                            Как же писали выше, склеивание всех файлов в папке — неправильный подход по двум причинам:
                                                            1. Придётся явно указывать порядок склейки и в будущем не забывать его обновлять.
                                                            2. Невозможно выделить большой функционал, использующийся на одной странице, в один файл и грузить его только для этой страницы.

                                                            А какой же подход правильный? Ответ: импорты и инклюды.
                                                            1. В html подключается файл project.css, который импортирует стили проекта в нужном порядке. После него подключается файл project.ie.css, импортирующий версии стилей для IE.
                                                            2. Подключается файл project.js, который инклюдит скрипты проекта в нужном порядке. (Написание функции include() оставляю в качестве домашнего задания.)
                                                            3. При выкладывании вёрстки на продакшн скрипт-сборщик проходится по файлам project.css, project.ie.css и project.js, рекурсивно заменяет строчки @import и include() на содержимое указанных файлов, сжимает получившиеся файлы компрессором и сохраняет под исходными именами, добив в начало знак подчёркивания.

                                                            Если для некой страницы выгодно выделить большой функционал в отдельный файл, то заводится page.css/page.js, содержащий необходимые импорты/инклюды, и также скармливается сборщику. Итоговый файл подключается только на данной странице, не увеличивая размер проектного файла.

                                                            Подсказка: если в начало project.ie.css добавить импорт файла project.css и проставить правильные условные комментарии для IE, то он будет грузить один файл вместо двух.
                                                              0
                                                              Спасибо за интересный комментарий.

                                                              1) Чем именно хранение порядка склейки файлов в скрипте (и который, к слову, можно вынести как в файл пропертис, так и вообще включить в шаблон, как описано в пункте TODO) отличается от хранения порядка склейки в файле project.js/css? Я думаю, только тем, что вам нравится один вариант, и не нравится другой. Что так в одном файле все лежит, и надо не забывать обновлять список, что эдак.

                                                              2) JavaScript. Очень и очень редко нужно указывать пофайловый порядок для склейки, чаще всего нужно указать только, какой файл (обычно — javascript framework) должен идти первым, и какой файл (какой-нибудь файл, в котором все инициализируется) должен идти последним. Порядок включения остальных файлов совершенно не важен и не сделает новых ошибок. Короче говоря, я считаю, что тот синтакс, что описан в TODO, гораздо лучше, чем ваш или мой способ.

                                                              3) CSS. В принципе, для css верно все, что сказано выше, хочется только уточнить один момент, насчет объединения файлов стилей в один. Пожалуйста, покажите пример сжатого какой-нибудь утилитой объединенного css-файла, в котором есть как обычные data:URL'ы, так и специализированные для использования в ie mtm'ы. Я уверен, что вы не предоставите, потому что после сжатия YUI Compressor'ом (или другой утилитой) картинки не будут отображаться в ie. Именно в этом причина того, что для ie заводится отдельный файл, который не обрабатывается сжималкой (и не включается из-за своего веса в общий css), а не в том, что условные комментарии представляют какую-нибудь сложность для освоения.

                                                              4) Отдельные файлы для отдельных страниц — если использовать тот синтакс, что описан в TODO, то составит отдельный файл для отдельной страницы — раз плюнуть.
                                                                0
                                                                1) Мы не затронули вопрос подключения файлов в разработке. При описанном мной способе достаточно подключить файлы project.*, которые сами нативно подключат все необходимые файлы. При этом они будут непожаты, чтобы сильно облегчит дебаггинг. Как подключаете файлы вы? Пересобираете при каждом изменении?

                                                                2) Согласен, порядок подключения скриптов редко важен. Однако сам механизм исключения по маске менее универсален, нежели механизм явного включения по имени. Если вы при явном указании списка файлов забудете подключить какой-либо скрипт, то сразу это заметите, получив ошибку. Если же при методе исключений вы забудете удалить неактуальный файл из папки, то он продолжит участвовать в сборке, понапрасну увеличивая итоговый размер.

                                                                3) При использовании указанных технологий (я не использую) файл лучше не сжимать, в этом вы правы.
                                                                  0
                                                                  1) Не могу не признать вашу правоту — действительно, для большинства платформ мой способ не слишком хорош в этом плане — мы действительно автоматически генерируем из шаблонов velocity и css и javascript, но нам удобно это делать просто потому, что проект так или иначе собирается с помощью ant, так что на общее время ожидания и сборки это никак не влияет.

                                                                  2) Ну, ничто не мешает слегка переписать скрипт с тем, чтобы он понимал имена файлов, а не маску.

                                                                  В целом — позвольте сказать вам спасибо за важные замечания, надеюсь, они помогут всем, кого интересует тема оптимизации.

                                                            Only users with full accounts can post comments. Log in, please.