Adjust output js


(Remco Huijser) #1

I want to change to make some small changes to the JS file outputted by the compilation process. What is the best way of doing this?

What I tried so far is overwriting the JS generator, but this seems like too much effort for what I want to achieve. The other approach that worked quite well is to open the output js file after it has been generated, make the changes and save it again. This does feel like dirty and involves IO operations. I was wondering whether there is a better approach…

As always thanks a lot for the input!

(Andy Li) #2

It all depends on what changes you wanna make. So what do you want to change?

(Remco Huijser) #3

That is a very valid question, sorry for using such a short problem description.

There are two cases I want to deal with:

  1. We obfuscate our code by adding @:native values to classes and functions. This means that fields and properties get renamed. In our templating system we sometimes need to use reflection (reference a field with a string). I was experimenting with the following: instead of using the field name “FieldName” directly I wrap this in a function like GetFieldName(Something). This gets compiled to GetFieldName(x) because of obfuscation. I can then search this with a regex and replace it with “x”.

  2. It is a bit much to explain (sorry) but we generate some code after typing has been done, because we need the rich information provided by the typed AST. This code is then inserted at the right location in the output js file. So again a simple search replace here.

Thanks for your attention so far!

(Remco Huijser) #4

@andyli: can you please take a look at this?


(Andy Li) #5

For the first use case, you may use macros instead of post-processing. The idea is to implement “GetFieldName” as a macro function, which lookup @:native of the given class field.

using Lambda;
class SmartReflect {
    macro static public function nativeFieldName(cls:ExprOf<Class<Dynamic>>, fieldName:String):ExprOf<String> {
        var clsStr = haxe.macro.ExprTools.toString(cls);
        var clsType = haxe.macro.Context.getType(clsStr);
        switch(clsType) {
            case TInst(t, _):
                var field = t.get().fields.get().find(function(f) return == fieldName);
                var nativeMeta = field.meta.extract(":native").pop();
                return if (nativeMeta == null) {
                    macro $v{fieldName};
                } else {
                    switch (nativeMeta.params[0].expr) {
                        case EConst(CString(realName)):
                            macro $v{realName};
                        case _:
                            throw "unknown @:native format";
            case _:
                return haxe.macro.Context.error('cls should be a Class', cls.pos);

class MyObject {
    @:native("a") public var myA:String = "A";
    public var myB:String = "B";
    public function new():Void {}

class Test
    public static function main() 
        var myObj = new MyObject();
        trace(Reflect.field(myObj, SmartReflect.nativeFieldName(MyObject, "myA")));
        trace(Reflect.field(myObj, SmartReflect.nativeFieldName(Test.MyObject, "myA")));
        trace(Reflect.field(myObj, SmartReflect.nativeFieldName(MyObject, "myB")));

For the second case, you can use rtti. Or, you can use haxe.macro.Context.onGenerate() to store the info you need in some custom metadata, which you can inspect at runtime using haxe.rtti.Meta.

(Remco Huijser) #6

Much appreciated! I will need to take closer look and will let you know if anything is unclear.