Emscripten: EXPORT_ES6 flag doesn't work in non-preprocessed environments

Created on 1 Oct 2018  路  13Comments  路  Source: emscripten-core/emscripten

Short version:
There exists no middle ground between the non-modularized default output and the EXPORT_ES6 output. This means that for anyone to consume the output, they must first run a preprocessor on the library. Instead if it were written in an ES5 syntax but still use ES6 modules, it would be consumable without preprocessing.

Long version:
I've been looking into using embind to output a TypeScript *.d.ts file. One of the first hurdles I ran into was getting the Module typed correctly. Consider the following embind syntax:

EMSCRIPTEN_BINDINGS(StringFactory) {
    class_<StringFactory>("StringFactory")
        .smart_ptr_constructor("StringFactory", &std::make_shared<StringFactory>)
        .function("getString", &StringFactory::getString)
        ;
}

For the EXPORT_ES6 output, the typescript definition would look like the following:

declare module "client-shared-js" {
    export interface StringFactory {
        new(): StringFactory;
        getString(): string;
    }

    export interface ClientShared {
        StringFactory: StringFactory;
    }

    export function Module(emscriptenArgs: any): ClientShared;;
}

But for the non EXPORT_ES6 output, this is actually much harder to type because it's not using a modular output, it's using a direct export. This means in TypeScript specifically it must be imported in an outdated manner

import Module = require("client-shared-js");

Which is not ideal because you have to go through a much less standard way of typing the module. Which would be preferred is the ES Module style

import Module, { ClientShared } from "client-shared-js";

Sadly, because the EXPORT_ES6 syntax uses ES6 syntax (export default Module) and not ES5 (module.exports.default = Module), it requires a preprocessor to execute for several javascript tooling who haven't updated to support ES6 such as many test frameworks (e.g. Mocha phantomjs).

Instead of requiring everyone who wants to use a more consistent syntax to include a preprocessor, my suggestion is to change Line 2691 of emcc.py from

f.write('''export default %s;''' % shared.Settings.EXPORT_NAME) 

To

f.write('''exports.default = %s;''' % shared.Settings.EXPORT_NAME) 

which is what it gets compiled out after babel's transpilation making consumption easier.

All 13 comments

Of course EXPORT_ES6 uses ES6 syntax! Why would it do anything else?

PhantomJS is a dead project. I don't know what's recommended instead of it now, but it's not reasonable to change working code for a dead project that hasn't has a release in two years.

I think many people are moving over to Jest... which also doesn't support direct ES6 imports and also requires a preprocessor:
https://stackoverflow.com/questions/29975815/react-unit-test-with-jest-in-es6

Changing this allows the output of this library to be used more flexibly while achieving the original goal of having an ES6 style output.

But you're not proposing anything remotely related to ES6 exports. You're proposing CommonJS exports, which is what you get (as part of UMD) if you don't turn on EXPORT_ES6.

But for the non EXPORT_ES6 output, this is actually much harder to type because it's not using a modular output, it's using a direct export. This means in TypeScript specifically it must be imported in an outdated manner ... Which is not ideal because you have to go through a much less standard way of typing the module. Which would be preferred is the ES Module style

Which is what you get with EXPORT_ES6. I don't understand the problem you've got.

The UMD outputs currently implemented don't export to default thus aren't compatible with ES modules. I could add a EXPORT_ES_MODULE flag that is a mix between ES_6 and the default if that is preferred but I figured that would be confusing since there is already an ES6 flag.

Using an ES import on Emscripten's UMD export will work fine in Node, so you can't say they aren't compatible with ES modules. I understand that the way to import them in Typescript is through the custom import Module = require("module"); syntax.

It is unfortunate that so many testing tools don't support ES modules, but I don't think introducing a third export format to Emscripten would be a good solution.

You might be able to pass --experimental-modules to Mocha? https://github.com/mochajs/mocha/pull/3438

> mocha --experimental-modules --require ts-node/register src/**/*.test.ts

D:\Projects\boilerplate-emscripten-typescript\client-shared-js\ship\client-shared-js.js:18
export default Module;
^^^^^^
SyntaxError: Unexpected token export
    at createScript (vm.js:80:10)
    at Object.runInThisContext (vm.js:152:10)
    at Module._compile (module.js:624:28)
    at Object.Module._extensions..js (module.js:671:10)
...

There hasn't been a Mocha release since that PR was merged, but you could try making the edit yourself, it's only one line: https://github.com/mochajs/mocha/commit/f7456513e00eea3fd925377504d4b6da9c8b10ba

As I said, it is unfortunate that these tools don't support ES modules properly. Including Node. But Emscripten's two export formats to my knowledge are working as intended. If there's something specific to embind then maybe that does need fixing though.

It was more I was looking into https://github.com/kripken/emscripten/issues/7083 since I had a project from a while ago that did something similar. I figured I could try merging it in but this ES module issue was always a sticking point. I actually wrote some custom code that worked around the problem using the --post-js flag but knowing that code, it should not be brought into this repository haha.

Another option is to add a third flag EXPORT_ESMODULE but again, I feel it would be much more confusing to have both than just writing the EXPORT_ES6 output in a transpiled form.

I was specifically thumbs-downing the idea of making EXPORT_ES6 output export.default, because that's just ridiculous. A third export option is definitely possible, but unwise IMO.

Sorry, but from my perspective it feels like you're trying to have your cake and eat it too. There are three safe and simple options:

  • Use CommonJS throughout
  • Use ES Modules throughout
  • Transpile ES Modules for tools that don't support them

Pretending that Emscripten's output has already been passed through a transpiler in order to suit one narrow use case in one non-standard implementation of CJS->ES is messy, and sure to result in complicated issues down the line.

Solving #7083 shouldn't require changing the export format. Or did you mean another open issue.

Generating the typings for the objects isn't the hard part. Tying it to the return variable of Module is.

For the UMD case, due to direct exports you can't export the types next to the definition of the Module function. ES6 resolves this because of ES Modules but it's hard for people who can't use it because it's not transpiled. So yea I guess cake and eat it too.

I'll just wait until ES6 is more standard and @babel/preset-es2015 becomes deprecated to continue on the issue then.

Just having a think after a couple of days, and doesn't the problem really have nothing to do with the exporting system - it's just what happens when you use MODULARIZE? The embind types aren't directly exported, they're exported on the inner Module returned when you call the outer factory Module, right?

If I understand your explanation I think you're correct.

Closing since it doesn't look like this wants to be fixed

Was this page helpful?
0 / 5 - 0 ratings