-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Description
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