fix: Mangled class names cause x is not a function in prod (#13184)

* fix: Mangled class names cause 'is not a function' in prod

* feedback, switch to WeakMap
This commit is contained in:
Tom Moor
2026-07-28 18:18:29 -04:00
committed by GitHub
parent 71e9f1a5df
commit 1fba97f8b6
2 changed files with 39 additions and 5 deletions
+32
View File
@@ -0,0 +1,32 @@
import { AfterChange, LifecycleManager } from "./Lifecycle";
describe("LifecycleManager", () => {
it("executes registered hooks", () => {
const hook = vi.fn();
class Model {
static onChange = hook;
}
AfterChange(Model, "onChange");
LifecycleManager.executeHooks(Model, "afterChange", "arg");
expect(hook).toHaveBeenCalledWith("arg");
});
it("does not leak hooks between classes that share a name", () => {
const hook = vi.fn();
// Minified builds can mangle two unrelated classes to the same name.
const One = class Model {
static onChange = hook;
};
const Two = class Model {};
AfterChange(One, "onChange");
expect(() =>
LifecycleManager.executeHooks(Two, "afterChange", "arg")
).not.toThrow();
expect(hook).not.toHaveBeenCalled();
});
});
+7 -5
View File
@@ -1,12 +1,14 @@
type ModelClass = { readonly name: string };
type ModelClass = Function;
type Hook = (...args: unknown[]) => unknown;
export class LifecycleManager {
private static hooks = new Map<string, Map<string, string[]>>();
// Keyed by the class itself minified builds mangle class names and two
// classes in separate chunks can end up sharing one.
private static hooks = new WeakMap<ModelClass, Map<string, string[]>>();
public static getHooks(target: ModelClass, lifecycle: string): string[] {
const key = `lifecycle:${lifecycle}`;
const modelHooks = this.hooks.get(target.name);
const modelHooks = this.hooks.get(target);
return modelHooks?.get(key) ?? [];
}
@@ -28,11 +30,11 @@ export class LifecycleManager {
lifecycle: string
): void {
const key = `lifecycle:${lifecycle}`;
let modelHooks = this.hooks.get(target.name);
let modelHooks = this.hooks.get(target);
if (!modelHooks) {
modelHooks = new Map();
this.hooks.set(target.name, modelHooks);
this.hooks.set(target, modelHooks);
}
let lifecycleHooks = modelHooks.get(key);