This is a small story of suffering, pain, ups and downs in an effort to speed up RaphaelJS on large and complex SVGs. If you suffer from such problems, then you should not wait for the silver bullet at the end of this article, but I hope that it will be interesting for everyone to read about our way of finding a solution.
How did it all begin?
It all started with the fact that in
ResumUP we draw svg, draw it a lot and beautifully.
')

Historically, all this is drawn with the help of the excellent library
RaphaelJS (there is no irony here, thanks to Dmitry Baranovsky for it). As soon as work began on the project, Raphael was the only solution that made it possible to implement everything that the designers had drawn up within the time frame.
But our joy was premature: drawing a resume took an average of 5-7 seconds on normal machines in normal browsers (when one of the conditions goes into the “abnormal” state — up to 30! Seconds). It is clear that in the long run this did not suit anyone and it was necessary to look for ways to optimize.
Part 1. (Tango and) cache
So what do we have?
- a bunch of js-code that draws beautiful pictures
- execution time from 5 to 30 seconds
- a little time and a lot of desire to speed it all up
The first thing that comes to mind is to see why Rafael draws everything for so long. A slight immersion in the library's code opened up interesting features regarding how browsers work with svg, the division has gone only into two camps: those who understand inline svg and those that do not.
So, for compatibility with old browsers, Rafael works directly with the DOM-tree of the document (this is all very rude, but actually so), it is clear that when there are a lot of elements, even normal browsers have a hard time (and if you remember that when drawing there is logic, it becomes really bad)
Armed with knowledge and realizing that we could not quickly fix Rafael, we decided to go around and cache the result on the server side. They invented it - they did it, the scheme turned out like this
- Man comes to resume
- Each of the 10 blocks is drawn and sent to the server.
- When re-entering the resume, the block is already inserted directly into the template on the server side, Rafael is not even called
- If there is no block in the cache, then only it is rendered.
This reduced the page load to a second, which was still quite a lot, but it seemed incredibly fast after 5-7. The only problem is browsers without inline svg support, they drew everything and always. (we tested the capabilities of the browser using
modernizr )
Part 2. We draw svg on the server. Deplorable experience
So now what do we have?
- a bunch of js-code that draws beautiful pictures
- execution time from 1 to 5-30 seconds
- a little time and business need to draw svg on the server
Since the passion for optimization is infinite, we decided not to dwell on the current caching system and solve the problem somehow more fundamentally. And here we are faced with the task of the authorities to study the possibility of drawing our resumes and vacancies on the server.
The first step was to look at what the world of ruby ​​has for this (backend on our tracks), but I did not find anything intelligible. Yes, even if I had found it, there was absolutely no desire to rewrite the entire pile of code that is responsible for drawing blocks.
Those. there was the first mandatory requirement - to leave the current js-code, drawing blocks.
The very first decision that we decided to try is to run everything under rubyracer, a wrapper around v8, which allows you to run js code in Ruby.
But the problem is that v8 is a machine for interpreting js, but not a browser, and, accordingly, there is no DOM tree in it, so Raphael rested, but didn’t want to draw anything. But we are not the first who faced the challenge of imitating the browser, so a quick nuggligi solution is -
jsdomThe scheme took off, but when we tried to render the entire database of the summary, we had already collapsed on the 10th with the error could not allocate memory. We looked at the statistics, measured the memory consumption and got about 50-70 megabytes of leakage and 8 seconds for execution. But the fact is: you can use the existing code for drawing SVGs on the server, so we are satisfied.
Therefore, we decided to simplify a little scheme and throw out chop. Remembering the main trend and the object of ridicule in 2011 - nodejs, especially since the whole scheme already worked under v8.
Sticking this frankestein into expressjs, we, albeit with a creak, but got our picture. The speed increased to 4-5 seconds, the leak dropped to 40-50.
After a long profiling, we came to the conclusion that the only way to get rid of the leak is to get rid of jsdom. But this required a completely different approach.
Part 3. We continue to draw SVG in the browser. First hope
After the first strange experiments, we decided that half-measures would not help us and would have to go to more global changes.
The basic idea was that we wanted to divide the SVG drawing process into two stages:
- Creating an abstract model that describes the future picture
- Convert this model to SVG
This approach had several advantages:
- Solved the problem with the lack of DOM in nodejs and were able to draw resumes on the server
- For browsers with inline svg support, we could now generate SVG as text, and then directly insert it into the document, which would be a serious acceleration of performance.
- Provided the ability to generate different views for our model using different adapters in the future (for example, VML for IE8)
First, the idea to raphael gut and get out of it all the logic that is not tied to the creation of DOM elements, and try to change the rest. However, after several hours of careful study of the source code of version 2.0, it became clear that this was a week job, and the deadline was rapidly approaching.
Having thought it over, we decided to write our own implementation by copying the raphael interface. Then everything is simple: each block on the resume was a separate class, in which, depending on the environment, they would throw either raphael or our bike instance.
So baileys was born.
The result was:

The user makes a request to rails, rails knocks on nodejs, nodejs calls the code of resume blocks and sends json to rails with SVG data, and rails, in turn, must convert json to SVG, and absinthe was born.
The scheme worked, but the production-solution was still far away: drawing took an average of 0.5 seconds and many issues arose with scaling.
Part 4. There is no limit to perfection.
There was a prototype, but I wanted to bring it to mind, so we decided to make a list of requirements that would satisfy us at the current moment:
- Unity of code: I wanted the code that draws on the server side, does not differ from the code that works on the client side
- I wanted to minimize the work of the ruby ​​part and render the entire SVG drawing entirely outside the main project.
As a result, it turned out that from the main repository we selected 4 more projects:
- Tetris - all block rendering code, abstracted from the library with which it will be drawn
- Baileys - copies the raphael interface, creates a json-description of future svg-objects. We rewrote it a lot, redid the work of the getBBox function, and generally significantly accelerated it.
- Absinthe - they decided that it makes no sense to keep it on the side of the Ruby server anymore, rewrote everything into a coffeescript and compiled the npm package, adapting it to an understanding of json given by baileys
- Polyomino is a server application on expressjs, receives a request and data from the main application, calls baileys and absinthe, returns the result.
As a result, we were able to use tetris on the side of the node in the form of an npm package, and on the side of the rail by connecting to the asset pipeline (for this purpose, we simply collected a simple gem that carries with it tetris + baileys + absinthe in a minimized form).

The current benchmarks show that we draw a summary for an average of 70ms, which is already quite a good result, if we recall the initial 7 seconds.
Our future plans include transferring the entire drawing system to a bunch of baileys and absinthe and a complete rejection of raphael. For the same browsers that do not support inline svg, give img indicating SVG, which is drawn on the server.
If the nodejs is not responding for some reason, you can always switch to the SVG drawing mode on the client.
We also carry bright thoughts to open the code baileys and absinthe and to share our work with everyone who wants it.
If you have reached the end, then you will receive a bonus - the mascot of our team:

Thank you for your attention, were with you
somebody32 and
Terminal