Emscripten: How to use TypeScript declarations for Emscripten

Created on 23 Jan 2020  路  8Comments  路  Source: emscripten-core/emscripten

Related: #9674 #7083

I would like to discuss the current best way to use typing features of TypeScript with Emscripten. If you are looking for a WebIDL -> TypeScript .d.ts converter for C++ application specifically, you may refer to the above two issues.

I recently found @types/emscripten from NPM and I spent a few hours to figure out how to integrate it with my Emscripten project and contributed the package a little bit too. The .d.ts typing file can be found here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/emscripten/index.d.ts

Suppose I make add.c like this:

// add.c
int add (int a, int b) {
    return a + b;
}

Two ways I have figured out:

  1. add--post-js.ts
  2. add.d.ts + add--post-js.js

In my case I found option 2 is better. Will be explained later.


1. add--post-js.ts

add鈥攑ost-js.ts

/// <reference types="emscripten" />
/** Above will import declarations from @types/emscripten, including Module etc. */

// This will merge to the existing EmscriptenModule interface from @types/emscripten
// If this doesn't work, try globalThis.EmscriptenModule instead.
interface EmscriptenModule {
    // Module.cwrap() will be available by doing this.
    // Requires -s "EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap']"
    cwrap: typeof cwrap;
    // Exported from add.cpp
    // Requires "EXPORTED_FUNCTIONS=['_add']"
    _add(number, number): number;
    // or using cwrap. See below
    add(number, number): number;
}

Module['onRuntimeInitialized'] = function() {
  // Just Module._add() will work, but I'm just demonstrating usage of cwrap()
  Module['add'] = cwrap('add', 'number', ['number', 'number']);
}

2. add.d.ts + add--post-js.js

add.d.ts

/// <reference types="emscripten" />
/** Above will import declarations from @types/emscripten, including Module etc. */

// This will merge to the existing EmscriptenModule interface from @types/emscripten
// If this doesn't work, try globalThis.EmscriptenModule instead.
export interface AddModule extends EmscriptenModule {
    // Module.cwrap() will be available by doing this.
    // Requires -s "EXTRA_EXPORTED_RUNTIME_METHODS=['cwrap']"
    cwrap: typeof cwrap;
    // Exported from add.cpp
    // Requires "EXPORTED_FUNCTIONS=['_add']"
    _add(number, number): number;
    // or using cwrap. See below
    add(number, number): number;
}

// Declare any name
declare const addModule: AddModule;
// Only for -s MODULARIZE=1
export = addModule;
// Only for -s MODULARIZE=1 -s EXPORT_ES6=1
export default addModule;

add鈥攑ost-js.js

/// <reference types="emscripten" />
/** Above will import declarations from @types/emscripten, including Module etc. */
/** It is not .ts file but declaring reference will pass TypeScript Check. */

Module['onRuntimeInitialized'] = function() {
  // Just Module._add() will work, but I'm just demontrating usage of cwrap
  Module['add'] = cwrap('add', 'number', ['number', 'number']);
}

I know, both of them don't particularly look pretty but they are the cleanest ones so far.

I found that the second one is better for two reasons: add--post-js.ts file must be compiled to js in order to work with emcc command. And there are problems when you want to use export statement because -s MODULARIZE=1 creates another export statement automatically.

Please share any better tricks if you have one. Also you will notice that @types/emscripten is not complete when you look at the source code it would be great if anyone can make the package better.

Thanks :)

Most helpful comment

I have written a tool that can generate type definitions for emscripten modules.

For now i have tested it only with the ammo.js project

The tool itself is based on the https://github.com/microsoft/TSJS-lib-generator. Unfortunately TSJS-lib-generator itself can not be used for emscripten as it is very specific in some parts. I borrowed some code and concepts to make it work for ammo and hopefully for other emscripten projects as well. Just needs some more testing to discover edge cases.

All 8 comments

cc @jgravelle-google (maybe this could benefit from interface types?)

If I understand correctly, this is about using C++ functions in a TypeScript application? And ideally having TS types for the C++ as well.

Probably the most ergonomic tool would be using embind's EMSCRIPTEN_BINDINGS mechanism (https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html#a-quick-example). Interface types should help here in the future, by giving us a custom section a TS tool could autogenerate the .d.ts from.

@jgravelle-google Right. This is the current available way for someone who want to use C/C++ functions in a TS application right now. I am not asking Emscripten devs to do something. I posted this because I cannot find any guides on how to achieve this on the internet, and I thought posting it as an issue here is better than writing it in my blog because someone might have a better idea.

As you mention, In the future we could automatically generate .d.ts using embind or the WebIDL binder. I believe #9674 and #7083 already discuss about it.

I am not asking Emscripten devs to do something.

Oh, I assumed you weren't yeah. Just thinking out loud "if I were to do this what would I do," in case that counts as having a better idea. And also thinking about how Interface Types would apply here specifically.

WebIDL binder is probably closer to what TS would expect. Is there already a general-purpose .d.ts generator from webidl for web APIs? Though I imagine there's still some bridging needed to make the formats line up.

Is there already a general-purpose .d.ts generator from webidl for web APIs?

I think microsoft/TSJS-lib-generator is the best bet. The TypeScript Team do not hand-write Web API type definitions from scratch but generate .d.ts files from various WebIDL sources (Source: https://github.com/microsoft/TypeScript/issues/3027). I haven't done any experiments with this but I guess we need some work to adapt it to Emscripten.

@jgravelle-google With protobuj.js supporting typescript, and the fact we're moving to a place where we'll want to interop across multiple languages (typescript, mono/c#, golang, rust, kotlin, c++, etc.) within the browser/webassembly, would it make more sense to focus on good protobuf support for emscripten? Then we can start using "event bus" patterns within the browser/webassembly to interop across different webassembly languages.

I have written a tool that can generate type definitions for emscripten modules.

For now i have tested it only with the ammo.js project

The tool itself is based on the https://github.com/microsoft/TSJS-lib-generator. Unfortunately TSJS-lib-generator itself can not be used for emscripten as it is very specific in some parts. I borrowed some code and concepts to make it work for ammo and hopefully for other emscripten projects as well. Just needs some more testing to discover edge cases.

Very interesting @giniedp !

If you think it's ready, a PR to add a link to that in the docs would be great.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kripken picture kripken  路  4Comments

answer1103 picture answer1103  路  4Comments

id01 picture id01  路  3Comments

rpellerin picture rpellerin  路  3Comments

juj picture juj  路  3Comments