Created
December 30, 2017 21:41
-
-
Save JoshuaKGoldberg/8b94ac3883919f682944328d6bdad9bb to your computer and use it in GitHub Desktop.
Ridiculously tiny IoC with almost no features
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IComponentListing } from "./listings"; | |
import { getFunctionName } from "../utils"; | |
export const component = (componentFunction: any, initializer?: Function) => { | |
const componentFunctionName = getFunctionName(componentFunction); | |
return initializer === undefined | |
? createComponentClass(componentFunction, componentFunctionName) | |
: createComponentInitializer(componentFunction, componentFunctionName, initializer); | |
}; | |
export const createComponentClass = (componentFunction: any, componentFunctionName: string) => { | |
return (parentClass: any, key: string) => { | |
const listing: IComponentListing = { componentFunction, componentFunctionName, key }; | |
if (parentClass.listings) { | |
parentClass.listings.push(listing); | |
} else { | |
parentClass.listings = [listing]; | |
} | |
parentClass.listings[key] = listing; | |
}; | |
}; | |
export const createComponentInitializer = (componentFunction: any, componentFunctionName: string, initializer: Function) => { | |
return (parentClass: any, key: string) => { | |
const listing: IComponentListing = { | |
componentFunction, | |
componentFunctionName, | |
initializer, | |
key | |
}; | |
if (parentClass.listings) { | |
parentClass.listings.push(listing); | |
} else { | |
parentClass.listings = [listing]; | |
} | |
parentClass.listings[key] = listing; | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { getFunctionName } from "../utils"; | |
import { IDependencyClassListing, IDependencyMethodListing } from "./listings"; | |
export const dependency = (dependencyClassName: string | Function) => { | |
if (typeof dependencyClassName === "function") { | |
dependencyClassName = getFunctionName(dependencyClassName); | |
} | |
return (parentClass: any, type: string | undefined, key: number | string) => { | |
if (typeof type === "string") { | |
const method = parentClass[type]; | |
const listing: IDependencyMethodListing = { dependencyClassName }; | |
if (method.dependencies) { | |
method.dependencies.push(listing); | |
} else { | |
method.dependencies = [listing]; | |
} | |
} else { | |
const listing: IDependencyClassListing = { dependencyClassName, key }; | |
if (parentClass.dependencies) { | |
parentClass.dependencies.push(listing); | |
} else { | |
parentClass.dependencies = [listing]; | |
} | |
} | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { expect } from "chai"; | |
import { component } from "./component"; | |
import { dependency } from "./dependency"; | |
import { injectable } from "./injectable"; | |
describe("Injectable", () => { | |
class Standalone { } | |
class WithDependency { | |
public constructor( | |
@dependency(Standalone) | |
public readonly standalone: Standalone, | |
) { } | |
} | |
class ViaMethod { | |
public constructor( | |
public readonly age: number, | |
public readonly withDependency: WithDependency, | |
) { } | |
} | |
class WithMethodInitializedDependency { | |
public constructor( | |
@dependency(ViaMethod) | |
public readonly viaMethod: ViaMethod, | |
) { } | |
} | |
@injectable | |
class MyClass { | |
@component(Standalone) | |
public readonly standalone: Standalone; | |
@component(WithDependency) | |
public readonly withDependency: WithDependency; | |
@component(ViaMethod, MyClass.prototype.methodInitializer) | |
public readonly viaMethod: ViaMethod; | |
@component(WithMethodInitializedDependency) | |
public readonly withMethodInitializedDependency: WithMethodInitializedDependency; | |
public readonly age = 7; | |
private methodInitializer( | |
@dependency(WithDependency) | |
withDependency: WithDependency, | |
) { | |
return new ViaMethod(this.age, withDependency); | |
} | |
} | |
it("E2E", () => { | |
const instance = new MyClass(); | |
expect(instance.withDependency.standalone).to.be.an.instanceOf(Standalone); | |
expect(instance.withDependency.standalone).to.be.equal(instance.standalone); | |
expect(instance.withDependency).to.be.an.instanceOf(WithDependency); | |
expect(instance.viaMethod).to.be.an.instanceOf(ViaMethod); | |
expect(instance.viaMethod.age).to.be.equal(instance.age); | |
expect(instance.viaMethod.withDependency).to.be.equal(instance.withDependency); | |
expect(instance.withMethodInitializedDependency.viaMethod).to.be.equal(instance.viaMethod); | |
}) | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { IComponentListing } from "./listings"; | |
export const injectable = (injectableClass: any): any => { | |
const createdComponents: any = {}; | |
const createComponent = (listing: IComponentListing, instance: any): any => { | |
if (listing.initializer !== undefined) { | |
const args = []; | |
for (const dependency of listing.initializer.dependencies) { | |
args.push(createdComponents[dependency.dependencyClassName]); | |
} | |
return listing.initializer.apply(instance, args); | |
} | |
if (listing.componentFunction.dependencies === undefined) { | |
return new listing.componentFunction(); | |
} | |
const args = []; | |
for (const dependency of listing.componentFunction.dependencies) { | |
args.push(createdComponents[dependency.dependencyClassName]); | |
} | |
return new listing.componentFunction(...args); | |
} | |
return class extends (injectableClass as { new(...args: any[]): any }) { | |
public constructor(...args: any[]) { | |
super(...args); | |
const listings = injectableClass.prototype.listings; | |
for (const listing of listings) { | |
const component = createComponent(listing, this); | |
this[listing.key] = component; | |
createdComponents[listing.componentFunctionName] = component; | |
} | |
} | |
}; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export const getFunctionName = (method: Function): string => { | |
if ((method as any).name !== undefined) { | |
return (method as any).name; | |
} | |
const stringified = method.toString(); | |
const typeDescriptorMatch = stringified.match(/class|function/)!; | |
const indexOfNameSpace = typeDescriptorMatch.index! + typeDescriptorMatch[0].length; | |
const indexOfNameAfterSpace = stringified.search(/\(|\{/); | |
return stringified.substring(indexOfNameSpace, indexOfNameAfterSpace).trim(); | |
}; | |
export const convertObjectToArray = <TItem>(object: { [i: string]: TItem }): TItem[] => { | |
const items: TItem[] = []; | |
for (const i in object) { | |
if ({}.hasOwnProperty.call(object, i)) { | |
items.push(object[i]); | |
} | |
} | |
return items; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment