A perfectly lightweight, utterly simple, asynchronous JavaScript module library.
- Asynchronous script loading, which does not block the HTML from populating, or other scripts from running at the same time.
- Modules can reference other modules, and even circular dependancies among modules work.
- No need to install anything, or run a transpiler or any other sort of preprocesser on your code.
- Modules alow for code reuse across projects.
- Seperation of code for better encapsulation.
- Modules inside closures do not polute the global scope.
index.html
<script type = "text/javascript" src = "recycle.js"></script> <!-- Load recycle.js -->
<script type = "text/javascript" src = "main.js"></script> <!-- Code that uses recycle.js -->
recycle.js has no dependencies on other libraries, so feel free to load it first.
Use mod.prepare(obj)
which allows you to prepare many modules at once. The path for each module should be relative to the HTML file.
main.js
mod.prepare({
"utils": "modules/utils.js",
"calc": "modules/calculus.js"
});
The order that you list the modules will not effect their ability to interact with one another.
All code that uses the modules should be placed inside the mod.loadPrepared(cback)
callback, to make sure that they are loaded before they are needed.
Do not use this callback in place of a body onload if you need one, since you can never be sure which will occur first.
main.js
mod.prepare({
"utils": "modules/utils.js",
"calc": "modules/calculus.js"
});
mod.loadPrepared(function() {
//...
});
If the modules have already been loaded when the callback is set, the callback will fire immediately, so do not worry about setting it too late. Your code will still run.
Use mod.get(identifier)
or mod.require(identifier)
to grab the modules that you want to use, one at a time. There is no need to get the modules that you won't be using in this file.
main.js
mod.prepare({
"utils": "modules/utils.js",
"calc": "modules/calculus.js"
});
mod.loadPrepared(function() {
var Utils = mod.get("utils"); //You can also use mod.require (an alias to mod.get)
console.log(Utils.captializeSentence("hello, recycle.js!"));
});
Hello, recycle.js! That's all there is to it.
Do not manually place the script tags for these .js files in your HTML. recycle.js takes care of loading the modules for you.
Place your code inside a closure so as not to pollute the global scope, while providing a private interface for your module.
Consider returning an object with mod.exports
, with various methods and properties for the user to interact with.
modules/utils.js
mod.exports = (function() {
var privateValue = "hunter2"; //Variables and functions inside the closure are private, and will not be exported unless returned.
return { //Variables and functions specifically returned from the closure are public.
captializeSentence: function(str) {
//...
},
changePrivate: function(new) {
privateValue = new; //Private values from a closure can be accessed and modified by public functions.
},
publicValue = "*******",
//...
}
})();
mod.prepare
returns a reference to mod, allowing for chaining from mod.prepare
.
main.js
mod.prepare({
EXCLAMER: "modules/exclamer.js",
ADDER: "modules/adder.js",
DEPEND: "modules/depend.js"
})
.loadPrepared(function() {
//...
});
You can even group sets of related module preperations.
main.js
mod.prepare({ //Algorithms
"search": "modules/searching.js",
"sort": "modules/sorting.js"
})
.prepare({ //Utilities
"formats": "modules/stringutils.js"
});
.prepare({ //Math
"calc": "modules/calculus.js",
"trig": "modules/trigfuncs.js"
})
.loadPrepared(function() {
//...
});
If you prefer, you can import one module at a time. (Possible remove this functionality?)(WIP)
main.js
mod.prepare(path, identifier); //Note that in this form, the path comes first.
.prepare(path2, identifier2);
You can leave out the quotes when creating identifiers for modules using the object form of mod.prepare
, and when getting the modules from those identifiers.
recycle.js will reserve the identifiers as global variables, so that JavaScript does not generate a ReferenceError.
mod.prepare({
BANK: "modules/banking.js",
ACCOUNT: "modules/bankaccount.js"
EMPLOYEE: "modules/worker.js",
})
.loadPrepared(function() {
var Bank = mod.get(BANK); /*We never declared the global BANK, but recycle.js saw that we used BANK as an identifier for a module, so it took the liberty for us.*/
var Acc = mod.get(ACCOUNT);
var Emp = mod.get(EMPLOYEE);
});
Caveat: This polutes the global namespace with reserved identifiers.
Make sure that you do not try to use a global variable that already exists, for example, alert
(recycle.js will throw in error instead of hijacking the identifier).
To be safe, it is good practice to use all caps (and underscores) for these identifiers, to keep them seperate.
Another good option is to suffix each identifier with 'Mod', (e.g. bankMod).
Consider the following example, with two modules. One represents a bank, and the other is a helper module which manages bank savings accounts.
main.js
mod.prepare({
BANK: "modules/banking.js",
ACCOUNT_MANAGER: "modules/accounts.js"
})
.loadPrepared(function() {
var Bank = mod.get(BANK);
var savingsAccount = Bank.setupAccount("Jones");
console.log(savingsAccount.customerName + ", " + savingsAccount.bankName);
});
The main file grabs the BANK
module and tells it to create a new account.
To do this, the Bank must itself get the new account object from the ACCOUNT_MANAGER
module.
Notice how in banking.js
the Accounts
variable is instantiated immediately, but Bank only attempts to get ACCOUNT_MANAGER
inside the loadPrepared callback. This is to ensure that the module is ready.
banking.js
mod.exports = (function() {
var Bank = {};
var Accounts;
mod.loadPrepared(function() {
Accounts = mod.get(ACCOUNT_MANAGER);
});
Bank.bankName = "United Bank of Iceland";
Bank.setupAccount = function(name) {
return Accounts.createAccount(name);
};
Bank.changeName = function(newName) {
Bank.bankName = newName;
};
return Bank;
})();
To finish creating the account, the ACCOUNT_MANAGER
module needs to ask for the name of the Bank.
To do this, it instantiates the Bank
variable, and then gets the BANK
module inside the loadPrepared callback. This is a circular dependancy, because BANK
uses ACCOUNT_MANAGER
, and ACCOUNT_MANAGER
uses BANK
.
In recycle.js, circular dependancies are legal, and very useful!
accounts.js
mod.exports = (function() {
var Accounts = {};
var Bank;
var bankName;
mod.loadPrepared(function() {
Bank = mod.get(BANK);
bankName = Bank.bankName;
});
Accounts.createAccount = function(name) {
var savingsAccount = {};
savingsAccount.customerName = name;
savingsAccount.bankName = bankName;
return savingsAccount;
};
return Accounts;
})();
Because the functions setupAccount
and createAccount
are called by main.js
inside a loadPrepared
callback, they themselves are not placed inside such a callback in their own module files. Those functions will have access to all module dependancies by the time they run. This is because the module's loadPrepared
callbacks happen before the loadPrepared callback in main.js
. The circular dependancy works!
However, you have no control over which module's loadPrepared
callback will happen first. In general, this is no problem. The account will be created successfully no matter the order that the two callbacks happen in.
But keep in mind that if modules initialize themselves in the loadPrepared
callback based on the values of other modules which do the same thing, you could run into problems.
The remedy is simple. Provide initialization functions for the private interfaces of modules whose initialization relies on other modules, so that you can choose the order to execute them in.
In this case, we should grab the bank's name inside initialize
, which will be called from main.js
before the ACOUNT_MANAGER
module is used:
accounts.js
mod.exports = (function() {
var Accounts = {};
var Bank;
var bankName;
mod.loadPrepared(function() {
Bank = mod.get(BANK);
});
Accounts.initialize = function() {
bankName = Bank.bankName;
}
Accounts.createAccount = function(name) {
var savingsAccount = {};
savingsAccount.customerName = name;
savingsAccount.bankName = bankName;
return savingsAccount;
};
return Accounts;
})();
Actually, in our case, we can simply have the createAccount
function dynamically grab the bankName when needed, and eliminate the initialize
function altogether. This has the bonus feature that if the Bank's name is changed later, all new accounts will be created using the new name:
accounts.js
mod.exports = (function() {
var Accounts = {};
var Bank;
var bankName;
mod.loadPrepared(function() {
Bank = mod.get(BANK);
});
Accounts.createAccount = function(name) {
var savingsAccount = {};
savingsAccount.customerName = name;
savingsAccount.bankName = Bank.bankName;
return savingsAccount;
};
return Accounts;
})();
Modules that depend on other modules can be very powerful indeed. However, keep in mind that the more coupled your modules are, the harder it will be to reuse them in future projects. Try to only implement coupling when necessary.