JavaScript require / import / include modules

Require, import or include, name it as you wish but never forget the importance of modular design. This functionality enables you to divide your code into meaningful, purposeful and reusable modules enabling you to use your code as many times as you want and easing it’s maintenance. This functionality is supported in almost all modern programming languages like: C/C++, Java, C#, PHP, Ruby, Python, etc. under different names stated above, but not in only (for now) web client side programming language JavaScript. Of course we can be clever and use trick to emulate this behavior. Flexibility of this dynamic functional language, characteristic that makes it very favorable, will aid us once again to develop similar behavior like any other more complex languages.

Lets say that we have file structure like this:

  • script.js
  • ivar
    • util
      • array.js
      • string.js
    • net
      • _all.js
      • JSON.js
      • REST.js
      • XML.js

In short all we want is to in script.js do this:

require('ivar.util.string');
require('ivar.net.*');
require('ivar/util/array.js');
require('http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js');

ready(function(){
//do something when required scripts are loaded
});

We also want to include only script.js in our HTML file. To require other scripts, we have to know their names and their locations. To find the root location we have to find the URL of script.js. We will remember it as the root url and then use the supplied namespaces, for example ‘ivar.util.string‘ and transform them into ‘<url of script.js>/ivar/util/string.js‘. Now we have to append to html head dom element scripts with given locations. We will put a loading stack so scripts can be loaded asynchronously. Before loading a script we have to check is the script currently loading or is it already loaded. When all of the required scripts are loaded we will fire the functions stacked with ready method. If we want to include all of the scripts in a directory, we have to have _all.js that requires all scripts in that directory. By requiring ‘ivar.net.*‘ we really require ‘ivar.net._all.js’, that requires all scripts required inside it.

The following code does the described job:


var _rmod = _rmod || {}; //require module namespace
_rmod.LOADED = false;
_rmod.on_ready_fn_stack = [];
_rmod.libpath = '';
_rmod.imported = {};
_rmod.loading = {
scripts: {},
length: 0
};
_rmod.findScriptPath = function(script_name) {
var script_elems = document.getElementsByTagName('script');
for (var i = 0; i < script_elems.length; i++) {
if (script_elems[i].src.endsWith(script_name)) {
var href = window.location.href;
href = href.substring(0, href.lastIndexOf('/'));
var url = script_elems[i].src.substring(0, script_elems[i].length-script_name.length);
return url.substring(href.length+1, url.length);
}
}
return '';
};
_rmod.libpath = _rmod.findScriptPath('script.js'); //Path of your main script used to mark the root directory of your library, any library
_rmod.injectScript = function(script_name, uri, callback, prepare, async) {
if(!prepare)
prepare(script_name, uri);
var script_elem = document.createElement('script');
script_elem.type = 'text/javascript';
script_elem.title = script_name;
script_elem.src = uri;
if(!async)
async = false;
script_elem.async = async;
script_elem.defer = false;
if(!callback)
script_elem.onload = function() {
callback(script_name, uri);
};
document.getElementsByTagName('head')[0].appendChild(script_elem);
};
_rmod.requirePrepare = function(script_name, uri) {
_rmod.loading.scripts[script_name] = uri;
_rmod.loading.length++;
};
_rmod.requireCallback = function(script_name, uri) {
_rmod.loading.length–;
delete _rmod.loading.scripts[script_name];
_rmod.imported[script_name] = uri;
if(_rmod.loading.length == 0)
_rmod.onReady();
};
_rmod.onReady = function() {
if (!_rmod.LOADED) {
for (var i = 0; i < _rmod.on_ready_fn_stack.length; i++){
_rmod.on_ready_fn_stack[i]();
});
_rmod.LOADED = true;
}
};
_.rmod = namespaceToUri = function(script_name, url) {
var np = script_name.split('.');
if (np.getLast() === '*') {
np.pop();
np.push('_all');
} else if (np.getLast() === 'js') {
np.pop();
}
if(!url)
url = '';
script_name = np.join('.');
return url + np.join('/')+'.js';
};
//you can rename based on your liking. I chose require, but it can be called include or anything else that is easy for you to remember or write, except import because it is reserved for future use.
var require = function(script_name, async) {
var uri = '';
if (script_name.indexOf('/') > -1) {
uri = script_name;
var lastSlash = uri.lastIndexOf('/');
script_name = uri.substring(lastSlash+1, uri.length);
} else {
uri = _rmod.namespaceToUri(script_name, ivar._private.libpath);
}
if (!_rmod.loading.scripts.hasOwnProperty(script_name)
&& !_rmod.imported.hasOwnProperty(script_name)) {
_rmod.injectScript(script_name, uri,
_rmod.requireCallback,
_rmod.requirePrepare, async);
}
};
var ready = function(fn) {
_rmod.on_ready_fn_stack.push(fn);
};
// —– USAGE —–
require('ivar.util.string');
require('ivar.net.*');
require('ivar/util/array.js');
require('http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js&#39;);
ready(function(){
//do something when required scripts are loaded
});

view raw

require.js

hosted with ❤ by GitHub

I chose to name the method ‘require’ but you can rename it however you like, remember import is a reserved word for future use in JavaScript, that means at some point module import will be enabled by standard, but until then we have to fallback to methods similar as one stated above. As we can see there is no special philosophy with importing / requiring / including modules in JavaScript. Combining this require module with namespace method described here provides the ultimate system for JavaScript modular development.

I have excluded this code from a large library I am writing so if you find an error or a typo please notify me, I would be grateful. Any suggestions, similar snippets  or any other kind of feedback is welcome in the comments section. I only hope that this peace of code helped you in some way. Thank you.

15 comments

  1. Thanks for your grate article.
    I prefer to Minify all my js files into a single file when I am in production environment of a project, but while developing I just include all js files in my header which is really ugly.
    Now I can use your include module and create a file called Terminal.js and that file include Others.

    1. Hey, I’m really glad it was of some use. :D
      I updated the code to be a bit more flexible. Now you can do require(‘other_script.js’) the script that is in the same level as your basic script that is used for location detection. And you can require(‘some_script’, true); Put true if you want the script to be required asynchronously, otherwise it is required synchronously which means that scripts are included by order you required them.
      Heh, maybe it’s not some explanation, i will rewrite the post to describe the abilities of the module.
      Also I will include a small python script that combines the required scripts, all you need to do is minify them later.

      Take care dude! And if you find a bug, please let me know! :)

  2. Hi everyone, it’s my first visit at this web site, and article is genuinely fruitful in favor of me, keep up posting these posts.

  3. […] I wrote a simple module that automates the job of importing/including module scripts in JavaScript. For detailed explanation of the code, refer to the blog post JavaScript require / import / include modules. […]

  4. […] I wrote a simple module that automates the job of importing/including module scripts in JavaScript. For detailed explanation of the code, refer to the blog post JavaScript require / import / include modules. […]

  5. […] I wrote a simple module that automates the job of importing/including module scripts in JavaScript. For detailed explanation of the code, refer to the blog post JavaScript require / import / include modules. […]

  6. First, thank you for this great piece of code that’s really meaningful for client-side apps. But i have a question: does this script-loader you gave us need to be included in the HTML file anyway ? How could i avoid doing that ?

    1. Thank you friend for showing the interest for this simple code!
      Well it has to be included in HTML in order to work, it is used only for developing the application, when you deploy the application you should remove this code and compile all of the codes with let’s say closure compiler. I will maybe write a simple python script to do just that, generate the HTML header based on the imports.
      I’m not sure I understood well, so my response is based on my understanding of your question.
      If you have any other questions feel free to ask! :)

      Peace bro!

      1. Ok, after refactoring the code a little bit, i got it running. Scripts are loaded. But any code inside the ready function is not executing… :'(

      2. At last !!! Got everything running. But the code have some issues… In method injectScript the if statement that executes the callback has its condition wrong. Its if(callback), not if(!callback). After that the code is up and running. I’m really sorry for overflow the topic with my comments. It’s just that a really really wanted to see this code running, it’s brilliant ! Once more, thank you a lot for it !!!!

      3. Hey friend! Sorry I couldn’t reply and help you! I was at the traveling from Rome to Toulouse. I’m glad you figured it out at the end!

  7. Oh yeah, doing a deploy program must do the work off course ^^. But, sadly, i’m still getting an error running the loader, in this method: _rmod.findScriptPath = function(script_name). In the main if statement: if (script_elems[i].src.endsWith(script_name)). It’s complaining “undefined is not a function” (that error you never know how to solve… ¬¬), just before the ‘e’ char in ‘endsWith’. I really want to see this code work. Help me brother !!!

  8. Oh, and there’s this typo in line 72: _.rmod = namespaceToUri = function(script_name, url) {.
    And other in line 67, closing the for statement: });

  9. I realized the problem was with the endsWith method, i created it in String prototype and called it a day. But, even so, i also realized that when you do this “_rmod.libpath =_rmod.findScriptPath(‘script.js’);” to get the root, it’s getting the empty string…

  10. Ok, after refactoring the code a little bit, i got it running. Scripts are loaded. But any code inside the ready function is not executing… :'(

Leave a reply to Rafael Cancel reply