⬆️ ⬇️

AJL - component for loading JS and CSS files with JavaScript

Hi, Habr !



Recent situations have pushed me to look for a simple and small in size resource loader. But all my searches led to require.js, which for some reason does not suit me (this is a topic for a separate article ).



Therefore, it was decided to write my bike and practice at the same time.

As a result, a component was implemented that occupies 6.28 Kb in uglify 'and 1.3 Kb in GZip.

')

His key "chips":





Here, in fact, a description of its main features.



Under the cut a short course on the use of AJL and a description of the development of some components.





We connect AJL

All you need to specify when connecting AJL is the data-ep attribute. Here is an example (we include AJL in the base layout):

//baseLayout.html <script src="js/vendor/AJL.min.js" data-ep="EntryPoint.js"></script> 


After loading the AJL, it loads EntryPoint.js, in which you configure the packages.

It is possible and not to prescribe data-ep. You have the right to decide where it is more convenient for you to customize AJL.

EntryPoint.js is essentially a script that, when loaded, is also executed.



We set the packages

Consider an example of connecting jQuery, its plugins and your scripts. In this case, the data-ep attribute was used.

 //EntryPoint.js AJL({ name: "jQuery", //  assets: ['js/vendor/jquery.min.js'], //  URL'    config: { //  async: false //   } }, { name: "jQuery Plugins", assets: ['js/vendor/jquery.plugin.js', 'js/vendor/jquery.plugin2.js'], config: { depend: ['jQuery'] //  ,       } }, { name: "My Scripts", assets: ['js/foo.js', 'js/bar.js'], config: { lazy: true //      window.onload } }).loadAll(); //  ,   PackageManager.  chainloading       


EntryPoint looks pretty clean. In name specify the name of the package. In assets , an array with asset URLs. In config , an object with parameters. You can pass six parameters in the config :



Honestly, I still do not understand why I brought attributes for tags to an object with parameters.



What happens in EntryPoint.js?



1. Three packages are created with the names jQuery, jQuery Plugins, My Scripts. After successful creation, AJL will return a PackageManager in which the loadAll () method exists. This method loads packages. All packages are loaded by iterating through the array and calling load (). An important factor that may affect the download, the download occurs in the order of the order of creation of packages. Therefore, it is better to specify the “serious” libraries first, and only then the rest.



2. The first loaded jQuery is not in asynchronous mode. After that, the jQuery Plugins download starts, but only after the jQuery download. And finally - My Scripts - after downloading all the resources on the page (Lazy Loading).



Thus, you can make different configurations with packages. Suppose we have two totally different pages. On one, the control panel for the user, and on the second, a cool editor with bundles of scripts. The scripts and styles that need to be loaded on these pages are completely different from each other. A lot of scripts. Do not load everything in a pile, what you need and do not need. Create two different basic layouts? What for?



With AJL, a solution to this situation can be proposed in a similar way.



We have an EntryPoint in which we create all the necessary packages:

  AJL({ name: "jQuery", assets: ['js/vendor/jquery.min.js'], config: { async: false } }, { name: "jQuery Plugins", assets: ['js/vendor/jquery.plugin.js', 'js/vendor/jquery.plugin2.js'], config: { depend: ['jQuery'] } }, { name: "Editor Scripts And Styles", assets: ['js/editor/foo.js', 'js/editor/bar.js', 'css/editor/style.css'], config: { depend: ['jQuery Plugins'] } }, { name: "My Dashboard Scripts", assets: ['js/foo.js', 'js/bar.js'], config: { lazy: true } }); 


Pay attention to loadAll (). He is not here. Since we want to completely delimit the loading of packages, we do not call loadAll (). We just create them. And since we have views for the editor and the statistics panel, in them we can trigger the download of the required package manually.

 //dashboard.html <script>AJL("My Dashboard Scripts").load();</script> //editor.html <script>AJL("Editor Scripts And Styles").load();</script> 


Please note that we only download a package called Editor Scripts And Styles. Since dependency resolution is done here by the recursive method, we can call the last link and that's it. And it, in turn, will load jQuery Plugins, and there and jQuery.



Thus, you can build a chain of packages and download only those that are really necessary for work.



What happens under the "hood"?



And now let's get down to how some AJL modules were developed.



Namespace

The most interesting, I think, is the implementation of namespaces in 17 lines of code. Everything is simple and the essence lies in splitting the namespace'a into array elements and its iteration. When we reach the last element, we assign a module to this element.

I give the code of the function that is called when creating the namespace.

 setNamespace: function (namespace, module) { var parts = namespace.split('.'), parent = window, partsLength, curPart, i; //Need iterate all parts of namespace without last one partsLength = parts.length - 1; for (i = 0; i < partsLength; i++) { //Remember current part curPart = parts[i]; if (typeof parent[curPart] === 'undefined') { //If this part undefined then create empty parent[curPart] = {}; } //Remember created part in parent parent = parent[curPart]; } //And last one of parts need to be filled by module param parent[parts[partsLength]] = module; //And not forgot return generated namespace to global scope return parent; }, 




As a result, when developing your modules, you can use a rather simple construction:

 AJL("Module.SubModule", function() { return "Hi, I'm Module.SubModule"; }); 




Package, PackageConfig, Loader

Packages and package configuration are only functions with a prototype (class, in one word). All that I store in their properties is the name of the package, the array of URLs, the instance of the package configuration. Package's load () method itself calls the static loadPackage () function from Loader.js using call ().

  load: function () { AJL.Loader.loadPackage.call(this); } 




This is done in order to protect yourself from bad code duplication. Packages are different, configurations are different, but there must be one loader. That's actually Loader.js and loadPackage () make decisions when you can add a tag to the DOM, and when not.

 loadPackage: function () { var helper = AJL.Helper, packageManager = AJL.PackageManager, pack = this, packageAssets = pack.getAssets(), packageConfig = pack.getConfig(), depend = packageConfig.getItem('depend'); //If assets array empty then halt loading of package if (helper.isEmpty(packageAssets)) { return false; } //If this package depend on other packages then load dependencies first if (!helper.isEmpty(depend)) { packageManager.loadByNames(depend); } //If need to wait window.load than call lazyLoad and return if (packageConfig.getItem('lazy') == true) { lazyLoad.call(pack); return true; } //In other cases just call startLoading directly for start loading startLoading.call(pack); return true; }, 




Pay attention to how dependency loading is implemented. If there are dependencies in this package, then we call recursive loading. Ship the dependencies, and so on, up the chain.



Packagemanager

Also an important part of AJL is PackageManager, which manages packages. Such a collector. There are getters, there are setters that check the requested name in the package instance array, and whether the object is a package instance. If so, then return it, or make the actions we need with it. For example, the considered loadAll () function acts as a normal search.

 loadAll: function () { var helper = AJL.Helper, curPack; for (var pack in packages) { if (packages.hasOwnProperty(pack)) { curPack = packages[pack]; if (helper.isInstanceOf(curPack, AJL.Package)) { curPack.load(); } } } return this; }, 


There is a search in the array, and if it is an instanse package, then we call load (). When loading dependent packages, I use the loadByNames () function.

 loadByNames: function (names) { var helper = AJL.Helper, curName, namesLength, i; namesLength = names.length; for (i = 0; i < namesLength; i++) { curName = names[i]; if (packages.hasOwnProperty(curName) && helper.isInstanceOf(packages[curName], AJL.Package)) { packages[curName].load(); } } return this; } 


We iterate over the entire array with the names and see if these names are in our storage packages. If yes and it is an instance of the Package, then we call load ().



Ajl

And finally, the most important thing. AJL function ().

 AJL = function () { var packageManager = AJL.PackageManager, namespace = AJL.Namespace, helper = AJL.Helper, packageInstance = {}, packageName = '', packageAssets = [], packageConfig = {}, argLength = arguments.length, argFirst, argSecond, i; //Switch of arguments length for detect what need to do switch (argLength) { case 0: //If arguments not exists then just return PackageManager instance return packageManager; case 1: argFirst = arguments[0]; //If this arg is string then return package with this name if (helper.isString(argFirst)) { return packageManager.getPackage(argFirst); } break; case 2: argFirst = arguments[0]; argSecond = arguments[1]; //If first arg is string and second object or function if (helper.isString(argFirst) && (helper.isObject(argSecond) || helper.isFunction(argSecond))) { //Then I think that it's namespace setting namespace.setNamespace(argFirst, argSecond); return packageManager; } break; default: break; } //If all predefined templates in arguments didn't decided then create packages from them for (i = 0; i < argLength; i++) { if (!helper.isUndefined(arguments[i])) { packageName = arguments[i].name; packageAssets = arguments[i].assets; packageConfig = arguments[i].config; packageInstance = new AJL.Package(packageName, packageAssets, packageConfig); packageManager.setPackage(packageInstance); } } return packageManager; }; 


First we look at the number of arguments passed to the function. If nothing is transmitted, then PackageManager returns immediately. If we passed one line, we assume that we have been given the name of the package and are looking for it. After the find, we return the Package object. If two parameters were passed and the first of them was a string, then we throw the second parameter into this namespace (from the first parameter). And finally, if none came up, then we believe that this is the starting configuration for AJL and create all the packages in it.



I thank everyone who has found the strength to read this far. If you want to try AJL, then there are all the necessary resources:



Home page

Sources on GitHub

Documentation



PS I would appreciate all the wishes and criticism. I am not going to throw development. Just run out of ideas :)

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



All Articles