const moduleInitalisers = new Map;
const moduleDirectory = new Map;
function define(moduleID, dependancies, moduleInitialiser) {
if (moduleInitalisers.has(moduleID))
throw new Error(`"${moduleID}" has already been defined`);
moduleInitalisers.set(moduleID, [dependancies, moduleInitialiser]);
}
function require(moduleID) {
function getModule(moduleID, parents) {
if (moduleDirectory.has(moduleID))
return moduleDirectory.get(moduleID);
const isCircular = parents.includes(moduleID);
parents.push(moduleID);
if (isCircular)
throw new Error(`Circular dependancy ${parents.join(" -> ")}`);
const [dependancies, initialiser] = moduleInitalisers.get(moduleID);
const mod = initialiser(...dependancies.map(m => getModule(m, parents.slice(0))));
moduleDirectory.set(moduleID, mod);
return mod;
}
return getModule(moduleID, []);
}
This is a very simple module loader. For each module you have you call "define" with a unique module name, a list of modules it depends on, and an initialiser function that returns the module.
To load a module you call "require" with the module name. It will load all the modules that it depends on, then calls the initialiser with those modules as arguments. The module is then returned.
Example use:
define("Lemon", ["Fruit"], Fruit => {
return class Lemon extends Fruit {};
})
define("Strawberry", ["Fruit"], Fruit => {
return class Strawberry extends Fruit {};
})
define("Peach", ["Fruit"], Fruit => {
return class Peach extends Fruit {};
})
define("Fruit", [], () => {
return class Fruit {};
})
define("FruitSalad", ["Lemon", "Strawberry", "Peach"], (Lemon, Strawberry, Peach) => {
return class FruitSalad {
constructor () {
this.contains = [
new Lemon,
new Strawberry,
new Peach
];
}
}
})
const FruitSalad = require("FruitSalad");
const mysalad = new FruitSalad;
There's a few minor restrictions you need to be aware of though. The module loader itself has to be loaded BEFORE anything else, the require call has to be AFTER all the needed modules have been defined and you cannot do circular dependencies.
The initialiser for a module is only ever called once, so this probably works best if you define the loader and the modules in script files. Then "require" the modules from inline script blocks as needed.
If you need it then it is possible to write module loaders that support circular dependencies but they add additional restrictions that are somewhat awkward.