Comparing version 1.1.0 to 1.2.0
131
container.js
@@ -6,3 +6,3 @@ const fs = require('fs'); | ||
const _isFunction = require('lodash.isfunction'); | ||
const _isObject = require('lodash.isobject'); | ||
const _isPlainObject = require('lodash.isplainobject'); | ||
const _isString = require('lodash.isstring'); | ||
@@ -44,2 +44,41 @@ const getParamNames = require('get-parameter-names'); | ||
let createModuleName = (initialName, relPath, opts = {}) => { | ||
let moduleName = initialName; | ||
if ('alias' in opts && !(_isString(opts.alias) && /^[a-zA-Z_$]{1}[a-zA-Z0-9_$]+$/.test(opts.alias))) { | ||
return throwError(`Invalid alias: ${opts.alias}`); | ||
} | ||
if (opts.alias) { | ||
moduleName = opts.alias; | ||
delete opts.alias; | ||
} else { | ||
moduleName = moduleName.replace(/[^a-zA-Z0-9]+(\w)/g, (match, letter) => letter.toUpperCase()); | ||
let dirname = _path.dirname(relPath).replace(/^([\/]+)(.*)/gi, ((_str, _g1, g2) => g2)); | ||
if (dirname.length > 1) { // ignores '.', '/', ... | ||
if (opts.reverseName === true) { | ||
moduleName = dirname.split('/').join('/').replace(/[^a-zA-Z0-9]+(\w)/g, (match, letter) => letter.toUpperCase()) + ucfirst(moduleName); | ||
} else { | ||
moduleName = moduleName + ucfirst(dirname.split('/').reverse().join('/').replace(/[^a-zA-Z0-9]+(\w)/g, (match, letter) => letter.toUpperCase())); | ||
} | ||
} | ||
if (opts.prefix) { | ||
moduleName = opts.prefix + ucfirst(moduleName); | ||
//delete opts.prefix; | ||
} | ||
if (opts.postfix) { | ||
moduleName = moduleName + ucfirst(opts.postfix); | ||
//delete opts.postfix; | ||
} | ||
} | ||
if (opts.deduplicate === true) { | ||
moduleName = moduleName.replace(/([A-Z])/g, (match, letter) => "_" + match).split('_').map(part => ucfirst(part)) | ||
.filter((part, index, arr) => arr.indexOf(part) === index).join(''); | ||
} | ||
return moduleName.charAt(0).toLowerCase() + moduleName.substr(1); | ||
}; | ||
let loadFile = (file, relPath = '', opts = {}) => { | ||
@@ -54,45 +93,31 @@ if (allowedExtensions && !(allowedExtensions.includes(file.match(/\w+$/)[0]))) return; | ||
let moduleFile = file.replace(/\.\w+$/, ''); | ||
let moduleName; | ||
let moduleName = createModuleName(_path.basename(moduleFile), relPath, opts); | ||
if ('alias' in opts && !(_isString(opts.alias) && /^[a-zA-Z_$]{1}[a-zA-Z0-9_$]+$/.test(opts.alias))) { | ||
return throwError(`Invalid alias: ${opts.alias}`); | ||
} | ||
if (opts.alias) { | ||
moduleName = opts.alias; | ||
delete opts.alias; | ||
} else { | ||
// Remove dashes from basename and camelcase result | ||
moduleName = _path.basename(moduleFile); | ||
moduleName = moduleName.replace(/[^a-zA-Z0-9]+(\w)/g, (match, letter) => letter.toUpperCase()); | ||
let dirname = _path.dirname(relPath).replace(/^([\/]+)(.*)/gi, ((_str, _g1, g2) => g2)); | ||
if (dirname.length > 1) { // ignores '.', '/', ... | ||
if (opts.reverseName === true) { | ||
moduleName = dirname.split('/').join('/').replace(/[^a-zA-Z0-9]+(\w)/g, (match, letter) => letter.toUpperCase()) + ucfirst(moduleName); | ||
} else { | ||
moduleName = moduleName + ucfirst(dirname.split('/').reverse().join('/').replace(/[^a-zA-Z0-9]+(\w)/g, (match, letter) => letter.toUpperCase())); | ||
} | ||
// Register module | ||
let module = require(moduleFile); | ||
if (_isPlainObject(module)) { | ||
let content = fs.readFileSync(file); | ||
if (!content) { | ||
return throwError(`Cannot load file contents: ${moduleFile}`); | ||
} | ||
if (opts.prefix) { | ||
moduleName = opts.prefix + ucfirst(moduleName); | ||
//delete opts.prefix; | ||
} | ||
content = content.toString(); | ||
let es6mode = /export (let|const|function|class|default)/m.test(content); | ||
if (es6mode) { | ||
// named ES6 exports | ||
Object.keys(module).forEach(key => { | ||
container.register( | ||
key === 'default' ? moduleName : createModuleName(key, relPath, opts), | ||
module[key], | ||
_extend(opts, {dependencies: resolveArguments(module[key])}) | ||
); | ||
}); | ||
} else { | ||
container.register(moduleName, module, opts); | ||
} | ||
if (opts.postfix) { | ||
moduleName = moduleName + ucfirst(opts.postfix); | ||
//delete opts.postfix; | ||
} | ||
content = null; | ||
} else { | ||
container.register(moduleName, module, opts); | ||
} | ||
if (opts.deduplicate === true) { | ||
let deduplicated = moduleName.replace(/([A-Z])/g, (match, letter) => "_" + match).split('_').map(part => ucfirst(part)) | ||
.filter((part, index, arr) => arr.indexOf(part) === index).join(''); | ||
moduleName = deduplicated.charAt(0).toLowerCase() + deduplicated.substr(1); | ||
} | ||
// Register module | ||
container.register(moduleName, require(moduleFile), opts); | ||
}; | ||
@@ -136,2 +161,10 @@ | ||
let makeClassInstance = (constructor, args) => { | ||
function F() { | ||
return constructor.apply(this, args); | ||
} | ||
F.prototype = constructor.prototype; | ||
return new F(); | ||
}; | ||
let getModuleInstance = (name, overrides = {}, visited = []) => { | ||
@@ -174,6 +207,16 @@ | ||
// create instance | ||
// create instance | ||
let instance; | ||
try { | ||
instance = factory.fn.apply(factory, args); | ||
try { | ||
// try to create instance of functional module | ||
instance = factory.fn.apply(factory, args); | ||
} catch (err) { | ||
if (!/Cannot call a class as a function/.test(err.message)) { | ||
throw err; | ||
} | ||
// try to create instance of class module | ||
instance = makeClassInstance(factory.fn, args); | ||
} | ||
} catch (err) { | ||
@@ -210,6 +253,2 @@ err.message = `Cannot create an instance of: ${name}. Error: ${err.message}`; | ||
register: (name, fn, opts = {}) => { | ||
if (_isObject(fn) && 'default' in fn) { | ||
fn = fn.default; | ||
} | ||
if (fn === undefined) { | ||
@@ -232,3 +271,3 @@ return throwError('Unable to register empty (undefined) module'); | ||
fn: fn, | ||
dependencies: resolveArguments(fn), | ||
dependencies: opts.dependencies ? opts.dependencies : resolveArguments(fn), | ||
opts: opts | ||
@@ -235,0 +274,0 @@ }; |
{ | ||
"name": "sdic", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Simple dependency injection container", | ||
@@ -29,2 +29,3 @@ "main": "index.js", | ||
"lodash.isobject": "^3.0.2", | ||
"lodash.isplainobject": "^4.0.6", | ||
"lodash.isstring": "^4.0.1" | ||
@@ -31,0 +32,0 @@ }, |
@@ -5,2 +5,4 @@ # SDIC: Simple Dependency Injection Container | ||
[![Build Status](https://travis-ci.org/josefzamrzla/sdic.svg?branch=master)](https://travis-ci.org/josefzamrzla/sdic) | ||
## Install | ||
@@ -102,3 +104,3 @@ ```bash | ||
or using ES6 **export default** syntax (currently only default exports are supported): | ||
or using ES6 **export default** syntax: | ||
@@ -128,3 +130,3 @@ ```javascript | ||
or using ES6 **export default** syntax (currently only default exports are supported): | ||
or using ES6 **export default** syntax: | ||
@@ -143,2 +145,30 @@ ```javascript | ||
### Multiple modules definition using ES6 named exports | ||
```javascript | ||
// module without dependencies | ||
export const firstFunctionalService = () => { | ||
return { | ||
method: () => ({passed: true}) | ||
} | ||
}; | ||
// module dependds on fooService | ||
export function secondFunctionalService (fooService) { | ||
return { | ||
method: () => fooService.method() | ||
} | ||
}; | ||
// module dependds on fooService | ||
export class ClassService { | ||
constructor(fooService) { | ||
this.fooService = fooService; | ||
} | ||
method() { | ||
return this.fooService.method(); | ||
} | ||
} | ||
``` | ||
### Manual module registration | ||
@@ -289,2 +319,6 @@ | ||
**ES6 note:** when loading named exports into the container, then: | ||
* exported name will be taken instead of a filename (with lowercased first letter) | ||
* filename will be taken for default (not-named) export | ||
## TODO | ||
@@ -297,5 +331,4 @@ * load both file and folder with the same name, eg: | ||
``` | ||
* add support for ES6 named exports | ||
* docs, docs, docs | ||
Based on the idea of: https://www.npmjs.com/package/adctd |
@@ -1,5 +0,7 @@ | ||
const service = (fooService) => ({ | ||
method: () => fooService.method() | ||
}); | ||
const service = (fooService) => { | ||
return { | ||
method: () => fooService.method() | ||
} | ||
}; | ||
export default service; |
@@ -149,4 +149,4 @@ const expect = require('chai').expect; | ||
describe('should be able to register module with "export default" syntax', () => { | ||
it('without dependencies', () => { | ||
describe('should be able to register es6 modules', () => { | ||
it('export default without dependencies', () => { | ||
container.load('./export-default/dummy-service'); | ||
@@ -158,3 +158,3 @@ expect(container.getAll()).to.contain.all.keys(['dummyService']); | ||
it('with dependencies', () => { | ||
it('export default with dependencies', () => { | ||
container.register('fooService', () => ({method: () => ({passed: true})})); | ||
@@ -166,3 +166,31 @@ container.load('./export-default/service-with-params'); | ||
}); | ||
it('named exports', () => { | ||
container.register('fooService', () => ({method: () => ({passed: true})})); | ||
container.load('./named-exports', {alias: null}); | ||
expect(container.getAll()).to.contain.all.keys(['firstFunctionalService', 'secondFunctionalService', 'classService', 'services']); | ||
expect(Object.keys(container.getAll()).length).to.eql(6); // 5 + container itself | ||
expect(container.get('firstFunctionalService').method()).to.deep.equal({passed: true}); | ||
expect(container.get('secondFunctionalService').method()).to.deep.equal({passed: true}); | ||
expect(container.get('classService').method()).to.deep.equal({passed: true}); | ||
expect(container.get('services').method()).to.deep.equal({passed: true}); // default export | ||
}); | ||
it('named exports respects opts rules (prefix, postfix, alias)', () => { | ||
container.register('fooService', () => ({method: () => ({passed: true})})); | ||
container.load('./named-exports', {prefix: 'pre', postfix: 'post'}); // alias == basedir | ||
expect(container.getAll()).to.contain.all.keys([ | ||
'preFirstFunctionalServiceNamedExportsPost', | ||
'preSecondFunctionalServiceNamedExportsPost', | ||
'preClassServiceNamedExportsPost', | ||
'preServicesNamedExportsPost']); | ||
expect(Object.keys(container.getAll()).length).to.eql(6); // 5 + container itself | ||
expect(container.get('preFirstFunctionalServiceNamedExportsPost').method()).to.deep.equal({passed: true}); | ||
expect(container.get('preSecondFunctionalServiceNamedExportsPost').method()).to.deep.equal({passed: true}); | ||
expect(container.get('preClassServiceNamedExportsPost').method()).to.deep.equal({passed: true}); | ||
expect(container.get('preServicesNamedExportsPost').method()).to.deep.equal({passed: true}); // default export | ||
}); | ||
}); | ||
}); |
44786
31
784
328
8
5
+ Addedlodash.isplainobject@^4.0.6
+ Addedlodash.isplainobject@4.0.6(transitive)