I have debug-time Typescript decorator @log
, which logs input/output/stats of the decorated functions.
I'd like to totally strip this particular @log
decorator when compiling release version.
It is easy to remove console.log statements from the release build or do things conditionally in the decorator code, but I would like to make sure there's no overhead with calling decorator function itself.
Is there any way this can be achieved with Typescript?
My project is webpack-based. If this is not possible with Typescript, maybe this can be done at later stage with Babel plugin, with UglifyJS or some other alternative plugin?
This compile-time Transformer removes Decorators from elements and from named imports.
I'll keep the code updated, as it's just a (working) test.
export default (decorators: string[]) => {
const importDeclarationsToRemove = [] as ts.ImportDeclaration[];
const updateNamedImports = (node: ts.NamedImports) => {
const newElements = node.elements.filter(v => !decorators.includes(v.name.getText()));
if (newElements.length > 0) {
ts.updateNamedImports(node, newElements);
} else {
importDeclarationsToRemove.push(node.parent.parent);
}
};
const createVisitor = (
context: ts.TransformationContext
): ((node: ts.Node) => ts.VisitResult<ts.Node>) => {
const visitor: ts.Visitor = (node: ts.Node): ts.VisitResult<any> => {
// Remove Decorators from imports
if (ts.isNamedImports(node)) {
updateNamedImports(node);
}
// Remove Decorators applied to elements
if (ts.isDecorator(node)) {
const decorator = node as ts.Decorator;
const identifier = decorator.getChildAt(1) as ts.Identifier;
if (decorators.includes(identifier.getText())) {
return undefined;
}
}
const resultNode = ts.visitEachChild(node, visitor, context);
const index = importDeclarationsToRemove.findIndex(id => id === resultNode);
if (index !== -1) {
importDeclarationsToRemove.splice(index, 1);
return undefined;
}
return resultNode;
};
return visitor;
};
return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) =>
sourceFile.fileName.endsWith('component.ts')
? ts.visitNode(sourceFile, createVisitor(context))
: sourceFile;
};
program.emit(
program.getSourceFile('test.component.ts'),
undefined,
undefined,
undefined,
{
before: [stripDecorators(['Stateful', 'StatefulTwo', 'StatefulThree'])]
}
);
Input:
import { Stateful, StatefulThree, StatefulTwo } from './decorators';
@Stateful
@StatefulTwo
@StatefulThree
export class Example {
private str = '';
getStr(): string {
return this.str;
}
}
JS output:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Example {
constructor() {
this.str = '';
}
getStr() {
return this.str;
}
}
exports.Example = Example;