webpack - Ta bort Typescript-dekorator i release-build
Jag har en Typescript-dekoration @log för debug-tid, som loggar indata/utdata/statistik för de dekorerade funktionerna.
Jag skulle vilja helt ta bort denna specifika @log-dekorerare när jag kompilerar releaseversionen.
Det är lätt att ta bort console.log-uttryck från releasebyggnaden eller göra saker villkorligt i dekoratorkoden, men jag vill försäkra mig om att det inte finns någon extra belastning vid att anropa dekoratorfunktionen själv.
Finns det något sätt detta kan uppnås med Typescript?
Mitt projekt är baserat på webpack. Om detta inte är möjligt med Typescript, kanske detta kan göras i ett senare skede med en Babel-plugin, med UglifyJS eller någon annan alternativ plugin?
Svar #1
Denna kompileringstid Transformer tar bort Dekoratorer från element och från namngivna importer.
Jag kommer att hålla koden uppdaterad, eftersom det bara är ett (fungerande) 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'])]
}
);
Inmatning :
import { Stateful, StatefulThree, StatefulTwo } from './decorators';
@Stateful
@StatefulTwo
@StatefulThree
export class Example {
private str = '';
getStr(): string {
return this.str;
}
}
JS-utdata:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class Example {
constructor() {
this.str = '';
}
getStr() {
return this.str;
}
}
exports.Example = Example;
Svar #2
När jag ställde denna fråga förbisedde jag helt ett pinsamt trivialt aspekt. Dekoratorfunktionen själv anropas bara en gång per metoddeklaration. Om dekoratorn utvärderas vid initialiseringstid till en no-op-funktion kommer overheaden bara att uppstå vid initialiseringstid och den kommer att vara ganska minimal som demonstreras i koden nedan.
Klassinstansiering och körningstidens funktionsanrop till funktionerna, markerade med @log-dekoreraren, kommer att vara fria från någon extra belastning.
const DEBUG = false;
const logDebug = function(_target: any, key: string, descriptor: PropertyDescriptor): any {
console.log("log(): called");
const originalMethod = descriptor.value;
descriptor.value = function(...args: any[]) {
const functionName = key;
console.log(functionName + "(" + args.join(", ") + ")");
const result = originalMethod.apply(this, args);
console.log("=> " + result);
return result;
};
return descriptor;
};
const logNoop = function() {};
const log = DEBUG ? logDebug : logNoop;
class Test {
@log
test(a: number, b: number) {
console.log("test(): called", a, b);
}
}
new Test().test(1, 2);
new Test().test(3, 5);
Fragment av den kompilerade JS för att visa att overhead faktiskt är minimal:
var Test = /** @class */ (function () {
function Test() {
}
Test.prototype.test = function (a, b) {
console.log("test(): called", a, b);
};
__decorate([
log
], Test.prototype, "test", null);
return Test;
}());
