📜 ⬆️ ⬇️

Dock the components in JavaScript

After the note “ We are joining asynchronous scripts and the proposed solution from Steve Souders, I thought about modular loading of some complex JavaScript application. And I realized that the proposed approach in this case would be rather cumbersome: we will need to insert the loader of the following modules at the end of each module. And if we need different sets of modules and different logic of their loading on different pages? Dead end?

But no. No wonder Steve mentions at the very beginning of his note on the onload / onreadystatechange for scripts. Using them, we can uniquely bind some code to the end of loading of a specific module. Things are simple: we need to define this very code in some way.

Solution one: boot tree


')
As the easiest way to determine the order of loading modules on a specific page, you can offer a global array containing a dependency tree. For example, such:

  var modules = [
	 [0, 'item1', function () {
		 alert ('item1 is loaded');
	 }],
	 [1, 'item2', function () {
		 alert ('item2 is loaded');
	 }],
	 [1, 'item3', function () {
		 alert ('item3 is loaded');
	 }]
 ]; 


As an element of this array, we have another array. The first element is the indication of the parent ( 0 if the element is a root and should be loaded immediately), then the file name or its alias. The last is an arbitrary function that can be performed by loading.

Let's look at how this structure can be used:

  / * brute force and module loading * /
 function load_by_parent (i) {
	 i = i ||  0;
	 var len = modules.length,
		 module;
 / * iterate the module tree * /
	 while (len--) {
		 module = modules [len];
 / * and load the required elements * /
		 if (! module [0]) {
			 loader (len);
		 }
	 }
 }

 / * declare function loader * /
 function loader (i) {
	 var module = modules [i];
 / * create a new script element * /
	 var script = document.createElement ('script');
	 script.type = 'text / javascript';
 / * set the file name * /
	 script.src = module [1] + '.js';
 / * set the text inside the tag to run on the download * /
	 script.text = module [2];
 / * remember the current index of the module * /
	 script.title = i + 1;
 / * set up the download handler for IE * /
	 script.onreadystatechange = function () {
		 if (this.readyState === 'loaded') {
 / * iterate through the modules and look for the ones you need to load * /
			 load_by_parent (this.title);
		 }
	 };
 / * set the load handler for the others * /
	 script.onload = function (e) {
 / * execute the text inside the tag (you need only for Opera) * /
		 if (/opera/i.test(navigator.userAgent)) {
			 eval (e.target.innerHTML);
		 }
 / * iterate through the modules and look for the ones you need to load * /
			 load_by_parent (this.title);
	 };
 / * attach the tag to the document * /
	 document.getElementsByTagName ('head') [0] .appendChild (script);
 }

 / * load the root elements * /
 load_by_parent (); 


We can take out the loading of root elements in the page loading event, and the functions themselves - in any library, or declare directly on the page. By setting our own tree on each page, we get full flexibility in asynchronous loading of any number of JavaScript modules. It is worth noting that dependencies in this case are resolved “from the root to the vertices”: we ourselves should know which basic components to load, and then download the more advanced ones.

Solution two: loading via DOM tree



In addition, I remembered that Andrey Sumin had already dealt with this problem and even offered his solution in the form of the JSX library, which allows you to assign a list of dependencies through a DOM tree. For this, elements that require loading any modules for user interaction are assigned a class with the prefix jsx-component , and then comes the list of components. The library itself bypasses the DOM tree, finds all the modules that need to be loaded, and loads them in sequence. Just great.

But what if we need to change the handler for loading this module? How to ask it? JSX itself uses the attributes of the required nodes of the DOM tree strictly to determine the parameters of these modules. This is quite convenient: in this way, you can also assign a module initializer.

The library also allows you to track the reloading of modules, to load additional modules in the event of a bad connection, and even to merge different modules into one source file through the alias system. Thus, the problem of asynchronous loading of an arbitrary tree of modules is solved. In the case of JSX, the task is resolved in the reverse order: we specify the main file (the top of the dependency tree), and it already loads all the modules it needs or checks that the modules are loaded.

It's all?

Solution three: JSX + YASS



Nearly. After some deliberation, JSX was taken as the basis for building a modular system that could become the basis for flexible and dynamic client applications. It was possible to combine both approaches described above, which ensured all the visible functional requirements for such a system.

For example, consider the following section of HTML code:

  <div id = "item1" class = "yass-module-utils-base-dom">
	 <span id = "item2" class = "yass-module-dom" title = "_ ('# item2') [0] .innerHTML = 'component is loading ...';"> </ span>
 </ div> 


Let's see what kind of boot logic it provides:

  1. During initialization, YASS bypasses the DOM tree of the document and selects all nodes with the yass-module-* class.
  2. After that, 2 module load streams are generated: for utils-base-dom and for dom . And in the latter case of loading, in fact, it will not be: the loader waits until the state of the dom component is set to loaded , and only then runs (via eval ) the code recorded in the title this element (in this case, it is a span ).
  3. The first download stream will asynchronously call 3 files from the server: yass.dom.js , yass.base.js and yass.utils.js . By loading all of these modules (because they are called in the dependency chain, in this case the dom depends on the base , which depends on the utils ) the corresponding initialization functions will be called (if defined). Thus, two types of handlers are possible: directly on the component load (to be called for all components in the chain) and after loading the entire specified chain of components (in our case, it is utils-base-dom ).
  4. If we want to somehow expand our chain, then at the end of each of the specified files, we can prescribe the loading of some other chain (for example, base-callbacks ), which will “freeze” the loading of the base module before receiving callbacks . This can be done (bearing in mind that we expand the dependencies of the base module) as follows:
      _.load ('callbacks-base'); 
  5. The previous step can also be performed using the DOM-Tree itself: we will need to prescribe the yass-module-callbacks-base class for an arbitrary element. This will add the desired chain to the dependency tree.


For greater clarity, the final tree of loadable modules described above can be represented as follows:

  dom
   -> base
	   -> utils
	   -> callbacks 


Moreover, the next page presents the process of loading a more complex version of the tree . Caution: there are no delays, so it can work very quickly :)

Naturally, all the specified functionality has already been added to the latest version of YASS . You can start using and writing reviews.

PS JSX site is still (scared habraeffekt?), You can try to get information from the Google cache

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


All Articles