Skip to content
Permalink
bem-info-data
Go to file
 
 
Cannot retrieve contributors at this time
475 lines (376 sloc) 12.6 KB

borschik

borschik is a simple but powerful builder for text-based file formats.

Its main purpose is the assembly of static files for web projects (CSS, JS, etc.).

borschik can perform the following operations with files:

  • merge
  • modify
  • minify

borschik is based on a plugin system. Plugins are called "technologies". There are technologies for CSS and JS included, and you can easily extend them or write you own.

borschik is based on two concepts — include and link.

  • include: you have a link to file, which is replaced with the file content.
  • link: you have a link to file, the link is transformed.

Each "technology" defines how to find and process link and include.

Merging files

CSS

borschik can find @import rules and replace them with the content of the imported files.

For example we have two CSS-files

  • 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");
}

And we want to merge them in a single file page.css which is located in the project root directory

@import url("b-header/b-header.css");
@import url("b-footer/b-footer.css");

We run borschik

$ borschik --input=page.css --minimize=no

and get the result

/* 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-footer
{
    border-top: 1px solid #000;
    background: url("b-footer/bg.png");
}

/* b-footer/b-footer.css end */

Comments wrapping can be disabled with --comments=no option.

Note: as you see, image bg.png is located in b-footer directory and url was transformed relative to page.css. Also all urls in @import rules will be processed appropriately.

###JS borschik can merge JS files similarly to the way it does with CSS . But there is no standard method for this in Javascript so borschik uses the following syntax borschik:include:path/to/fie.js. This expression must be in a block comment /*borschik:include:file.js*/ or string "borschik:include:file.js". Comment and string have several semantic.

If include is in a comment it will be replaced by the file content without any transformation. page.js

var prj = {};
/* borschik:include:components/cookie.js */

Run borschik

$ borschik --input=page.js --minimize=no

and get the result

var prj = {};
/* components/cookie.js begin */
prj.cookie = {
    set: function(){},
    get: function(){}
};

/* components/cookie.js end */

If include is in a string it will be replaced with the result of applying JSON.stringify to the file content. page.js

prj.STATIC_HOST = "borschik:include:components/host.txt";

page.js will be transformed to:

prj.STATIC_HOST = "//yandex.st";

CSS and JS minification

borschik minifies CSS with CSSO and JS with UglifyJS (v1.2).

You can disable minification with --minimize=no option. It's enabled by default.

Relative links to resources

borschik can tranform urls in CSS (and other technologies) from relative to absolute, or relative to a different base, according to configuration. (.borschik file in any directory). Configuration relates to the directory where it's located. Configuration is more important as higher in file system hierarchy is located.

Example:

    {
        "paths" : {
            "./": "/",
            "css/": "//yandex.st/my-prj/css/"
        }
    }

This transform all urls to be rooted at / and urls for files in the css/ directory to be rooted at //yandex.st/my-prj/css/.

For example, we have the file css/my.css with the following rule: background-image: url(../a/b.gif); (a relative path to the image). The URL will be transform to background-image: url("/a/b.gif"); because of .borschik rule "./": "/".

Following symlinks

There is a follow_symlinks rule in configuration. You can indicate which files and directories must be followed by symlinks.

Example:

    "follow_symlinks" : {
        "./a/b/c.css" : true,
        "./a" : true
    }

Static resources «freeze»

Why do we need to freeze static

Well-known techniques for static resource loading are:

  • Setup HTTP-headers for better caching
Cache-Control:max-age=315360000
Expires:Thu, 31 Dec 2037 23:55:55 GMT
  • Load static from another domain
  • Add a version identifier to the url, so you can invalidate it in case of new version

For example, link to CSS

<link rel="stylesheet" href="//yandex.st/my-prj/1.0.0/css/page.css"/>

Assume we have the background image url:

.b-page
{
    background-image: url('../i/bg.png')
}

So the url to the image is:

//yandex.st/my-prj/1.0.0/i/bg.png

The problem: when you change CSS you have to change the url to invalidate browser cache

<link rel="stylesheet" href="//yandex.st/my-prj/1.0.1/css/page.css"/>

And the url of the image is also changed

//yandex.st/my-prj/1.0.1/i/bg.png

Users then download not only the new CSS but also all images in it. A similar analogy can be made with JS and HTML.

How to solve the problem?

  1. You can manually add a version to each resource. It's simple. But uncomfortable, hard to mark all files and there a great risk of making a mistake
  2. You add automatically add version info from a Version Control System or modification timestamp. It's harder to realize, relieve from manually work but still hard to mark all files

borschik propose simple but complex solution - download files by url not related to version but related to file content, sha1 checksum for example.

Transforming file url to url with hash we called "freeze".

And you can automatize this task with borschik.

.borschik config

At first we need config. It's located in file .borschik.

File .borschik relates to its own directory and all subdirectories.

{
    "freeze_paths": {
        "i/bg": "../../_",
        "i/ico": "../../_"
    }
}

freeze_paths — this key defines which files will be frozen, and where any transformations in the file path of the frozen result.

For example, when borschik processes CSS file and finds links to images in i/bg or i/ico, borschik freezes these links, changing their path to ../../_ and creates image copy in this path.

Object key — directories whose files will be frozen. Key value - directory for resulting frozen files, relative to their initial path.

Example:

{
    "freeze_paths": {
        "i/bg": "_"
    }
}

borschik freezes files from directory i/bg to i/bg/_

Important note:

  • borschik does not freeze all files in directories but only those linked by processed files.
  • borschik creates a copy of original files in freeze dir whose filename is a checksum of the file content.

Freeze links in a CSS file

For example, we have CSS css/main.css

.b-page
{
    background-image: url('../i/bg/main.png')
}

Freeze it

$ borschik --input=css/main.css --freeze=yes

And get the result

.b-page
{
    background-image: url('//yandex.st/my-prj/_/wFPs-e1B3wMRud8TzGw7YHjS08I.png')
}

From now you just need to deploy frozen files and CSS to your server. There is no version in frozen URLs, all files are loaded by checksum. All frozen files may be deployed to the same directory. There is no ploblem if several files refer to the same image - the duplicates with the same content will end up pointing to the same frozen file. So if you change CSS version and do not change images, the URLs will not be changed and the browser will therefore load them from cache.

JS

There isn't a problem to find links to images in CSS because it is a markup language. But JS is not, links can be declared in infinitely many ways and may be dynamic.

Static URLs

Here is a regular way to load image in JS

new Image().src = 'i/bg/main.png'

To help borschik find and freeze this image you should mark it with borschik.link()

new Image().src = borschik.link('i/bg/main.png')

Then run the command

borschik --tech=js --freeze=yes --input=1.js

And get the result

new Image().src = '//yandex.st/my-prj/_/wFPs-e1B3wMRud8TzGw7YHjS08I.png'

You can declare borschik.link() in a development environment as

borschik.link = function(link) {
    return link;
}

so borschik.link() just returns the first argument

Dynamic URLs

There are some problems with resources with dynamic URLs because the URL depends on the value of a variable.

var icoName = 'yandex';
new Image().src = 'i/ico/' + iconName + '.png'

You have to declare these links in a JSON file

{
    "ico-yandex-png": "i/ico/yandex.png",
    "ico-github-png": "i/ico/github.png",
    "ico-nodejs-png": "i/ico/nodejs.png"
}

Object key - resource name, value - path to file. Now we should rewrite our JS. Refer to the dynamic resources not by URL but by their JSON key

var icoName = 'yandex';
new Image().src = borschik.link('@ico-' + iconName + '-png')

@ means that this is dymanic image. We must use it to differentiate names between JSON keys and real paths to files. In this case the borschik.link() call won't be transformed after build process and the frozen URL will be returned dynamically.

Pass the JSON file to the special function borschik.addLinks() as an include

borschik.addLinks(/* borschik:include:_images.json */)
var icoName = 'yandex';
new Image().src = borschik.link('@ico-' + iconName + '-png')
# to freeze the content in our JSON object
$ borschik --tech=json-links --input=images.json > _images.json
# and then build our JS
$ borschik --tech=js --input=1.js

And get the result

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() and borschik.addLinks() looks like this code:

(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;
    };

})();

HTML

borschik also can freeze static resources in HTML.

For example,

<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>

Run borschik

$ borschik --tech=html --input=index.html

Result

<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>

«Total freeze»

As you see below borschik can freeze only those files which are linked from the processed files. But we may have cases when we have no links, for example dynamic JS modules loading with RequireJS In this case it will be useful to freeze all files in directory. borschik has a subcommand borschik freeze which freezes all files in the specified directory according to the .borschik configuration

$ borschik freeze \
  --input=path/to/dir \ # directory to freeze
  --output=freeze-info.json # JSON wit path-mapping original file -> frozen file

Example:

$ borschik freeze --input=js > freeze-info.json

Result

{
    "js/index.js": "//yandex.st/my-prj/_/434046cd5d1b54ae2374868a7363d7d8.js",
    "js/setup.js": "//yandex.st/my-prj/_/bcbf293578cfda2d4543d401d12e2e49.js"
}

Now you can use this JSON in your loader or pass it to RequireJS (with a little transformation).

Freeze advantages

As you see borschik can freeze urls to static resources in files and processed files as well. This is a simple and powerful solution. So when you've deployed a new version of your site the browser downloads only modified resources. Benefits:

  • reduce static server workload
  • reduce number of resources need to be downloaded by browser
  • speed up page loading
You can’t perform that action at this time.