⬆️ ⬇️

Javascript: tests, compilation and MVVM

Good day.

The modern world leaves little opportunity not to encounter javascript. Nodejs was the last straw for me and, disappointed in RoR (too much magic and generators - no holivars, rubists!), I again succumbed to insanity: one language on the client and the server. Although javascript is beautiful as a language, there are no frameworks that implement MVVM or at least MVC and that I would like. They are all heavy and require writing extra (garbage) code. Therefore, I would like to present my vision of MVVM to the court and get kicks from the community in the right direction. The best direction would be: “You missed the library, look at% library_name%”, for everything on the surface (angularjs, knockoutjs, etc.) I looked at. Well, since the framework is raw and unlikely to benefit anyone now, in exchange for the long-awaited kicks, I will try to briefly formulate my experience gained while writing it.



Testing



He brought Bilbo plenty of handkerchiefs, a favorite pipe and tobacco.




Just as a decent hobbit won't go on a journey without a handkerchief, it is possible to live without testing in the modern world, but sadly. I think it’s not a secret to anyone that browsers perceive javascript slightly differently, hence the natural desire to quickly and painlessly find problems, and not to look for them with a great effort of will, relying on instinct.

As a testing framework, I chose QUnit . I really like jquery, and the fact that it’s from the jquery foundation was the last weight on the scale. I have never regretted my choice, although I must admit that the first encounter with asynchronous tests caused me a slight shock with its unobvious behavior, but after careful reading of the documentation everything fell into place.



Strange as it may sound, we will need an html document to start the tests.

I look like it
<!DOCTYPE html> <html> <head> <title>Binding tests</title> <link rel="stylesheet" href="css/qunit.css" type="text/css" media="screen"> <script type="text/javascript" src="js/libs/require.js"></script> <script type="text/javascript" src="js/libs/qunit.js"></script> <script type="text/javascript" src="js/binding.js"></script> </head> <body> <h1 id="qunit-header">Tests</h1> <h2 id="qunit-banner"></h2> <div id="qunit-testrunner-toolbar"></div> <h2 id="qunit-userAgent"></h2> <ol id="qunit-tests"></ol> <div id="trash"></div> </body> </html> 




You can see how it works by tiling the repository or here.

')

It remains to write your tests. I really liked the idea of ​​grouping tests in a module, the module method. Use the file name as the name of the module - and, voila, this allows you to quickly find the falling off parts.

Well, the test itself is written using the test function. In the official documentation it is described quite well.
Here again, an example
 test("Hash tests", function() { var obj = {}; ok(HashUtils.get(0) == HashUtils.get(0), "Equal objects, one hash (Number)"); ok(HashUtils.get(obj) == HashUtils.get(obj), "Equal objects, one hash (Object)"); ok(HashUtils.get({}) != HashUtils.get({}), "Different objects, different hashes"); }); 






QUnit has some amenities that warm the soul of any programmer. It is always nice to configure something for yourself, especially when you want to be sure that the compiler obediently performs its role, rather than trying to become a co-author, correcting the logic with which he does not agree.



 QUnit.config.urlConfig.push({ id: "min", label: "Minified source", tooltip: "Load minified source files instead of the regular unminified ones." }); QUnit.config.autostart = false; ... require( tests.concat(document.location.href.indexOf("min=true") > 0 ? productionRequire : developmentRequire), function() { QUnit.start(); } ); 




The whole description, again, is available on the project website here , but for those who are too lazy to click:



Add a checkbox to the qunit menu, and if it is enabled, then we will see min = true in the url

We disable the automatic start of tests and, in the end, there is a wonderful ruquirejs library, with whose help we load either compiled code or code for developers.



I will not give here a description of the methods ok, equal or deepEqual - in my opinion it is unnecessary if there is a complete and clear description of them here . Therefore, about testing and qunit, probably, everything.



Compiling code


I dont know. Saruman believes that only great power can curb evil, but something else has been revealed to me. I realized that various little things, everyday actions of ordinary people help to contain the darkness. Ordinary love and kindness. Why Bilbo Baggins? Probably because I'm scared and he gives me courage.




Perhaps the fight for a few kilobytes in the modern world seems a bit strange, but we live in a world where increasing the download time of a site for a fraction of a second reduces the number of buyers by 10-20%, so we will treat this philosophically, especially since it is not so difficult .



As a compiler, I chose Google Closure and I got the following script:

build.sh
rm -r -f out

mkdir out



cat js / binding / utils.js js / binding / dom.js js / binding / model.js js / binding / events.js js / binding / binding.js js / binding / templates.js> out / binding.js

java -jar libs / compiler.jar --js out / binding.js --js_output_file out / binding-min.js --compilation_level SIMPLE_OPTIMIZATIONS



cat out / binding.js js / ui / button.js js / ui / input.js> out / binding-ui.js

java -jar libs / compiler.jar --js out / binding-ui.js --js_output_file out / binding-ui-min.js --compilation_level SIMPLE_OPTIMIZATIONS





Actually, only one line is of interest here, namely

java -jar libs / compiler.jar --js out / binding.js --js_output_file out / binding-min.js --compilation_level SIMPLE_OPTIMIZATIONS



js - the name of the file being compiled

js_output_file - the name of the file where to put the compiled code

compilation_level - compilation level



If I missed something important for you, more information can be obtained by running

 java -jar compiler.jar --help 




BindItJS



That was the style of Thorin. He was an important person. If he was not stopped, he would continue in the same vein without end, while he would not be out of breath at all, but he would never tell the public anything new.


I would not criticize Thorin in the eyes and, of course, I will not comment on successful and certainly wonderful existing frameworks, without them it would be much more difficult to live. Therefore, I propose the following format - I will simply fantasize about how an ideal (IMHO) data binding framework should look like.



Download what will be discussed later here.

And all the examples I will bring from the XO project, you can play here



Further, in the text, curtsies “IMHO” will not, but reading further you should understand that I am writing a subjective opinion that can offend your religious or any other feeling, and if you disagree with something, I will read with pleasure your reasoned comment and reasonably answer it. We are all adults here, yeah.







As a result, we get the holy trinity: html layout, view, model. Linking data and presentation through code when we have a declarative interface representation seems to me a bit redundant, so I would like to specify the representation and data in the layout, since html allows it. As a result, I got the following syntax:

 <div class="pull-left" bind-data="board" bind-handler="BoardBinding"></div> 


bind-data tells where to get the data, and bind-handler how to show it to the user. A little thought about the convenience, I entered another bind-form , which specifies the context for the child elements. With attributes, that's all, except that the bind-handler can be omitted, then the bind-handler will be found in the Binding.DefaultHandlers by the tag name.

 var ButtonBinding = { init : function(binding) { binding.element.onclick = function() { binding.callBindingFunction(); } }, modelChanged : function(binding) { if (ObjectUtils.getObjectType(binding.getModel()) != ObjectUtils.TYPE_FUNCTION) { binding.element.setAttribute("disabled", "disabled"); return; } binding.element.removeAttribute("disabled"); } }; Binding.DefaultHandlers["BUTTON"] = ButtonBinding; 


 <button class="btn btn-success" bind-data="newGame">New game</button> 




Let's take a close look at the ButtonBinding — it has 2 methods: init , which is called when the model and view connection is initialized, and modelChanged , which is called when the model changes. In this example, this is not used, but modelChanged has two additional arguments: model is the changed object, and event contains the event description.

It is worth explaining that the link is not built with the object instance itself.

In xo, we have $ data.Game.board - this is the object that contains the description of the playing field. When we click on the “New game” button, the method from $ data.Game is called.

 newGame : function() { this.board = new Board(DEFAULT_SIZE); this.state = { player : 0, winner : null }; } 


This function creates a new instance, but we will see that the modelChanged of the corresponding view was called, and $ object.Game will be passed as the object that triggers the event, and the event description will be passed to the event.



In practice, this means something like the following: we can be sure that view operates on the data specified in bind-data, and not some kind of ghostly object that deserves only one attention, namely, the garbage collector.



Current State and Roadmap


Now it's pre-pre-pre-alpha and everything can change. The purpose of this article is to get a response from you, will this syntax be convenient in your projects or not, what is missing? Library checked only in chrome.

Roadmap





Finally, I would like to ask a few questions to the collective mind:



Source: https://habr.com/ru/post/185238/



All Articles