How to include a JS extern in a project that builds another JS extern?

;tl;dr version: I needed this in order to cascade-load JS across 3 layers of dependency (game, mod list, mods), but it’s not exactly possible. All JS has to be loaded in the top layer, which is fine because the 2nd layer can offer a method to specify scripts to load (a sum of the scripts residing on the 3rd layer, in my case, game mods).

Patient Haxer version:

I’m working on a moddable game targeting Haxe. This is composed of a number of projects. It’s a bit of a long post since it basically contains all the architecture of the game, but I do hope that this discussion will later show a good example about a way to do type-safe content loading in Haxe without recompiling the main application. If we get it to work :). If not, well… at least I tried :D.

  • the game, “Nexus”, which I seek to NOT recompile for every added mod.
  • an SDK that is a haxelib and does not concern this question (but this is where DataPackBase is defined)
  • a DataPackList project (which CAN be recompiled: the purpose of which being that HERE is where mods are added, as externs).
  • the DataPacks (mods). All content is stored here, even the game’s main content comes in a DataPack.

Now, the DataPackList project will gradually gain new DataPacks, for example a Global Data Pack (where I intend to store content that applies to all Mods) or the Fantasy Data Pack. These DataPacks are also Haxe projects targeting JS. I want to use them via extern in my DataPackList.

DataPackList calls the giveDataPacks for each DataPack. For now, there is only one DataPack called NDPGlobal (the Global Data Pack I just mentioned).

The game calls getDataPacks on DataPackList and should obtain a list of packs from ALL the mods contained in DataPackList.

But the problem is that the DataPackList itself is just a JS file. Despite the fact that the code does compile, it crashes upon using it in the game itself (it crashes when the game calls DataPackList.getDataPacks saying that the NDPGlobal is not defined.

I tried adding @:jsrequire in the DataPackList, but it only results in an error when loading the game:

“require is not defined” (it’s strange that the error is being thrown inside the constructor of DataPackBase, in the SDK, which is a Haxelib for both DataPackList and in NDPGlobal. This is necessary because DataPackList returns a list of DataPackBase from all data packs, such as NDPGlobal).

I’m pasing the files starting with the game call so you can see how these are used.

This is the game calling DataPackList. This works fine. The error is within DataPackList, see below.

var externDataPackList: DataPackList = new DataPackList();
var packs: Array<DataPackBase> = externDataPackList.getDataPacks();

This is the DataPackList file, which contains the NDPGlobalMod referred in as an extern. It compiles but crashes when used in the game. I commented where the crash happens.

package nexus.datapacklist;

import nexus.externs.datapacks.NDPGlobal;
import nexus.datapacks.*;

@:expose
//@:jsRequire('nexus.datapacks.NDPGlobal')
class DataPackList
{

	static public function main ()
	{
		trace("Hello from Data Pack List!");
	}

	public function new ()
	{
		trace("Data Pack List Constructor");
	}

	public function getDataPacks(): Array<DataPackBase>
	{
		trace ("Data Pack List getDataPacks was called");
		return new NDPGlobal().giveDataPacks(); //NDPGlobal is not defined when running this in the game.
	}

}

The extern for the file above:

package nexus.externs.datapacklist;

import nexus.datapacks.DataPackBase;

//@:jsRequire('nexus.datapacks.NDPGlobal')
@:native('nexus.datapacklist.DataPackList')
extern class DataPackList
{
	function new(): Void;
	function getDataPacks(): Array<DataPackBase>;
}

Finally, here is the NDPGlobal mod file:

package nexus.datapacks;

@:expose
class NDPGlobal
{
	static public function main ()
	{
		trace("Hello from the NDP Global.");
	}

	public function new ()
	{
		trace("Data Pack Global Constructor");
	}

	public function giveDataPacks(): Array<DataPackBase>
	{
		trace ("NDP Global giveDataPacks called");
		var packs: Array<DataPackBase> = new Array<DataPackBase>();
		var newdp: DataPackBase = new DataPackBase();
		newdp.name = "Data Pack Global";
		packs.push(newdp);
		return packs;
	}

}

And its extern definition:

package nexus.externs.datapacks;

import nexus.datapacks.DataPackBase;

@:native('nexus.datapacks.NDPGlobal')
extern class NDPGlobal
{
	function new(): Void;
	function giveDataPacks(): Array<DataPackBase>;
}

Now, in closing, I guess since I can recompile DataPackList, I could simply add the mods as Haxe Libraries in there and be done with it. However, this forces modders to necessarily write Haxe, whereas using extern I could allow them to use TypeScript or JavaScript too :slight_smile:

Thank you for your attention :slight_smile:

Well, turns out the @:require wasn’t required after all. I went and coded my own “dynamic require” :).

My solution now means that the DataPacks (such as NDPGlobal in the example above) will have to be deployed alongside DataPackList itself. That’s fine because this is part of the modding operation: adding data files alongside the game engine.

Of course, there’s the matter that the game engine then needs to load the scripts required by DataPackList. I solved that by adding a getNecessaryScripts method in DataPackList. The method informs the engine about what scripts need to be loaded in the HTML page which currently contains the game.

And that’s it, it works :).

At least at my current level of Haxe knowledge, I’m cool with this. I can’t find any way to bundle the JS file @:require’d in DataPackList into the output of the DataPackList project, which is kind of what I was after.

Of course, if you guys have other ideas, feel free to mention this.

1 Like