
I have been using / developing the
MaskJS library for some time. Initially, I used it only as a template engine, and over time, it completely replaced HTML. In the article I will tell you what advantages the component approach has in the development of applications and this implementation in particular. If we select by points, we will get approximately the following list:
- Speed
- Tag Handlers
- Pre and Post Processors
- Ioc
- Isolation / Decomposition
- Layout - Model - Code - Styles
More information about the library itself and examples can be found here -
libjs / Mask , and the source code here is
github / Mask§ Speed
Performance
Immediately about it, as it is always the first question, after a long description of all the advantages, I hear - “Well, well, you have implemented it through your DOM Builder and everything is tasty, but probably the performance suffers?”. Therefore, I will hasten to assure you that this library is even faster than what you are currently using. Sorry for the pop statement, maybe I’m wrong, but let's dive.
Let's analyze the test that was already given earlier, jsperf .
I note that for me, both for my projects and for my work, Webkits JavaScriptCore and V8 are important. Apparently from tests - compilation and rendering advances other solutions in many browsers.
Mustache is a bit of an advantage here, as it caches the compiled template.
Compile / Parsing a template. First win
From the template we build a JSON tree of the form {tagName:'div',attr:{...},nodes:[...]}
. My first idea was to prepare templates for the client - for further var template = JSON.parse(serializedJsonDom)
, but when I conducted the tests, it turned out that mask.compile in the target engines was faster than JSON.parse - jsperf , and in other browsers it was not much inferior the last one. So precompilation is no longer due to uselessness.
Building a dom. Second win
Having received a JSON tree, we build a DocumentFragment , which we insert into the live DOM. From the test it is clear that here the performance level. I left a clean .appendChild(documentFragment)
to show that all the salt in creating a DocumentFragment . And what's interesting again, .innerHTML here is also inferior in performance (Chrome).
')
The important point is also that we initially render everything in one go. Let's look at an example with jQuery widgets. As usual, everything happens:
- in the layout we specify the container
<div id='myWidget'></div>
- in javascript-e
$('#myWidget').myWidgetInit(config);
The huge disadvantage of this approach is that we create a widget in an already ready DOM element. And as you know, changing a “live” DOM thing is relatively expensive. And while we must recognize that often we have more than one widget in the application.
That's actually the result is a very fast DOM Builder.
Development speed
We will look at this in more detail in the following paragraphs, for the sake of it this article was started. Here I just note that arbitrary tags significantly increase the speed and ease of development. Consider this layout:
header > menuBar { li target='item1' > 'Item1 Title' li target='item2' > 'Item2 Title' } viewsManager { view#item1 > carousel { img src='img1.png'; img src='img2.png'; } view#item2 > scroller { 'About Content' } }
If we have the menuBar, viewsManager, scroller, carousel slider
components ready, then we don’t even need to write anything in javascript-e, that the menus and views would switch, that the scrolling would work, that the pictures would spin. Is not it lovely? And isn't there a layout for that? Of course, many widgets can also self-initialize, but mostly it is implemented through the Dom Ready Event and the search / replacement of the necessary tags - all this is an additional “overhead” . Here everything goes through this very DOM Builder. You can insert such a layout at any time and it will work.
§ Tag Handlers
These are our components. Builder encounters the handler tag, initializes the class object, and passes the rendering context to this handler - no magic. If no one is registered under the tag, it will create the element itself. For a more complete and convenient work with components, there is also a small “abstract and not only” Compo class (
Sources ,
Documentation )
§ Preprocessors
Given that we have a tree
render flow , we get a powerful tool for influencing the rendering and layout as a whole. I call preprocessors components that modify the bottom layout. Thus, as if we are introducing our component into the layout, we rule at our discretion the lower template or replace the input data of the model and continue rendering. This is a very convenient pattern for building different layouts. Here is an example from
MasterPages (asp.net hello) :
layout:master#twoColumnLayout { div.wrap { div.layoutLeft > placeholder#left; div.layoutLeft > placeholder#right; } } layout:view master='twoColumnLayout' { left { } right { } }
The #twoColumnLayout component should of course additionally load its styles, but more about that another time.This is a very simple example, but here you can see how elementary we can substitute different templates for our presentation. Implementation example -
layout.js§ Postprocessors
They can also be called decorators. Here we are already changing the parents, and not the template, since it is already
pro-renderin , but directly
HTMLElement
(we remember that we work with DocumentFragment, so that all changes are painless). This approach is also a powerful assistant in design. On the example of binding:
div { bind value='name'; bind value='status' attr='class'; }
Here the postprocessor will associate the model with the
div
element.
var person = { name: "Alex", status: "happy" } container.appendChild(mask.renderDom(template, person)); setTimeout(function(){ person.status = 'busy' ; person.name="Anonym" }, 1000);
I think the code is clear. And the implementation can be viewed here -
github§ Inversion of Control (Dependency Injection)
Sorry for the title of the paragraph - the name “painfully strong” is like. Oh, it sounds like ... Not so I like the pattern myself, as its name - power is felt. Sorry for the lyrics, could not resist. Returning to our topic, we can say that arbitrary tags open up the horizons of new or well forgotten old patterns.
Plans to implement, for example, a property editors factory:
form { propertyEditor value='name'; propertyEditor value='birthday'; propertyEditor value='age'; }
And the implementation will look something like this:
mask.registerHandler('propertyEditor', Class({ render: function(values, container, cntx){ var value = Object.getProperty(values, this.attr.value), template; switch(Object.typeOf(value)){ case 'string': template = Object.format('input type="text" value="#{%1}" > bind value="#{%1}" prop="value";',this.attr.value); break; case 'datetime': template = Object.format('datePicker date="%1";', value.toString()); break; } mask.renderDom(template, values, container, cntx); }); }}
The important point here is that we can inject into the template by redefining any of the tags, even the same “DIV” - this opens up unlimited horizons for us to manipulate the presentation, test and other things that come to mind.
§ Isolation
Arbitrary tags, or rather their handlers, lend themselves to good isolation. As it should be with a block composition, any block can be selected as a separate project (decomposition), designed, tested and connected back. For example, if from the example above we do not have the component “carousel” - we close the main project, develop a component that rotates its children (pictures), return to the main project and connect it. Our loader should be able to load all the resources needed by the component, such as auxiliary images and styles. I have already spoken about the bootloader, and then I want to say a few more words, since several interesting ideas have appeared. But this is in the next article.
Isolation is important not only in the development process, but also in the application architecture. This is achieved through an event model.
§ Markup - Model - Code - Styles
I really like the web programming structure (markup - code - styles), so I tried to stick to it in MaskJS - as much as possible to remove the logic from the markup by concentrating on the layout and data. Look at other template engines - loops, conditions, expressions, and more. It seems to me that it is much more pleasant to have one (no code, only a layout):
ul > list value='users' > li > '#{name}'
Tag (component)
list;
take the array
"users"
from the model passed to the template, and duplicate its template
li > "#{name}"
N (users.length) times. Everything else can also be implemented through components, or use a ready-made solution. Thus, the layout is the layout, the code is the code, and the styles are styles. All this should be intertwined to a minimum. Of course - this is all subjective, and perhaps I am fundamentally wrong, and you know everything is much better.
§ Avoiding HTML
By virtue of all the above, I completely refused to write plain HTML. In the page we have a tag script with the type “mask / template” whose contents are rendered in the DOM. For convenience, I try to wrap many ready-made libraries in a component, as for example -
TimePickerI apologize if someone offended or mistaken somewhere. Although Chrome has an excellent spell checker, it doesn’t help with punctuation, but probably “oh, as it should.”
Have a good day!