Skip to content

Commit

Permalink
feat(crayon): prevent memory leaks and cpu overhead when function ret…
Browse files Browse the repository at this point in the history
…urns big variety of outputs

This commit introduces new Crayon property: usesFunc
It indicates whether Crayon instance used mapped function, if so - prevent further caching of this instance.
This is done to prevent memory leaks or unnecessary cpu overhead
caused when function has many different output possibilities
  • Loading branch information
Im-Beast committed Jul 5, 2022
1 parent 8be4bb5 commit f5ee542
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 10 deletions.
41 changes: 31 additions & 10 deletions src/crayon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export type Crayon<
& typeof prototype
& {
styleBuffer: string;
usesFunc: boolean;
keyword(style: Style): Crayon<C, O>;
keyword(style: string): Crayon<C, O>;
ansi3(code: number): Crayon<C, O>;
Expand All @@ -127,7 +128,10 @@ export type Crayon<
}
& O;

export function buildCrayon<T extends Crayon = Crayon>(styleBuffer = ""): T {
export function buildCrayon<T extends Crayon = Crayon>(
styleBuffer = "",
usesFunc = false,
): T {
function crayon(
this: T,
single: unknown & { raw?: boolean },
Expand All @@ -145,6 +149,7 @@ export function buildCrayon<T extends Crayon = Crayon>(styleBuffer = ""): T {
}

crayon.styleBuffer = styleBuffer;
crayon.usesFunc = usesFunc;

return Object.setPrototypeOf(crayon, prototype);
}
Expand Down Expand Up @@ -198,7 +203,7 @@ function mapFunc(name: string, func: CrayonStyleFunction, only = false): void {
return crayon;
}
: function (this: Crayon, ...args: unknown[]) {
return buildCrayon(this.styleBuffer + (func(...args) ?? ""));
return buildCrayon(this.styleBuffer + (func(...args) ?? ""), true);
},
});
return;
Expand All @@ -210,7 +215,10 @@ function mapFunc(name: string, func: CrayonStyleFunction, only = false): void {
return crayon;
}
: function (this: Crayon, ...args: unknown[]) {
return buildCrayon(this.styleBuffer + (func(...args, false) ?? ""));
return buildCrayon(
this.styleBuffer + (func(...args, false) ?? ""),
true,
);
},
});

Expand All @@ -221,7 +229,10 @@ function mapFunc(name: string, func: CrayonStyleFunction, only = false): void {
return crayon;
}
: function (this: Crayon, ...args: unknown[]) {
return buildCrayon(this.styleBuffer + (func(...args, true) ?? ""));
return buildCrayon(
this.styleBuffer + (func(...args, true) ?? ""),
true,
);
},
});
}
Expand Down Expand Up @@ -282,7 +293,10 @@ function mapStyle(
const prepareCrayon = () => {
const $code = typeof code === "function" ? code() : code;

const builtCrayon = buildCrayon(this.styleBuffer + $code);
const builtCrayon = buildCrayon(
this.styleBuffer + $code,
this.usesFunc,
);
// Instead of building crayon every time property gets accessed
// simply replace getter with built crayon instance
Object.defineProperty(this, name, {
Expand All @@ -292,12 +306,19 @@ function mapStyle(
return builtCrayon;
};

// Overwrite crayon instance when colorSupport value changes to adapt styles
eventTarget.addEventListener("update", () => {
prepareCrayon();
});
const crayon = prepareCrayon();

// Don't cache crayon when it uses function:
// This is done to prevent memory leaks or cpu overhead
// caused when function has many different output possibilities
if (!crayon.usesFunc) {
// Overwrite crayon instance when colorSupport value changes to adapt styles
eventTarget.addEventListener("update", () => {
prepareCrayon();
});
}

return prepareCrayon();
return crayon;
};
}

Expand Down
9 changes: 9 additions & 0 deletions tests/crayon.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,15 @@ Deno.test("Chaining", async (t) => {
);
});

await t2.step("Proper usesFunc value", () => {
assertEquals(crayon.red.bgBlue.bold.usesFunc, false);
assertEquals(crayon.red.rgb(230, 127, 30).bold.usesFunc, true);
assertEquals(crayon.yellow.rgb(230, 127, 30).bold.usesFunc, true);
assertEquals(crayon.yellow.bold.usesFunc, false);
assertEquals(crayon.rgb(127, 255, 50).red.bold.usesFunc, true);
assertEquals(crayon.red.bgBlue.rgb(127, 255, 50).bold.usesFunc, true);
});

await t2.step("Error handling", () => {
assertThrows(() => crayon.hex(""));
assertThrows(() => crayon.hex("c"));
Expand Down

0 comments on commit f5ee542

Please sign in to comment.