dart-lang / sdk

The Dart SDK, including the VM, JS and Wasm compilers, analysis, core libraries, and more.
https://dart.dev
BSD 3-Clause "New" or "Revised" License
10.12k stars 1.57k forks source link

JS interop support for ES6 modules #53783

Open eyebrowsoffire opened 11 months ago

eyebrowsoffire commented 11 months ago

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:

extension MyModuleExtension extends MyModule { external JSAny foo(JSAny param); }

@JS() @staticImport('relative/path/to/module.js') external MyModule get myModule;



* In `dart:js_interop`, expose a builtin `Future<ModuleExports> import<ModuleExports implements JSAny>(String url)` function for dynamic imports. Currently trying to call `import` via static interop doesn't actually work because it's not a real function, it's a browser builtin.
srujzs commented 11 months ago

Related request regarding idea 2: https://github.com/dart-lang/sdk/issues/52852

srujzs commented 9 months ago

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.

pattobrien commented 7 months ago

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:

srujzs commented 7 months ago

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.

pattobrien commented 7 months ago

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.

srujzs commented 7 months ago

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.