Update ignores
This commit is contained in:
parent
ac656b51e8
commit
fc25f95792
|
@ -12,5 +12,5 @@
|
||||||
/node_modules/debug
|
/node_modules/debug
|
||||||
/node_modules/jugglingdb
|
/node_modules/jugglingdb
|
||||||
/node_modules/mocha
|
/node_modules/mocha
|
||||||
/node_modules/asteroid-module-loader
|
/node_modules/sl-module-loader
|
||||||
/node_modules/merge
|
/node_modules/merge
|
|
@ -1,11 +0,0 @@
|
||||||
.DS_Store
|
|
||||||
*.seed
|
|
||||||
*.log
|
|
||||||
*.csv
|
|
||||||
*.dat
|
|
||||||
*.out
|
|
||||||
*.pid
|
|
||||||
*.swp
|
|
||||||
*.swo
|
|
||||||
node_modules/
|
|
||||||
.idea
|
|
|
@ -1,246 +0,0 @@
|
||||||
# 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"
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/**
|
|
||||||
* 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'));
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"prod": {
|
|
||||||
"dependencies": {
|
|
||||||
"store": "mongo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
{
|
|
||||||
"module": "data-model",
|
|
||||||
"options": {
|
|
||||||
"collection": "users"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"store": "memory"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "responder",
|
|
||||||
"options": {
|
|
||||||
"msg": "this is bar"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"*": {
|
|
||||||
"options": {
|
|
||||||
"msg": "foo bar"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dev": {
|
|
||||||
"options": {
|
|
||||||
"msg": "foo bar dev"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "responder",
|
|
||||||
"options": {
|
|
||||||
"msg": "this is foo"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "responder",
|
|
||||||
"options": {
|
|
||||||
"msg": "hello"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "./users-route.js",
|
|
||||||
"dependencies": {
|
|
||||||
"user": "user"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
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'
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "memory-store",
|
|
||||||
"options": {
|
|
||||||
"database": "my-memory-db"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"module": "mongo-store",
|
|
||||||
"options": {
|
|
||||||
"database": "sl-module-loader-testing-db",
|
|
||||||
"host": "127.0.0.1",
|
|
||||||
"port": "27017"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = AnotherModuleClass;
|
|
||||||
|
|
||||||
function AnotherModuleClass(options) {
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
AnotherModuleClass.prototype.foo = function () {
|
|
||||||
console.log('foo', this.options.msg);
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"module": "./another-module-class.js",
|
|
||||||
"options": {
|
|
||||||
"msg": "bar bat baz",
|
|
||||||
"name": "another-module"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
var ml = require('../../').create('.');
|
|
||||||
|
|
||||||
ml.load(function () {
|
|
||||||
console.log('module loaded...');
|
|
||||||
});
|
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"module": "./my-module-class.js",
|
|
||||||
"main": "main.js",
|
|
||||||
"options": {
|
|
||||||
"msg": "hello world",
|
|
||||||
"name": "my-module"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
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
9
node_modules/sl-module-loader/example/module-script/my-module/my-module-class.js
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
module.exports = MyModuleClass;
|
|
||||||
|
|
||||||
function MyModuleClass(options) {
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
MyModuleClass.prototype.hello = function () {
|
|
||||||
console.log(this.options.msg);
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "module",
|
|
||||||
"scripts": {
|
|
||||||
"constructed": "construction.js"
|
|
||||||
}
|
|
||||||
}
|
|
9
node_modules/sl-module-loader/example/sample-app/custom-scripts/construction.js
generated
vendored
9
node_modules/sl-module-loader/example/sample-app/custom-scripts/construction.js
generated
vendored
|
@ -1,9 +0,0 @@
|
||||||
/**
|
|
||||||
* 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);
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "../my-module-type",
|
|
||||||
"options": {
|
|
||||||
"msg": "this module uses the default my-module-type"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
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();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "./hello.js",
|
|
||||||
"options": {
|
|
||||||
"msg": "world"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"module": "./my-module",
|
|
||||||
"dependencies": {
|
|
||||||
"custom-dep": "hello"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
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);
|
|
|
@ -1,9 +0,0 @@
|
||||||
module.exports = MyModuleType;
|
|
||||||
|
|
||||||
function MyModuleType(options) {
|
|
||||||
this.options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
MyModuleType.prototype.speak = function () {
|
|
||||||
console.log(this.options.msg || 'no message provided...');
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
/**
|
|
||||||
* sl-module-loader ~ public api
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = require('./lib/module-loader');
|
|
||||||
module.exports.Module = require('./lib/module');
|
|
|
@ -1,283 +0,0 @@
|
||||||
/**
|
|
||||||
* 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];
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,209 +0,0 @@
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
/**
|
|
||||||
* 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
|
@ -1,24 +0,0 @@
|
||||||
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);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
|
@ -1,48 +0,0 @@
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
|
@ -1,5 +0,0 @@
|
||||||
/**
|
|
||||||
* sl-module-loader test setup and support.
|
|
||||||
*/
|
|
||||||
|
|
||||||
assert = require('assert');
|
|
Loading…
Reference in New Issue