Initial commit
This commit is contained in:
152
README.md
Normal file
152
README.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# MODULAR
|
||||
Path abstraction and mocks for NodeJS.
|
||||
|
||||
## Functionalities
|
||||
* Automatically load javascript modules
|
||||
* Abstract modules path. Access any module using namespace and module name
|
||||
* Easy mock
|
||||
* Live reload of changes
|
||||
|
||||
## Quickstart
|
||||
```javascript
|
||||
var Modular = require('modular.js');
|
||||
|
||||
var application = new Modular({
|
||||
files : './app',
|
||||
liveReload : true,
|
||||
restartOnChanges : true,
|
||||
}).on ('start', function(modular) {
|
||||
modular('main').start();
|
||||
}).on ('stop', function(modular) {
|
||||
modular('main').stop();
|
||||
}).on ('log', function (modular, level, message, error) {
|
||||
console.log(error);
|
||||
}).start();
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
The modular constructor accept several settings :
|
||||
|
||||
```javascript
|
||||
{
|
||||
/* files can be a string for one file or folder */
|
||||
files : 'app/module.js',
|
||||
/* or an array to load several files or folders */
|
||||
files : [ 'app/module.js', 'app/module2.js', 'app/subfolder' ],
|
||||
/* an object allowing to setup the way the modules are loaded */
|
||||
files : {
|
||||
/* the path is either a string for one file or folder */
|
||||
path : 'app/module.js',
|
||||
/* or an array */
|
||||
path : [ 'app/module.js', 'app/module2.js', 'app/subfolder' ]
|
||||
override : true, /* modules override already loaded modules in case of collision
|
||||
default value : false */
|
||||
namespace : 'namespace', /* namespace is appended before the deduced namespace */
|
||||
basePath : 'path/path2', /* the base path used to deduce the namespace of modules
|
||||
default value : path value (when path is a directory) */
|
||||
fullName : 'namespace.name',/* specify the full name of the module
|
||||
Only when path is one file. Doesn't work with namespace setting */
|
||||
liveReload : true, /* enable or disable liveReload on a file/folder basis
|
||||
default value : false */
|
||||
},
|
||||
basePath : /* Path used to deduce the namespace
|
||||
default value : path of the file that load Modular */
|
||||
liveReload : true, /* enable live modification reload for whole application
|
||||
default value : false */
|
||||
restartOnChange : true, /* automaticaly restart application when a module is reloaded
|
||||
default value : liveReload value */
|
||||
exclude : ['app/module3.js'], /* those files won't be loaded */
|
||||
default value : file that load Modular
|
||||
logLevel : 0 /* level of logging. 0 : error, 1 : info, 2 : verbose
|
||||
default value : 0 */
|
||||
```
|
||||
|
||||
## Events
|
||||
Modular communicate through messages. Those event aren't asynchronous.
|
||||
This way, modular won't try to start application before it stopped.
|
||||
If start or stop event return a Promise object, modular will wait for the Promise to be fulfilled to continue.
|
||||
|
||||
### start
|
||||
Emitted when the application must be started.
|
||||
* Parameter : modular
|
||||
* Return : Promise (optional)
|
||||
|
||||
### stop
|
||||
Emitted when the application must be stopped.
|
||||
This event is optional
|
||||
* Parameter : modular
|
||||
* Return : Promise (optional)
|
||||
|
||||
### restart
|
||||
Emitted when the application must be restarted.
|
||||
This event is optional
|
||||
If this event is not used, modular will emit the stop event followed by start
|
||||
* Parameter : modular
|
||||
* Return : Promise (optional)
|
||||
|
||||
### log
|
||||
Emitted whenever log occurs.
|
||||
This event is optional.
|
||||
If this event is not defined, logs are written on stdout.
|
||||
* Parameter : modular, message, error
|
||||
* Return : nothing
|
||||
|
||||
### loadModule
|
||||
Emitted whenever a module will be loaded.
|
||||
This event is optional.
|
||||
You can change the settings used to load the module.
|
||||
* Parameter : modular, settings (module parameters)
|
||||
* Return : false to cancel module loading
|
||||
|
||||
## Modules
|
||||
Modular modules must look likes
|
||||
```javascript
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
var mod = modular('namespace.module');
|
||||
}
|
||||
```
|
||||
Or
|
||||
```javascript
|
||||
'use strict';
|
||||
|
||||
module.exports.onLoad = function (modular) {
|
||||
var mod = modular('namespace.module');
|
||||
}
|
||||
```
|
||||
|
||||
Modular will simply call those functions each time the module is required by another.
|
||||
Those function are never invoked using the new statement and are called even if the nodejs module is already cached.
|
||||
This means that each module manage its instance (a module can act as a singleton for example, another can always return a new instance).
|
||||
|
||||
If the module does not directly exports a function or does not export onLoad function, the module is return as it is, but won't be able to load any modular module.
|
||||
|
||||
## Tips
|
||||
|
||||
### Exclude your main javascript file
|
||||
Loading the main javascript file with Modular is useless, watching it for changes can lead to weird behavior.
|
||||
If no file is excluded, Modular will exclude its parent by default.
|
||||
|
||||
You can still load the directory containing your main file by excluding it. The easiest way is to use the node __filename property :
|
||||
```javascript
|
||||
var Modular = require('modular.js');
|
||||
|
||||
var application = new Modular({
|
||||
files : './',
|
||||
exclude : [ __filename],
|
||||
liveReload : true,
|
||||
restartOnChange : true,
|
||||
}).on ('start', function(modular) {
|
||||
modular('main').start();
|
||||
}).on ('stop', function(modular) {
|
||||
modular('main').stop();
|
||||
}).on ('log', function (modular, level, message, err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
}).start();
|
||||
```
|
||||
|
||||
###
|
||||
538
lib/modular.js
Normal file
538
lib/modular.js
Normal file
@@ -0,0 +1,538 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
var SyncEventEmitter = require(path.join(__dirname, 'synceventemitter.js'));
|
||||
|
||||
function Modular (options) {
|
||||
SyncEventEmitter.call(this);
|
||||
|
||||
var settings;
|
||||
var modules = new Map(); //Map fullName => Module
|
||||
var instance = this;
|
||||
var watchers;
|
||||
var watchedFiles;
|
||||
var autoloadModules = [];
|
||||
|
||||
process.chdir(path.dirname(module.parent.filename));
|
||||
|
||||
//Default options if not provided
|
||||
settings = options || {};
|
||||
|
||||
watchedFiles = new Map();
|
||||
watchers = new Map();
|
||||
settings.restartOnChange = settings.restartOnChange === true;
|
||||
|
||||
if (settings.files === undefined) {
|
||||
settings.files = [];
|
||||
}
|
||||
|
||||
if (settings.exclude === undefined) {
|
||||
settings.exclude = [module.parent.filename];
|
||||
}
|
||||
|
||||
if (settings.basePath === undefined) {
|
||||
settings.basePath = path.dirname(module.parent.filename);
|
||||
}
|
||||
|
||||
if (settings.logLevel === undefined) {
|
||||
settings.logLevel = 0;
|
||||
}
|
||||
|
||||
/* process.on('uncaughtException', function (err) {
|
||||
log ('error', 'Unhandled exception', err);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', function(reason, p) {
|
||||
log('error', 'Unhandled rejection at: Promise ', p, ' reason: ', reason);
|
||||
});
|
||||
*/
|
||||
|
||||
var applicationLoaded = false;
|
||||
|
||||
//Start the application
|
||||
this.start = function () {
|
||||
//Load application files
|
||||
if (!applicationLoaded && settings.files && settings.files !== undefined) {
|
||||
if (!Array.isArray(settings.files)) {
|
||||
settings.files = [ settings.files ];
|
||||
}
|
||||
|
||||
for (let file of settings.files) {
|
||||
if (file instanceof Object) {
|
||||
load (file);
|
||||
}
|
||||
else {
|
||||
load ({ path : file });
|
||||
}
|
||||
}
|
||||
|
||||
applicationLoaded = true;
|
||||
}
|
||||
|
||||
try {
|
||||
//Load modules tagged as autoload
|
||||
for (var mod of autoloadModules) {
|
||||
loadModule(mod);
|
||||
}
|
||||
|
||||
autoloadModules = [];
|
||||
return instance.emit('start', get.bind(undefined, undefined));
|
||||
}
|
||||
catch (exception) {
|
||||
log ('error', exception);
|
||||
}
|
||||
};
|
||||
|
||||
//Stop the application
|
||||
this.stop = function () {
|
||||
return instance.emit('stop', get.bind(undefined, undefined));
|
||||
};
|
||||
|
||||
//Restart the application
|
||||
this.restart = function () {
|
||||
if (instance.hasListener('restart')) {
|
||||
return instance.emit('restart', get.bind(undefined, undefined));
|
||||
}
|
||||
else if (instance.hasListener('stop')){
|
||||
|
||||
var promise = instance.stop();
|
||||
|
||||
if (promise && promise.then) {
|
||||
promise.then(function() {
|
||||
instance.start();
|
||||
});
|
||||
}
|
||||
else {
|
||||
instance.start();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//Log
|
||||
function log (level, message) {
|
||||
if (instance.hasListener('log')) {
|
||||
instance.emit('log', get.bind(undefined, undefined), message);
|
||||
}
|
||||
else {
|
||||
console.log('['+level+']', message);
|
||||
|
||||
if (level === 'error') {
|
||||
if (message instanceof Error) {
|
||||
throw message;
|
||||
}
|
||||
else if (message) {
|
||||
throw new Error(message);
|
||||
}
|
||||
else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Function passed to every modular module
|
||||
function get (caller, name) {
|
||||
var mod;
|
||||
|
||||
//Search for the module in caller namespace first
|
||||
if (caller && modules.has (caller.namespace && caller.namespace.length > 0
|
||||
? caller.namespace + '.' + name
|
||||
: name)) {
|
||||
mod = modules.get (
|
||||
caller.namespace && caller.namespace.length > 0
|
||||
? caller.namespace + '.' + name
|
||||
: name);
|
||||
}
|
||||
else if (modules.has(name)) {
|
||||
mod = modules.get(name);
|
||||
}
|
||||
|
||||
//If the module as been found
|
||||
if (mod) {
|
||||
if (caller) {
|
||||
if (mod.childrenOf === undefined) {
|
||||
mod.childrenOf = [];
|
||||
}
|
||||
|
||||
if (mod.childrenOf.indexOf(caller) < 0) {
|
||||
mod.childrenOf.push(caller);
|
||||
}
|
||||
|
||||
if (caller.parentOf === undefined) {
|
||||
caller.parentOf = [];
|
||||
}
|
||||
|
||||
if (caller.parentOf.indexOf(mod) < 0) {
|
||||
caller.parentOf.push(mod);
|
||||
}
|
||||
}
|
||||
|
||||
return loadModule(mod);
|
||||
}
|
||||
else {
|
||||
log ('error', 'Unresolved dependency ' + name);
|
||||
}
|
||||
}
|
||||
|
||||
function loadModule (mod) {
|
||||
if (mod.module === undefined) {
|
||||
mod.module = require(mod.fullPath);
|
||||
}
|
||||
|
||||
if (mod.module instanceof Function) {
|
||||
let instanceModule = mod.module(get.bind(undefined,mod));
|
||||
return instanceModule ? instanceModule : mod.module;
|
||||
}
|
||||
else if (mod.module.onLoad instanceof Function){
|
||||
let instanceModule = mod.module.onLoad(get.bind(undefined, mod));
|
||||
return instanceModule ? instanceModule : mod.module;
|
||||
}
|
||||
else {
|
||||
return mod.module;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Register a new module
|
||||
//file : path of the module
|
||||
//name (optional) : override the full name of the module (namespace and name)
|
||||
//override (optional) : if a module with the same full name is already registered, it's overriden
|
||||
function register (options) {
|
||||
var autoload = false;
|
||||
var opts = options;
|
||||
|
||||
//Default options
|
||||
if (opts.override === undefined) {
|
||||
opts.override = false;
|
||||
}
|
||||
|
||||
if (opts.liveReload === undefined) {
|
||||
opts.liveReload = settings.liveReload;
|
||||
}
|
||||
|
||||
opts.fullPath = path.isAbsolute(opts.path) ? opts.path : path.resolve(opts.path);
|
||||
|
||||
//If the file is excluded
|
||||
if (settings.exclude.indexOf(opts.fullPath) >= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.fullPath.endsWith('autoload.js')) {
|
||||
autoload=true;
|
||||
}
|
||||
|
||||
//Name parameter override module namespace and module name
|
||||
if (opts.namespace === undefined && opts.name === undefined) {
|
||||
//Every folder of the module path is a part of the namespace
|
||||
opts.namespace = path.relative((opts.basePath || settings.basePath) , path.dirname(opts.fullPath))
|
||||
.replace(/\./g,'')
|
||||
.replace(/\//g,'.')
|
||||
.replace(/^\.*/g,'');
|
||||
|
||||
//Name is the basename of the module
|
||||
opts.name = path.basename(opts.fullPath, autoload ? '.autoload.js' : path.extname(opts.fullPath));
|
||||
|
||||
//Replace all . char in class name by _
|
||||
if (opts.name.indexOf('.') >= 0) {
|
||||
opts.name = opts.name.replace(/\./g,'_');
|
||||
}
|
||||
|
||||
//Adding the base namespace
|
||||
if (opts.baseNamespace !== undefined) {
|
||||
opts.namespace = opts.baseNamespace.replace('/\./g','_') + '.' + opts.namespace;
|
||||
}
|
||||
}
|
||||
|
||||
//Calling the module loading hook
|
||||
if (instance.hasListener('loadModule') && instance.emit('loadModule', opts) === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fullName = opts.namespace && opts.namespace.length > 0 ? opts.namespace + '.' + opts.name : opts.name;
|
||||
|
||||
var alreadyLoaded = modules.has(fullName);
|
||||
|
||||
if (!alreadyLoaded || opts.override) {
|
||||
if (alreadyLoaded && opts.fullPath !== modules.get(fullName).fullPath) {
|
||||
//Delete overriden module cache
|
||||
delete require.cache[modules.get(fullName).fullPath];
|
||||
}
|
||||
|
||||
if (settings.logLevel > 0) {
|
||||
log ('info', (fullName + ' loaded from ' + opts.fullPath));
|
||||
}
|
||||
|
||||
opts.module = undefined;
|
||||
|
||||
//Clear the cache of the module if exists
|
||||
if (require.cache[opts.fullPath]) {
|
||||
delete require.cache[opts.fullPath];
|
||||
}
|
||||
|
||||
//Autoload the module
|
||||
if (autoload) {
|
||||
autoloadModules.push(opts);
|
||||
}
|
||||
|
||||
modules.set(fullName, opts);
|
||||
|
||||
if (settings.liveReload || opts.liveReload) {
|
||||
watchedFiles.set(opts.fullPath, opts);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (settings.logLevel > 0) {
|
||||
log ('info', fullName + ' skipped from ' + opts.fullPath);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//Load all the modules and submodules from a specified path
|
||||
function load (options) {
|
||||
if (!Array.isArray(options.path)) {
|
||||
options.path = [options.path];
|
||||
}
|
||||
|
||||
for (var file of options.path) {
|
||||
var opts = clone(options);
|
||||
var fullPath;
|
||||
|
||||
opts.path = file;
|
||||
|
||||
if (!path.isAbsolute(file)) {
|
||||
fullPath = path.resolve(file);
|
||||
}
|
||||
else {
|
||||
fullPath = file;
|
||||
}
|
||||
|
||||
var stats = fs.statSync(fullPath);
|
||||
|
||||
if (stats.isFile()) {
|
||||
if (settings.liveReload || options.liveReload) {
|
||||
watch (fullPath);
|
||||
}
|
||||
|
||||
register(opts);
|
||||
}
|
||||
else if (stats.isDirectory()) {
|
||||
loadFolder(fullPath, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Load files from a path.
|
||||
//Since it's only used before the application start, file operations are sync
|
||||
function loadFolder (filePath, options) {
|
||||
if (settings.liveReload || options.liveReload) {
|
||||
watch(filePath);
|
||||
}
|
||||
|
||||
var files = fs.readdirSync(filePath);
|
||||
for (var file of files) {
|
||||
var fullPath = path.join(filePath, file);
|
||||
var stats = fs.statSync(fullPath);
|
||||
|
||||
if (stats.isFile && path.extname(fullPath) === '.js') {
|
||||
let opts = clone(options);
|
||||
opts.path = fullPath;
|
||||
|
||||
register(opts);
|
||||
}
|
||||
else if (stats.isDirectory()) {
|
||||
loadFolder(fullPath, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Watch a folder for changes
|
||||
function watch(watchedPath) {
|
||||
var reloadTimeout;
|
||||
var modifiedFiles = [];
|
||||
|
||||
//Search for module already watched in this directory
|
||||
var itWatch = watchers.keys();
|
||||
var cur = itWatch.next();
|
||||
|
||||
var fileStats = fs.statSync(watchedPath);
|
||||
var isFile = true;
|
||||
|
||||
if (watchers.has(watchedPath)) {
|
||||
//Path is already watched
|
||||
return;
|
||||
}
|
||||
else if (fileStats.isDirectory()) {
|
||||
//Close all watchers aiming files in the directory
|
||||
while (cur && cur.value) {
|
||||
if (path.extname(cur.value) === '.js'
|
||||
&& path.dirname(cur.value) === watchedPath) {
|
||||
var watchedFile = watchers.get(cur.value);
|
||||
watchedFile.close();
|
||||
watchers.delete(cur.value);
|
||||
}
|
||||
cur = itWatch.next();
|
||||
}
|
||||
isFile = false;
|
||||
}
|
||||
else if (fileStats.isFile()){
|
||||
//Target is a file in a directory already watched
|
||||
var directory = path.dirname(watchedPath);
|
||||
if (watchers.has(directory)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Watcher is used to detect file changes
|
||||
var watcher = fs.watch(watchedPath, function (ev, fileName) {
|
||||
var fullName = isFile ? watchedPath : path.join(watchedPath, fileName);
|
||||
|
||||
//Some editors like vim will actually rename the file, then modify it.
|
||||
//If temporary file is used, the last event can be on a file that does not exists any more
|
||||
//We listen for all the events until 1 second occurs without anything. Then we treat all modified files
|
||||
if (modifiedFiles.indexOf(fullName) < 0) {
|
||||
modifiedFiles.push(fullName);
|
||||
}
|
||||
|
||||
if (reloadTimeout) {
|
||||
clearTimeout(reloadTimeout);
|
||||
}
|
||||
|
||||
reloadTimeout = setTimeout(function() {
|
||||
reloadTimeout = undefined;
|
||||
var stats;
|
||||
|
||||
for (let file of modifiedFiles) {
|
||||
let exists = true;
|
||||
|
||||
//Check if the file exists
|
||||
try {
|
||||
stats = fs.statSync(file);
|
||||
}
|
||||
catch (err) {
|
||||
exists = false;
|
||||
}
|
||||
|
||||
var isModule = path.extname(file) === '.js';
|
||||
var module = isModule ? watchedFiles.get(file) : null;
|
||||
var fileWatcher = watchers.has(file) ? watchers.get(file) : null;
|
||||
var needRestart = false;
|
||||
var clearParentsCache = false;
|
||||
|
||||
if (exists) {
|
||||
if (stats.isFile() && isModule) {
|
||||
//This is a module
|
||||
try {
|
||||
if (module !== null) {
|
||||
if (!module.override) {
|
||||
module.override = true;
|
||||
}
|
||||
clearParentsCache = true;
|
||||
}
|
||||
register(module);
|
||||
needRestart = settings.restartOnChange;
|
||||
}
|
||||
catch (exception) {
|
||||
if (settings.logLevel >= 0) {
|
||||
log ('error','Module '+file+ ' not reloaded : ' + exception );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (stats.isDirectory() && !watchers.has(file)) {
|
||||
watch(file);
|
||||
}
|
||||
|
||||
//Reset the watcher if needed
|
||||
if (ev === 'rename' && fileWatcher !== null) {
|
||||
fileWatcher.close();
|
||||
watchers.delete(file);
|
||||
watch(file);
|
||||
}
|
||||
}
|
||||
else if (module !== null) {
|
||||
unRegister(module);
|
||||
|
||||
if (settings.logLevel > 0) {
|
||||
log ('info', module.fullName + ' unregistered');
|
||||
}
|
||||
|
||||
clearParentsCache = true;
|
||||
needRestart = settings.restartOnChange;
|
||||
}
|
||||
else if (fileWatcher !== null) {
|
||||
fileWatcher.close();
|
||||
watchers.delete(file);
|
||||
}
|
||||
|
||||
if (clearParentsCache) {
|
||||
if (module.childrenOf !== undefined) {
|
||||
for (let parentMod of module.childrenOf) {
|
||||
clearCache(parentMod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needRestart) {
|
||||
instance.restart();
|
||||
}
|
||||
}
|
||||
|
||||
modifiedFiles = [];
|
||||
|
||||
},1000);
|
||||
});
|
||||
|
||||
watchers.set(watchedPath, watcher);
|
||||
}
|
||||
|
||||
//Remove a module and clear its nodejs cache
|
||||
function unRegister (module) {
|
||||
if (modules.has(module.fullName)){
|
||||
if (module.parentOf !== undefined) {
|
||||
for (var children of module.parentOf) {
|
||||
if (children.childrenOf !== undefined && children.childrenOf.length > 0) {
|
||||
children.childrenOf.splice(children.childrenOf.indexOf(module), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modules.delete(module.fullName);
|
||||
delete require.cache[module.file];
|
||||
}
|
||||
}
|
||||
|
||||
//Clear module and all depedencies cache
|
||||
function clearCache(module) {
|
||||
if (module.childrenOf !== undefined) {
|
||||
for (let parentMod of module.chidrenOf) {
|
||||
clearCache(parentMod);
|
||||
}
|
||||
}
|
||||
module.override = true;
|
||||
|
||||
register(module);
|
||||
}
|
||||
}
|
||||
|
||||
function clone(obj) {
|
||||
var object = {};
|
||||
|
||||
for (let prop in obj) {
|
||||
if (Array.isArray(obj[prop])) {
|
||||
object[prop] = obj[prop].slice(0);
|
||||
}
|
||||
else {
|
||||
object[prop] = obj[prop];
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
Modular.prototype = new SyncEventEmitter();
|
||||
Modular.prototype.constructor = SyncEventEmitter;
|
||||
|
||||
module.exports = function (settings) {
|
||||
return new Modular(settings);
|
||||
};
|
||||
39
lib/synceventemitter.js
Normal file
39
lib/synceventemitter.js
Normal file
@@ -0,0 +1,39 @@
|
||||
'use strict';
|
||||
|
||||
function SyncEventEmitter () {
|
||||
this.listeners = new Map();
|
||||
}
|
||||
|
||||
SyncEventEmitter.prototype.on = function (ev, callback) {
|
||||
if (this.listeners.has(ev)) {
|
||||
this.listeners.remove(ev);
|
||||
}
|
||||
|
||||
this.listeners.set(ev, callback);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
SyncEventEmitter.prototype.emit = function (ev) {
|
||||
if (this.listeners.has(ev)) {
|
||||
let params = [];
|
||||
|
||||
for (var i = 1 ; i < arguments.length ; ++i) {
|
||||
params.push(arguments[i]);
|
||||
}
|
||||
|
||||
return this.listeners.get(ev).apply(undefined, params);
|
||||
}
|
||||
};
|
||||
|
||||
SyncEventEmitter.prototype.removeListener = function (ev) {
|
||||
if (this.listeners.has(ev)) {
|
||||
this.listeners.remove(ev);
|
||||
}
|
||||
};
|
||||
|
||||
SyncEventEmitter.prototype.hasListener = function (ev) {
|
||||
return this.listeners.has(ev);
|
||||
};
|
||||
|
||||
module.exports = SyncEventEmitter;
|
||||
24
package.json
Normal file
24
package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "modular",
|
||||
"version": "1.0.0",
|
||||
"description": "Module path abstraction and mock framework",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://lab.cherboiche.org/boudin/modular.git"
|
||||
},
|
||||
"keywords": [
|
||||
"module",
|
||||
"mock",
|
||||
"live",
|
||||
"reload"
|
||||
],
|
||||
"author": "boudin",
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"mocha": "^2.2.5"
|
||||
}
|
||||
}
|
||||
4
test/autoload/test.autoload.js
Normal file
4
test/autoload/test.autoload.js
Normal file
@@ -0,0 +1,4 @@
|
||||
'use strict';
|
||||
module.exports = function() {
|
||||
throw new Error('autoloaded');
|
||||
};
|
||||
9
test/constructor/nested/four.js
Normal file
9
test/constructor/nested/four.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run : function () {
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/constructor/nested/last/five.js
Normal file
9
test/constructor/nested/last/five.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run : function () {
|
||||
return 5;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/constructor/one.js
Normal file
9
test/constructor/one.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run : function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/constructor/two.js
Normal file
9
test/constructor/two.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run : function () {
|
||||
return 2;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/loading/module.js
Normal file
9
test/loading/module.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run: function () {
|
||||
return modular('module2').run();
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/loading/module2.js
Normal file
9
test/loading/module2.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function() {
|
||||
return {
|
||||
run : function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/loading/module3.js
Normal file
9
test/loading/module3.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run: function () {
|
||||
return modular('nested.module4').run();
|
||||
}
|
||||
};
|
||||
};
|
||||
11
test/loading/module5.js
Normal file
11
test/loading/module5.js
Normal file
@@ -0,0 +1,11 @@
|
||||
'use strict';
|
||||
|
||||
var mod;
|
||||
|
||||
module.exports.onLoad = function (modular) {
|
||||
mod = modular('module6');
|
||||
};
|
||||
|
||||
module.exports.run = function () {
|
||||
return mod.run();
|
||||
};
|
||||
9
test/loading/module6.js
Normal file
9
test/loading/module6.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function() {
|
||||
return {
|
||||
run : function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/loading/nested/module4.js
Normal file
9
test/loading/nested/module4.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function() {
|
||||
return {
|
||||
run : function () {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/override/constructor/one.js
Normal file
9
test/override/constructor/one.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run : function () {
|
||||
return 3;
|
||||
}
|
||||
};
|
||||
};
|
||||
9
test/override/constructor/two.js
Normal file
9
test/override/constructor/two.js
Normal file
@@ -0,0 +1,9 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function (modular) {
|
||||
return {
|
||||
run : function () {
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
};
|
||||
244
test/test.js
Normal file
244
test/test.js
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Unit tests for modular
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var modular = require('../lib/modular.js');
|
||||
|
||||
var assert = require('assert');
|
||||
|
||||
//Load a file without any options
|
||||
describe('Modular', function() {
|
||||
describe('Settings', function() {
|
||||
it('load a single module', function() {
|
||||
modular({
|
||||
files : './constructor/one.js',
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('constructor.one').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load a single module with settings', function() {
|
||||
modular({
|
||||
files : {
|
||||
path: './constructor/one.js',
|
||||
}
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('constructor.one').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load two modules', function() {
|
||||
modular({
|
||||
files : [ './constructor/one.js', './constructor/two.js' ],
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('constructor.one').run());
|
||||
assert.equal (2, modular('constructor.two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load two modules with settings', function() {
|
||||
modular({
|
||||
files : [ {
|
||||
path: './constructor/one.js'
|
||||
}, {
|
||||
path: './constructor/two.js'
|
||||
}],
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('constructor.one').run());
|
||||
assert.equal (2, modular('constructor.two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load a directory', function() {
|
||||
modular({
|
||||
files : './constructor',
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('constructor.one').run());
|
||||
assert.equal (2, modular('constructor.two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load a directory with settings', function() {
|
||||
modular({
|
||||
files : {
|
||||
path: './constructor'
|
||||
}
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('constructor.one').run());
|
||||
assert.equal (2, modular('constructor.two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load a single module with namespace and name', function() {
|
||||
modular({
|
||||
files : {
|
||||
path: './constructor/one.js',
|
||||
namespace: 'namespace',
|
||||
name: 'class'
|
||||
}
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('namespace.class').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load a directories with override', function() {
|
||||
modular({
|
||||
files : ['./constructor', {
|
||||
path: './override',
|
||||
basePath: './override',
|
||||
override: true
|
||||
}
|
||||
]
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (3, modular('constructor.one').run());
|
||||
assert.equal (4, modular('constructor.two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load a directories with no override', function() {
|
||||
modular({
|
||||
files : ['./constructor', {
|
||||
path: './override',
|
||||
override: false
|
||||
}
|
||||
]
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('constructor.one').run());
|
||||
assert.equal (2, modular('constructor.two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load nested directories', function() {
|
||||
modular({
|
||||
files : './constructor'
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (4, modular('constructor.nested.four').run());
|
||||
assert.equal (5, modular('constructor.nested.last.five').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load directory with global base path', function() {
|
||||
modular({
|
||||
files : './constructor',
|
||||
basePath : './constructor'
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('one').run());
|
||||
assert.equal (2, modular('two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load directory with per directory base path', function() {
|
||||
modular({
|
||||
files : [{
|
||||
path: './constructor',
|
||||
basePath : './constructor'
|
||||
},{
|
||||
path: './override'
|
||||
}]
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('one').run());
|
||||
assert.equal (2, modular('two').run());
|
||||
assert.equal (3, modular('override.constructor.one').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load file with global base path', function() {
|
||||
modular({
|
||||
files : './constructor/one.js',
|
||||
basePath : './constructor'
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('one').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load file with per file base path', function() {
|
||||
modular({
|
||||
files : {
|
||||
path: './constructor/one.js',
|
||||
basePath : './constructor'
|
||||
}
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('one').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load file with base namespace', function() {
|
||||
modular({
|
||||
files : {
|
||||
path: './constructor/one.js',
|
||||
baseNamespace: 'namespace'
|
||||
}
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('namespace.constructor.one').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load directory with base namespace', function() {
|
||||
modular({
|
||||
files : {
|
||||
path: './constructor',
|
||||
baseNamespace: 'namespace'
|
||||
}
|
||||
}).on('start', function(modular) {
|
||||
assert.equal (1, modular('namespace.constructor.one').run());
|
||||
assert.equal (2, modular('namespace.constructor.two').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('autoload modules', function() {
|
||||
assert.throws(function(){
|
||||
modular({
|
||||
files : {
|
||||
path: './autoload',
|
||||
}
|
||||
}).start();
|
||||
},
|
||||
/autoloaded/);
|
||||
});
|
||||
|
||||
it('exclude module', function() {
|
||||
assert.throws(function(){
|
||||
modular({
|
||||
files : './constructor',
|
||||
exclude: ['./constructor/two.js']
|
||||
}).on('start', function(modular) {
|
||||
modular('two').run();
|
||||
}).start();
|
||||
},
|
||||
/Unresolved dependency two/);
|
||||
});
|
||||
});
|
||||
|
||||
describe ('Modules', function() {
|
||||
it('load modules in the same namespace', function() {
|
||||
modular({
|
||||
files: './loading'
|
||||
}).on('start', function (modular) {
|
||||
assert.equal (1, modular('loading.module').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('load modules in sub namespace', function() {
|
||||
modular({
|
||||
files: './loading'
|
||||
}).on('start', function (modular) {
|
||||
assert.equal (1, modular('loading.module3').run());
|
||||
}).start();
|
||||
});
|
||||
|
||||
it('access modules through onLoad function', function() {
|
||||
modular({
|
||||
files: ['./loading/module5.js',
|
||||
'./loading/module6.js' ]
|
||||
}).on('start', function (modular) {
|
||||
assert.equal (1, modular('loading.module5').run());
|
||||
}).start();
|
||||
});
|
||||
});
|
||||
});
|
||||
/* A tester
|
||||
* Live reload
|
||||
* Autorestart
|
||||
* Log
|
||||
*/
|
||||
Reference in New Issue
Block a user