In this article, we analyse the Component decorator in TypeDoc.
Let’s take a step back and first understand what’s a decorator in TypeScript.
Decorator in TypeScript
A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor, property, or parameter. Decorators use the form @expression
, where expression
must evaluate to a function that will be called at runtime with information about the decorated declaration. — Source.
For example, given the decorator @sealed
we might write the sealed
function as follows:
function sealed(target) {
// do something with 'target' ...
}
Class decorator in TypeScript
Let’s pick a simple and easy to understand example from TypeScript documentation about how to use class decorator.
@sealed
class BugReport {
type = "report";
title: string;
constructor(t: string) {
this.title = t;
}
}
Here @sealed is a class decorator applied just above the class declaration. This @sealed is a decorator that is applied at run time.
If you want to prevent any modifications to the class BugReport, you could define sealed function as below:
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
When @sealed
is executed, it will seal both the constructor and its prototype, and will therefore prevent any further functionality from being added to or removed from this class during runtime by accessing BugReport.prototype
or by defining properties on BugReport
itself — Source
With this knowledge, we are now prepared to understand the @Component decorator in TypeDoc code base.
@Component Decorator in TypeDoc
@Component decorator is imported from lib/utils/components.ts
This is a decorator factory that returns an arrow function that is executed at run time. You can read more about decorator factory in TS docs.
export function Component(options: ComponentOptions) {
// _context is ClassDecoratorContext, but that then requires a public constructor
// which Application does not have.
return (target: Function, _context: unknown) => {
const proto = target.prototype;
if (!(proto instanceof AbstractComponent)) {
throw new Error(
"The `Component` decorator can only be used with a subclass of `AbstractComponent`.",
);
}
if (options.childClass) {
if (!(proto instanceof ChildableComponent)) {
throw new Error(
"The `Component` decorator accepts the parameter `childClass` only when used with a subclass of `ChildableComponent`.",
);
}
childMappings.push({
host: proto,
child: options.childClass,
});
}
const name = options.name;
if (name) {
proto.componentName = name;
}
// If not marked internal, and if we are a subclass of another component T's declared
// childClass, then register ourselves as a _defaultComponents of T.
const internal = !!options.internal;
if (name && !internal) {
for (const childMapping of childMappings) {
if (!(proto instanceof childMapping.child)) {
continue;
}
const host = childMapping.host;
host["_defaultComponents"] = host["_defaultComponents"] || {};
host["_defaultComponents"][name] = target as any;
break;
}
}
};
}
There is a lot happening in this Component decorator, instead of trying to understand it all, let’s pick up on the easy ones we can deduce.
- proto instanceOf
This check is used to throw an error in case the instance is not supported.
2. proto.componentName
proto.componentName is updated based on name passed to the decorator. In this case, the name is set to “application”.
3. childMappings
// If not marked internal, and if we are a subclass of
// another component T's declared
// childClass, then register ourselves as a _defaultComponents of T.
const internal = !!options.internal;
if (name && !internal) {
for (const childMapping of childMappings) {
if (!(proto instanceof childMapping.child)) {
continue;
}
const host = childMapping.host;
host["_defaultComponents"] = host["_defaultComponents"] || {};
host["_defaultComponents"][name] = target as any;
break;
}
}
There are some updates made to childMapping.host
About me:
Hey, my name is Ramu Narasinga. I study large open-source projects and create content about their codebase architecture and best practices, sharing it through articles, videos.
I am open to work on an interesting project. Send me an email at ramu.narasinga@gmail.com
My Github - https://github.com/ramu-narasinga
My website - https://ramunarasinga.com
My Youtube channel - https://www.youtube.com/@thinkthroo
Learning platform - https://thinkthroo.com
Codebase Architecture - https://app.thinkthroo.com/architecture
Best practices - https://app.thinkthroo.com/best-practices
Production-grade projects - https://app.thinkthroo.com/production-grade-projects
References:
https://github.com/TypeStrong/typedoc/blob/master/src/lib/application.ts#L100
https://www.typescriptlang.org/docs/handbook/decorators.html
https://github.com/TypeStrong/typedoc/blob/master/src/lib/utils/component.ts#L39
https://www.typescriptlang.org/docs/handbook/decorators.html#decorator-factories
Top comments (0)