borschik — это простой, но мощный сборщик файлов текстовых форматов.
Основная задача – сборка статических файлов веб-проектов (CSS, JS и т.д.).
Что он умеет делать с файлами:
- склеивать
- преобразовывать
- минимизировать
borschik построен на системе плагинов, которые в его терминах называются "технологиями". На данный момент существуют технологии для CSS, JS. Вы легко можете написать свою собственную технологию как для других языков, так и расширить существующие.
borschik оперирует двумя понятиями — include
и link
.
include
: вы указываете ссылку на файл, после сборки ссылка заменяется на содержимое файла.link
: вы указываете ссылку на файл, после сборки ссылка преобразовывается.
Фактически каждая технология определяет как найти link
или include
и как их обработать.
Из коробки borschik умеет раскрывать правила @import
и заменять их на содержимое указанного файла.
Допустим, у нас есть два CSS-файла
b-header/b-header.css
.b-header
{
border: 1px solid #000;
}
b-footer/b-footer.css
.b-footer
{
border-top: 1px solid #000;
background: url("bg.png");
}
И мы хотим собрать их в один файл page.css
, который будет лежать в корне проекта
@import url("b-header/b-header.css");
@import url("b-footer/b-footer.css");
С помощью команды
$ borschik --input=page.css --minimize=no
Получаем следующий результат
/* b-header/b-header.css begin */
.b-header
{
border: 1px solid #000;
}
/* b-header/b-header.css end */
.b-title
{
font-size: 120%
}
/* b-footer/b-footer.css begin */
.b-header
{
border-top: 1px solid #000;
background: url("b-footer/bg.png");
}
/* b-footer/b-footer.css end */
Добавление комментариев при раскрытии можно отлючить с помощью опции --comments=no
.
Примечание: несмотря на то, что картинка bg.png
лежит в папке b-footer
, путь до нее преобразовался
и стал относительным файла page.css
.
Точно также можно указывать вложенные правила @import
: пути до них будут правильно раскрыты.
###JS
По аналогии с CSS можно склеивать и JS-файлы. Так как в JS нет стандартных методов для импорта файлов,
в borschik придуман специльный синтаксис borschik:include:path/to/fie.js
.
Такие конструкции нужно указывать либо в блочных комментариях /*borschik:include:file.js*/
, либо как строку "borschik:include:file.js"
.
Если include
указан как комментарий, то он заменится на содержимое файла без какой-либо обработки. page.js
var prj = {};
/* borschik:include:components/cookie.js */
Собираем:
$ borschik --input=page.js --minimize=no
Получаем:
var prj = {};
/* components/cookie.js begin */
prj.cookie = {
set: function(){},
get: function(){}
};
/* components/cookie.js end */
Если include
указан как строка, то содержимое файла будет обработано с помощью JSON.stringify
. page.js
prj.STATIC_HOST = "borschik:include:components/host.txt";
Преобразуется в:
prj.STATIC_HOST = "//yandex.st";
По умолчанию borschik минимизирует CSS посредством CSSO , а JS с помощью UglifyJS v1.2.
Чтобы отключить минимизацию, используйте опцию --minimize=no
. По умолчанию, она включена.
Упоминание картинок в CSS файлах можно изменить с относительных путей на
абсолютные (или другие относительные) с помощью конфига (файл .borschik
в любой директории). Конфиг считается относящимся к той директории, в которой
он находится. Конфиги в директории более верхнего уровня (ближе к корню
проекта) важнее конфигов в директории более нижнего.
Пример синтаксиса:
{
"paths" : {
"./": "/",
"css/": "//yandex.st/my-prj/css/"
}
}
Находяcь в корне проекта, этот конфиг приводит все ссылки к абсолютному в пределах домена пути, а файлы
в директории css/
– к //yandex.st/my-prj/css/
.
Например, если в проекте будет файл css/my.css
, в котором написано
background-image: url(../a/b.gif);
(относительная ссылка на картинку), то
из-за мапинга в .borschik
про "./": "/"
в результате будет
background-image: url("/a/b.gif");
.
В конфиге можно использовать ключ follow_symlinks
Например,
"follow_symlinks" : {
"./a/b/c.css" : true,
"./a" : true
}
Семантика такая: указываются те файлы и папки, при обработке относительных путей от которых нужно проследовать по символической ссылке.
Стандартные способы омптимизации загрузки статики выглядят так:
- Выставить «правильные», навечно кеширующие, заголовки на сервере
Cache-Control:max-age=315360000
Expires:Thu, 31 Dec 2037 23:55:55 GMT
- Унести статику на отдельный домен
- Добавить версию в урл, чтобы инвалидировать ее при выпуске новой версии
К примеру, вот так вставляем CSS
<link rel="stylesheet" href="//yandex.st/my-prj/1.0.0/css/page.css"/>
Допустим, в нем есть ссылка на картинку:
.b-page
{
background-image: url('../i/bg.png')
}
Получается, что картинка загружается по следующему URL
//yandex.st/my-prj/1.0.0/i/bg.png
Проблема: если CSS поменяется, необходимо будет изменить версию проекта, что изменит урл
<link rel="stylesheet" href="//yandex.st/my-prj/1.0.1/css/page.css"/>
Урл до картинки также поменяется
//yandex.st/my-prj/1.0.1/i/bg.png
Значит, из-за изменившегося урла пользователю придется заново загрузить не только новый CSS, но и все картинки в нем.
Подобную аналогию можно провести с JS и HTML.
- Можно вручную добавлять к каждому ресурсу его версию. Это просто. Но неудобно, сложно покрыть все файлы и есть большой риск ошибиться.
- Можно автоматически добавлять к каждому ресурсу ревизию из VCS или timestamp последнего изменения. Сложнее в реализации, избавляет от ручной работы, но не от сложности покрытия всех файлов.
borschik предлагает простой, но комплексный вариант решения – загрузка ресурсов по урлу, не зависящему от версии проекта, а зависящему от содержимого файла, например SHA1-суммы.
Процесс преобразования урла до файла, когда урл заменяется на хеш от контента файла, мы называем freeze.
С помощью borschik вы сможете это автоматизировать.
Для начала нам нужен конфиг. Он записывается в файл .borschik
.
Файл .borschik
влияет на директорию, где он находится, и на все вложенные директории.
Если есть несколько файлов в разных директориях, то конфиги в этих файлах переопределяют друг друга в порядке "кто выше, тот главнее".
{
"freeze_paths": {
"i/bg": "../../_",
"i/ico": "../../_"
}
}
freeze_paths
— это path-mapping, который определяет, какие файлы и в какую папку замораживать.
Например, когда borschik будет обрабатывать CSS-файл и найдет ссылки на ресурсы в папках i/bg
или i/ico
,
он их заморозит.
Ключ — это папка, файлы в которой будут заморожены.
Значение ключа указывает папку, в которую нужно cложить замороженые файлы. Эта папка считается относительно оригинальной папки.
Например,
{
"freeze_paths": {
"i/bg": "_"
}
}
заморозит файлы из папки i/bg
в папку i/bg/_
Важно:
- Замораживаются не все файлы в папке, а только те, на которые есть ссылки в обрабатываемых файлах.
- В папке фриза создаются копии оригинальных файлов, но с другим именем. Имя – хеш-сумма от содержимого файла.
###CSS freeze
Например, есть такой CSS css/main.css
.b-page
{
background-image: url('../i/bg/main.png')
}
Мы его замораживаем
$ borschik --input=css/main.css --freeze=yes
И получаем такой файл
.b-page
{
background-image: url('//yandex.st/my-prj/_/wFPs-e1B3wMRud8TzGw7YHjS08I.png')
}
Теперь достаточно просто положить замороженные картинки и CSS на сервер. После заморозки в урле больше нет версии, и все файлы грузятся по хешу. Все ресурсы всех версий могут спокойно выкладываться в одну и ту же папку. Даже если у них будут одинаковые картинки, это не вызовет никаких проблем. Если при обновлении версии картинки не поменяются, урл к ним не изменится и пользователь не будет их заново загружать.
Так как CSS является декларативным языком, то найти ссылки на картинки или шрифты не проблема. С JS все сложнее: ссылку на картинку можно указать бесконечно разными способами, а ссылки могут быть динамическими.
Вот так обычно выглядит ссылка на картинку
new Image().src = 'i/bg/main.png'
Чтобы помочь borschik найти и заморозить картинку, надо ее разметить с помощью borschik.link()
new Image().src = borschik.link('i/bg/main.png')
Запускаем
borschik --tech=js-link --freeze=yes --input=1.js
Получаем
new Image().src = '//yandex.st/my-prj/_/wFPs-e1B3wMRud8TzGw7YHjS08I.png'
Чтобы было проще разрабатываться, можно определить функцию borschik.link
.
Она возвращает значение, которое ей передали
borschik.link = function(link) {
return link;
}
С динамическими ссылками на картинки, где урл зависит от значения переменных, возникаются сложности.
var icoName = 'yandex';
new Image().src = 'i/ico/' + iconName + '.png'
Такие картинки придется объявлять в отдельном JSON-файле
{
"ico-yandex-png": "i/ico/yandex.png",
"ico-github-png": "i/ico/github.png",
"ico-nodejs-png": "i/ico/nodejs.png"
}
Ключ — это любое название, которое вам больше нравится, а значение — путь до файла. JS придется немного переписать: ссылаться на картинку не по урлу, а по имени из JSON
var icoName = 'yandex';
new Image().src = borschik.link('@ico-' + iconName + '-png')
@
в начале имени указывает на то, что это динамическая картинка.
Ее нужно добавлять обязательно, чтобы уметь отличать имя от пути.
При таком использовании вызов borschik.link()
не изменится,
а правильный урл будет отдаваться динамически.
Теперь берем JSON, который мы описали, и отдаем его в специальную функцию borschik.addLinks()
borschik.addLinks(/* borschik:include:_images.json */)
var icoName = 'yandex';
new Image().src = borschik.link('@ico-' + iconName + '-png')
# замораживаем JSON
$ borschik --tech=json-links --input=images.json > _images.json
# собираем JS
$ borschik --tech=js --input=1.js
Получаем
borschik.addLinks({
"ico-yandex-png": "//yandex.st/my-prj/_/wFPs-e1B3wMRud8TzGw7YHjS08I.png",
"ico-github-png": "//yandex.st/my-prj/_/8ge7HHM3UfpIESgvrpN3bi-Nz0.png",
"ico-nodejs-png": "//yandex.st/my-prj/_/1z-l36qqomllvJek_InjAYnHrOE.png"
})
var icoName = 'yandex';
new Image().src = borschik.link('@ico-' + iconName + '-png')
borschik.link()
и borschik.addLinks()
выглядит так:
(function() {
var borschik = window['borschik'] = {};
var links = {};
borschik.addLinks = function(json) {
for (var link in json) {
links[link] = json[link];
}
};
borschik.link = function(link) {
// link with "@" is dynamic
if (link.charAt(0) === '@') {
return links[link.substr(1)];
}
return link;
};
})();
borschik умеет замораживать ссылки на статические ресурсы в HTML.
Например,
<html>
<head>
<link rel="stylesheet" href="1.css"/>
</head>
<body>
<!-- <img src="1.png"> -->
<img src="1.png">
<script src="1.js"></script>
</body>
</html>
Замораживаем
$ borschik --tech=html --input=index.html
Получаем
<html>
<head>
<link rel="stylesheet" href="//yandex.st/prj/_/n8mJAmybm5i9sdsO92s6y0.css"/>
</head>
<body>
<!-- <img src="1.png"> -->
<img src="//yandex.st/prj/_/jUK5O9GsS2gPWOhRMeBxR0GThf0.png">
<script src="//yandex.st/prj/_/1qHhHrD9m5i9sdDbCe590URPaBw.js"></script>
</body>
</html>
Как рассказано выше, borschik замораживает только те файлы, на которые есть ссылка внутри обрабатываемого файла.
Естественно могут быть ситуации, когда ссылок нет.
Например, при динамической загрузке JS-модулей через RequireJS.
В такой ситуации будет полезно заморозить все файлы.
Для этого воспользуйтесь подкомандой borschik freeze
,
которая согласно конфигу .borschik
заморозит все файлы в указанной папке
$ borschik freeze \
--input=path/to/dir \ # папка, в которой надо заморозить все файлы
--output=freeze-info.json # JSON с mapping'ом оригинальный файл -> замороженный файл
Пример
$ borschik freeze --input=js > freeze-info.json
{
"js/index.js": "//yandex.st/my-prj/_/434046cd5d1b54ae2374868a7363d7d8.js",
"js/setup.js": "//yandex.st/my-prj/_/bcbf293578cfda2d4543d401d12e2e49.js"
}
Дальше этот JSON можно использовать в вашей системе загрузки или с небольшими преобразованиями в RequireJS.
С помощью простых операций, вы сможете заморозить статические ресурсы. При выпуске новых версий пользователи будут загружать заново только изменившиеся ресурсы. Польза:
- уменьшение нагрузки на статический кластер
- уменьшение количества ресурсов, необходимых для загрузки пользователем
- ускорение загрузки страницы