fable-compiler / Fable.Store

Manage state logic in Svelte, React apps and more
MIT License
58 stars 5 forks source link

typescript definition not honoring JS output directory #9

Open sebastian opened 3 years ago

sebastian commented 3 years ago

Firstly, thanks @alfonsogarciacaro for creating this tool!

I just got started playing with the tool, so chances are high this is user error. If so, my apologies ahead of time.

I have created an Elmish Svelte Store and annotated the makeStore function with [<SveltePlugins.GenerateDeclaration>] in the hopes that I would get a typescript d.ts file as well. When compiling with Fable, I get nothing besides a .js version of my F# script.

I am using:

I'll try to debug a bit more thoroughly tomorrow (when my head is fresher), but wanted to put down some notes while it's fresh.

The F# file I am compiling has a makeStore function that looks like this:

[<SveltePlugins.GenerateDeclaration>]
let makeStore () =
  let dispatchRef: (Msg -> unit) ref = ref (fun _ -> ())
  let hub = SignalR.connect<Shared.SignalRHub.Action, _, _, Shared.SignalRHub.Response, _>(fun hub -> ...)

  let store, dispatch = SvelteStore.makeElmish (fun () -> init hub) update (fun _ -> hub.stopNow()) ()
  store, SvelteStore.makeDispatcher dispatch

the generated JavaScript ends up looking like this:

export function makeStore() {
    // omitted some generated code for brevity ...
    const patternInput = makeElmish(() => init(hub_2), update, (_arg5) => {
        HubConnection$5__stopNow(hub_2);
    }, void 0);
    const store = patternInput[0];
    const dispatch = patternInput[1];
    return [store, {
        serverMsg: (Item) => dispatch(new Msg(0, Item)),
        unexpectedServerResponse: () => dispatch(new Msg(1)),
        setConnected: (Item) => dispatch(new Msg(2, Item)),
        createElection: () => dispatch(new Msg(3)),
        addCandidate: (name) => dispatch(new Msg(4, name)),
    }];
}

The code is mostly as I would expect, but unfortunately the type definition interface file is missing.

The dotnet fable command I am using is modeled after the one you are using in your samples, namely: dotnet fable watch ../Absolutally.Client/ -o src/bin --exclude Fable.SveltePlugins

I have also tried using the built in --typescript flag in Fable (dotnet fable watch ../Absolutally.Client/ -o src/bin --typescript --typedArrays false), but unfortunately it ends up spitting out any types across the board, which unfortunately doesn't help me much further (but that's a fable problem, not a Fable.Store problem, hence a problem for another day):

    ...
    const store = patternInput[0];
    const dispatch = patternInput[1];
    return [store, {
        serverMsg: (Item: any): any => dispatch(new Msg(0, Item)),
        unexpectedServerResponse: (): any => dispatch(new Msg(1)),
        setConnected: (Item: any): any => dispatch(new Msg(2, Item)),
        createElection: (): any => dispatch(new Msg(3)),
        addCandidate: (name: any): any => dispatch(new Msg(4, name)),
    }];
}

Any pointers are of course welcome!

sebastian commented 3 years ago

Just as an FYI: I tried creating a very simple reproduction of the bug, but when I did the d.ts file was created... This is slightly mysterious. I'll look into it more carefully tomorrow.

One notable difference I noticed: in my original project my F# file was compiled down to FileName.js, whereas in the repro repository it became FileName.fs.js (i.e. one retained the .fs the other did not). This is slightly odd too, but maybe it rings a bell of sorts.

sebastian commented 3 years ago

Aha! I found the problem! The d.ts file is generated alongside the original F# file, and does not honour the --outDir flag given to fable! Hurra! One step forward!

sebastian commented 3 years ago

I uploaded the mini repro. You can find it here: https://github.com/sebastian/fable.store-repro

What I noticed is that the dispatch function isn't typed? Is this intentional? https://github.com/sebastian/fable.store-repro/blob/main/Library.fs.d.ts#L5

alfonsogarciacaro commented 3 years ago

Hi @sebastian! Thanks a lot for reporting and investigating! And sorry for the late reply 😅 I've cloned and tested your repro. Unfortunately both problems seem to have roots in the Fable compiler itself so I need to fix them there, I will open issues accordingly:

sebastian commented 3 years ago

Thank you very much for investigating! One day I'll take a deeper look at Fable itself too so I can become a better citizen of the community and contribute to the core engine, so to speak, too!

Thank you for all your work!

alfonsogarciacaro commented 3 years ago

Hi @sebastian! I've pushed new versions of Fable and the Svelte plugins that hopefully fix the issues. To update the library and the fable compiler you can do the following (in the .fsproj directory):

dotnet add package Fable.SveltePlugins -v 1.0.0-beta-006
dotnet tool update fable

Then run dotnet fable watch ../Absolutally.Client/ -o src/bin (the --exclude argument shouldn't be necessary).

Could you please give it a try and let me know if it works? Thank you!

sebastian commented 3 years ago

This is wonderful!

I am now using:

What works and what doesn't:


As an aside, and this likely(?) is a Svelte issue (I am rather new to Svelte). When using lists in my store, rather than arrays, then Svelte doesn't want to iterate over them in templates as it requires something that is array-like. I'd argue a list can be iterated, but maybe that's just me.

I.e. the following is not permitted from my Fable.Store store

  {#each $store.NumbersList as n}
    <div>{n}</div>
  {/each}

This does work however, but is nasty:

  {#each [...($store.NumbersList)] as n}
    <div>{n}</div>
  {/each}

Have you come across this too?

alfonsogarciacaro commented 3 years ago

Thanks @sebastian! Sorry, it's a bit confusing but the plugins are in a different package (Fable.SveltePlugins) so that's the one that needs to be updated. Anyways, I've updated Fable.SvelteStore to bump the dependency so if you donwload v1.0.0-beta-007 it should work.

About F# lists, yes, they're implemented as linked lists so if Svelte wants an ArrayLike (to use an array index, I assume) it won't work (the JS spread ... operator does work though because the Fable list implementation adds Symbol.iterator). If you want to interop with a JS library using collections, the easiest solution is to use array. It's a bit less idiomatic in F# but if you're careful to use only immutable methods (Array.map, etc) they will work more or less like lists in F# (pattern matching works too) and they can be used as "normal" arrays from JS.

sebastian commented 3 years ago

Sorry, it's a bit confusing but the plugins are in a different package (Fable.SveltePlugins)

My bad! You made the upgrade instructions super clear, and I should have paid a bit more attention. It works really well now!

Thank you again!

sebastian commented 3 years ago

Do you have a donation button somewhere, so I can send you a coffee? I couldn't find one on your github page.

alfonsogarciacaro commented 3 years ago

Thanks a lot for the nice words @sebastian! I haven't set any sponsorship. Just by reporting issues you are already helping a lot :) And if you build something cool with Fable and Svelte please do share! In any case, there are other big contributors to the Fable/F# community with open sponsorships, like Zaid-Ajaj (in his Github page) or the OpenCollective for Ionide.