HTML5 'final' build crashing

openfl 3.3.5
lime 2.6.4
haxe 3.2.0

Has anyone been able to get a HTML5-target ‘final’ (minified) build to run? As long as I can remember, this has always failed for me due to crashes. The debug / release builds run properly, but I’d like to post a final build and not release. The immediate crash I see is this:

Uncaught TypeError: Cannot read property '0' of undefined

It is failing to parse an enum value in my source code because the enum table is null. Any ideas? Thanks!

Are you using Reflect?

Using a browser debugger, can you tell where this code occurs? Perhaps something needs a @:keep on it?

EDIT: Have you tested the OpenFL samples, or a newer release of Lime and OpenFL?

The latest versions yield the same crash/error:

openfl 3.4.0
lime 2.7.0
haxe 3.2.1

As far as the debugging goes, it crashes in get_name() of one of my classes, reporting state is null:

JS:

MyClass.prototype = {
get_name: function() {
	return this.state[0];
}

HAXE:

public var state( get, null ):EnumValue;
private function get_name():String { return state.getName(); }
public function new( stateId:EnumValue ) { 
    state = stateId;
}

So my object sets that variable immediately inside the constructor. The getter just converts the EnumValue to String. I am almost positive that stateId is not coming in null anywhere, but I will triple check that just in case (though I would expect the debug/release to also crash if this were the case).

I would try adding a few checks, like trace ("state == " + state); before you call state.getName() and similar additional traces to get an idea of how far it gets before it fails

I will try to navigate around the errors and see what I can figure out. I’ll also put together a simple test case if the issue persists and post it. I feel like after minifcation, some things are getting called in different orders, so I’ll see if I can support/debunk this theory (or any others). Thanks.

So in the final build, I put a Browser.alert call (traces don’t appear to trigger in final) in the getter, in the constructor, and in the function calling the getter (“myFunc”).

The correct order should be: constructor, myFunc, getter.
The order that I’m seeing is: myFunc, getter,
Since the constructor is not getting called, it is clearly the case that the variable is null. Also noting that this behaves as expected in a debug or release build. The constructor is triggered via Type.createInstance().

The code is essentially this:

public var name(get,null):String;
private function get_name(): String  { return myVal.getName(); }

function new ( val: EnumValue ) {
    myVal = val;
}

private function myFunc(): Void {
    trace( "name= " + name );
}

Update1: Pretty sure I’ve found the problem. In a final build, Type.createInstance() doesn’t function. When I trace the object, it only shows as empty “{ }”. When I manually called ‘new MyClass()’ where I could, then the game runs correctly in final mode. However, anywhere else where Type.createInstance() is used, it fails to create a new class. It is weird that it knows the correct function to call in that class (so maybe it is creating the structure?), but it definitely does not call the constructor ever.

Update2: I noticed that Type.createInstance() is used all over the place in openfl (like Type.createInstance(DocumentClass,[]) ), and those seem to be working. So for measure, here’s my function that is called where I see Type.createInstance failing to trigger the class constructors, in case it’s something I’m doing to make it fail.

function createState( id: EnumValue, t: Class<IGameState> ): Void {
    var state = Type.createInstance( t, [ id ] );    // new SomeClass( id );
}

Update3: I tried a very simple example of Type.createInstance() and confirmed the same behavior there, too.

//Main.hx
var t = Type.createInstance( TestClass, [] );
Browser.alert( t );  //final: {   }    //release: { someStr:TEST CLASS STR, m_int:22 }
t.debug();           //final: { undefined, undefined }    //release: { TEST CLASS STR, 22 }

//TestClass.hx
public var someStr:String;
private var m_int:Int;

public function new()  {
    someStr = "TEST CLASS STR";
    m_int = 22;
}

public function debug(): Void {
    Browser.alert( someStr + ", " + m_int );
}

I’ve heard that -final implies -dce full. DCE removes all functions that don’t seem to be referenced. However, it isn’t smart enough to understand reflection, so if that’s the only way you access a particular function, it’ll be deleted.

Fortunately, you can tell DCE not to mess with a given function:

@:keep function new ( val: EnumValue ) {
    myVal = val;
}

Thanks! I will give that a try.

Update: Ah-ha! That did the trick. Thanks a bunch! I’m still concerned about needing to do that for everything that is reflected, but at least I have a fix/work-around.

You can also add @:keep to the class itself, in which case all of the class’s functions will be kept. There’s also @:keepSub, which applies to that class and every single subclass. (Warning: this may keep a lot of functions you don’t need, which means the download will take longer than necessary.)