Есть ли способ изменить параметр декораторов во время сборки?

У меня есть стандартное приложение Angular, и я хочу переключать некоторые компоненты во время сборки. Я хочу изменить с помощью одного ast trasformer параметры декоратора @Component следующим образом:

  • логин.component.ts

@Component({селектор: 'логин' .... })

в

@Component({селектор: 'not-use-this-login' .... })

  • обычай-login.component.ts

@Component({селектор: 'пользовательский логин' .... })

в

@Component({селектор: 'логин' .... })

Если я смогу изменить файлы ts перед процессом сборки Angular, я предполагаю, что Angular отобразит custom-login.component.ts, а не стандартный. Это может быть очень полезно, потому что я могу скомпилировать приложение для многих клиентов и не менять стандартный код. Я прочитал код сборки Angular, и они делают что-то действительно похожее с опцией шаблона для вставки встроенного html. Я создал репозиторий github для проверки этого трюка: https://github.com/gioboa/ng-ts-transformer @angular-builders/custom-webpack позволяет определить дополнительную конфигурацию веб-пакета. По ts-loader вызываю свой преобразователь (файл transformer.js). Я пробовал много способов заменить селектор, но, к сожалению, безуспешно. Документация по ast API в очень плохом состоянии.


person gioboa    schedule 21.07.2019    source источник
comment
проверьте ссылки базального Netanel netbasal.com/   -  person Eliseo    schedule 22.07.2019
comment
Спасибо за ваш ответ. Пользовательские декораторы оцениваются во время выполнения, когда создается класс, и для этой цели уже слишком поздно.   -  person gioboa    schedule 22.07.2019


Ответы (1)


В этом ответе используется только API-интерфейс компилятора, поскольку я не очень хорошо знаком с angular или процессом сборки, но, надеюсь, он вам поможет, и вы сможете его адаптировать.

  1. Найдите декораторы компонентов, которые соответствуют тому, что вы ищете.
  2. Преобразуйте строковые литералы, которые вы хотите изменить, в свойстве литерала объекта первого аргумента выражения вызова декоратора, которое является назначением свойства с инициализатором имени «селектор».

Использование моего инструмента 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 {
}
person David Sherret    schedule 22.07.2019
comment
Я реализовал решение в сборке Angular, но кажется, что updatePropertyAssignment создает еще один stringLiteral и не заменяет предыдущий. В узле теперь у меня есть 2 селектора: «логин» и «не использовать этот логин», возможно, я что-то упустил. - person gioboa; 22.07.2019
comment
В этом примере это работает. Возможно, опубликуйте какой-нибудь код в вопросе, который показывает, что проблема возникает? - person David Sherret; 22.07.2019
comment
Используя util ( var util = require('util'); ), я наконец понимаю объект узла. Вы правы, код правильный. Я вижу два объекта селектора, потому что один из них завернут в исходный: объект. В любом случае компилятор Angular не заботится о моей модификации. Я изучу код Angular, чтобы лучше понять, что добавляется. Спасибо за поддержку. - person gioboa; 22.07.2019