Share how to output ESM module to js

The js code that compiled by haxe is hard to work together with modern js toolchain.
It can be require or import in nodejs env, but can’t in webpack, rollup and esbuild. The code register itself to window in browser envirment and exports nothing!! It’s reminds me of 10 years ago.

There’s the method:
Add a line to start of generated code var exports = {};.

// Generated by Haxe 4.3.4
var exports = {};
(function ($hx_exports, $global) { "use strict";

Edit the last line:

})(typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this, typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this);

to

})(exports);
export default exports;

finally, rename file to .mjs;

Now it’s an esm. Maybe use macro can make it be auto-generated, but i’m a beginner of haxe, still not know how to do this.

Update 2024/05/31:

I wrote a macro function for it, paste the code to your project and call makeESM in main(), it will do replace automatically.

genes is fine, but it can’t make a bundle and it’s can’t works with heaps.io, so it’s not a solution for me.


macro function makeESM() {
	if (Context.defined("js")) {
		Context.onAfterGenerate(() -> {
			var outFile = haxe.macro.Compiler.getOutput();
			var output = sys.io.File.getContent(outFile);

			var header = "(function ($hx_exports, $global) { \"use strict\";";
			var headerNew = 'var exports = {};\n' + header;
			var headerStart = output.indexOf(header);
			if (headerStart == -1)
				trace("ERROR: Can't generate esm because header string is not found");
			else
				output = output.substring(0, headerStart) + headerNew + output.substring(headerStart + header.length);

			var footer = "})(typeof exports != \"undefined\" ? exports : typeof window != \"undefined\" ? window : typeof self != \"undefined\" ? self : this, typeof window != \"undefined\" ? window : typeof global != \"undefined\" ? global : typeof self != \"undefined\" ? self : this);";
			var footerNew = '})(exports);\nexport default exports;';
			var footerStart = output.indexOf(footer, output.length - 500);
			if (footerStart == -1)
				trace("ERROR: Can't generate esm because footer string is not found");
			else
				output = output.substring(0, footerStart) + footerNew + output.substring(footerStart + footer.length);

			sys.io.File.saveContent(outFile, output);
			// trace("ESM module rewrite is done.");
		});
	}

	return {expr: EConst(CString("", SingleQuotes)), pos: Context.makePosition({file: '', min: 0, max: 0})};
}

Unfortunately this has been in Haxe’s backlog for a long time: [js][es6] add ES6 module exports · Issue #8033 · HaxeFoundation/haxe · GitHub

I built a custom generator to ouput esm which also splits the output into separate js files:

Still hopeful that Haxe will someday support both.

1 Like

I’ve tried genes, and it works very well! I can confirm that bundlers handle its output nicely.

Good job! I thought it’s the best esm tool for now.
but i have a request: Could I export a bundle instead of seprated files?
Tree shaking is available for genes, much better than my trick.

I’ve tested genes with Webpack, Vite, and Rollup, and all three worked well on my projects.