Remove aux test
This commit is contained in:
parent
bd2bf60467
commit
ac656b51e8
|
@ -0,0 +1,11 @@
|
|||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.swp
|
||||
*.swo
|
||||
node_modules/
|
||||
.idea
|
|
@ -0,0 +1,246 @@
|
|||
# sl-module-loader
|
||||
v0.0.1
|
||||
|
||||
## Purpose
|
||||
|
||||
The `sl-module-loader` allows your program to register classes (or types) that are instantiated via configuration files. Configuration files point to an implementation `module` constructor. The `module`'s job is to construct a useful instance with the given configuration options. This allows programs to be free from bootstrapping code and manageable via config.
|
||||
|
||||
## Install
|
||||
|
||||
slnode install sl-module-loader
|
||||
|
||||
## Example
|
||||
|
||||
Given a simple `Dog` module:
|
||||
|
||||
function Dog(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
Dog.prototype.speak = function() {
|
||||
console.log('roof', 'my name is', this.options.name);
|
||||
}
|
||||
|
||||
module.exports = Dog;
|
||||
|
||||
And a set of `config.json` files:
|
||||
|
||||
/my-app
|
||||
/fido
|
||||
config.json
|
||||
/santas-little-helper
|
||||
config.json
|
||||
/rex
|
||||
config.json
|
||||
/node_modules
|
||||
/dog
|
||||
index.js
|
||||
package.json
|
||||
|
||||
Where a `config.json` looks like this:
|
||||
|
||||
{
|
||||
"module": "dog", // the "dog" module
|
||||
"options": {
|
||||
"name": "fido"
|
||||
}
|
||||
}
|
||||
|
||||
We can load up all the dogs like so (app.js):
|
||||
|
||||
var moduleLoader = require('sl-module-loader').create('my-app');
|
||||
|
||||
moduleLoader.load(function (err, modules) {
|
||||
if(err) throw err;
|
||||
|
||||
moduleLoader
|
||||
.instanceOf('dog') // a module in node_modules or declared as a dependency in package.json
|
||||
.forEach(function (m) {
|
||||
m.speak();
|
||||
});
|
||||
});
|
||||
|
||||
The above calls a method on all module instances that inherit from `Dog` and outputs:
|
||||
|
||||
roof my name is fido
|
||||
roof my name is santa's little helper
|
||||
roof my name is rex
|
||||
|
||||
## Creating Module Classes
|
||||
|
||||
The purpose of a module class is to take meaningful input (configuration, options, dependencies) and create a useful output: a module instance.
|
||||
|
||||
### Module Classes
|
||||
|
||||
A module class is a `node_module` that exports a constructor inheriting from the `Module` class.
|
||||
|
||||
var inherits = require('util').inherits;
|
||||
var Module = require('sl-module-loader').Module;
|
||||
|
||||
module.exports = Dog;
|
||||
|
||||
function Dog(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
inherits(Dog, Module);
|
||||
|
||||
Dog.prototype.speak = function() {
|
||||
console.log('roof', 'my name is', this.options.name);
|
||||
}
|
||||
|
||||
Module classes may define dependency contracts that tell the module loader to provide dependencies of a given module class during construction.
|
||||
|
||||
function MyComplexModule() {
|
||||
Module.apply(this, arguments);
|
||||
console.log('loaded dependencies', this.dependencies); // {'my-dependency': <module instance>}
|
||||
}
|
||||
|
||||
MyComplexModule.dependencies = {
|
||||
'my-dependency': 'another-module-class'
|
||||
}
|
||||
|
||||
#### Module Class Options
|
||||
|
||||
Module classes may also describe the options they accept. This will validate the configuration of module instance and guarantee the module class constructor has enough information to construct an instance.
|
||||
|
||||
Here is an example options description for a database connection module class.
|
||||
|
||||
DatabaseConnection.options = {
|
||||
'hostname': {type: 'string', required: true},
|
||||
'port': {type: 'number', min: 10, max: 99999},
|
||||
'username': {type: 'string'},
|
||||
'password': {type: 'string'}
|
||||
};
|
||||
|
||||
**key** the option name given in `config.json`.
|
||||
|
||||
**type** must be one of:
|
||||
|
||||
- string
|
||||
- boolean
|
||||
- number
|
||||
- array
|
||||
|
||||
**min/max** depend on the option type
|
||||
|
||||
{
|
||||
min: 10, // minimum length or value
|
||||
max: 100, // max length or value
|
||||
}
|
||||
|
||||
#### Module Events
|
||||
|
||||
Module classes may also emit and listen to events. By default a Module will emit the following events:
|
||||
|
||||
**destroy**
|
||||
|
||||
Emitted when a module instance is being destroyed during a `moduleLoader.reset()`. Modules should cleanup any connections and unbind all event listeners.
|
||||
|
||||
### Configuration
|
||||
|
||||
Each module instance is defined by creating a `config.json` file in a directory with the module's name.
|
||||
|
||||
/my-module-instance
|
||||
config.json
|
||||
other-files.txt
|
||||
index.js
|
||||
|
||||
This directory should contain files related to the module instance. For example it might contain a script that `require()`s the module instance.
|
||||
|
||||
#### config.module
|
||||
|
||||
The node module name that exports the module class that constructs the config's module instance.
|
||||
|
||||
{
|
||||
"module": "my-module-class"
|
||||
}
|
||||
|
||||
#### config.options
|
||||
|
||||
Defines arbitrary options. A `file-upload` module might have an `uploads` option.
|
||||
|
||||
{
|
||||
"module": "file-upload",
|
||||
"options": {
|
||||
"uploads": "/tmp"
|
||||
}
|
||||
}
|
||||
|
||||
#### config.dependencies
|
||||
|
||||
Defines other module instances the configured instance depends on.
|
||||
|
||||
{
|
||||
"module": "collection"
|
||||
"dependencies": {
|
||||
"db": "my-db-module"
|
||||
},
|
||||
"options": {
|
||||
"collection-name": "my-collection"
|
||||
}
|
||||
}
|
||||
|
||||
Where `my-db-module`'s config looks like this:
|
||||
|
||||
{
|
||||
"module": "couchdb-connector",
|
||||
"options": {
|
||||
"database": "my-db"
|
||||
}
|
||||
}
|
||||
|
||||
#### config.env.json
|
||||
|
||||
Separate file that overrides `config.json` depending on the current `NODE_ENV`. Useful for including config information that is environment specific or should not be committed to source control.
|
||||
|
||||
{
|
||||
// overrides for all envs
|
||||
"*": {
|
||||
"options": {
|
||||
"upload-dir": "/uploads"
|
||||
}
|
||||
},
|
||||
"dev": {
|
||||
"options": {
|
||||
"upload-dir": "/dev-uploads"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## Requiring Modules
|
||||
|
||||
To use module instances, you can `require()` them anywhere in your program like you normally would require a node module. For example, you can get a reference to the `fido` object like this:
|
||||
|
||||
var fido = require('fido'); // `fido` is the directory name containing the fido module config
|
||||
|
||||
### Require Behavior
|
||||
|
||||
After your program runs `require('sl-module-loader')` the `require()` function's behavior will change slightly to make referencing module instances simpler. Since some module instances may not have any program specific code, they can't be `require()`d with `node`'s existing require() implementation.
|
||||
|
||||
## Config Loader
|
||||
|
||||
`sl-module-loader` inherits from [sl-config-loader](https://github.com/strongloop/sl-config-loader).
|
||||
|
||||
|
||||
## Bundled Modules / Aliasing
|
||||
|
||||
Some modules need to be distributed together. For example, you have a set of related modules that all live under a single version number since they depend on features from each other. In this case you should bundle your sub modules using the package.json `bundledDependencies` array.
|
||||
|
||||
Reference bundled modules by relative location (just like require).
|
||||
|
||||
// config.json
|
||||
{
|
||||
"module": "myBundle/node_modules/foo"
|
||||
}
|
||||
|
||||
You may also provide aliases to any module path when creating a `ModuleLoader`.
|
||||
|
||||
var moduleLoader = require('sl-module-loader').create('my-app', {alias: {'foo': 'myBundle/node_modules/foo'}});
|
||||
|
||||
Now the config can reference `foo` instead of the qualified path.
|
||||
|
||||
// config.json
|
||||
{
|
||||
"module": "foo"
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* Main application
|
||||
*/
|
||||
|
||||
var express = require('express')
|
||||
, http = require('http')
|
||||
, path = require('path');
|
||||
|
||||
var app = express();
|
||||
|
||||
var ModuleLoader = require('../../');
|
||||
|
||||
var ml = ModuleLoader.create('.', {ttl: 0, ignore: ['node_modules']});
|
||||
|
||||
var options = {};
|
||||
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
ml.load(function (err, config) {
|
||||
if(err) {
|
||||
throw err;
|
||||
} else {
|
||||
// simple routing
|
||||
var module = ml.getByName(req.url.replace('/', ''));
|
||||
|
||||
if(module) {
|
||||
module.handle(req, res, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.use(app.router);
|
||||
|
||||
http.createServer(app).listen(app.get('port'), function(){
|
||||
console.log("express-app listening on port " + app.get('port'));
|
||||
});
|
||||
|
7
node_modules/sl-module-loader/example/express-app/models/user/config.env.json
generated
vendored
Normal file
7
node_modules/sl-module-loader/example/express-app/models/user/config.env.json
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"prod": {
|
||||
"dependencies": {
|
||||
"store": "mongo"
|
||||
}
|
||||
}
|
||||
}
|
9
node_modules/sl-module-loader/example/express-app/models/user/config.json
generated
vendored
Normal file
9
node_modules/sl-module-loader/example/express-app/models/user/config.json
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"module": "data-model",
|
||||
"options": {
|
||||
"collection": "users"
|
||||
},
|
||||
"dependencies": {
|
||||
"store": "memory"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "express-app",
|
||||
"description": "express-app",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "node app"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "latest",
|
||||
"ejs": "latest",
|
||||
"mongodb": "~1.2.14"
|
||||
}
|
||||
}
|
6
node_modules/sl-module-loader/example/express-app/routes/bar/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/express-app/routes/bar/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "responder",
|
||||
"options": {
|
||||
"msg": "this is bar"
|
||||
}
|
||||
}
|
12
node_modules/sl-module-loader/example/express-app/routes/foo/config.env.json
generated
vendored
Normal file
12
node_modules/sl-module-loader/example/express-app/routes/foo/config.env.json
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"*": {
|
||||
"options": {
|
||||
"msg": "foo bar"
|
||||
}
|
||||
},
|
||||
"dev": {
|
||||
"options": {
|
||||
"msg": "foo bar dev"
|
||||
}
|
||||
}
|
||||
}
|
6
node_modules/sl-module-loader/example/express-app/routes/foo/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/express-app/routes/foo/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "responder",
|
||||
"options": {
|
||||
"msg": "this is foo"
|
||||
}
|
||||
}
|
6
node_modules/sl-module-loader/example/express-app/routes/hello/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/express-app/routes/hello/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "responder",
|
||||
"options": {
|
||||
"msg": "hello"
|
||||
}
|
||||
}
|
6
node_modules/sl-module-loader/example/express-app/routes/users/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/express-app/routes/users/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "./users-route.js",
|
||||
"dependencies": {
|
||||
"user": "user"
|
||||
}
|
||||
}
|
27
node_modules/sl-module-loader/example/express-app/routes/users/users-route.js
generated
vendored
Normal file
27
node_modules/sl-module-loader/example/express-app/routes/users/users-route.js
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
var MODULE_LOADER = '../../../../';
|
||||
var Module = require(MODULE_LOADER).Module;
|
||||
var inherits = require('util').inherits;
|
||||
|
||||
module.exports = UsersRoute;
|
||||
|
||||
function UsersRoute(options) {
|
||||
Module.apply(this, arguments);
|
||||
}
|
||||
|
||||
inherits(UsersRoute, Module);
|
||||
|
||||
UsersRoute.prototype.handle = function (req, res, next) {
|
||||
var user = this.dependencies.user;
|
||||
|
||||
user.getAll(function (err, users) {
|
||||
if(err) {
|
||||
next(err);
|
||||
} else {
|
||||
res.send(users);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UsersRoute.dependencies = {
|
||||
'user': 'data-model'
|
||||
}
|
6
node_modules/sl-module-loader/example/express-app/stores/memory/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/express-app/stores/memory/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "memory-store",
|
||||
"options": {
|
||||
"database": "my-memory-db"
|
||||
}
|
||||
}
|
8
node_modules/sl-module-loader/example/express-app/stores/mongo/config.json
generated
vendored
Normal file
8
node_modules/sl-module-loader/example/express-app/stores/mongo/config.json
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"module": "mongo-store",
|
||||
"options": {
|
||||
"database": "sl-module-loader-testing-db",
|
||||
"host": "127.0.0.1",
|
||||
"port": "27017"
|
||||
}
|
||||
}
|
9
node_modules/sl-module-loader/example/module-script/another-module/another-module-class.js
generated
vendored
Normal file
9
node_modules/sl-module-loader/example/module-script/another-module/another-module-class.js
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = AnotherModuleClass;
|
||||
|
||||
function AnotherModuleClass(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
AnotherModuleClass.prototype.foo = function () {
|
||||
console.log('foo', this.options.msg);
|
||||
}
|
7
node_modules/sl-module-loader/example/module-script/another-module/config.json
generated
vendored
Normal file
7
node_modules/sl-module-loader/example/module-script/another-module/config.json
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"module": "./another-module-class.js",
|
||||
"options": {
|
||||
"msg": "bar bat baz",
|
||||
"name": "another-module"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
var ml = require('../../').create('.');
|
||||
|
||||
ml.load(function () {
|
||||
console.log('module loaded...');
|
||||
});
|
8
node_modules/sl-module-loader/example/module-script/my-module/config.json
generated
vendored
Normal file
8
node_modules/sl-module-loader/example/module-script/my-module/config.json
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"module": "./my-module-class.js",
|
||||
"main": "main.js",
|
||||
"options": {
|
||||
"msg": "hello world",
|
||||
"name": "my-module"
|
||||
}
|
||||
}
|
7
node_modules/sl-module-loader/example/module-script/my-module/main.js
generated
vendored
Normal file
7
node_modules/sl-module-loader/example/module-script/my-module/main.js
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
var myModule = require('./');
|
||||
|
||||
myModule.hello(); // hello world (via config.json's options.msg)
|
||||
|
||||
var anotherModule = require('../another-module');
|
||||
|
||||
anotherModule.foo();
|
9
node_modules/sl-module-loader/example/module-script/my-module/my-module-class.js
generated
vendored
Normal file
9
node_modules/sl-module-loader/example/module-script/my-module/my-module-class.js
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
module.exports = MyModuleClass;
|
||||
|
||||
function MyModuleClass(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
MyModuleClass.prototype.hello = function () {
|
||||
console.log(this.options.msg);
|
||||
}
|
6
node_modules/sl-module-loader/example/sample-app/custom-scripts/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/sample-app/custom-scripts/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "module",
|
||||
"scripts": {
|
||||
"constructed": "construction.js"
|
||||
}
|
||||
}
|
9
node_modules/sl-module-loader/example/sample-app/custom-scripts/construction.js
generated
vendored
Normal file
9
node_modules/sl-module-loader/example/sample-app/custom-scripts/construction.js
generated
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* not yet supported...
|
||||
*/
|
||||
|
||||
var modules = require('modules');
|
||||
var currentModule = modules(__dirname);
|
||||
|
||||
console.log('this script is executed after a module is constructed');
|
||||
console.log('currentModule', currentModule);
|
6
node_modules/sl-module-loader/example/sample-app/default/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/sample-app/default/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "../my-module-type",
|
||||
"options": {
|
||||
"msg": "this module uses the default my-module-type"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
var ModuleLoader = require('../../');
|
||||
var moduleLoader = ModuleLoader.create('.');
|
||||
|
||||
moduleLoader.load(function (err, modules) {
|
||||
if(err) throw err;
|
||||
|
||||
console.log('loaded modules...');
|
||||
|
||||
moduleLoader.instanceOf('MyModuleType').forEach(function (m) {
|
||||
m.speak();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "./hello.js",
|
||||
"options": {
|
||||
"msg": "world"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
var MyModuleType = require('../my-module-type');
|
||||
var util = require('util');
|
||||
|
||||
module.exports = HelloModule;
|
||||
|
||||
function HelloModule(options) {
|
||||
MyModuleType.call(this, options);
|
||||
}
|
||||
|
||||
util.inherits(HelloModule, MyModuleType);
|
||||
|
||||
HelloModule.prototype.speak = function () {
|
||||
console.log('from hello module', this.options);
|
||||
}
|
6
node_modules/sl-module-loader/example/sample-app/inter-deps/config.json
generated
vendored
Normal file
6
node_modules/sl-module-loader/example/sample-app/inter-deps/config.json
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"module": "./my-module",
|
||||
"dependencies": {
|
||||
"custom-dep": "hello"
|
||||
}
|
||||
}
|
18
node_modules/sl-module-loader/example/sample-app/inter-deps/my-module.js
generated
vendored
Normal file
18
node_modules/sl-module-loader/example/sample-app/inter-deps/my-module.js
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
var Module = require('../../../').Module;
|
||||
var inherits = require('util').inherits;
|
||||
|
||||
|
||||
module.exports = MyModule;
|
||||
|
||||
function MyModule(options) {
|
||||
Module.apply(this, arguments);
|
||||
|
||||
console.log('the "inter-deps" module loads a dependency', this.dependencies['custom-dep']);
|
||||
}
|
||||
|
||||
MyModule.dependencies = {
|
||||
// the dep name and path to a module that exports its supported type
|
||||
'custom-dep': '../my-module-type'
|
||||
};
|
||||
|
||||
inherits(MyModule, Module);
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = MyModuleType;
|
||||
|
||||
function MyModuleType(options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
MyModuleType.prototype.speak = function () {
|
||||
console.log(this.options.msg || 'no message provided...');
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/**
|
||||
* sl-module-loader ~ public api
|
||||
*/
|
||||
|
||||
module.exports = require('./lib/module-loader');
|
||||
module.exports.Module = require('./lib/module');
|
|
@ -0,0 +1,283 @@
|
|||
/**
|
||||
* Expose `ModuleLoader`.
|
||||
*/
|
||||
|
||||
module.exports = ModuleLoader;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var ConfigLoader = require('sl-config-loader')
|
||||
, PatchedModule = require('./patched-module')
|
||||
, fs = require('fs')
|
||||
, path = require('path')
|
||||
, debug = require('debug')('sl-module-loader')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Create a new `ModuleLoader` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {ModuleLoader}
|
||||
*/
|
||||
|
||||
function ModuleLoader(options) {
|
||||
ConfigLoader.apply(this, arguments);
|
||||
|
||||
this.types = {};
|
||||
this._modules = {};
|
||||
|
||||
// throw an error if args are not supplied
|
||||
// assert(typeof options === 'object', 'ModuleLoader requires an options object');
|
||||
|
||||
this.options = options;
|
||||
|
||||
debug('created with options', options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `ConfigLoader`.
|
||||
*/
|
||||
|
||||
inherits(ModuleLoader, ConfigLoader);
|
||||
|
||||
/**
|
||||
* Simplified APIs
|
||||
*/
|
||||
|
||||
ModuleLoader.create =
|
||||
ModuleLoader.createModuleLoader = function (root, options) {
|
||||
options = options || {};
|
||||
options.root = root;
|
||||
return new ModuleLoader(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the configuration and build modules.
|
||||
*/
|
||||
|
||||
ModuleLoader.prototype.load = function (fn) {
|
||||
if(this.remaining()) {
|
||||
// wait until the current operation is finished
|
||||
this.once('done', function () {
|
||||
fn(null, this._files);
|
||||
});
|
||||
// callback with an error if it occurs
|
||||
this.once('error', fn);
|
||||
} else if(this.ttl() > 0) {
|
||||
fn(null, this._modules);
|
||||
} else {
|
||||
this.reset();
|
||||
|
||||
this.once('error', fn);
|
||||
|
||||
this.bindListeners();
|
||||
|
||||
// load config files
|
||||
this.task(fs, 'readdir', this.root);
|
||||
this.once('done', function () {
|
||||
this._mergeEnvConfigs();
|
||||
// create instances from config
|
||||
this._constructModules(this._files);
|
||||
fn(null, this._modules);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an sl module type from the given module name.
|
||||
*/
|
||||
|
||||
ModuleLoader.prototype.loadType = function (name) {
|
||||
// get alias if it exists
|
||||
var alias = (this.options.alias && this.options.alias[name]) || name;
|
||||
|
||||
assert(alias, 'you must provide a name or alias when loading a type');
|
||||
|
||||
// use the root as the base dir for loading
|
||||
var paths = PatchedModule.resolveLookupPaths(this.root)[1];
|
||||
|
||||
// add in node module paths
|
||||
paths = PatchedModule.nodeModulePaths(this.root).concat(paths);
|
||||
|
||||
var Type = PatchedModule.load(PatchedModule.resolveFilename(alias, {paths: paths}));
|
||||
|
||||
// patch the module with a moduleName for reference
|
||||
Type.moduleName = name;
|
||||
|
||||
return Type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the loaded modules and cache.
|
||||
*/
|
||||
|
||||
ModuleLoader.prototype.reset = function () {
|
||||
// clear require cache
|
||||
clearObject(require.cache);
|
||||
|
||||
// clear module cache
|
||||
clearObject(this._modules, function (module) {
|
||||
if(typeof module.destroy === 'function') {
|
||||
module.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
// clear type cache
|
||||
clearObject(this.types);
|
||||
|
||||
ConfigLoader.prototype.reset.apply(this, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build modules from a list of config files.
|
||||
*/
|
||||
|
||||
ModuleLoader.prototype._constructModules = function (configs) {
|
||||
Object.keys(configs).forEach(function (p) {
|
||||
this.constructModuleFromConfigAtPath(configs[p], p);
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
ModuleLoader.prototype.constructModuleFromConfigAtPath = function (config, p) {
|
||||
config.options = config.options || {};
|
||||
config.options._moduleLoader = this;
|
||||
config.options._name = path.basename(path.dirname(p));
|
||||
|
||||
var Type = this.getTypeFromConfig(config, p);
|
||||
|
||||
assert(typeof Type === 'function', config.module + ' does not export a constructor');
|
||||
|
||||
// private options
|
||||
config.options._path = p;
|
||||
|
||||
// cache discovered types
|
||||
var T = Type;
|
||||
|
||||
while(T) {
|
||||
this.types[T.name] = T;
|
||||
|
||||
T = T.super_;
|
||||
}
|
||||
|
||||
var m = this._modules[p] = new Type(config.options, config.dependencies);
|
||||
|
||||
// create NodeModule
|
||||
var nmFilename = path.resolve(path.dirname(p));
|
||||
var nm = new PatchedModule(nmFilename, module);
|
||||
nm.exports = m;
|
||||
nm.filename = nmFilename;
|
||||
nm.loaded = true;
|
||||
require('module')._cache[nmFilename] = nm;
|
||||
|
||||
// load main
|
||||
if(config.main) {
|
||||
var mnmFilename = path.resolve(path.dirname(p), config.main);
|
||||
var mnm = new PatchedModule(mnmFilename, nm);
|
||||
mnm.load(mnmFilename);
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a module with the provided name.
|
||||
*/
|
||||
|
||||
ModuleLoader.prototype.getByName = function (name) {
|
||||
var paths = Object.keys(this._modules);
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
var n = path.basename(path.dirname(paths[i]));
|
||||
|
||||
if(n === name) {
|
||||
return this._modules[paths[i]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a module config by module name.
|
||||
*/
|
||||
|
||||
ModuleLoader.prototype.getConfigAndPathByName = function (name) {
|
||||
var paths = Object.keys(this._files);
|
||||
|
||||
for (var i = 0; i < paths.length; i++) {
|
||||
if(path.basename(path.dirname(paths[i])) === name) {
|
||||
return {config: this._files[paths[i]], path: paths[i]};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ModuleLoader.prototype.getTypeFromConfig = function (config, configPath) {
|
||||
var ml = this;
|
||||
var Type;
|
||||
var typeName = config.module;
|
||||
|
||||
if(!typeName) return;
|
||||
|
||||
// pointing to a relative type file
|
||||
if(typeName[0] === '.') {
|
||||
try {
|
||||
var relPath = path.join(path.dirname(configPath), typeName);
|
||||
|
||||
|
||||
debug('requiring relative module %s', relPath);
|
||||
Type = require(relPath);
|
||||
} catch(e) {
|
||||
fail(e);
|
||||
}
|
||||
} else {
|
||||
// pointing to a module
|
||||
try {
|
||||
// load from the programs main module
|
||||
Type = require('module')._load(typeName, process.mainModule);
|
||||
} catch(e) {
|
||||
fail(e);
|
||||
}
|
||||
}
|
||||
|
||||
if(Type) {
|
||||
return Type;
|
||||
}
|
||||
|
||||
function fail(e) {
|
||||
console.error('failed to load module at', configPath, typeName);
|
||||
ml.emit('error', e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all module instances that inherit from the given type.
|
||||
*/
|
||||
|
||||
ModuleLoader.prototype.instanceOf = function (typeName) {
|
||||
var Type = this.types[typeName];
|
||||
var results = [];
|
||||
|
||||
if(!Type) {
|
||||
throw new Error(typeName + ' is not a used type');
|
||||
}
|
||||
|
||||
Object.keys(this._modules).forEach(function (k) {
|
||||
if(this._modules[k] instanceof Type) {
|
||||
results.push(this._modules[k]);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function clearObject(obj, fn) {
|
||||
Object.keys(obj).forEach(function (k) {
|
||||
if(fn) {
|
||||
fn(obj[k]);
|
||||
}
|
||||
|
||||
delete obj[k];
|
||||
});
|
||||
}
|
|
@ -0,0 +1,209 @@
|
|||
/**
|
||||
* Expose `Module`.
|
||||
*/
|
||||
|
||||
module.exports = Module;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter
|
||||
, debug = require('debug')('module')
|
||||
, path = require('path')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Create a new `Module` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Module}
|
||||
*/
|
||||
|
||||
function Module(options, dependencies) {
|
||||
dependencies = dependencies || {};
|
||||
|
||||
EventEmitter.apply(this, arguments);
|
||||
|
||||
this.options = options;
|
||||
|
||||
if(this.constructor.dependencies) {
|
||||
assert(typeof this.constructor.dependencies === 'object', this.constructor.name
|
||||
+ '.dependencies does not allow any dependencies!');
|
||||
|
||||
// merge dependencies for inheritence chain
|
||||
var constructor = this.constructor;
|
||||
var mergedDeps = {};
|
||||
|
||||
while(constructor) {
|
||||
var deps = constructor.dependencies;
|
||||
|
||||
if(deps) {
|
||||
Object.keys(deps).forEach(function (key) {
|
||||
if(!mergedDeps[key]) mergedDeps[key] = deps[key];
|
||||
});
|
||||
}
|
||||
|
||||
// move up the inheritence chain
|
||||
constructor = constructor.super_;
|
||||
}
|
||||
|
||||
this.dependencies = this._resolveDependencies(dependencies, mergedDeps);
|
||||
}
|
||||
|
||||
if(this.constructor.options) {
|
||||
assert(typeof this.constructor.options === 'object', this.constructor.name
|
||||
+ '.options must be an object!');
|
||||
|
||||
// merge options for inheritence chain
|
||||
var constructor = this.constructor;
|
||||
var mergedOpts = {};
|
||||
|
||||
while(constructor) {
|
||||
var opts = constructor.options;
|
||||
|
||||
if(opts) {
|
||||
Object.keys(opts).forEach(function (key) {
|
||||
if(!mergedOpts[key]) mergedOpts[key] = opts[key];
|
||||
});
|
||||
}
|
||||
|
||||
// move up the inheritence chain
|
||||
constructor = constructor.super_;
|
||||
}
|
||||
|
||||
validateOptions(options, mergedOpts);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from `EventEmitter`.
|
||||
*/
|
||||
|
||||
inherits(Module, EventEmitter);
|
||||
|
||||
/**
|
||||
* Build dependencies from constructor description (MyCustomModule.dependencies) and dependencie
|
||||
* configuration (config.dependencies).
|
||||
*/
|
||||
|
||||
Module.prototype._resolveDependencies = function (depsConfig, desc) {
|
||||
var types = {};
|
||||
var deps = {};
|
||||
var moduleLoader = this.options._moduleLoader;
|
||||
|
||||
// iterate the class description of dependencies
|
||||
Object.keys(desc).forEach(function (depName) {
|
||||
var depRequired = true;
|
||||
|
||||
if(typeof desc[depName] === 'object' && desc[depName].optional) {
|
||||
depRequired = false;
|
||||
}
|
||||
|
||||
var depInstanceName = depsConfig[depName];
|
||||
|
||||
if(!depInstanceName) {
|
||||
if(depRequired) {
|
||||
throw new Error('Required dependency not defined: "' + depName + '"');
|
||||
} else {
|
||||
// don't load the optional dep
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// load the described type
|
||||
try {
|
||||
var modPath = desc[depName].module || desc[depName];
|
||||
|
||||
if(modPath[0] === '.') {
|
||||
modPath = path.resolve('.', path.dirname(this.options._path), desc[depName]);
|
||||
}
|
||||
|
||||
types[depName] = require('module')._load(modPath, process.mainModule);
|
||||
} catch(e) {
|
||||
e.message = 'Failed to load dependency "' + depName + ': ' + modPath + '" for ' + this.options._path + '. ' + e.message;
|
||||
throw e;
|
||||
}
|
||||
|
||||
var configAndPath = moduleLoader.getConfigAndPathByName(depInstanceName);
|
||||
var config = configAndPath && configAndPath.config;
|
||||
var configPath = configAndPath && configAndPath.path;
|
||||
|
||||
// try to get the dependency by given dependency instance name
|
||||
if(config) {
|
||||
var m = moduleLoader.getByName(depInstanceName);
|
||||
|
||||
if(!m) {
|
||||
// construct the module now
|
||||
m = moduleLoader.constructModuleFromConfigAtPath(config, configPath);
|
||||
}
|
||||
|
||||
if(!types[depName]) {
|
||||
throw new Error(modPath + ' does not correctly export a constructor or does not exist.');
|
||||
}
|
||||
|
||||
if(!(m instanceof types[depName])) {
|
||||
throw new Error('Dependency ' + depName + ' is not an instance of ' + types[depName].name);
|
||||
}
|
||||
|
||||
deps[depName] = m;
|
||||
} else {
|
||||
console.log(depsConfig);
|
||||
|
||||
throw new Error('Could not find dependency "'+ depInstanceName +'" config while resolving dependencies for ' + this.options._path);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return deps;
|
||||
}
|
||||
|
||||
Module.prototype.destroy = function () {
|
||||
this.emit('destroy');
|
||||
}
|
||||
|
||||
function validateOptions(options, def) {
|
||||
if(!def) {
|
||||
return options;
|
||||
}
|
||||
|
||||
Object.keys(def).forEach(function (key) {
|
||||
var val = options[key];
|
||||
var keyDef = def[key] || {};
|
||||
|
||||
if(keyDef.required) {
|
||||
assert(val, key + ' is required!');
|
||||
}
|
||||
|
||||
if(typeof val === 'undefined') {
|
||||
// stop validation if a value
|
||||
// wasnt provided
|
||||
return;
|
||||
}
|
||||
|
||||
if(keyDef.type === 'array') {
|
||||
assert(Array.isArray(val), key + ' must be a ' + keyDef.type)
|
||||
} else {
|
||||
// type
|
||||
assert(typeof val == keyDef.type, key + ' must be a ' + keyDef.type);
|
||||
}
|
||||
|
||||
// size / length
|
||||
if(typeof val.length === 'number') {
|
||||
if(keyDef.min) {
|
||||
assert(val.length >= keyDef.min, key + ' length must be greater than or equal to ', keyDef.min);
|
||||
}
|
||||
if(keyDef.max) {
|
||||
assert(val.length <= keyDef.min, key + ' length must be less than or equal to ', keyDef.max);
|
||||
}
|
||||
} else if(typeof val === 'number') {
|
||||
if(keyDef.min) {
|
||||
assert(val >= keyDef.min, key + ' must be greater than or equal to ', keyDef.min);
|
||||
}
|
||||
if(keyDef.max) {
|
||||
assert(val <= keyDef.max, ' must be less than or equal to ', keyDef.max);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Expose `PatchedModule`.
|
||||
*/
|
||||
|
||||
module.exports = PatchedModule;
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Module = require('module')
|
||||
, debug = require('debug')('configurable-module')
|
||||
, path = require('path')
|
||||
, util = require('util')
|
||||
, inherits = util.inherits
|
||||
, assert = require('assert');
|
||||
|
||||
/**
|
||||
* Create a new `PatchedModule` with the given `options`.
|
||||
*
|
||||
* @param {Object} options
|
||||
* @return {Module}
|
||||
*/
|
||||
|
||||
function PatchedModule(id, parent) {
|
||||
Module.apply(this, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inherit from node's core `Module`.
|
||||
*/
|
||||
|
||||
inherits(PatchedModule, Module);
|
||||
|
||||
/**
|
||||
* Override the default require implementation to check the cache first.
|
||||
*/
|
||||
|
||||
PatchedModule.prototype.require = function (p) {
|
||||
if(p === '.') p = './';
|
||||
|
||||
// for relative modules, check the cache first
|
||||
var sub = p.substr(0, 2);
|
||||
var isRelative = (sub === './' || sub === '..');
|
||||
|
||||
if(isRelative) {
|
||||
var resolvedPath = path.resolve(path.dirname(this.filename), p);
|
||||
|
||||
// check cache first (node's implementation checks the file first)
|
||||
var cached = Module._cache[resolvedPath];
|
||||
if(cached) {
|
||||
return cached.exports;
|
||||
}
|
||||
}
|
||||
|
||||
return Module.prototype.require.apply(this, arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias functions in case they needs to be patched in the future.
|
||||
*/
|
||||
|
||||
PatchedModule.resolveFilename = Module._resolveFilename;
|
||||
PatchedModule.load = Module._load;
|
||||
PatchedModule.resolveLookupPaths = Module._resolveLookupPaths;
|
||||
PatchedModule.nodeModulePaths = Module._nodeModulePaths;
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,24 @@
|
|||
var ModuleLoader = require('../');
|
||||
|
||||
// describe('ModuleLoader', function(){
|
||||
// var moduleLoader;
|
||||
//
|
||||
// beforeEach(function(){
|
||||
// moduleLoader = new ModuleLoader;
|
||||
// });
|
||||
//
|
||||
// describe('.myMethod', function(){
|
||||
// // example sync test
|
||||
// it('should <description of behavior>', function() {
|
||||
// moduleLoader.myMethod();
|
||||
// });
|
||||
//
|
||||
// // example async test
|
||||
// it('should <description of behavior>', function(done) {
|
||||
// setTimeout(function () {
|
||||
// moduleLoader.myMethod();
|
||||
// done();
|
||||
// }, 0);
|
||||
// });
|
||||
// });
|
||||
// });
|
|
@ -0,0 +1,48 @@
|
|||
var Module = require('../lib/module.js');
|
||||
var inherits = require('util').inherits;
|
||||
|
||||
describe('Module', function(){
|
||||
var module;
|
||||
|
||||
beforeEach(function(){
|
||||
module = new Module;
|
||||
});
|
||||
|
||||
it('should merge all dependency objects', function() {
|
||||
function FooModule() {
|
||||
Module.apply(this, arguments);
|
||||
}
|
||||
|
||||
inherits(FooModule, Module);
|
||||
|
||||
FooModule.dependencies = {
|
||||
'foo': 'foo',
|
||||
'baz': 'baz'
|
||||
}
|
||||
|
||||
function BarModule() {
|
||||
FooModule.apply(this, arguments);
|
||||
}
|
||||
|
||||
inherits(BarModule, FooModule);
|
||||
|
||||
BarModule.dependencies = {
|
||||
'foo': 'complex-foo',
|
||||
'bar': 'bar'
|
||||
};
|
||||
|
||||
var called = false;
|
||||
|
||||
BarModule.prototype._resolveDependencies = function (depsConfig, desc) {
|
||||
assert(desc.foo == 'complex-foo');
|
||||
assert(desc.bar == 'bar');
|
||||
assert(desc.baz == 'baz');
|
||||
called = true;
|
||||
}
|
||||
|
||||
var bar = new BarModule({}, {});
|
||||
|
||||
assert(called);
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
/**
|
||||
* sl-module-loader test setup and support.
|
||||
*/
|
||||
|
||||
assert = require('assert');
|
|
@ -1,24 +1,2 @@
|
|||
var Asteroid = require('../');
|
||||
|
||||
describe('Asteroid', function(){
|
||||
var asteroid;
|
||||
|
||||
beforeEach(function(){
|
||||
asteroid = new Asteroid;
|
||||
});
|
||||
|
||||
describe('.myMethod', function(){
|
||||
// example sync test
|
||||
it('should <description of behavior>', function() {
|
||||
asteroid.myMethod();
|
||||
});
|
||||
|
||||
// example async test
|
||||
it('should <description of behavior>', function(done) {
|
||||
setTimeout(function () {
|
||||
asteroid.myMethod();
|
||||
done();
|
||||
}, 0);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue