Skip to content

Instantly share code, notes, and snippets.

@JoshuaKGoldberg
Created December 30, 2017 21:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshuaKGoldberg/8b94ac3883919f682944328d6bdad9bb to your computer and use it in GitHub Desktop.
Save JoshuaKGoldberg/8b94ac3883919f682944328d6bdad9bb to your computer and use it in GitHub Desktop.
Ridiculously tiny IoC with almost no features
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;
};
};
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];
}
}
};
};
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);
})
});
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;
}
}
};
};
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