14 мая 2014 в 21:03

Fast-static — Node.js модуль сильно облегчающий жизнь

При разработке js и css файлы удобнее держать в исходном виде, а при в продакшене их необходимо минизировать и соединять. Существует несколько систем сборки, однако, пользоваться ими весьма сложно и проблематично. По крайней мере я устал от них до такой степени, что решил написать свой сборщик. Получился он очень удобным, поэтому я решил с вами им поделиться.



Другая проблема с которой я решил побороться — не удобство подключения файлов в html. Для того чтобы подключить один файл необходимо писать:
     <script type="text/javascript" src="..."></script>


Если файлов штук 20, то все это превращается в мешанину, где сложно что-то найти. При этом скрипты и css нужно подключать в разных частях страницы. И если нужно, например, bootstrap перекинуть в другую директорию, то нужно изменять пути в начале и в конце страницы. В общем я решил, что так будет намного быстрее подключать файлы:

     <use>
          file1.css
          file2.js
          dir/
               file3.js
               file4.css
     </use>

Также в use я добавил несколько приятных фич. Например, вставку текущего домена (хоста). Но песнь моя не о том.

Чтобы use тега было достаточно и не нужно было создавать файл с конфигом сборки пришлось писать свой middleware к express.

Т.е. сборка происходит «на лету»: когда браузер запрашивает файл в продакшене, то он возвращается минимизированным, хотя на диске он в исходном виде. Я подключил автоматическую компиляцию less, sass, coffeescript, jade, haml и md. Которая тоже происходит на лету.

Естественно все это не лучшим образом может сказаться на производительности продакшен сервера. Поэтому в продакшене результаты кешируются в памяти. Между делом добавил кеширование результатов после gzip (зачем процессор почем зря нагружать). Также доступно кеширование на стороне браузера (ETag). В общем получилось быстрее чем у стандартного static middleware Express/Connect и меньше грузит диск и камень.

Стандартный middleware дважды снимает статистику файла и считывает файл с диска, а потом зипует результат. Мой просто выплевывает данные из памяти.

Подключается это в несколько строк
     npm install fast-static


     var fastStatic= require('fast-static');
     app.use('/static',fastStatic.use('./static'));   // app.use('/static',app.static('./static'));


Потом вместо script/link используете тег use в html файлах.

Вот и все. Только не забывайте env менять у продашен серверов.

В общем получилась удобная система сборки.

Фичи


  • Конвертирует: coffee, haml, less, jade, sass, md
  • Упрощенное подключение файлов через тег use
  • Автоопределение mime-type по расширению
  • поддерживает gzip (кеширует gzip в памяти)
  • поддерживает кеш на стороне браузера (ETag)


Когда env=production производит оптимизацию
  • минимизация css, js и html
  • слияние файлов
  • вставляет небольшие изображения в css
  • кеширует результаты в памяти


Полная документация на гитхабе: https://github.com/Hkey1/fast-static

UPDATE


Новая версия появился инклуд файлов в директории dir/*
Появился простой инклуд библиотек — просто добавить название и версию в use тег. Например «bootstrap 3.1.1»
Значительно ускорен кеш на стороне браузера.
@Hkey
карма
44,0
рейтинг 0,0
Похожие публикации
Самое читаемое Разработка

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

  • НЛО прилетело и опубликовало эту надпись здесь
    • 0
      Гембеля много с настройкой. Появляются лишние файлы в файловой системе. Но в grunt есть например возможность автоматом запускать тесты.
      • +6
        Сори за одессизм. «Гембель» это тоже самое, что и «геморрой» в переносном смысле.
        • 0
          Я думал, что это распространенное везде слово.
      • +2
        Простите, с чем конкретно связан гемор?
        Один раз настроил, и потом наслаждаешься результатом.
        Банальная связка sass + coffee + uglify + filerev + copy + watch дает вам возможность иметь статику с версионностью, и минимизацией.
        • 0
          А если не хочется лишних файлов, то, возможно, вам стоит посмотреть на Gulp.
          • 0
            Или, как еще одна альтернатива — заюзать grunt-contrib-clean, и вручную прописать пути к runtime/tmp директориям, которые надо чистить «до» и «после» сборки.
            • 0
              Лишний гембель — у меня все это идет из коробки.
              • 0
                Ну нет же… Это фрагментация сборки. А что если у вас появится необходимость генерировать спрайты и подставлять их в CSS? А если возникнет задача делать сборку CommonJS? AMD? Как ни крути, эко-система Gulp/Grunt покрывает 100% задач сборки, а ваш вариант, увы, нет. Плюс поддержка и расширение вашего инструмента — то еще приключение для пользователей.

                Я понимаю ваше желание найти собственное решение, и это здорово, но попробуйте критически посмотреть на Gulp/Grunt — их использование обойдется гораздо дешевле в долгосрочной перспективе именно благодаря комьюнити, эко-системе, и сотням готовых конфигов.
  • +3
    Спасибо за ваш труд, полезная штука.

    У нас мы изобрели внутренний велосипед, который формирует список подключаемых файлов автоматически, добавляя все файлы из целевой папки. То есть, чтобы добавить, переименовать или удалить файл, достаточно сделать это физически и сбилдить проект.

    Я думаю, в вашем случае тоже было бы здорово иметь возможность просто указать папку, чтобы все файлы из неё подключались автоматически.
    • +1
      Вот так dir/*?
      • 0
        Вроде того, да)
        • 0
          добавил
  • 0
    А есть ли возможность автосборки при изменении файлов, входящих в проект?
    • 0
      Из script и link урлы парсить?
      Сделаю, как опцию, если с регулярками кто поможет. (это не моя сильная сторона).
      Но, имхо, use удобнее.
      • 0
        Ой, что я несу, вы же написали что сборка на лету, извиняюсь, я думал он собирает один раз и потом не обновляет кеш при изменении исходников, извиняюсь
        • 0
          Ой я тоже несу. При env=development он ничего не кеширует при production — он кеширует и не смотрит на время.

          Сверять время не нужно на production. При обновлении ведь его рестартят.

          Да и невозможно поскольку в haml, jade, less, sass есть подключаемые файлы и большинство компиляторов не возвращают их список. Даже если бы возвращали, то для показа одного файла могло бы потребоваться получение статистики по 10-20 файлам, что могло тормозить при сильных нагрузках.
  • +2
    Меня одного смущает идея хранить статику в оперативной памяти?

    В большинстве случаев отдача через res.sendfile более эффективна по всем критериям (отдача ядром ОС, использование кэширования ОС, меньший объем потребляемой памяти). А для gzip лучше бы использовать идею nginx gzip_static.

    Хотя в большинстве случаев проще повесить nginx перед node.
    • 0
      Отправляется не строка а Buffer, который по сути является низкоуровневой областью памяти (V8 heap).
      Он точно не копируется и, как мне кажется, не маршалится. Т.е. потерь нет никаких, в теории.
      А если если даже маршалится то это делается через одно memcpy().

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

      Я не могу представить себе нагрузку, которая может убить ноду на примерно таком коде
      var buffer=new Buffer(«bla bla bla… 50K»);
      app.get('/',function(req,res){
      res.send(buffer);
      });

      Узкое место у вебсервисов почти всегда — диск. Будет ли работать ngnix быстрее зависит от нагрузки на диск. ngnix, как мне кажется, полюбому mtime проверяет. Т.е. диск грузит.
      • 0
        sendfile API просто требует read and write descriptors, ему mtime ни к чему. ну и всегда можно еще добавить file open cache.
        • 0
          А как он кеш на стороне браузере поддерживает? Нужен либо mtime или посчитать чексум файла. etag или modified-time ведь не посчитаешь без этого.

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

          • 0
            статику обычно отдают с expires max.

            мы просто к имени файла прибавляем git sha. смотрите плагин grunt-urlrevs.

            и никаких проблем с кэшированием.
            • 0
              grunt-urlrevs это только для картинок
              к урлу хтмл-файлов хеш не добавишь.

              В общем можно сделать все что угодно на чем угодно даже на си++. Вопрос в том насколько сложно.

              Hash к урлам добавляет грунт, а вот max-age должен отправлять ваш код. Т.е. этот пример хорошо иллюстрирует почему решение сборщик+стат-мидлеваре в одном флаконе лучше.

              Реализовал в последней версии эту фичу. Из коробки идет по умолчанию.
  • +1
    А чем ваше решение лучше уже существующих (например, mincer, connect-assets)?
  • 0
    Решил подобную задачу с помощью Grunt тасков:

    grunt-include-source
    Вставляет теги script и css в html один раз при добавлении нового файла

    grunt-usemin
    Пробегает по html и находит все коментарии вида:

    <!-- build:js /app/app.min.js -->
    <script src="/js/app.js"></script>
    ...
    <!-- endbuild -->
    

    И прогоняет их через concat, uglifyjs для js, и csso и autoprefixer для сss, и заменяет эти коментарии на подключение собранного файла, то есть для примера выше будет:

    <script src="/app/app.min.js"></script>
    


    Плюс в дальнейшем добавлю grunt-filerev, что бы он менял номер ревизии у файлов во избежании загрузки из кэша браузера старых вместо новых версий файла.

    Вполне себе так удобно и очень гибко. Возможно конечно настройка и займет немного больше времени.
    • 0
      сравните синтаксис. Какой более чистый у USE тега или у кучи script/style да и еще и с комментами? Какой проще использовать?
      сколько времени нужно настраивать и устанавливать плагины?
      • 0
        У USE чище наверно, но я же не добавляю теги руками, а только размечаю комментариями куда плагин их добавит. Плюс на выходе у меня просто html без кастомных тегов. Также мне не нужно каждый файл прописывать вручную. И в моем подходе можно использовать всю мощь и гибкость грунта, то есть я могу изменить процесс обработки файлов так как мне нужно.

        А так, установка и настройка занимает минут 10 — 15. И в итоге подход получается более универсальным, так как можно не только с nodejs и express использовать.
        • 0
          Ну необязательно Express у многих фреймворков ноды совместимость middleware
          • 0
            Кстати, насчет миддлвейров, хочется отметить, что комплексное решение уступает набору простых миддлвейров в гибкости. Это похоже на философию UNIX, где есть миллионы маленьких утилит, данные между которыми можно передавать через пайпы, контекст использования каждой из таких утилит достаточно широк, плюс создание цепочек обработки обеспечивает гибкость и настраивоемость решения, в вашем же случае, увы, при возникновении задачи, не предусмотренной вашим решением, возникает проблема, исправить которую можно а) написав PR, дождавшись вашего обновления, б) Открыв issue, но не факт6 что вы будете заинтересованы в проблеме, в) добавив сборщик (grunt/gulp и тд), но это приведет к фрагментированности сборки, что не правильно. Но в случае с простыми mw, проблему возможно решить, просто подключив нужный mw к express. Ну вот, например, я использую stylus, и я боюсь, что подключить его к вашей системе у меня не получится без приключений.
            • 0
              Насчет гибкости то есть фильтры: Вот вам стилус:
                  var stylus= require('stylus');
                  fastStatic.addFilter({
                      exts:'stylus,styl',
                      ext:'css',
                      order:100,
                      fun:function(data,filename, ext, cb){
                          stylus.render(data,{},cb);
                      }
                  });
              


              Для статика кроме фильторв единственный полезный middleware это gzip, которые идет в комплекте с кешированием.
              • 0
                Да, это выглядит достаточно просто! Может быть я перегнул :)

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.