Skip to content

jsinterop: define a mechanism to export an API without wrapping #56906

@sigmundch

Description

@sigmundch

Today, we provide @JSExport as a mechanism with dart:js_interop to expoe objects to JavaScript with a public API. This was primarily meant to be used as a mechanism to create fakes/mocks and not for logic in a performance sensitive critical path. We'd like to explore more cost effective ways to achieve the same goals, so the mechanism could be used in performance critical code.

Current mechanism

The declaration involves annotations on a class and an explicit export operation on an object instance:

class MyClass {
  @JSExport('p1')
  void method2() { ... }

  @JSExport('p2')
  void method2(JSString x) { ... }
}

...
var o = createJSInteropWrapper<MyClass>(MyClass());

The createJSInteropWrapper creating an actual object that then contains a closure per exported method. Each closure has the name used in the JSexport annotation and can be invoked from JavaScript. Closures capture the receiver object and forwards the call to the internal methods:

   {
     'p1': () => obj.minified_method1_$0(),
     'p2': (a) => obj.minified_method2_$1(a),
   }

This approach allows us to export public names without interfering with the internal naming conventions used by our compilers (in the example above methods have been renamed). It also provides equivalent semantics in dart2wasm. As hinted earlier, this is expensive: it creates an object and a closure per method.

Note that this approach is consistently supported in JS and Wasm backends.

Alternatives to consider

One option available to JS backends like dart2js, is to leverage that Dart objects are JS objects and bypass the wrapping by also exposing the public names as part of the class prototypes used for implementing Dart classes.

In the case of dart2js we already have method stubs to help manage some Dart language features (e.g. default parameter values).

For example:

MyClass.prototype = {
  minified_method1_$0() { ... }
  minified_method2_$1(a) { ... }

  // include stubs in the prototype to forward public APis
  // to private APis
  p1() => this.minified_method1_$0();
  p2(a) => this.minified_method2_$1(a);
}

Then we could provide a new API, like createJSInteropWrapper that instead of sharing a wrapper shares a reference to the actual object using a similar mechanism as toExternalReference in JS backends (since the object itself satisfies the public interface expected by the JS code), but uses a wrapper in dart2wasm.

/cc @srujzs @leonsenft

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2A bug or feature request we're likely to work onarea-web-jsIssues related to JavaScript support for Dart Web, including DDC, dart2js, and JS interop.web-js-interopIssues that impact all js interop

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions