Repository navigation
Description
What?
Basically, I want to improve the interop with TypeScript and asking for help and directions.
This means extending the fable-compiler in two areas:
- Emit TypeScript bindings (we currently write them by hand)
- Consume/Import TypeScript (we currently use ts2fable for this)
Please feel free to correct me throughout the post, suggest alternative implementations or even point out why this is a bad idea. Any help regarding the design, implementation or else is welcome. Feel free to connect privately via Twitter or Slack (or Mail).
Why?
The linked related issues (see the end of this post) should give a couple of reasonable use cases. But in particular:
- After "1." it is easier to publish fable-based libraries on npm without any additional manual work.
- "1." should allow
import "./MyFile.fs"in TypeScript/Javascript with proper IDE support. (It is not exactly clear what additional thing we would need to do here for the IDE to pick up the typings) - "2." Would make it a lot easier for newcomers to use packages in the npm ecosystem
- "2." Might allow us to reduce our work of maintaining and keeping the typings updated.
- (Future discussions, just thinking our loud) After 1 + 2 we could potentially publish packages on npm. For F# tooling we might still need to include a reference assembly (or just the .net assembly, considering WebAssembly...). There are however a lot of other issues to be solved. Just putting this here to put it up for discussion if this is what we want eventually.
How?
I'd like to talk about 1 only for now (and extend the post later)
Emit TypeScript bindings
Looking at the available APIs and the related discussions I feel like the best bet is to use the TypeScript API as there are online tools which make is easy to understand and work with.
I have played a bit with the code base and noticed the following:
- We could just use the TypeScript-API in
fable-compiler-js, we just need a couple of bindings or use unsafe calls. - However, in regular .NET based fable we cannot use the TypeScript API, so we need a intermediate serializable datastructure for type declarations built from
Fable.AST. It will look similar toFable.ASTbut only type-specific stuff (all Expression stuff will be removed) - I haven't figured out what the best way is to inject TypeScript definitions into webpack via fable-loader, but as a first step I would just output the
.d.tsfiles somewhere (alongside the.fsfile for example).
Yes it probably is a bit of work but all it all it sounds doable to me. In practice:
- We could start with a quite minimal implementation only supporting simple interfaces (for example) and using
anyfor everything else - We probably want to make this
opt-inuntil we are more confident - We can extend this feature for feature (ie typings will become better over time)
Consume/Import TypeScript
I will expand this section later or throughout this discussion. But my current ideas are:
- Create a "build-in" type-provider
type MyTypings = TscImport("typings.d.ts") - Write
.fsfiles, for example based on attributes -> add to project file via globbing
Related issues
This issue is a continuation of:
Activity
[-][Help Wanted] Improve TypeScript integration[/-][+][Help Wanted/Design] Improve TypeScript integration[/+]Luiz-Monad commentedon Aug 31, 2019
The other day I was working on making ts2fable work better translating types. But I stopped after I started hitting problems with the need for type tagged unions. fsharp/fslang-suggestions#538
Also I didn't find a good way to represent the Pick row polymorphism with F# column polymorphism. They don't quite fit together for a 1:1 translation of types.
Luiz-Monad commentedon Aug 31, 2019
Perhaps we could do the opposite, convert all the TypeScript AST to F# AST. [insert evil mad computing scientist laugh]
alfonsogarciacaro commentedon Sep 1, 2019
Thanks a lot for this detailed issue @matthid. As we've talked some types, it's true I'm a bit skeptical about the Typescript integration because as @Luiz-Monad says, they type systems of both languages have differed quite a bit. But I'd love to be proven wrong and see how we can take advantage of .d.ts declarations.
I can see you've done your homework and listed the older issues around the topic so I don't need to repeat the info... or actually look for it myself because I've already forgotten it :) But I'll still try to add some comments as I remember things. For now two quick notes:
matthid commentedon Sep 1, 2019
Thanks!
Yes, I think this is indeed a problem, but my current thinking is this is only really a problem for "2." not for "1." (or do you know any examples where this is a problem for "1."?). In general it feels like the TypeScript type system is more powerfull than the F# one. This most likely a general problem in the ecosystem as TypeScript just tries to "type" existing javascript. So this basically means we have problems mapping existing code to our type-system.
To solve this for "2." we are not lost either:
I did some research with google but I cannot figure out what the benefit of this is. If babel cannot write
.d.tsfiles out of this what is it good for? Maybe you or @ncave can explain?Yes I think we need a bit of fiddling around to make that import work flawlessly but in worst case scenario we can also generate a
.tsfile which imports the declaration and proper.fsimport and then we can tell people to import.tsinstead of.fs?MangelMaxime commentedon Sep 1, 2019
If I remember correctly, Babel only supports compiling TypeScript into JavaScript. It doesn't generate TypeScript.
At first Fable Conf, at least it was explained that they wanted to support TypeScropt -> JavaScript transpilation and do a "better job" than TypeScript compiler because of all the Babel ecosystem, configuration and optimisation.
alfonsogarciacaro commentedon Sep 2, 2019
In the first attempts to generate .d.ts files I used babel-dts-generator that was capable of extracting the annotations from Babel AST to create the declarations. But it seems the plugin is not supported now :/ Another option would be to output the annotations as JSDoc comments, as Typescript can also use them to provide intellisense.
If I'm not mistaken, the purpose of @ncave was to make Babel output the type annotations as comments, then do another pass to remove them and finally see if the result was compatible with one these Typescripts subsets targeting WebAssembly like AssemblyScript. Yes, black magic as usual 😉 But if in both cases the purpose is to include type annotations in the output it will be a chance to take two birds with one stone.
matthid commentedon Sep 2, 2019
I have seen that suggestion, but I doubt it would we easier than writing TypeScript definitions via API. On the other hand, if we get them 'for free' from babel that would be another story and probably the easier approach.
Question is how this fits libraries? I assume we would suggest to include comments in the bundle?
ncave commentedon Sep 2, 2019
@alfonsogarciacaro
fable-librarycompiles to something the TypeScript compiler can accept.Side Note:
I hope this changes in the future, but in the mean time targetting TypeScript should be much easier.
matthid commentedon Sep 2, 2019
Not sure what these mean. Does this mean the way forward is to forward the JSDoc-formatted output into the TypeScript compiler, which will itself write the
.d.tsfiles? Or do you mean writing.d.tsfiles from fable itself? Can you please clarify @ncave ?Or put differently: I'm open to invest some time into this problem, however after all these discussions I'm still not sure what our best bet is at this time?
ncave commentedon Sep 2, 2019
@MattiD Adding type annotations in the Babel AST generates types in the Babel output directly. It's just a matter of completeness (output the correct type annotations for all corner cases). For example:
compiles to (with type annotations turned on):
As you can see, it's not perfect and there is some work to do (quite a few types are just stubbed as
anyfor now), but IMO you can get quite far with that.matthid commentedon Sep 2, 2019
@ncave And I guess TypeScript can consume annotated JavaScript ootb? Or do we need to process this further?
ncave commentedon Sep 2, 2019
@matthid The above is valid TypeScript produced by Babel, just rename
.jsto.ts.But as I said, the PR needs some love to add the proper types, it's hasn't really changed much since @alfonsogarciacaro first added type annotations long, long time ago in Fable 1.
matthid commentedon Sep 2, 2019
Will take a closer look at the weekend, thanks for clarifying
alfonsogarciacaro commentedon Sep 11, 2019
I'll soon create a
nextbranch for the next major release to merge #1839. We could also use it to merge #1615 and experiment with it :)3 remaining items
chrisvanderpennen commentedon Jun 17, 2020
Moved to #2096
ncave commentedon Jun 17, 2020
@chrisvanderpennen Thank you for the detailed explanation, it's probably best to convert this into its own issue [feature request], as it probably goes a bit beyond the initial scope of just adding types.
chrisvanderpennen commentedon Jun 17, 2020
Sure, I'll create one and edit the above to point to it so it isn't cluttering this discussion.
AngelMunoz commentedon Sep 23, 2020
So far I think the idea is to avoid javascript/typescript but what if you can embrace it?
I recently worked with Bolero which is just webassembly, and the way it does javascript interop is by doing function invocations
https://github.com/AngelMunoz/Mandadin/blob/master/src/Mandadin.Client/Views/Notes.fs#L79
and in the javascript side, I still have to write code by hand
https://github.com/AngelMunoz/Mandadin/tree/master/src/Mandadin.Client/wwwroot/js
and include my js libraries, if this grows big enough I believe you would need to bundle those files and dependencies anyways
Fable already uses Webpack and it's just javascript typescript/javascript files, in the end, at least that's what I think.
The typings/dependencies are just an "npm install" away.
and could be consumed in a similar way like this
My thought process here was that if Fable could implement an option for interop in a similar matter, the complexity of js interop would be at the F#/JS boundary, not in the lack of hands to invest in tooling/bindings
now, this would be the "Worst Case" meaning that you would only resort to this if the library is really big enough for yourself or there's no effort on the community to write some bindings the safety will always be in the F# side
I believe most of the time you don't really need external libraries and the popular ones might be covered already
anyway... this is just an idea I had when working with Bolero I believe Fable has a better chance to improve that interop model than Blazor/Bolero since the packages already live in npm, there's too few browser ready builds of libraries for Blazor/Bolero to work, in the end, I feel they will still resort to bundling in one way or another
alfonsogarciacaro commentedon Sep 24, 2020
Thanks a lot for your comments @AngelMunoz! I'm not sure I understand, there are already several ways to interop with JS code from Fable either in a typed or untyped manner, what are you specifically looking for? https://fable.io/docs/communicate/js-from-fable.html
AngelMunoz commentedon Sep 24, 2020
my comment is about third party library integration, that is complicated to automate (ts2fable) and may be prone to errors, also the only other alternative is creating bindings for third party libraries and sometimes there's just not enough hands hence the need to improve typescript integration, or that's what I understood from the issue thread.
the summary would be the following
if Fable includes any user authored javascript/typescript bundle within the same app you should use the js from fable usual mechanisms without having to write bindings for each library, the bindings would be for your specific code
maybe this is a different issue and I'm not catching the idea
AngelMunoz commentedon Sep 24, 2020
@alfonsogarciacaro
following from yesterday this is what I meant
https://github.com/AngelMunoz/fable-plus-typescript-files-poc/blob/master/src/App.fs#L8
https://github.com/AngelMunoz/fable-plus-typescript-files-poc/blob/master/src/tsfiles/interop.ts#L18
this doesn't addresses point 1
but it provides less friction for point 2 of the issue
it of course brings it's own set of issues, the first one I can think of is safety, the typescript type system can be really good but needs user reinforcements unlike F# which is safer by default
alfonsogarciacaro commentedon Sep 25, 2020
About point 1, @ncave is working on that, although work is a bit on hold until we release a stable Fable 3. Point 2 is about consuming directly typescript files in a type-safe manner with bindings (or with auto-generated bindings on the fly). I think this is complicated but could be done with a type provider or a tool for code generation.
In any case, as commented above, it's already possible to consume Typescript or JS files by using dynamic operators or writing some ad-hoc bindings (I do that all the time). No need for any additional mechanism.
CedricDumont commentedon Dec 14, 2020
@alfonsogarciacaro : when you say point 1 is in progress, does this mean usage of "--typescript" in fable 3 ? Is that what you are talking about ?
if yes, it seems this flag does create real typescript code (not only declarations)
my goal :create a library in Fable which I would like to use in another library (that second one in typescript). when using the "--typescript" flag I get some errors
I understand it's a work in progress but could you add a link to the work?
Where should we add issues regarding that specific feature?
what is the correct way currently to use a Fable library in another typescript library ?
alfonsogarciacaro commentedon Dec 15, 2020
@CedricDumont yes, that's right. Although I must confess that I haven't worked at on this feature 😅 so I don't really know what's the current status. @ncave probably can answer to that better. IIRC the fable-library imports do give issues because atm we're not packing the fable-library files compiled to typescript. We can try to solve that but I'm not sure if there're other issues pending. If we want to give an impulse to this feature, we should try to make the tests run when compiling to Typescript.
Right now, basically writing the .d.ts declaration yourself for the Fable methods you're consuming from TS. I've found the following configuration to work:
For example, if your App.d.ts declaration contains the following export (corresponding to an actual export in App.fs.js):
You can consume it from Typescript like this:
Note the extension is omitted in the import. You need to edit your webpack.config.js so Webpack looks for files with
.fs.jsextension in this case:Note this is also useful to consume Fable code if you're using just Javascript with the
// @ts-checkstatement.ncave commentedon Dec 15, 2020
Here is the current state of the TypeScript support, based on this comment:
So we have a small hump to go over first, and a bigger one after that, but hopefully it can be semi-usable after the first, if we bundle the TS version of
fable-librarywith the Fable compiler. Opinions and contributions are welcome, build steps for TSfable-libraryare outlined in the comment mentioned above.alfonsogarciacaro commentedon Jul 10, 2021
Closing as there's no work happening at the moment in this direction, please reopen if someone wants to contribute to the TS integration.
jkone27 commentedon Jul 11, 2022
Helo guys, someone can maybe publish a blog post or a guide on how to have a mixed typescript and fable solution ?
right now i have a .ts (mainly es6 with ts extension) codebase, and would like to partially move it to F# or eventually progressively convert it to F#, but i couldnt get the grasp of what i need to do so.
maybe some babel plugin to turn es6-to-fsharp ? cheers and thanks a lot fable is amazing!!!
AngelMunoz commentedon Jul 11, 2022
hey @jkone27 this post (disclaimer I wrote that) might give you a hint on how to integrate Fable into existing JS projects
In your case I think you might need to just switch the
allowJS: truesetting in your tsconfig.json, from there on, you might want to slowly use F# as your entry point and consume your existing typescript until you're able to replace what you want/need to replace