📜 ⬆️ ⬇️

YM modular system

Another one? What for? Are there CommonJS and AMD? Guards can pass under the cat.


I will not go into why modules and modular systems are needed at all; there is an article on this article, The JavaScript module path from azproduction , let's go straight to the main point : why another modular system? After all, there are already CommonJS and AMD. But both of them have one drawback, which for most of the projects in which I participate is, if neither fatal, then very uncomfortable - they, one way or another, are more or less synchronous. And we often had situations when it was necessary to invent and write crutches, allowing to bypass this drawback.

Consider a simple example: we have modules moduleA , moduleB , moduleC , and the latter depends on the previous two.
To begin with, we will write the code describing these modules for all three modular systems: CommonJS, AMD, YM.
')
Commonjs

moduleA.js:
module.exports = 'A'; 

moduleB.js:
 module.exports = 'B'; 

moduleC.js:
 var moduleA = require('A'), moduleB = require('B'); module.exports = moduleA + moduleB + 'C'; 

Connection and use:
 var moduleC = require('C'); console.log(moduleC); // prints "ABC" 


AMD

moduleA.js:
 define('A', function() { return 'A'; }); 

moduleB.js:
 define('B', function() { return 'B'; }); 

moduleC.js:
 define('', ['A', 'B'], function(moduleA, moduleB) { return moduleA + moduleB + 'C'; }); 

Connection and use:
 require([''], function(moduleC) { console.log(moduleC); // prints "ABC" }); 


YM

moduleA.js:
 modules.define('A', function(provide) { provide('A'); }); 

moduleB.js:
 modules.define('B', function(provide) { provide('B'); }); 

moduleC.js:
 modules.define('C', ['A', 'B'], function(provide, moduleA, moduleB) { provide(moduleA + moduleB + 'C'); }); 

Connection and use:
 modules.require([''], function(moduleC) { console.log(moduleC); // prints "ABC" }); 


So far, nothing interesting. All three examples are equivalent. Now, pay attention to one detail in YM modules — the provide function is transferred to the callback function of the module declaration. It is especially unclear why it is there, but now imagine a situation where the modules moduleA and moduleB cannot split up immediately, synchronously (as required by both CommonJS and AMD), that for this they need to perform some kind of asynchronous action. For simplicity, let it be setTimeout . With the help of YM, the previous example can be easily rewritten as follows (which cannot be expressed either by CommonJS or AMD, although the latter even contains Asynchronous in the title, but it only affects the declaration method and the method of requiring the module):

moduleA.js:
 modules.define('A', function(provide) { setTimeout(function() { provide('A'); }); }); 

moduleB.js:
 modules.define('B', function(provide) { setTimeout(function() { provide('B'); }); }); 

moduleC.js:
 modules.define('C', ['A', 'B'], function(provide, moduleA, moduleB) { provide(moduleA + moduleB + 'C'); }); 

In this case, we note that the moduleC itself knows nothing at all about the asynchronous nature of moduleA and moduleB . Profit.

Life example

From a synthetic example, let's move on to the real one. In projects, we use the Yandex.Map API, which in principle does not know how to boot synchronously (inside it uses multi-stage loading). This means that it is impossible to simply write and hope that all my subsequent scripts will be able to work with ready-made api. To get started, you need to wait for the event ymaps.ready . Our project is quite complicated, and we use quite a lot of inheritance from the base classes from api. Consider the example of one of them. We have our own layer class ComplexLayer, which we want to inherit from the base layer of ymaps: ymaps.Layer . With the help of YM, this is done simply: we define the module ymaps, which loads the API, then it waits for the desired event (ymaps.ready) and then it will provide itself. All modules that depended on the api module (ymaps) begin their rezolving only after that. Thus, our modules, again, do not know anything about the asynchronous nature of the Yandex.Maps API. No crutches in the code!

ymaps.js:
 modules.define( 'ymaps', ['loader', 'config'], function(provide, loader, config) { loader(config.hosts.ymaps + '/2.1.4/?lang=ru-RU&load=package.full&coordorder=longlat', function() { ymaps.ready(function() { provide(ymaps); }); }); }); 

The code of the loader and config modules is not given here: the first is able to load scripts by urla, the second is just a hash of constants.

ComplexLayer.js:
 modules.define('ComplexLayer', ['inherit', 'ymaps'], function(provide, inherit, ymaps) { var ComplexLayer = inherit(ymaps.Layer, ...); provide(ComplexLayer); }); 

We do the same if, for example, we need a dependency on jQuery.
Define the jquery module:
 modules.define( 'jquery', ['loader', function(provide, loader) { loader('//yandex.st/jquery/2.1.0/jquery.min.js', function() { provide(jQuery.noConflict(true)); }); }); 

And we use the dependence on the jquery module in all other modules.

Thus, the entire code of our project is only modules, no globality, no agreement on how to connect other scripts (including third-party) or other modules, no crutches about asynchrony.

In the end, I bring api of our modular system YM (in fact, there are several more methods, only the main ones are given here):

Module declaration

 void modules.define( String moduleName, [String[] dependencies], Function( Function(Object objectToProvide) provide, [Object resolvedDependency, ...], [Object previousDeclaration] ) declarationFunction ) 

Module connection

 void modules.require( String[] dependencies, Function( [Object resolvedDependency, ...] ) callbackFunction ) 


Project repository: github.com/ymaps/modules

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


All Articles