fable-compiler / Fable

F# to JavaScript, TypeScript, Python, Rust and Dart Compiler
http://fable.io/
MIT License
2.85k stars 295 forks source link

[Help!] Dart/Flutter bindings #2878

Open alfonsogarciacaro opened 2 years ago

alfonsogarciacaro commented 2 years ago

I think we discussed it before but it'll be nice to have a semi-automatic way to generate Dart/Flutter bindings so users can start writing apps when the compiler is ready, so I'm opening this issue for discussion.

I've uploaded a dead-simple Elmish Flutter app here: https://github.com/alfonsogarciacaro/fable-flutterapp

I manually wrote a few bindings for the things I needed in this file. In general, bindings are very similar to the JS ones, there's some discussion about writing bindings in general for Snake Island in #2779. A couple of specifics for Dart:

Ideally the bindings should be extracted from this repo. In previous conversations @Nhowka commented there are introspection tools for Dart so maybe we could use those, or is it simpler to just write a simple custom parser? Pinging also @adelarsq who also has experience with Dart and @davedawkins because he's good at writing scripts for code generation as he did with the Feliz API πŸ‘Ό

davedawkins commented 2 years ago

Let me take a look then

davedawkins commented 2 years ago

The Mirror reflection API looks promising.

davedawkins commented 2 years ago

Wow. F# app on macbook

image
davedawkins commented 2 years ago

A very quick summary of my findings so far:

What to look at next:

Nhowka commented 2 years ago

Dartdoc is the tool that generates this kind of documentation and the ones on pub.dev that already describes the public API for each library. Looking at the html it generates it could be feasible to parse them directly, but I still believe that the ideal solution could be reviving this issue so we could use the output to generate our bindings or forking/rewriting to our needs to generate them directly.

It even has an --auto-include-dependencies flag that generates documentation files for direct and indirect dependencies, so pretty powerful tool.

adelarsq commented 2 years ago

Nice! I will help on this πŸ€—

Nhowka commented 2 years ago

When importing the dartdoc package, we can create a Dartdoc instance calling the constructor Dartdoc.withEmptyGenerator. There is a brittle generator field on it with a @visibleForTesting annotation that we could leverage. The Generator receives a PackageGraph that describes everything on the package and a FileWriter to write the result.

I'm doing some experiments with it to learn the layout to extract everything we need, but that way we probably can use the tool as is.

adelarsq commented 2 years ago

A hand-made parser would make sense. So would be possible to extend ts2fable to support Dart and more languages?

Nhowka commented 2 years ago

I upload the test file I made in this repository. I started handling only the constructors for now and got this result for the flutter library. Function types, the generics on the arguments and the translation from core types are still not correctly handled, but it's already a start.

While it's cool that it's using the metadata from the language and loading everything needed into the context so we could handle as we want, it makes it terribly slow. I ran it against my elmish-like lib with almost no dependencies and it takes around 2 minutes to reach the generator.

davedawkins commented 2 years ago

Amazing. The speed issue makes it hard to test/debug, but once completed this thing only needs to be run occasionally (to keep bindings in sync), I would think?

Nhowka commented 2 years ago

Yes, we could plug some pre-analysis step to check for changes only start the generation if there is a new or different version package. Maybe we could create a project and save a copy of the pubspec.lock and compare them. If there is a change, we regenerate all files again as it is easier than detecting changes on a single package.

For the named constructors I mapped them as static methods that return an instance of the class. Is that the right way to map them?

alfonsogarciacaro commented 2 years ago

Thanks everybody for your help! This is great @Nhowka, even if it requires some manual tweaking it's already a great help to make some demos/samples for the alpha release πŸŽ‰ As @davedawkins says speed probably it's not an issue if we only need to run the tool once in a while. For prototyping, is it faster if we work with a small code sample or most of the boot up time is taken by Dartdoc.

@adelarsq About ts2fable, probably the only part we could reuse would be printing and it has its own AST which may not have all the information we need (const constructors, named params) so it may be faster for now to do things in Dart directly as @Nhowka did πŸ‘

alfonsogarciacaro commented 2 years ago

About compiling named constructors as static members, this is fine as in Dart they're syntactically the same in the call site. You can also use the [<IsConst>] attribute in a static member if needed: https://github.com/alfonsogarciacaro/fable-flutterapp/blob/1a80389301c32f8a763bdac93f60c864ee07608d/src/Flutter.Material.fs#L65

alfonsogarciacaro commented 2 years ago

@Nhowka I'm trying to edit your code a bit to get bindings for the constructors of the Material widgets. See this commit: https://github.com/alfonsogarciacaro/DartToFableBindings/commit/b7728ac5afbf9fd829585cd2765c9f0b14afb1e0

I tried with a test Dart package and it seemed to work fine, however I downloaded the flutter repo, followed the instructions here and managed to run flutter analyze in packages/flutter dir without issues. However when I try to run the fsgen from that dir I don't get any output. This is the command I'm using:

dart run ../../../DartToFableBindings/bin/fsgen.dart \
  --exclude 'dart:async,dart:collection,dart:convert,dart:core,dart:developer,dart:io,dart:isolate,dart:math,dart:typed_data,dart,dart:ffi,dart:html,dart:js,dart:ui,dart:js_util' \
  --show-progress  \
  --no-auto-include-dependencies  \
  --no-validate-links  \
  --no-verbose-warnings  \
  --no-allow-non-local-warnings \
  --no-allow-tools

I only get many errors, this is the shortened log (only start and finish):

../../../../AppData/Local/Pub/Cache/hosted/pub.dartlang.org/dartdoc-5.0.1/lib/src/model/model_element.dart:706:14: Warning: Operand of null-aware operation '?.' has type 'LineInfo' which excludes null.
 - 'LineInfo' is from 'package:analyzer/source/line_info.dart' ('../../../../AppData/Local/Pub/Cache/hosted/pub.dartlang.org/analyzer-3.4.1/lib/source/line_info.dart').
      return lineInfo?.getLocation(nameOffset);
             ^
Documenting flutter...
  error: private API of package:Dart is reexported by libraries in other packages:
    from cupertino.Color: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart:94:7)
    referred to by cupertino: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/cupertino.dart:23:9)
    referred to by material: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/material.dart:21:9)
    referred to by painting: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/painting.dart:18:9)
    referred to by rendering: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/rendering.dart:22:9)
    referred to by widgets: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/widgets.dart:13:9)

...

  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:424:16)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.ByteData: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:428:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.view: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:459:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
  error: private API of package:Dart is reexported by libraries in other packages:
    from services.ByteData.sublistView: (file:///C:/Users/alfon/repos/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart:481:20)
    referred to by services: (file:///c:/users/alfon/repos/flutter/packages/flutter/lib/services.dart:11:9)
-
dartdoc 5.0.1 (/C:/Users/alfon/repos/DartToFableBindings/bin/fsgen.dart) failed: Command executables must exist. The file "c:\users\alfon\repos\flutter\bin\cache\dart-sdk\bin\dart" does not exist for tool snippet..

Did you manage to run the fsgen from the flutter repo? Can you help? πŸ™

Nhowka commented 2 years ago

Flutter is somewhat unique. They generate a dummy pubspec to make it compatible with dartdoc. Maybe you can get some output by creating a dummy empty flutter project that depends only on flutter and generating all bindings using it as a base.

alfonsogarciacaro commented 2 years ago

You're right, that worked! Now I've a ton of bindings... I only need to make them compile πŸ˜… Getting excited now!

bentok commented 1 year ago

I'm gonna take a stab at getting that code generator to produce translations for function types. I'm using @alfonsogarciacaro's latest changes to the generator and it seems like functions are still not being translated. Correct me if I'm wrong on that, and I'm open to suggestions if anyone already has ideas.

alfonsogarciacaro commented 1 year ago

Thanks a lot for your help @bentok! I think my latest changes are in this branch: https://github.com/alfonsogarciacaro/DartToFableBindings/tree/material-widgets

I was making several attempts and also did some manual changes. I think this code can output the module functions, but in any case the generator is a bit dirty at the moment, so some cleanup would be much appreciated. See the comment from @Nhowka about the dummy project necessary to make the doc generator work πŸ‘ https://github.com/alfonsogarciacaro/DartToFableBindings/blob/f001c9621d018a082a37cd593745d7549d33f72c/lib/fsgen.dart#L358-L361

bentok commented 1 year ago

Flutter is somewhat unique. They generate a dummy pubspec to make it compatible with dartdoc. Maybe you can get some output by creating a dummy empty flutter project that depends only on flutter and generating all bindings using it as a base.

@Nhowka do you think generating bindings for 3rd party Flutter libraries would require the same approach? I have made some modifications to both your code and @alfonsogarciacaro's fork (https://github.com/alfonsogarciacaro/DartToFableBindings/tree/material-widgets) and am getting close, but can't quite get it right.

For example, if I generate bindings for flutter_secure_storage, it will generate dozens of files - some of which have only a few bindings and others that have more. However it doesn't seem like any of the files have all of the bindings.

Nhowka commented 1 year ago

@bentok, I believe that for typical packages with standard pubspec files, the same surface outputted on their dartdoc documentation on pub.dev should be available to us. Could there be some filter pruning some of those before they reach the code generation stage?

If I'm not mistaken, we could generate bindings for them without using a dummy project, but it could be amazing if we could create bindings for all packages an actual project is referencing on the fly. We could have a cache, so we skip generating for known versions, but as dart has the advantage that the libraries are packaged with raw code that dartdoc could read, I believe it is an achievable goal.

johnstorey commented 11 months ago

Hey, l've come late to this thread, but @bentok and I have connected to start working on this again. I'm able to reproduce everything up to @alfonsogarciacaro getting errors on his first try to run this. But I have not yet worked out what to do once the empty flutter project is set up to get the bindings. I know it's been more than a year, but if someone remember and can give the exact step that would speed up the work a bit.

octaviordz commented 7 months ago

Hi everyone, I got the source code from https://github.com/alfonsogarciacaro/fable-flutterapp and after updating fable it compiled and ran correctly, but the auto/hot-reload does not work. Is there something that can be done to make this work?

I'm using vscode under Windows 10. Thank you.

MangelMaxime commented 7 months ago

Hello,

in the readme of the repo there is a mention that you have to trigger hot reload manually.

Did you give it a try ?

I never used Flutter so I don’t know if there is some requirements like in JavaScript for it to work.

octaviordz commented 7 months ago

Hi, I had not tried the "Ctr+F5" that works, I was just manually updating the .dart generated file to trigger the hot reload. But my question was more in regard to what is the missing/link/configuration the 'build.cmd' is running fable in watch mode so when you update the source code it creates the .dart file(s) and vscode does load the updated .dart file(s) and like I said I was manually updating the .dart file to manual trigger hot reload hence my question is why if vscode 'knows' the file has changed the hot reload is not triggered.

Thank you.

MangelMaxime commented 7 months ago

Hello @octaviordz,

I am not sure I understood everything because it was a really long sentence πŸ˜…

But, the first thing I was check is what is the behaviour of hot reload in a pure Dart project because if the behaviour is the same then there is nothing that can be improve.

If not, then someone with Dart/Flutter knowledge will need to have a look at it.

octaviordz commented 7 months ago

Hi @MangelMaxime, Hot reload works in a pure Dart project as expected. Thank you for your response.