我有一个Angular标准应用程序,我想在构建时切换一些组件。 我想使用一个ast变形器更改@Component装饰器选项,如下所示:
@Component({选择器:'登录'....})
进入
@Component({选择器:'not-use-this-login'....})
@Component({选择器:'custom-login'....})
进入
@Component({选择器:'登录'....})
如果我可以在Angular构建过程之前修改ts文件,我想Angular将呈现custom-login.component.ts而不是标准的。 这可能非常有用,因为我可以为许多客户编译应用程序,而无需更改标准代码。 我阅读了Angular的构建代码,并且它们的功能与注入内嵌html的模板选项非常相似。 我创建了一个github仓库来测试这个技巧: https://github.com/gioboa/ng-ts-transformer @ angular-builders / custom-webpack允许您定义一个额外的webpack配置。通过ts-loader,我将其称为变压器(transformer.js文件)。 我尝试了很多方法来替换选择器,但不幸的是没有成功。 AST API文档的使用情况非常差。
答案 0 :(得分:0)
此答案仅使用编译器api,因为我对angular或生成过程不太熟悉,但是希望会对您有所帮助,并且您应该能够适应它。
使用我的工具ts-ast-viewer.com帮助查看需要检查的内容...
// Note: This code mixes together the act of analyzing and transforming.
// You may want a stricter separation, but that requires creating an entire
// architecture around this.
import * as ts from "typescript";
// create a source file ast
const sourceFile = ts.createSourceFile("/file.ts", `import { Component } from 'whereever';
@Component({ selector: 'login' })
class Test {
}
`, ts.ScriptTarget.Latest);
// transform it
const transformerFactory: ts.TransformerFactory<ts.SourceFile> = context => {
return file => visitChangingDecorators(file, context) as ts.SourceFile;
};
const transformationResult = ts.transform(sourceFile, [transformerFactory]);
const transformedSourceFile = transformationResult.transformed[0];
// see the result by printing it
console.log(ts.createPrinter().printFile(transformedSourceFile));
function visitChangingDecorators(node: ts.Node, context: ts.TransformationContext) {
// visit all the nodes, changing any component decorators
if (ts.isDecorator(node) && isComponentDecorator(node))
return handleComponentDecorator(node);
else {
return ts.visitEachChild(node,
child => visitChangingDecorators(child, context), context);
}
}
function handleComponentDecorator(node: ts.Decorator) {
const expr = node.expression;
if (!ts.isCallExpression(expr))
return node;
const args = expr.arguments;
if (args.length !== 1)
return node;
const arg = args[0];
if (!ts.isObjectLiteralExpression(arg))
return node;
// Using these update functions on the call expression
// and decorator is kind of useless. A better implementation
// would only update the string literal that needs to be updated.
const updatedCallExpr = ts.updateCall(
expr,
expr.expression,
expr.typeArguments,
[transformObjectLiteral(arg)]
);
return ts.updateDecorator(node, updatedCallExpr);
function transformObjectLiteral(objectLiteral: ts.ObjectLiteralExpression) {
return ts.updateObjectLiteral(objectLiteral, objectLiteral.properties.map(prop => {
if (!ts.isPropertyAssignment(prop))
return prop;
if (!prop.name || !ts.isIdentifier(prop.name))
return prop;
if (prop.name.escapedText !== "selector")
return prop;
if (!ts.isStringLiteral(prop.initializer))
return prop;
if (prop.initializer.text === "login") {
return ts.updatePropertyAssignment(
prop,
prop.name,
ts.createStringLiteral("not-use-this-login")
);
}
return prop;
}));
}
}
function isComponentDecorator(node: ts.Decorator) {
// You will probably want something more sophisticated
// that analyzes the import declarations or possibly uses
// the type checker in an initial pass of the source files
// before transforming. This naively just checks if the
// decorator is a call expression and if its expression
// has the text "Component". This definitely won't work
// in every scenario and might possibly get false positives.
const expr = node.expression;
if (!ts.isCallExpression(expr))
return false;
if (!ts.isIdentifier(expr.expression))
return false;
return expr.expression.escapedText === "Component";
}
输出:
import { Component } from "whereever";
@Component({ selector: "not-use-this-login" })
class Test {
}