GithubHelp home page GithubHelp logo

elmish / browser Goto Github PK

View Code? Open in Web Editor NEW
35.0 7.0 20.0 2.64 MB

Routing and Navigation for browser apps

Home Page: https://elmish.github.io/browser

License: Other

F# 100.00%
navigation router elmish

browser's Introduction

Elmish: Elm-like abstractions for F# applications.

Gitter Windows Build status NuGet version

Elmish implements core abstractions that can be used to build applications following the “model view update” style of architecture, as made famous by Elm. The library however does not model any "view" and is intended for use in conjuction with a DOM/renderer, like React/ReactNative or VirtualDOM. Those familiar with Redux may find Elmish a more natural fit when targeting React or ReactNative as it allows one to stay completely in idiomatic F#.

Elmish abstractions have been carefully designed to resemble Elm's "look and feel" and anyone familiar with post-Signal Elm terminology will find themselves right at home.

See the docs site for more information.

Using Elmish

v2.0 and above releases use dotnet SDK and can be installed with dotnet nuget or paket:

For use in a Fable project: paket add nuget Fable.Elmish -i

For use in a WebSharper project: paket add nuget WebSharper.Elmish -i

If targeting CLR, please use Elmish package: paket add nuget Elmish -i

For v1.x release information please see the v1.x branch For v2.x release information please see the v2.x branch For v3.x release information please see the v3.x branch

Building Elmish

Elmish depends on dotnet SDK 6:

  • dotnet fsi build.fsx or ./build.fsx on a *nix system.

Contributing

Please have a look at the guidelines.

browser's People

Contributors

2scomplement avatar albertodepena avatar alfonsogarciacaro avatar banashek avatar davidpodhola avatar davidtme avatar dependabot[bot] avatar dsyme avatar et1975 avatar forki avatar inchingforward avatar jkulubya avatar johlrich avatar mangelmaxime avatar maxwilson avatar maxwilsonms avatar mlaily avatar ncave avatar zaid-ajaj avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

browser's Issues

Value restriction makes it difficult to work with parsing combinators

Description

The way the combinators are written, it is tough to work with them since the code doesn't compile due to value restriction errors until a parse function is applied. It might be worthwhile to make the combinators work such that the developer can build them up independently and not have to compose and pass them to a parse function in order to know whether it compiles.

Repro code

let matchOnHome = s "home"
let matchOnPost = s "blog" </> i32

Expected and actual results

I think it would be helpful to be able to write something like the above so that the match expressions can be encapsulated, but right now that code generates value restrictions errors until you apply a parse function.

Related information

  • fable-elmish version: 0.9.2
  • fable-compiler version: 1.1.9
  • fable-core version: 1.1.7
  • Operating system: Windows 10

Calling `Navigation.newUrl` from the `init` user function does not trigger a call to `urlUpdate`

Description

From what I could see, urlUpdate is automatically called whenever a navigation even occurs, like one triggered by a call to Navigation.newUrl.

This is not the case when calling Navigation.newUrl from the user init function though.

From what I could see, this seems to be due to the fact subscriptions are initialized after the init function is called, so the browser navigation event goes unnoticed (the onLocationChange handler supposed to intercept it is not setup yet).

This is inconvenient because it means trying to do a url redirection when the app starts does not work as expected (the browser url will change, but will be out of sync with the model).

It might be a known issue/shortcoming, but I could not find documentation on this so I'm opening this issue.

Workaround

A simple workaround is to create a user message (e.g. DelayedRedirectIndexPage), and use it from the init function instead of calling Navigation.newUrl.

Then in the update function, handle the DelayedRedirectIndexPage message and do the call to Navigation.newUrl there.

Doing it that way ensures the onLocationChange subscription of Elmish.Navigation is setup when the navigation event is triggered.

Related information

  • elmish version: 4.0.2 (latest)
  • fable-compiler version: 4.1.4
  • fable-core version: 4.1

Tracers.console not working

Description

Program.withTrace Tracers.console not working (Tracers.console comes from Fable.Elmish.Browser).

Repro code

module Main

open Elmish
open Elmish.React

// had to add this wrap since Program.withTrace now expects an extra parameter (subId)
let console (msg: 'Msg) (state: 'State) subId =
    printfn "%A" subId
    Tracers.console msg state

Program.mkProgram Application.init Application.update Application.render
|> Program.withReactSynchronous "fable-elmish-app"
#if DEBUG
|> Program.withTrace console
|> Program.withConsoleTrace
#endif
|> Program.run
<TargetFramework>netstandard2.0</TargetFramework>

<ItemGroup>    
    <PackageReference Include="Fable.Elmish.Browser" Version="4.0.0" />    
    <PackageReference Include="Fable.Elmish.React" Version="4.0.0" />
    <PackageReference Include="Feliz" Version="2.0.0-prerelease-003" /> <- this is required for Program.withReactSynchronous  / Program.withReactBatched
    <PackageReference Include="FsToolkit.ErrorHandling" Version="4.2.1" />
  </ItemGroup>

[]
2Reflection.js:287

   Uncaught TypeError: Cannot read properties of null (reading 'cases')
at getUnionCases (Reflection.js:287:11)
at getCaseName (tracers.fs.js:14:39)
at getMsgNameAndFields (tracers.fs.js:37:16)
at console$ (Main.fs.js:10:44)
at Main.fs.js:17:5
at Program$4.update (program.fs.js:94:9)
at Program$4.traceUpdate [as update] (program.fs.js:74:40)
at processMsgs (program.fs.js:184:48)
at dispatch (program.fs.js:167:17)
at onClick (Application.fs.js:80:13)
at HTMLUnknownElement.callCallback (react-dom.development.js:4164:14)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:16)
at invokeGuardedCallback (react-dom.development.js:4277:31)
at invokeGuardedCallbackAndCatchFirstError (react-dom.development.js:4291:25)
at executeDispatch (react-dom.development.js:9041:3)
at processDispatchQueueItemsInOrder (react-dom.development.js:9073:7)
at processDispatchQueue (react-dom.development.js:9086:5)
at dispatchEventsForPlugins (react-dom.development.js:9097:3)
at react-dom.development.js:9288:12
at batchedUpdates$1 (react-dom.development.js:26140:12)
at batchedUpdates (react-dom.development.js:3991:12)
at dispatchEventForPluginEventSystem (react-dom.development.js:9287:3)
at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (react-dom.development.js:6465:5)
at dispatchEvent (react-dom.development.js:6457:5)
at dispatchDiscreteEvent (react-dom.development.js:6430:5)

TypeError: Cannot read properties of null (reading 'cases')
getUnionCases
/__parcel_source_root/fable_modules/fable-library.3.7.20/Reflection.js:287:10
284 | }
285 | // FSharpType
286 | export function getUnionCases(t) {

287 | if (t.cases != null) {
| ^ 288 | return t.cases();
289 | }
290 | else {
View compiled
getCaseName
/__parcel_source_root/fable_modules/Fable.Elmish.Browser.4.0.0/tracers.fs.js:14:40
11 | const t_1 = t_1_mut, acc = acc_mut, x_1 = x_1_mut;
12 | const caseName = getCaseName_1(x_1);
13 | let uci_1;
14 | const array = getUnionCases(t_1);
| ^ 15 | uci_1 = array.find((uci) => (name(uci) === caseName));
16 | const acc_1 = cons(getCaseName_1(x_1), acc);
17 | const fields = getCaseFields(x_1);
View compiled
getMsgNameAndFields
/__parcel_source_root/fable_modules/Fable.Elmish.Browser.4.0.0/tracers.fs.js:37:15
34 | }
35 | };
36 | if (isUnion(x)) {
37 | return getCaseName(t, empty(), x);
| ^ 38 | }
39 | else {
40 | return ["Msg", x];
View compiled
console$
/__parcel_source_root/Main.fs.js:10:45
7 |
8 | export function console$(msg, state, subId) {
9 | toConsole(printf("%A"))(subId);
10 | const patternInput = getMsgNameAndFields(null, msg);
| ^ 11 | const msg_1_1 = patternInput[0];
12 | const fields = patternInput[1];
13 | console.log(some(msg_1_1), fields);
View compiled
(anonymous function)
/__parcel_source_root/Main.fs.js:17:4
14 | }
15 |
16 | ProgramModule_run(ProgramModule_withConsoleTrace(ProgramModule_withTrace((msg_1, state_2, subId) => {
17 | console$(msg_1, state_2, subId);
| ^ 18 | }, Program_withReactSynchronous("fable-elmish-app", ProgramModule_mkProgram(init, update, render)))));
19 |
20 |
View compiled
Program$4.update
/__parcel_source_root/fable_modules/Fable.Elmish.4.0.0/program.fs.js:94:8
91 | const state = patternInput[0];
92 | const cmd = patternInput[1];
93 | const subIds = map_1((tuple) => tuple[0], program.subscribe(state));
94 | trace(msg, state, subIds);
| ^ 95 | return [state, cmd];
96 | };
97 | return new Program$4(program.init, update, program.subscribe, program.view, program.setState, program.onError, program.termination);

Add flagParam for valueless query string parameters

Description

To deal with boolean paramaters it would be nice to have a 'flagParam' function, where the presence of the parameter is enough to set it to true, otherwise the value is false.

Sometimes it is enough to simply need the presence of a parameter.

Repro code

../mypage?nav

where

flagParam "nav" returns true if nav is present, otherwise false.

With the current customParam function I get None when I provide only the key part of the query string parameter so it is not possible to differentiate between the presence or absence of the parameter and implement flagParam based on customParam.

Related information

  • elmish version: 2.0.3
  • elmish-browser version: 2.1
  • fable-compiler version: 2.0.10
  • fable-core version: 2.0.1
  • Operating system: Windows 10

3.0.1 not on nuget (needed to fix #22)

Description

I am currently updating https://github.com/SAFE-Stack/SAFE-ConfPlanner to the latest and greates and I am getting the following error when building:

ERROR in ./.fable/Fable.Elmish.Browser.3.0.0/parser.fs
Module Error (from ./node_modules/fable-loader/index.js):
C:/src/SAFE-ConfPlanner/.fable/Fable.Elmish.Browser.3.0.0/parser.fs(272,19): (272,46) error FSHARP: A unique overload for method 'TryParse' could not be determined based on type information prior to this program point. A type annotation may be needed. Candidates: System.Int32.TryParse(s: System.ReadOnlySpan<char>, result: byref<int>) : bool, System.Int32.TryParse(s: string, result: byref<int>) : bool (code 41)

It seems that this is fixed in 3.0.1 (#22) but this version is not published to nuget. Would be awesome if you could do this.

grafik

Fable Compiler 3 Fails to compile

Description

I'm testing the waters by upgrading to Fable3
and all went smoothly except for an internal binding which

C:/Users/scyth/source/repos/PalabratorFS/src/.fable/Fable.Elmish.Browser.3.0.0/parser.fs(272,19): (272,46) error FSHARP: A unique overload for method 'TryParse' could not be determined based on type information prior to this program point. A type annotation may be needed.
Known type of argument: string
Candidates:
System.Int32.TryParse(s: System.ReadOnlySpan, result: byref) : bool

System.Int32.TryParse(s: string, result: byref) : bool (code 41)

Watching src

refers to this code

let internal intParamHelp =
    Option.bind
        (fun value ->
            match System.Int32.TryParse value with
            | (true,x) -> Some x
            | _ -> None)

It fixes locally if ...(fun (value: string) -> ... is added but I'm not sure if that's the actual fix

Let me know if this issue should go here or in the fable repository

Fable3 Compiler Output
┌──────────────────>
│PalabratorFS •NET v5.0.100-rc.2.20479.15 via ⬢ v14.13.0 on aws🧇(us-west-2)
└─> on  main [!]
[😏]~>  dotnet fable watch src --run webpack-dev-server
Fable: F# to JS compiler 3.0.0-nagareyama-beta-002
Thanks to the contributor! @KevinLamb
src> cmd /C "dotnet" restore App.fsproj
  Determining projects to restore...
C:\Users\scyth\source\repos\PalabratorFS\src\App.fsproj : warning NU1608: Detected package version outside of dependency constraint: Feliz.UseListener 0.6.2 requires FSharp.Core (>= 4.7.2 && < 5.0.0) but 
version FSharp.Core 5.0.0 was resolved.
  Restored C:\Users\scyth\source\repos\PalabratorFS\src\App.fsproj (in 356 ms).
Parsing src\App.fsproj...
Initializing F# compiler...
Compiling src\App.fsproj...
F# compilation finished in 9803ms
Compiled src\.fable\Fable.Promise.2.0.0\PromiseImpl.fs
Compiled src\.fable\Fable.Elmish.3.1.0\prelude.fs
Compiled src\.fable\Fable.Elmish.Browser.3.0.0\prelude.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.ReactDom.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.fs   
Compiled src\.fable\Fable.Elmish.3.1.0\cmd.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.Extensions.fs
Compiled src\.fable\Fable.Elmish.Browser.3.0.0\navigation.fs   
Compiled src\.fable\Fable.Elmish.3.1.0\program.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.Standard.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.Props.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.ReactiveComponents.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.ReactServer.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.Hooks.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.FunctionComponent.fs
Compiled src\.fable\Fable.Elmish.3.1.0\ring.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.Isomorphic.fs
Compiled src\.fable\Fable.Elmish.Browser.3.0.0\parser.fs
Compiled src\.fable\Fable.React.7.0.1\Fable.React.Helpers.fs
Compiled src\.fable\Fable.Elmish.React.3.0.1\common.fs
Compiled src\.fable\Fable.Elmish.React.3.0.1\react.fs
Compiled src\.fable\Feliz.1.14.0\StyleTypes.fs
Compiled src\.fable\Fable.Elmish.HMR.4.0.1\bindings.fs
Compiled src\.fable\Feliz.1.14.0\Types.fs
Compiled src\.fable\Feliz.1.14.0\ReactTypes.fs
Compiled src\.fable\Feliz.1.14.0\Interop.fs
Compiled src\.fable\Feliz.1.14.0\Transform.fs
Compiled src\.fable\Feliz.1.14.0\Fonts.fs
Compiled src\.fable\Fable.Elmish.HMR.4.0.1\hmr.fs
Compiled src\.fable\Feliz.1.14.0\Key.fs
Compiled src\.fable\Feliz.1.14.0\Colors.fs
Compiled src\.fable\Fable.Elmish.React.3.0.1\react-native.fs
Compiled src\.fable\Feliz.1.14.0\TextDecorationStyle.fs
Compiled src\.fable\Feliz.1.14.0\TextDecorationLine.fs
Compiled src\.fable\Feliz.1.14.0\BorderStyle.fs
Compiled src\.fable\Feliz.1.14.0\Length.fs
Compiled src\.fable\Fable.Elmish.HMR.4.0.1\common.fs
Compiled src\.fable\Feliz.1.14.0\TransitionProperty.fs
Compiled src\.fable\Fable.Promise.2.0.0\Promise.fs
Compiled src\.fable\Feliz.1.14.0\ReactDOM.fs
Compiled src\.fable\Feliz.UseListener.0.6.2\PromiseRejectionEvent.fs
Compiled src\.fable\Thoth.Json.4.0.0\Types.fs
Compiled src\.fable\Feliz.UseElmish.1.4.1\UseElmish.fs
Compiled src\.fable\Feliz.UseListener.0.6.2\Listener.fs
Compiled src\.fable\Fable.Elmish.Debugger.3.2.0\Fable.Import.RemoteDev.fs
Compiled src\.fable\Thoth.Json.4.0.0\Extra.fs
Compiled src\Types.fs
Compiled src\.fable\Fable.Elmish.Debugger.3.2.0\debugger.fs
Compiled src\.fable\Feliz.1.14.0\Html.fs
Compiled src\.fable\Feliz.1.14.0\React.fs
Compiled src\.fable\Feliz.1.14.0\Styles.fs
Compiled src\.fable\Feliz.1.14.0\Properties.fs
Compiled src\Playground.fs
Compiled src\App.fs
Compiled src\Admin.fs
Compiled src\.fable\Feliz.Router.3.2.0\Router.fs
Compiled src\Main.fs
Compiled src\.fable\Thoth.Json.4.0.0\Encode.fs
Compiled src\Components\Game.fs
Compiled src\.fable\Thoth.Json.4.0.0\Decode.fs
Fable compilation finished in 1896ms
C:/Users/scyth/source/repos/PalabratorFS/src/.fable/Fable.Elmish.Browser.3.0.0/parser.fs(272,19): (272,46) error FSHARP: A unique overload for method 'TryParse' could not be determined based on type information prior to this program point. A type annotation may be needed.

Known type of argument: string

Candidates:

  • System.Int32.TryParse(s: System.ReadOnlySpan, result: byref) : bool
  • System.Int32.TryParse(s: string, result: byref) : bool (code 41)
    Watching src

Repro code

clone this repository

Expected and actual results

I don't expect to have errors compiling the libraries

Related information

  • elmish version: N/A
  • fable-compiler version: 3.0.0-nagareyama-beta-002
  • fable-core version: N/A
  • Operating system: Windows 10.0.19041

TypeError: 对象不支持此操作 (object does not support this operation)

Description

Navigation.newUrl does not work on IE11. I got error says object does dot support this operation.

Below code can be a workaround for me:

  let goToUrl url =
    Browser.Dom.window.history.pushState((), "", url)
    let evt = Browser.Dom.document.createEvent("Event")
    evt.initEvent("NavigatedEvent", false, false)
    Browser.Dom.window.dispatchEvent evt |> ignore
    Cmd.none

Expected and actual results

Should work on IE 11

Related information

  • elmish version: 3.0.1
  • fable-compiler version: 2.3.12
  • fable-core version: 3.0
  • Operating system: windows 10

Clean old events handler when HMR is trigger

Description

When working on Thoth.Elmish.Toast I discovered that previous event handler are not cleaned HMR update. This is normal, because HMR can't know how to clean the old events handlers.

I managed to write a fix for this

// If HMR support is active, then we provide have a custom implementation.
// This is needed to avoid:
// - flickering (trigger several react renderer process)
// - attaching several event listener to the same event
if not (isNull HMR.``module``.hot) then
    if HMR.``module``.hot.status() <> HMR.Idle then
        Browser.window.removeEventListener(eventIdentifier, !!Browser.window?(eventIdentifier))

    Browser.window?(eventIdentifier) <- fun (ev : Browser.Event) ->
        let ev = ev :?> Browser.CustomEvent
        dispatch (Add (unbox ev.detail))

    Browser.window.addEventListener(eventIdentifier, !!Browser.window?(eventIdentifier))
else
    Browser.window.addEventListener(eventIdentifier, !^(fun ev ->
        let ev = ev :?> Browser.CustomEvent
        dispatch (Add (unbox ev.detail))
))

This is a known problem when using HMR, we need to clean the "global states" (event listener, global variables, etc) by ourself even in pure JavaScript.

At the time I was thinking that the Program.toNavigable was ok in current state because it's checking capturing when the newLocation is equal to lastLocation.

But it's not enough in fact, because when we have an HMR update I think we are creating a new Elmish instance. And so each instance is having its own handlers and lastLocation reference.

So for example, if in your code base when you have a page change you send a request to the server then you will send X request to your server. In the same way, I do have some concurrent race and each application try to render it's own view when a page change and it's causing some visual flickering.

In general, the renderered view is the newest program registered so this is why no one noticed this problem yet.

I will send a PR to remove old events listeners when using Program.toNavigable


On a side note, I will try to see if we can also completly kill the previous Elmish instance as they are still in running in memories.

Here I triggered 3 updates of the code and so have:

  • Fleet Mapping aka instance n°1
  • Instance n°2
  • Instance n°3
  • Instance n°4

capture d ecran 2018-11-06 a 11 58 43

Elmish.Navigation.Program.Internal.unsubscribe doesn't unsubscribe

Description

Elmish.Navigation.Program.Internal.unsubscribe () doesn't unsubscribe.

Repro code

  1. Set up a test like
    describe "App navigation" <| fun () ->
      it "should remove event listener after unsubscribing" <| fun () -> promise {
        use! container =
            app () |> Program.mountAndTest 
        
        do document.location.hash <- "#/randomroute"
    
        Elmish.Navigation.Program.Internal.unsubscribe ()
    
        window.location.hash <- "#/someotherroute"
      }
  2. Manually modify navigation.fs.js to include a console.log inside onChange

Expected

Navigating, #/randomroute

Actual

Navigating, #/randomroute
Navigating, #/someotherroute

Related information

  • elmish version: 3.1.0
  • fable-compiler version: 3.7.9
  • fable-core version: 3.6.2

It looks (to me) like this is because removeEventListener gets given unbox onChangeRef which is getting compiled by Fable as (clo1 = ProgramModule_Internal_onChangeRef, (arg10) => { clo1(arg10); }).

If I manually edit navigation.fs.js to be just ProgramModule_Internal_onChangeRef, it works.

navigation.fs.js

export function ProgramModule_Internal_subscribe(dispatch) {
    let clo1, clo1_1, clo1_2;
    let lastLocation = void 0;
    const onChange = (_arg1) => {
        console.log ("Navigating", _arg1); // <--------- manually added
        let href;
        let value;
        let pattern_matching_result;
        if (lastLocation != null) {
            if ((href = lastLocation, href === window.location.href)) {
                pattern_matching_result = 0;
            }
            else {
                pattern_matching_result = 1;
            }
        }
        else {
            pattern_matching_result = 1;
        }
        switch (pattern_matching_result) {
            case 0: {
                value = (void 0);
                break;
            }
            case 1: {
                lastLocation = window.location.href;
                value = dispatch(new Navigable$1(0, window.location));
                break;
            }
        }
        return void 0;
    };
    ProgramModule_Internal_onChangeRef = onChange;
    
    window.addEventListener("popstate", (clo1 = ProgramModule_Internal_onChangeRef, (arg10) => {
        clo1(arg10);
    }));
    window.addEventListener("hashchange", (clo1_1 = ProgramModule_Internal_onChangeRef, (arg10_1) => {
        clo1_1(arg10_1);
    }));
    window.addEventListener("NavigatedEvent", (clo1_2 = ProgramModule_Internal_onChangeRef, (arg10_2) => {
        clo1_2(arg10_2);
    }));
}

export function ProgramModule_Internal_unsubscribe() {
    let clo1, clo1_1, clo1_2;
    
    window.removeEventListener("popstate", (clo1 = ProgramModule_Internal_onChangeRef, (arg10) => {
        clo1(arg10);
    }));
    window.removeEventListener("hashchange", (clo1_1 = ProgramModule_Internal_onChangeRef, (arg10_1) => {
        clo1_1(arg10_1);
    }));
    window.removeEventListener("NavigatedEvent", (clo1_2 = ProgramModule_Internal_onChangeRef, (arg10_2) => {
        clo1_2(arg10_2);
    }));
}

Fable.Elmish.Browser broken after Fable.Browser.Dom update

I did the following paket update:

image

which leads to:

 /home/gitlab-runner/builds/e76ee70f/0/products/eCarApp/.fable/Fable.Elmish.Browser.3.0.4/navigation.fs(70,13): (70,67) error FSHARP: A unique overload for method 'addEventListener' could not be determined based on type information prior to this program point. A type annotation may be needed.
 Known types of arguments: string * 'a
 Candidates:
  - abstract member EventTarget.addEventListener : type:string * listener:(Event -> unit) * ?options:AddEventListenerOptions -> unit
  - abstract member EventTarget.addEventListener : type:string * listener:(Event -> unit) * ?useCapture:bool -> unit (code 41)
  @ ./src/Client/App.fs 6:0-143 19:15-54
  @ ./src/Client/Client.fsproj
  @ multi whatwg-fetch @babel/polyfill ./src/Client/Client.fsproj ./src/Client/index.js

Fable 2

Could you please update dependencies to Fable.Core/Fable.PowerPack 2 (prerelease) and release a prerelease version?

add Parser.pathUrl

The current parser works on Browser.Types.Location. It would be great for React Native to have one that works on strings. Can we use the following?

    let urlParser (url:string) =
        let pos = url.IndexOf "?"
        if pos >= 0 then
            let path = url.Substring(0,pos)
            let search = url.Substring(pos)
            parse configParser path (parseParams search)
        else
            parse configParser url Map.empty

How to parse a string literal

Description

Referring to Parsing Routes in the docs, I was not able to parse a string literal.

Repro code

open Elmish.Browser.UrlParser

parse (s "blog") "blog"

Expected and actual result

I figured it would compile and yield Some "blog", however, the expression doesn't compile due to the following error:

Type mismatch. Expecting a 
  'Parser<('a -> 'a)>, 'a>'
but given a
  'Parser<('a -> 'a), ('a -> 'a)>'
the types ''a' and ''a -> 'a' cannot be unified

Related information

  • fable-elmish version: 0.9.2
  • fable-compiler version: 1.1.9
  • fable-core version: 1.1.7
  • Operating system: Windows 10

How to notify the user that the Parser module has been moved to its own pacakge?

Description

With the release of v4, the urlParser module has been removed from this package and moved to its own package.

How can we inform the user of that fact? As right now, if they upgrade to v4 the project is going to fail to compile without much explanation.

Should we keep an empty shell in place with Obsolete message asking the user to use the new package?

Should we handle the onChangeRef differently?

Description

Now that v4 support termination, it is possible that the user will spawn several Elmish program.

Meaning that this is possible for them to register Program.toNavigable several times. This will result in onChangeRef being override by the last program which registered.

  • Program1 register with Program.toNavigable
  • Program2 register with Program.toNavigable
  • Program1 exit, it will try to unsubscribe() but it will remove the events listeners of Program2 instead of Program1

Originally, we exposed the unsubscribe() API for Elmish.HMR but now that Elmish.Browser can terminate itself, I think it is possible to move the let mutable private onChangeRef : obj -> obj = inside let toNavigableWith scope meaning that the reference will be scoped to the Program instance.

Naming conflict with the new Fable.Browser.XXX packages

When discussing the naming for the new Fable.Browser packages it was decided to just use the Browser namespace so users only had to open Browser, however this is causing conflicts whenever you open Elmish and then open Browser if this package is being referenced. The F# compiler thinks you're trying to open Elmish.Browser through a partially qualified namespace and gives you an error.

image

I've made some tests and it seems we can fix this without creating breaking changes (Fable.Browser.XXX packages have already been released in stable version) by turning Elmish.Browser into a module.

module Elmish.Browser

module Option = ...
module UrlParser = ...
module Navigation = ...

The only problem is this would force us to use a single file for the package because you cannot split a module across multiple files in F#. What do you think, @et1975? I know it's not ideal but it would improve user experience without asking for more changes. If you agree I can send a PR.

cc @forki @MangelMaxime

Problems with Fable 4

Compiling with Fabel 4.0.0-theta-018
gives following error

error EXCEPTION: Cannot find inline member: Elmish.UrlParser_op_LessDivideGreater
   at Fable.Transforms.State.CompilerImpl.Fable.Compiler.GetInlineExpr(String memberUniqueName) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\State.fs:line 250
   at Fable.Transforms.FSharp2Fable.Util.inlineExpr(IFableCompiler com, Context ctx, FSharpOption`1 r, Type t, FSharpOption`1 callee, CallInfo info, String membUniqueName) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.Util.fs:line 2206
   at Fable.Transforms.FSharp2Fable.Util.|Inlined|_|(IFableCompiler com, Context ctx, FSharpOption`1 r, Type t, FSharpOption`1 callee, CallInfo info, FSharpMemberOrFunctionOrValue memb) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.Util.fs:line 2265
   at Fable.Transforms.FSharp2Fable.Util.makeCallWithArgInfo(IFableCompiler com, Context ctx, FSharpOption`1 r, Type typ, FSharpOption`1 callee, FSharpMemberOrFunctionOrValue memb, MemberRef membRef, CallInfo callInfo) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.Util.fs:line 2291
   at Fable.Transforms.FSharp2Fable.Compiler.transformExpr@729-43.Invoke(Context ctx) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.fs:line 729
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at [email protected](Unit unitVar0) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 16
   at MonadicTrampoline.run[a](Thunk`1 _arg1) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\MonadicTrampoline.fs:line 8
   at Fable.Transforms.FSharp2Fable.Compiler.transformMemberValue(IFableCompiler com, Context ctx, String name, FSharpMemberOrFunctionOrValue memb, FSharpExpr value) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.fs:line 1183
   at Fable.Transforms.FSharp2Fable.Compiler.transformMemberDecl(FableCompiler com, Context ctx, FSharpMemberOrFunctionOrValue memb, FSharpList`1 args, FSharpExpr body) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.fs:line 1403
   at Fable.Transforms.FSharp2Fable.Compiler.transformDeclarations@1460.Invoke(FSharpImplementationFileDeclaration fsDecl) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.fs:line 1495
   at Microsoft.FSharp.Primitives.Basics.List.collectToFreshConsTail[T,TResult](FSharpFunc`2 f, FSharpList`1 list, FSharpList`1 cons) in /home/dev/Projects/fsharp/src/FSharp.Core/local.fs:line 434
   at Microsoft.FSharp.Primitives.Basics.List.collect[T,TResult](FSharpFunc`2 f, FSharpList`1 list) in /home/dev/Projects/fsharp/src/FSharp.Core/local.fs:line 442
   at Fable.Transforms.FSharp2Fable.Compiler.transformFile(Compiler com) in C:\Users\alfon\repos\Fable\src\Fable.Transforms\FSharp2Fable.fs:line 1944
   at [email protected](Unit unitVar) in C:\Users\alfon\repos\Fable\src\Fable.Cli\Pipeline.fs:line 122
   at Microsoft.FSharp.Control.AsyncPrimitives.CallThenInvoke[T,TResult](AsyncActivation`1 ctxt, TResult result1, FSharpFunc`2 part2) in /home/dev/Projects/fsharp/src/FSharp.Core/async.fs:line 510
   at Microsoft.FSharp.Control.Trampoline.Execute(FSharpFunc`2 firstAction) in /home/dev/Projects/fsharp/src/FSharp.Core/async.fs:line 112

Navigation with different base path?

Description

Hey Elmish team :)

What if you're serving your app from /foo/ instead of just /? Since the Parser passed to Program.toNavigatable is Location -> 'a it cannot know any possible configuration that's inside of your model. I understand that the parser is called before the user's init is called, so there's no initial model yet.

I don't know how to tackle this issue really, for now I put a window.__BASE_PATH__ string into our HTML when the SSR runs and apply it to my parser beforehand so I can cut it out if the location.pathname. For that I copied UrlParser.parseParams and UrlParser.toKeyValuePair btw because they're not public.

Ideas?

Maybe a helper function like UrlParser.parsePathWithPrefix : string -> Location -> 'a would already suffice?

Related information

  • elmish version: 2.0
  • fable-compiler version: 2.0
  • fable-core version: 2.0
  • Operating system: all

Uncaught Error Thrown in Routed Elmish Applications

Description

The Fable-Compiler v2.4.12 update changed the way Substring works. It now throws an error.
The parseHash method relies on the JS substring behavior that returns an empty string when the index is greater than the input string length. This error prevents routed, browser-based Elmish apps from loading when a base URL is used for the first request.

Repro code

Line 352 of parser.fs:
let hash = location.hash.Substring 1

Throws this error:

Uncaught Error: Invalid startIndex and/or length
    at substring (webpack-internal:///./.fable/fable-library.2.4.12/String.js:935)
    at parseHash (webpack-internal:///./.fable/Fable.Elmish.Browser.3.0.2/parser.fs:313)
    at parser (webpack-internal:///./src/App.fs:178)
    at init (webpack-internal:///./.fable/Fable.Elmish.Browser.3.0.2/navigation.fs:142)
    at Elmish_Program.eval [as init] (webpack-internal:///./.fable/fable-library.2.4.12/Util.js:988)
    at traceInit (webpack-internal:///./.fable/Fable.Elmish.3.0.6/program.fs:90)
    at init (webpack-internal:///./.fable/Fable.Elmish.Debugger.3.0.3/debugger.fs:92)
    at eval (webpack-internal:///./.fable/fable-library.2.4.12/Util.js:988)
    at Elmish_Program.eval [as init] (webpack-internal:///./src/App.fs:379)
    at ProgramModule$$$runWith (webpack-internal:///./.fable/Fable.Elmish.3.0.6/program.fs:151)

When the application is accessed without a hash segment in the URL (e.g. the root url)

Expected and actual results

Application successfully loads without error when requested using the root URL

Related information

  • elmish version: 3.0.6
  • fable-compiler version: 2.4.12
  • fable-core version: 3.1.4
  • Operating system: Windows x64-10

Fable compilation error "A unique overload for method 'TryParse' could not be determined..."

Description

Hello, I am getting some sort of Fable error during the compilation phase but it does not seem to actually prevent the build from completing and the program from running correctly. The error is the following:

...
[./src/Program.fs] 3.25 KiB {main} [built]
[./src/Program.fsproj] 29 bytes {main} [built]
    + 48 hidden modules

ERROR in ./.fable/Fable.Elmish.Browser.3.0.0/parser.fs
Module Error (from ./node_modules/fable-loader/index.js):
C:/Users/usr/Code/Hobby/Voyage/frontend/.fable/Fable.Elmish.Browser.3.0.0/parser.fs(272,19): (272,46) error FSHARP: A unique overload for method 'TryParse' could not be determined based on type information prior to this program point. A type annotation may be needed. Candidates: System.Int32.TryParse(s: System.ReadOnlySpan<char>, result: byref<int>) : bool, System.Int32.TryParse(s: string, result: byref<int>) : bool (code 41)
 @ ./src/Program.fs 3:0-98 29:27-30 29:56-60 29:68-71 29:97-101 29:112-115 29:142-146 31:11-16 66:11-20
 @ ./src/Program.fsproj
i 「wdm」: Failed to compile.
i 「wdm」: Compiling...
Parsing ./src/Program.fsproj...
fable: Compiled src\Program.fs
fable: Compiled .fable\Fable.Elmish.Browser.3.0.0\parser.fs
...

Repro code

Here is a minimal example.

  1. npm install
  2. cd src
  3. dotnet restore
  4. cd ..
  5. npm run start

example.zip

Expected and actual results

The expected result would be for there to be no Fable compilation error. The actual result is the error specified above during the Fable compilation process.

Related information

  • elmish version: 3.0.0
  • fable-compiler version: 2.3.8
  • fable-core version: 3.0.1
  • Operating system: Windows 10

Sneaky question while I'm at it...

How would one do nested routing? I can't envision how that would work with the whole composed MVU architecture thing. Do you know of any examples in the wild that I might check out?

Fix for Fable substring behavior for v4

Description

A fix for #30 was made to the v3 branch but not made to the v4 branch.

Repro code

Same as #30.

Expected and actual results

Same as #30.

Related information

  • elmish version: 4.0.0-beta-3
  • fable-compiler version: >2.4.12
  • fable-core version: >3

Impossible to use Program.toNavigable together with Program.runWith

Description

I couldn't get Program.toNavigable to work with Program.runWith.

toNavigable produces a Program which discards the argument type of the previous Program.

Repro code

Program.mkProgram init update view
|> Program.toNavigable parser urlUpdate
|> Program.runWith () // unit is the only possible argument

Expected and actual results

My expectation would be that toNavigable carries on the argument type.
This means that the init function not only receives the route but also the init parameter.

Expected result:

Program<'a, 'model, 'msg, 'view> becomes Program<'a, 'model, Navigable<'msg>, 'view> where 'a is the argument to the init function.

Actual result:

Program<'a, 'model, 'msg, 'view> becomes Program<unit, 'model, Navigable<'msg>, 'view>

Related information

  • elmish version: 2.0.3
  • elmish-browser version: 2.1
  • fable-compiler version: 2.0.10
  • fable-core version: 2.0.1
  • Operating system: Windows 10

MapInit error

Hi there,

updating to the newest Elmish (4.0.0-beta-8) produces following error:

String.js:522 Uncaught (in promise) Error: Invalid startIndex and/or length
at substring (String.js:522:15)
at parseParams (parser.fs.js:274:72)
at parsePath (parser.fs.js:5:56)
at urlParser (Pages.js:197:21)
at init (navigation.fs.js:96:56)
at Program$4.init (Util.js:552:12)
at traceInit (program.fs.js:66:38)
at init (debugger.fs.js:36:30)
at Util.js:552:12
at mapInit (App.js:2909:42)

I didn't make any changes from the version 3 to version 4 yet.

image

Any idea how to fix that?

ToNavigable hides non-navigation message names from debugger

Description

Using toNavigable in the program initialization causes "wrapped" the underlying "message" names to be hidden from both the elmish-debugger and the console debugger.

toNavigable wraps the underlying message case in the Parser type. Elmish-debugger then uses reflection to get the event name, which resolves to the "wrapped" name instead.

Messages display in Redux Devtools inspector pane as one of either "Change" for a url change or "UserMsg" for anything else. I.e. the two Parser union cases from Elmish.Navigation.

This also effects the name shown in the console by "withConsoleTrace".

Repro code

Program.mkProgram init update view
|> Program.toNavigable (parseHash route) urlUpdate  // anything downstream of this gets wrapped message type.
#if DEBUG
|> Program.withConsoleTrace
#endif
|> Program.withReactBatched "elmish-app"
#if DEBUG
|> Program.withDebugger
#endif
|> Program.run

Expected and actual results

Expected result is that, when using Elmish.Navigation, original message names will still be displayed correctly in the Redux Devtools inspector panel. For example "Increment", "Decrement", "ModelLoaded".

Actual result is that, when using Elmish.Navigation, all non url-change messages are displayed in Redux Devtools inspector pane with the name "UserMsg".

Related information

  • elmish version: 3.0.4
  • fable-compiler version: 2.3.12
  • fable-core version: 3.0
  • Operating system: Windows 10

Base64 string padding char seems to trip up parser

Description

For base 64 encoded strings, they are often padded with an = character. This seems to break the parser and return None for a query string parameter with padding.

Workaround
String.replace ("=", "@") //or some other character that isn't in the standard base64 set and is allowed in a query param
//https://en.wikipedia.org/wiki/Base64

Repro code

let route = s "someRoute"  <?> stringParam "data"

Then run any query string with padding. For example:
domain.com/someRoute?data=YQ==
where data is "a" in this context.

Expected and actual results

  • Expected
    ** data = Some "YQ=="
  • Actual
    ** data = None

Related information

  • elmish version: Fable.Elmish (3.0.6)
  • fable-compiler version: 2.4.11
  • fable-core version:Fable.Core (3.1.3)
  • Operating system: Windows 10

Hopefully this is quite clear. I imagine it is probably due to the parser maybe splits on = rather than parse based on previous context - eg a parameter can only start with ? or & and is followed by an =.

I know that base64 isnt that common, but its handy for passing around complex serialised graphs compactly. Tokens are also also sometimes sent back to a client this way, although they do use hashtag navigation.

Let me know if anything else is needed.

Naming conflict with the new Fable.Browser.

Description

Please provide a succinct description of your issue.

Repro code

Please provide the F# code to reproduce the problem.
Ideally, it should be possibe to easily turn this code into a unit test.

Expected and actual results

Please provide the expected and actual results.

Related information

  • elmish version:
  • fable-compiler version:
  • fable-core version:
  • Operating system:

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.