Type registration via decorators (e.g., Decorators.registerClass, Decorators.registerEditor, etc.) is now deprecated (it still works but is not recommended):
import { Decorators, Widget } from "@serenity-is/corelib";
@Decorators.registerClass("MyProject")
export class MyType extends Widget<any> {
}
Instead, use a static [Symbol.typeInfo] property declaration:
import { Widget } from "@serenity-is/corelib";
export class MyType extends Widget<any> {
static [Symbol.typeInfo] =
this.registerClass("MyProject.MyModule.MyType");
}
This new approach offers several advantages over decorators. Most importantly, decorator information is not emitted by TypeScript in declaration files (e.g., .d.ts), making it difficult or impossible to identify registration names for referenced project/npm package types.
Another reason for this change is a longstanding bug in esbuild (see issue) with decorators and property initializers, which currently only has a workaround by using experimentalDecorators: true in tsconfig.json.
The new registration methods also support passing interfaces or attributes as an array in the second argument. For example, to set `@Decorators.panel(true)` on a class, you can do:
import { Widget, PanelAttribute } from "@serenity-is/corelib";
export class MyType extends Widget<any> {
static [Symbol.typeInfo] =
this.registerClass("MyProject.MyModule.MyType",
[new PanelAttribute(true)]);
}
Note: When using this new registration method, TypeScript will not allow any decorators on the containing type, as the this expression is referenced in a static field initializer. If you need to use third-party decorators, you can use an alternative registration style:
import { Widget, classTypeInfo, registerType } from "@serenity-is/corelib";
@someCustomDecorator("test")
export class MyType extends Widget<any> {
static [Symbol.typeInfo] =
classTypeInfo("MyProject.MyModule.MyType");
static { registerType(this); }
}
This is equivalent to this.registerType, as the registerType static method in Widget calls classTypeInfo and registerType internally.
This style is also useful for formatter registration, as formatters typically do not have a base type and cannot simply call this.registerType like widget subclasses:
export class MyFormatter implements Formatter {
static [Symbol.typeInfo] =
formatterTypeInfo("MyProject.MyModule.MyFormatter");
static { registerType(this); }
}
For consistency, you may also choose to extend the new FormatterBase class:
export class MyFormatter extends FormatterBase implements Formatter {
static [Symbol.typeInfo] =
this.registerFormatter("MyProject.MyModule.MyFormatter");
}
For all registration styles, you can now pass only the namespace ending with a dot (the enclosing type name will be auto-appended):
import { Widget } from "@serenity-is/corelib";
export class MyType extends Widget<any> {
static [Symbol.typeInfo] = this.registerClass("MyProject.MyModule.");
}
Sergen / Serenity.Pro.Coder now also generates namespace constants under the Modules/ServerTypes/Namespaces.ts file, so you can use them to avoid typos:
import { Widget } from "@serenity-is/corelib";
import { nsMyModule } from "../../ServerTypes/Namespaces";
export class MyType extends Widget<any> {
static [Symbol.typeInfo] = this.registerClass(nsMyModule);
}
We hope this will reduce mistakes when renaming classes or using a namespace with mismatched casing.