Open eyebrowsoffire opened 11 months ago
Related request regarding idea 2: https://github.com/dart-lang/sdk/issues/52852
FYI - I've just landed a member to address the second request. Since the result is a promise that resolves to an object, you can use an extension type/@staticInterop
class to interface with the resulting JS object.
In case its useful, I wanted to make a note on usability issues found using a real example use case when implementing a node_interop
package.
@JS('fs')
library fs;
import 'dart:js_interop';
// getter used for importing the `fs` module
@JS()
Future<FS> get fs async => FS._(await importModule('fs').toDart);
// defines all of the members on `fs`
extension type FS._(JSObject _) implements JSObject {
external JSAny readFileSync(
JSAny file, [
JSAny? options,
]);
...
}
// USAGE (i.e. users of `package:node_interop`
Future<void> main() async {
final thisDir = '/Users/pattobrien/dev/pattobrien/dart3_node_interop';
final files = (await fs).readdirSync(thisDir.toJS);
for (final file in files.toDart) {
console.log(file);
}
}
A couple things to point out:
library
directive declarationfs
module within a FS
or FSModule
extension type feels unintuitive and non-Dart like, even though it does accurately reflect what's going on behind the scenes(await fs)
statement, especially since this is a static import included with node
. Ideally there would be a way to import a module synchronously, based on the use case.Great feedback, I agree with all your points!
I can imagine an annotation on the library that would signal to the compiler to import a module as a namespace that can then be used in lowering the interop calls. That way, top-level interop declarations would correspond to top-levels in that module, linking the Dart library to the JS module. We don't have support for interop to tell any of the web compilers to statically import a module, but it might be possible without too much effort - TBD.
I can imagine an annotation on the library that would signal to the compiler to import a module as a namespace that can then be used in lowering the interop calls.
That would be amazing! Ideally inferring js semantics from how users are already using Dart would be the way to go. If I import a dart fs
library into my entrypoint, then the generated JS code should import the respective js fs
module.
The translation from dart code to js code would then look something like the following:
Dart | JS Transpiled code |
---|---|
import 'package:node/fs.dart'; |
import * as fs from 'fs'; |
import 'package:node/fs.dart' as foo; |
import * as foo from 'fs'; |
import 'package:node/fs.dart' show readdirSync; |
import { readdirSync } from 'fs'; |
I've also opened a related but separate issue regarding accessing the top-level module
object in non-browser environments. There are IMO a couple other small things that would need to change with how Dart handles js compilation today to better support ESM, so not sure if this issue should exist as a catchall for ESM support or specifically an issue about the module/namespace definition semantics.
It would be much harder to convert import
statements into something specific for interop as that's likely a language change, but we can leverage some annotation for that syntax instead.
Better support for ESM is likely an intersection between interop and the compilers. We should create a meta issue linking all the relevant issues together here.
Some sort of first-class support for ES modules in JS interop would be very useful for both JS backends and dart2wasm. Currently, there doesn't appear to be a way to do dynamic or static importing of ES modules through JS interop, so here are a few ideas:
@staticImport()
annotation with a const string url parameter, which transforms into a static import of an ES module:extension MyModuleExtension extends MyModule { external JSAny foo(JSAny param); }
@JS() @staticImport('relative/path/to/module.js') external MyModule get myModule;