├── .gitattributes
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .npmrc
├── LICENSE.md
├── README.md
├── RELEASE_NOTES.md
├── appveyor.yml
├── babel.config.json
├── build.fsx
├── docs
├── _partials
│ └── footer.jsx
├── index.fsx
├── prelude.fsx
├── release_notes.md
├── scss
│ └── fable-font.scss
├── static
│ ├── fonts
│ │ └── fable-font
│ │ │ ├── fable-font.eot
│ │ │ ├── fable-font.svg
│ │ │ ├── fable-font.ttf
│ │ │ └── fable-font.woff
│ └── img
│ │ └── logo.png
└── style.scss
├── global.json
├── nacara.config.json
├── package-lock.json
├── package.json
└── src
├── Fable.Elmish.Debugger.fsproj
├── Fable.Import.RemoteDev.fs
└── debugger.fs
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ### Contributor guidelines
2 |
3 | First of all - thanks for taking the time to contribute!
4 |
5 | With that in mind, elmish is a young project and as such while we welcome the contributions from non-member there are certain things we'd like to get more right than fast. To make everyone's experience as enjoyable as possible please keep the following things in mind:
6 |
7 | * Unless it's a trivial fix, consider opening an issue first to discuss it with the team.
8 | * If you are just looking for something to take on, check the *help wanted" labeled items
9 |
10 |
11 | ### Opening an Issue
12 |
13 | * Before you do, please check if there's a known work around, existing issue or already a work in progress to address it.
14 | * If you just don't know how to do something consider asking in the gitter, there are always helpful people around.
15 | * Provide as much info as possible - follow the template if it makes sense, attach screenshots or logs if applicable.
16 |
17 |
18 | ### Pull requests
19 |
20 | To make it easier to review the changes and get you code into the repo keep the commit history clean:
21 |
22 | * [rebase your pulls](https://coderwall.com/p/tnoiug/rebase-by-default-when-doing-git-pull) on the latest from repo
23 | * only push the commits relevant to the PR
24 |
25 | If adding a feature, also consider adding a sample (or link to one).
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Description
2 |
3 | Please provide a succinct description of your issue.
4 |
5 | ### Repro code
6 |
7 | Please provide the F# code to reproduce the problem.
8 | Ideally, it should be possibe to easily turn this code into a unit test.
9 |
10 | ### Expected and actual results
11 |
12 | Please provide the expected and actual results.
13 |
14 | ### Related information
15 |
16 | * elmish version:
17 | * fable-compiler version:
18 | * fable-core version:
19 | * Operating system:
20 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: .NET
2 |
3 | on:
4 | push:
5 | branches: [ v4.x ]
6 | pull_request:
7 | branches: [ v4.x ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: Setup .NET
17 | uses: actions/setup-dotnet@v1
18 | with:
19 | dotnet-version: 6.0.x
20 | - name: Build
21 | run: ./build.fsx
22 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | branches:
5 | - v4.x
6 | workflow_dispatch:
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - name: Setup .NET
13 | uses: actions/setup-dotnet@v1
14 | with:
15 | dotnet-version: '6.0.x'
16 | - name: Setup Node.js
17 | uses: actions/setup-node@v2
18 | with:
19 | node-version: '16'
20 | - name: Install and use custom npm version
21 | run: npm i -g npm@7
22 | - name: Install NPM dependencies
23 | run: npm install
24 | - name: Build site
25 | run: ./build.fsx -t GenerateDocs
26 | - name: Deploy site
27 | uses: peaceiris/actions-gh-pages@v3
28 | with:
29 | personal_token: ${{ secrets.GITHUB_TOKEN }}
30 | publish_dir: ./docs_deploy
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # OSX
2 | #
3 | .DS_Store
4 |
5 | # node.js
6 | #
7 | **/node_modules/
8 | **/npm/
9 | npm-debug.log
10 |
11 | # F#
12 | .fake/
13 | packages/
14 | paket-files
15 | .paket/load
16 | build/
17 | obj
18 | bin
19 | out
20 |
21 | # git
22 | *.orig
23 |
24 | .vs
25 |
26 | docs/output
27 | docs/temp
28 |
29 | Directory.Build.props
30 | temp
31 | .ionide
32 |
33 | .nacara
34 | docs_deploy/
35 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2016 elmish contributors
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Elmish-Debugger: [Remote DevTools](https://github.com/zalmoxisus/remotedev) integration for [elmish](https://github.com/fable-compiler/elmish) applications.
2 | =======
3 |
4 | [](https://ci.appveyor.com/project/et1975/debugger/branch/v4.x) [](https://badge.fury.io/nu/Fable.Elmish.Debugger)
5 |
6 | For more information see [the docs](https://elmish.github.io/debugger).
7 |
8 | ## Installation
9 | ```shell
10 | yarn add remotedev@^0.2.4 -D
11 | paket add nuget Fable.Elmish.Debugger
12 | ```
13 |
14 | Follow the monitor installation instructions at [Remote DevTools](https://github.com/zalmoxisus/remotedev) site.
15 |
16 |
17 |
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
1 | ## 4.2.2
2 |
3 | * `actionCreators` support, thanks to @mardukbp
4 |
5 | ## 4.2.1
6 |
7 | * Fix regression in treatment of action types, thanks to @mardukbp
8 |
9 | ## 4.2.0
10 |
11 | * Connect to local extension with only 'jsan`, thanks to @mardukbp
12 |
13 | ## 4.1.0
14 |
15 | * Allow using Redux Devtools directly without remotedev
16 |
17 | ## 4.0.0
18 |
19 | * Breaking: elmish v4 subscriptions support
20 |
21 | ## 4.0.0-beta-2
22 |
23 | * New subscriptions support
24 |
25 | ## 4.0.0-beta-1
26 |
27 | * Elmish 4.0 support
28 |
29 | ## 3.3.0
30 |
31 | * Upgrade Thoth.Json to version 6
32 | * Keep FSharp.Core requirements lowered to 4.7.2
33 |
34 | ## 3.2.0
35 |
36 | * Release stable version
37 |
38 | ## 3.2.0-beta-1
39 |
40 | * Upgrade Thoth.Json to version 4
41 |
42 | ## 3.1.0
43 |
44 | * Fix #38: Upgrade Thoth.Json to latest version
45 |
46 | ## 4.0.0-alpha-1
47 |
48 | * Elmish 3.0 release
49 |
50 | ## 3.0.3
51 |
52 | * Fix #33: getActionType via remotedev
53 | * Better display of nested messages
54 |
55 | ## 3.0.2
56 |
57 | * Fix #30: Add Thoth extra coders
58 |
59 | ## 3.0.1
60 |
61 | * Fix #28: Converting circular structure to JSON
62 |
63 | ## 3.0.0
64 |
65 | * Elmish 3.0 release
66 |
67 | ## 3.0.0-beta-3
68 |
69 | * Elmish 3.0 opaque `Program` support
70 |
71 | ## 3.0.0-beta-2
72 |
73 | * Elmish 3.0 compat
74 |
75 | ## 2.0.2
76 |
77 | * Thoth serialization by Alfonso
78 |
79 | ## 2.0.0
80 |
81 | * Stable release for Fable2
82 |
83 | ## 2.0.0-beta-5
84 |
85 | * inlining for Fable2 reflection to work
86 |
87 | ## 2.0.0-beta-4
88 |
89 | * Re-releasing v1 for Fable2
90 |
91 | ## 1.0.2
92 |
93 | * backporting the build
94 |
95 | ## 1.0.1
96 |
97 | * report errors fix by @zaaack
98 |
99 | ## 1.0.0
100 |
101 | * debounce by Alfonso
102 |
103 | ## 1.0.0-beta-1
104 |
105 | * dotnet 2.0 SDK build
106 |
107 | ## 0.9.0
108 |
109 | * Fable 1.1.x build
110 |
111 | ## 0.9.0-beta-6
112 |
113 | * Using FSharp.Reflection, all the glory goes to Jonathan Ohlrich
114 |
115 | ## 0.9.0-beta-1
116 |
117 | * Targeting Fable 1.x
118 |
119 | ## 0.1.2
120 |
121 | * Safely continue if the monitor is not available
122 |
123 | ## 0.1.1
124 |
125 | * Adding basic import support
126 |
127 | ## 0.1.0
128 |
129 | * Releasing the debugger support
130 |
131 | ## 0.1.0-alpha.5
132 |
133 | * Everyting that Fable can `inflate` should work when time-traveling
134 |
135 | ## 0.1.0-alpha.2
136 |
137 | * Simple state works
138 |
139 | ## 0.1.0-alpha.1
140 |
141 | * Initial debugger release
142 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2022
2 |
3 | install:
4 | - ps: Install-Product node 17 x64
5 | - ps: dotnet tool restore
6 |
7 | build_script:
8 | - cmd: dotnet fsi build.fsx
9 |
10 | cache:
11 | - "%LOCALAPPDATA%\\Yarn"
12 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/build.fsx:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env -S dotnet fsi
2 | #r "nuget: Fake.Core.Target, 5.23.1"
3 | #r "nuget: Fake.IO.FileSystem, 5.23.1"
4 | #r "nuget: Fake.DotNet.Cli, 5.23.1"
5 | #r "nuget: Fake.Core.Target, 5.23.1"
6 | #r "nuget: Fake.Core.ReleaseNotes, 5.23.1"
7 | #r "nuget: Fake.Tools.Git, 5.23.1"
8 |
9 | #nowarn "52"
10 |
11 | open System
12 | open System.IO
13 | open Fake.Core
14 | open Fake.Core.TargetOperators
15 | open Fake.DotNet
16 | open Fake.Tools
17 | open Fake.IO
18 | open Fake.IO.FileSystemOperators
19 | open Fake.IO.Globbing.Operators
20 |
21 |
22 | let gitName = "debugger"
23 | let gitOwner = "elmish"
24 | let gitHome = sprintf "https://github.com/%s" gitOwner
25 | let gitRepo = sprintf "git@github.com:%s/%s.git" gitOwner gitName
26 |
27 | // Filesets
28 | let projects =
29 | !! "src/**.fsproj"
30 |
31 | System.Environment.GetCommandLineArgs()
32 | |> Array.skip 2 // fsi.exe; build.fsx
33 | |> Array.toList
34 | |> Context.FakeExecutionContext.Create false __SOURCE_FILE__
35 | |> Context.RuntimeContext.Fake
36 | |> Context.setExecutionContext
37 |
38 | let withWorkDir = DotNet.Options.withWorkingDirectory
39 |
40 | Target.create "Clean" (fun _ ->
41 | Shell.cleanDir "src/obj"
42 | Shell.cleanDir "src/bin"
43 | )
44 |
45 | Target.create "Restore" (fun _ ->
46 | projects
47 | |> Seq.iter (fun s ->
48 | let dir = Path.GetDirectoryName s
49 | DotNet.restore (fun a -> a.WithCommon (withWorkDir dir)) s
50 | )
51 | )
52 |
53 | Target.create "Build" (fun _ ->
54 | projects
55 | |> Seq.iter (fun s ->
56 | let dir = Path.GetDirectoryName s
57 | DotNet.build (fun a ->
58 | a.WithCommon
59 | (fun c ->
60 | let c = c |> withWorkDir dir
61 | {c with CustomParams = Some "/p:SourceLinkCreate=true"}))
62 | s
63 | )
64 | )
65 |
66 | let release = ReleaseNotes.load "RELEASE_NOTES.md"
67 |
68 | Target.create "Meta" (fun _ ->
69 | [ ""
70 | ""
71 | "Debugger for Elmish apps"
72 | "https://github.com/elmish/debugger"
73 | "https://raw.githubusercontent.com/elmish/debugger/master/LICENSE.md"
74 | "https://raw.githubusercontent.com/elmish/debugger/master/docs/files/img/logo.png"
75 | "https://github.com/elmish/debugger.git"
76 | sprintf "%s" (List.head release.Notes)
77 | "fable;elmish;fsharp;debugger"
78 | "Eugene Tolmachev"
79 | sprintf "%s" (string release.SemVer)
80 | ""
81 | ""]
82 | |> File.write false "Directory.Build.props"
83 | )
84 |
85 | // --------------------------------------------------------------------------------------
86 | // Build a NuGet package
87 |
88 | Target.create "Package" (fun _ ->
89 | projects
90 | |> Seq.iter (fun s ->
91 | let dir = Path.GetDirectoryName s
92 | DotNet.pack (fun a ->
93 | a.WithCommon (withWorkDir dir)
94 | ) s
95 | )
96 | )
97 |
98 | Target.create "PublishNuget" (fun _ ->
99 | let exec dir =
100 | DotNet.exec (fun a ->
101 | a.WithCommon (withWorkDir dir)
102 | )
103 |
104 | let args = sprintf "push Fable.Elmish.Debugger.%s.nupkg -s nuget.org -k %s" (string release.SemVer) (Environment.environVar "nugetkey")
105 | let result = exec "src/bin/Release" "nuget" args
106 | if (not result.OK) then failwithf "%A" result.Errors
107 | )
108 |
109 |
110 | // --------------------------------------------------------------------------------------
111 | // Generate the documentation
112 | Target.create "GenerateDocs" (fun _ ->
113 | let res = Shell.Exec("npm", "run docs:build")
114 |
115 | if res <> 0 then
116 | failwithf "Failed to generate docs"
117 | )
118 |
119 | Target.create "WatchDocs" (fun _ ->
120 | let res = Shell.Exec("npm", "run docs:watch")
121 |
122 | if res <> 0 then
123 | failwithf "Failed to watch docs: %d" res
124 | )
125 |
126 | // --------------------------------------------------------------------------------------
127 | // Release Scripts
128 |
129 | Target.create "ReleaseDocs" (fun _ ->
130 | let res = Shell.Exec("npm", "run docs:publish")
131 |
132 | if res <> 0 then
133 | failwithf "Failed to publish docs: %d" res
134 | )
135 |
136 | Target.create "Publish" ignore
137 |
138 | // Build order
139 | "Clean"
140 | ==> "Meta"
141 | ==> "Restore"
142 | ==> "Build"
143 | ==> "Package"
144 | ==> "PublishNuget"
145 | ==> "Publish"
146 |
147 | // start build
148 | Target.runOrDefault "Build"
149 |
--------------------------------------------------------------------------------
/docs/_partials/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SitemapSection = ({ title, children }) => (
4 |
5 |
6 | {title}
7 |
8 |
11 |
12 | )
13 |
14 | const SitemapSectionItem = ({ text, icon, url }) => (
15 |
16 |
17 |
18 |
19 |
20 |
21 | {text}
22 |
23 |
24 |
25 | )
26 |
27 | const CopyrightScript = () => (
28 |
34 | )
35 |
36 | export default (
37 |
38 |
39 |
40 |
44 |
45 |
49 |
50 |
54 |
55 |
56 |
60 |
61 |
65 |
66 |
70 |
71 |
75 |
76 |
80 |
81 |
85 |
86 |
87 |
88 |
92 |
93 |
97 |
98 |
102 |
103 |
107 |
108 |
109 |
110 |
111 | Built with Nacara
112 |
113 |
114 | Copyright © 2021- Elmish contributors.
115 |
116 |
117 |
118 | )
119 |
--------------------------------------------------------------------------------
/docs/index.fsx:
--------------------------------------------------------------------------------
1 | (**
2 | ---
3 | layout: standard
4 | title:
5 | toc: false
6 | ---
7 | **)
8 |
9 | (*** hide ***)
10 |
11 | #load "./prelude.fsx"
12 |
13 | let view _ _ = failwith "view not implemented"
14 | let update _ _ = failwith "update not implemented"
15 | let init _ = failwith "init not implemented"
16 |
17 | open Elmish
18 |
19 | (**
20 |
21 | ## Remote DevTools debugger
22 |
23 | Elmish applications can benefit from sophisticated time-travelling debugger with deep state and message inspection capabilities and import/export functionality.
24 | Wether you target browser, native or any other platform, as long as you can connect to a monitor you can start collecting and visualizing the events.
25 | For SPAs running in a browser, it's as easy as installing a plugin.
26 |
27 | 
28 |
29 |
30 | ### Installation
31 | For local debugging using the Redux DevTools browser extension add the following devDependency:
32 |
33 | ```sh
34 | yarn add jsan@^3.1.14 -D
35 | ```
36 |
37 | For connecting to a remote debugger add the Remote DevTools client package as a devDependency:
38 | ```sh
39 | yarn add remotedev@^0.2.4 -D
40 | ```
41 |
42 | Then add Fable package:
43 |
44 | ```sh
45 | dotnet add package Fable.Elmish.Debugger
46 | ```
47 |
48 | Follow the monitor installation instructions at [Remotedev tools](https://github.com/zalmoxisus/remotedev) site.
49 |
50 | Among all the monitoring apps mentioned there, for local web debugging in Chrome we recommend using [Redux DevTools Chrome extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) as the fastest and the most seamless monitoring option.
51 |
52 | ### Program module functions
53 | Augment your program instance with a debugger, making sure it's the last item in the line of `Program` modifications:
54 |
55 | Usage:
56 | *)
57 |
58 |
59 | open Elmish.Debug
60 |
61 | Program.mkProgram init update view
62 | |> Program.withDebugger // connect to a devtools monitor via Chrome/Firefox extension if available
63 | |> Program.run
64 |
65 | (**
66 |
67 | the extension options can be configured as follows (currently only `name` is supported) :
68 |
69 | *)
70 |
71 |
72 | open Elmish.Debug
73 | open Fable.Import.RemoteDev
74 |
75 | Program.mkProgram init update view
76 | |> Program.withDebuggerOptions (new ExtensionOptions(name = "Elmish Debugger"))
77 | |> Program.run
78 |
79 |
80 | (**
81 |
82 | or in case of a remote debugger:
83 |
84 | *)
85 |
86 |
87 | open Elmish.Debug
88 |
89 | Program.mkProgram init update view
90 | |> Program.withDebuggerAt (Debugger.Remote("localhost",8000)) // connect to a server running on localhost:8000
91 | |> Program.run
92 |
93 |
94 | (**
95 |
96 | or, using a custom connection:
97 |
98 | *)
99 |
100 |
101 | open Elmish.Debug
102 |
103 | let connection = Debugger.connect (Debugger.Remote("localhost",8080)) // obtain the connection, for example if sending some information directly
104 |
105 | Program.mkProgram init update view
106 | |> Program.withDebuggerConnection connection
107 | |> Program.run
108 |
109 |
110 | (**
111 |
112 | ### Conditional compilation
113 | If don't want to include the debugger in production builds surround it with `#if DEBUG`/`#endif` and define the symbol conditionally in your build system.
114 |
115 | *)
116 |
--------------------------------------------------------------------------------
/docs/prelude.fsx:
--------------------------------------------------------------------------------
1 | #I "../src/bin/Debug/netstandard2.0"
2 |
3 | #r "Fable.Elmish.Debugger.dll"
4 | #r "nuget: Fable.Elmish, 4.0.0-beta-3"
5 |
--------------------------------------------------------------------------------
/docs/release_notes.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: changelog
3 | changelog_path: ./../RELEASE_NOTES.md
4 | ---
5 |
--------------------------------------------------------------------------------
/docs/scss/fable-font.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'fable-font';
3 | src: url('/static/fonts/fable-font/fable-font.eot?qh7nog');
4 | src: url('/static/fonts/fable-font/fable-font.eot?qh7nog#iefix') format('embedded-opentype'),
5 | url('/static/fonts/fable-font/fable-font.ttf?qh7nog') format('truetype'),
6 | url('/static/fonts/fable-font/fable-font.woff?qh7nog') format('woff'),
7 | url('/static/fonts/fable-font/fable-font.svg?qh7nog#fable-font') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | // Prepare bulma to accept our customs icons
13 | .icon {
14 | .faf {
15 | font-size: 21px;
16 | }
17 |
18 | &.is-small {
19 | height: 1rem;
20 | width: 1rem;
21 |
22 | .faf {
23 | font-size: 14px;
24 | }
25 | }
26 |
27 | &.is-medium {
28 | height: 2rem;
29 | width: 2rem;
30 |
31 | .faf {
32 | // font-size: 28px;
33 | font-size: 1.33333em;
34 | line-height: 0.75em;
35 | vertical-align: -.0667em;
36 | }
37 | }
38 |
39 | &.is-large {
40 | height: 3rem;
41 | width: 3rem;
42 |
43 | .faf {
44 | font-size: 42px;
45 | }
46 | }
47 | }
48 |
49 | .faf {
50 | display: inline-block;
51 | font: normal normal normal 14px/1 'fable-font';
52 | font-size: inherit;
53 | text-rendering: auto;
54 | -webkit-font-smoothing: antialiased;
55 | -moz-osx-font-smoothing: grayscale;
56 | }
57 |
58 | $icons: (
59 | fable: "\e900",
60 | fsharp-org: "\e901"
61 | );
62 |
63 | @each $name, $icon in $icons {
64 | .faf-#{$name}:after {
65 | content: $icon;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/docs/static/fonts/fable-font/fable-font.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elmish/debugger/cc6f687fedef8abad7d943765cd776eafbae41f4/docs/static/fonts/fable-font/fable-font.eot
--------------------------------------------------------------------------------
/docs/static/fonts/fable-font/fable-font.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/docs/static/fonts/fable-font/fable-font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elmish/debugger/cc6f687fedef8abad7d943765cd776eafbae41f4/docs/static/fonts/fable-font/fable-font.ttf
--------------------------------------------------------------------------------
/docs/static/fonts/fable-font/fable-font.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elmish/debugger/cc6f687fedef8abad7d943765cd776eafbae41f4/docs/static/fonts/fable-font/fable-font.woff
--------------------------------------------------------------------------------
/docs/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elmish/debugger/cc6f687fedef8abad7d943765cd776eafbae41f4/docs/static/img/logo.png
--------------------------------------------------------------------------------
/docs/style.scss:
--------------------------------------------------------------------------------
1 | @import "./../node_modules/bulma/sass/utilities/initial-variables";
2 |
3 | // Color palette
4 | // https://lospec.com/palette-list/fluffy8
5 |
6 | /////////////////////////////////
7 | /// Customize Bulma
8 | /////////////////////////////////
9 | $primary: #2d3947;
10 | $text: #2b2b2b;
11 | $danger: #c43636;
12 |
13 | @import "./../node_modules/bulma/sass/utilities/derived-variables";
14 |
15 | /////////////////////////////////
16 | /// nacara-layout-standard customizations
17 | /// Do not touch unless you know what you are doing
18 | /////////////////////////////////
19 | $navbar-breakpoint: 0px;
20 | $navbar-padding-vertical: 0.5rem;
21 | $navbar-padding-horizontal: 1rem;
22 | /////////////////////////////////
23 |
24 | // Specific to gatsby-remark-vscode usage
25 | $content-pre-padding: unset;
26 |
27 | /////////////////////////////////
28 | /// Customize Bulma
29 | /////////////////////////////////
30 |
31 | $navbar-item-color: $white;
32 | $navbar-background-color: $primary;
33 | $navbar-item-active-color: $white;
34 | $navbar-item-active-background-color: lighten($primary, 12%);
35 | $navbar-item-hover-color: $white;
36 | $navbar-item-hover-background-color: lighten($primary, 12%);;
37 | $navbar-dropdown-item-active-background-color: $primary;
38 | $navbar-dropdown-item-hover-background-color: $primary;
39 | $navbar-dropdown-item-hover-color: $white;
40 | $navbar-dropdown-item-active-color: $white;
41 |
42 | $menu-item-active-background-color: $primary;
43 | $menu-item-active-color: $white;
44 | $menu-item-hover-color: $primary;
45 | $menu-item-hover-background-color: transparent;
46 | $menu-label-font-size: $size-6;
47 | $menu-item-radius: $radius-large $radius-large;
48 |
49 | $footer-background-color: $primary;
50 | $footer-color: $white;
51 |
52 | $code: $red;
53 |
54 | $nacara-navbar-dropdown-floating-max-width: 450px;
55 |
56 | $body-size: 14px;
57 |
58 | @import "../node_modules/bulma/sass/utilities/_all.sass";
59 | @import "./../node_modules/bulma/bulma.sass";
60 | @import "./../node_modules/nacara-layout-standard/scss/nacara.scss";
61 | @import "./scss/fable-font.scss";
62 |
63 | // Begin gatsby-remark-vscode specific
64 | :root {
65 | --grvsc-padding-v: 1.25rem;
66 | }
67 |
68 | // Make the code use the full width for when user use line highlighting
69 | .content {
70 | pre > code {
71 | width: 100%;
72 | }
73 | }
74 | // End gatsby-remark-vscode specific
75 |
76 | // Override bulma
77 | .navbar {
78 | .navbar-dropdown {
79 | @include desktop {
80 | // Force navbar item text color otherwise it is the same as $navbar-item-color
81 | // Which is white in our case...
82 | .navbar-item {
83 | color: $text;
84 | }
85 | }
86 | }
87 |
88 | .navbar-link {
89 | &:not(.is-arrowless)::after {
90 | border-color: $white;
91 | }
92 | }
93 | }
94 |
95 | .footer a {
96 | color: $white;
97 | }
98 |
99 | .sitemap {
100 |
101 | max-width: 1024px;
102 | margin: 0 auto 2rem;
103 | display: grid;
104 | grid-gap: 4rem;
105 | grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
106 | font-size: $size-6;
107 |
108 | @include mobile {
109 | grid-template-columns: 1fr;
110 | grid-gap: 1rem;
111 | }
112 |
113 | a {
114 | color : white;
115 | }
116 |
117 | .sitemap-section {
118 | width: 100%;
119 |
120 | .sitemap-section-title {
121 | font-size: $size-4;
122 | font-weight: $weight-bold;
123 | text-align: center;
124 | padding-bottom: 1rem;
125 |
126 | @include mobile {
127 | text-align: left;
128 | }
129 | }
130 |
131 | .sitemap-section-list {
132 |
133 | li {
134 | border-top: 2px solid lighten($primary, 8%);
135 | }
136 |
137 | .sitemap-section-list-item {
138 | padding: 1rem 0.5rem;
139 | width: 100%;
140 |
141 | &:hover {
142 | background-color: lighten($primary, 4%);
143 | }
144 |
145 | .icon-text:hover {
146 | .sitemap-section-list-item-text {
147 | text-decoration: underline;
148 | }
149 | }
150 | }
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "6.0.100",
4 | "rollForward": "latestMinor"
5 | }
6 | }
--------------------------------------------------------------------------------
/nacara.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteMetadata": {
3 | "title": "Elmish.Debbuger",
4 | "url": "https://elmish.github.io",
5 | "baseUrl": "/debugger/",
6 | "editUrl" : "https://github.com/elmish/debugger/edit/v4.x/docs",
7 | "favIcon": "static/img/logo.png"
8 | },
9 | "navbar": {
10 | "start": [ ],
11 | "end": [
12 | {
13 | "url": "https://github.com/elmish/debugger",
14 | "icon": "fab fa-2x fa-github",
15 | "label": "GitHub"
16 | }
17 | ]
18 | },
19 | "remarkPlugins": [
20 | {
21 | "resolve": "gatsby-remark-vscode",
22 | "property": "remarkPlugin",
23 | "options": {
24 | "theme": "Atom One Light",
25 | "extensions": [
26 | "vscode-theme-onelight"
27 | ]
28 | }
29 | }
30 | ],
31 | "layouts": [
32 | "nacara-layout-standard"
33 | ]
34 | }
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hmr",
3 | "private": true,
4 | "type": "module",
5 | "engines": {
6 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0",
7 | "npm": ">=7.0.0"
8 | },
9 | "scripts": {
10 | "docs:watch": "nacara watch",
11 | "docs:build": "nacara build",
12 | "docs:publish": "nacara build && gh-pages -d docs_deploy"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/elmish/hmr.git"
17 | },
18 | "devDependencies": {
19 | "@babel/preset-react": "^7.16.0",
20 | "bulma": "^0.9.3",
21 | "gatsby-remark-vscode": "^3.3.1",
22 | "nacara-layout-standard": "^1.8.0",
23 | "react": "^18.2.0",
24 | "react-dom": "^18.2.0",
25 | "unified": "^10.1.1",
26 | "vscode-theme-onelight": "github:akamud/vscode-theme-onelight"
27 | },
28 | "dependencies": {
29 | "nacara": "^1.8.0",
30 | "remark-parse": "^10.0.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Fable.Elmish.Debugger.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 | true
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/Fable.Import.RemoteDev.fs:
--------------------------------------------------------------------------------
1 | namespace Fable.Import
2 |
3 | open Fable.Core
4 |
5 | module RemoteDev =
6 | module MsgTypes =
7 | []
8 | let Start = "START"
9 | []
10 | let Action = "ACTION"
11 | []
12 | let Dispatch = "DISPATCH"
13 |
14 | module PayloadTypes =
15 | []
16 | let ImportState = "IMPORT_STATE"
17 | []
18 | let JumpToState = "JUMP_TO_STATE"
19 | []
20 | let JumpToAction = "JUMP_TO_ACTION"
21 |
22 | type Options<'msg> =
23 | { port : int
24 | hostname : string
25 | secure : bool
26 | getActionType : ('msg->obj) option }
27 |
28 | []
29 | type ActionCreator
30 | []
31 | (
32 | args: string[],
33 | name: string
34 | ) =
35 | member val args: string[] = jsNative with get, set
36 | member val name: string = jsNative with get, set
37 |
38 | []
39 | type ExtensionOptions
40 | []
41 | (
42 | ?actionCreators: ActionCreator[],
43 | ?getActionType: obj -> obj,
44 | ?name: string
45 | ) =
46 | member val actionCreators: ActionCreator[] option = jsNative with get, set
47 | member val getActionType: (obj -> obj) option = jsNative with get, set
48 | member val name: string option = jsNative with get, set
49 |
50 | type Action =
51 | { ``type``: string
52 | fields : obj array }
53 |
54 | type LiftedState =
55 | { actionsById : Action array
56 | computedStates : obj array
57 | currentStateIndex : int
58 | nextActionId : int }
59 |
60 | type Payload =
61 | { nextLiftedState : LiftedState
62 | ``type``: string }
63 |
64 | type Msg =
65 | { state : string
66 | action : obj
67 | ``type`` : string
68 | payload : Payload }
69 |
70 | type Listener = Msg -> unit
71 |
72 | type Unsubscribe = unit -> unit
73 |
74 | type Connection =
75 | abstract init: obj * obj Option -> unit
76 | abstract subscribe: Listener -> Unsubscribe
77 | abstract unsubscribe: Unsubscribe
78 | abstract send: obj * obj -> unit
79 | abstract error: obj -> unit
80 |
81 | []
82 | let connect<'msg> (options: Options<'msg>): Connection = jsNative
83 |
84 | []
85 | let connectViaExtension (options: ExtensionOptions): Connection = jsNative
86 |
87 | []
88 | let parse (x: string): obj = jsNative
89 |
90 | let extractState message =
91 | parse message.state
92 |
--------------------------------------------------------------------------------
/src/debugger.fs:
--------------------------------------------------------------------------------
1 | namespace Elmish.Debug
2 |
3 | open Fable.Import.RemoteDev
4 | open Fable.Core.JsInterop
5 | open Fable.Core
6 | open Microsoft.FSharp.Reflection
7 | open Thoth.Json
8 |
9 | []
10 | module Debugger =
11 | let showError (msgs: obj list) = JS.console.error("[ELMISH DEBUGGER]", List.toArray msgs)
12 | let showWarning (msgs: obj list) = JS.console.warn("[ELMISH DEBUGGER]", List.toArray msgs)
13 |
14 | type ConnectionOptions =
15 | | Remote of address:string * port:int
16 | | Secure of address:string * port:int
17 |
18 | let makeMsgObj (case, fields) =
19 | createObj ["type" ==> case; "msg" ==> fields]
20 |
21 | let getCase (x: obj) =
22 | if Reflection.isUnion x then
23 | let rec getCaseName acc (x: obj) =
24 | let acc = (Reflection.getCaseName x)::acc
25 | let fields = Reflection.getCaseFields x
26 | if fields.Length = 1 && Reflection.isUnion fields.[0] then
27 | getCaseName acc fields.[0]
28 | else
29 | // Case names are intentionally left reverted so we see
30 | // the most meaningfull message first
31 | makeMsgObj(acc |> String.concat "/", fields)
32 | getCaseName [] x
33 | else
34 | makeMsgObj("NOT-AN-F#-UNION", x)
35 |
36 | let fallback = { hostname = "remotedev.io"
37 | port = 443
38 | secure = true
39 | getActionType = Some getCase }
40 |
41 | let inline connect<'msg> opt =
42 | match opt with
43 | | Remote (address,port) ->
44 | { fallback with hostname = address; port = port; secure = false }
45 | |> connect
46 | | Secure (address,port) ->
47 | { fallback with hostname = address; port = port }
48 | |> connect
49 |
50 | let inline connectViaExtension<'msg> (options: ExtensionOptions) =
51 | let actionCreators =
52 | typeof<'msg>
53 | |> FSharpType.GetUnionCases
54 | |> Array.map (fun case ->
55 | new ActionCreator(name = case.Name, args = (case.GetFields() |> Array.map (fun fieldInfo -> fieldInfo.Name))))
56 |
57 | options.getActionType <- Some getCase
58 | if options.actionCreators.IsNone then options.actionCreators <- Some actionCreators
59 | connectViaExtension options
60 |
61 | type Send<'msg,'model> = 'msg*'model -> unit
62 |
63 | []
64 | module Program =
65 | open Elmish
66 |
67 | let inline private getTransformersWith encoder decoder =
68 | let deflate x =
69 | try encoder x
70 | with er ->
71 | Debugger.showWarning [er.Message]
72 | box x
73 | let inflate x =
74 | match Decode.fromValue "$" decoder x with
75 | | Ok x -> x
76 | | Error er -> failwith er
77 | deflate, inflate
78 |
79 | let inline private getTransformers<'model>() =
80 | try
81 | let coders =
82 | Extra.empty
83 | |> Extra.withDecimal
84 | |> Extra.withInt64
85 | |> Extra.withUInt64
86 | // |> Extra.withBigInt
87 | let encoder = Encode.Auto.generateEncoder<'model>(extra = coders)
88 | let decoder = Decode.Auto.generateDecoder<'model>(extra = coders)
89 | getTransformersWith encoder decoder
90 | with er ->
91 | Debugger.showWarning [er.Message]
92 | box, fun _ -> failwith "Cannot inflate model"
93 |
94 | let inline withDebuggerUsing (deflater: 'model->obj) (inflater: obj->'model) (connection:Connection) (program : Program<'a,'model,'msg,'view>) : Program<'a,'model,'msg,'view> =
95 | let constructMessage name args : 'msg =
96 | let cases = Map.ofSeq [for case in FSharpType.GetUnionCases typeof<'msg> -> case.Name, case]
97 | FSharpValue.MakeUnion(cases[name], args) :?> 'msg
98 |
99 | let init userInit a =
100 | let (model,cmd) = userInit a
101 | connection.init (deflater model, None)
102 | model,cmd
103 |
104 | let update userUpdate msg model : 'model * Cmd<'msg> =
105 | let (model',cmd) = userUpdate msg model
106 | connection.send(msg, deflater model')
107 | (model',cmd)
108 |
109 | let sub dispatch =
110 | function
111 | | (msg:Msg) when msg.``type`` = MsgTypes.Action ->
112 | dispatch (constructMessage msg.payload?name msg.payload?args)
113 | | (msg:Msg) when msg.``type`` = MsgTypes.Dispatch ->
114 | try
115 | match msg.payload.``type`` with
116 | | PayloadTypes.JumpToAction
117 | | PayloadTypes.JumpToState ->
118 | let state = extractState msg |> inflater
119 | Program.setState program state dispatch
120 | | PayloadTypes.ImportState ->
121 | let state = msg.payload.nextLiftedState.computedStates |> Array.last
122 | let state = inflater state?state
123 | Program.setState program state dispatch
124 | connection.send(null, msg.payload.nextLiftedState)
125 | | _ -> ()
126 | with ex ->
127 | Debugger.showError ["Unable to process monitor command"; ex.Message; msg]
128 | | _ -> ()
129 | |> connection.subscribe
130 | |> fun unsub -> { new System.IDisposable with
131 | member _.Dispose() = unsub() }
132 |
133 | let subscribe userSubscribe model =
134 | Sub.batch
135 | [ [["debugger"],sub]
136 | userSubscribe model |> Sub.map "user" id ]
137 |
138 | let onError userOnError (text,ex: exn) =
139 | userOnError (text, ex)
140 | connection.error (text + ex.Message)
141 |
142 | let termination (predicate,terminate) =
143 | predicate, terminate
144 |
145 | program
146 | |> Program.map init update id id subscribe termination
147 | |> Program.mapErrorHandler onError
148 |
149 | let inline withDebuggerConnection connection program : Program<'a,'model,'msg,'view> =
150 | let deflater, inflater = getTransformers<'model>()
151 | withDebuggerUsing deflater inflater connection program
152 |
153 | let inline withDebuggerCoders (encoder: Encoder<'model>) (decoder: Decoder<'model>) program : Program<'a,'model,'msg,'view> =
154 | let deflater, inflater = getTransformersWith encoder decoder
155 | let connection = Debugger.connectViaExtension<'msg> (new ExtensionOptions())
156 | withDebuggerUsing deflater inflater connection program
157 |
158 | let inline withDebuggerAt options program : Program<'a,'model,'msg,'view> =
159 | try
160 | let deflater, inflater = getTransformers<'model>()
161 | let connection = Debugger.connect<'msg> options
162 | withDebuggerUsing deflater inflater connection program
163 | with ex ->
164 | Debugger.showError ["Unable to connect to the monitor, continuing w/o debugger"; ex.Message]
165 | program
166 |
167 | let inline withDebuggerOptions (options: ExtensionOptions) (program : Program<'a,'model,'msg,'view>) : Program<'a,'model,'msg,'view> =
168 | try
169 | let deflater, inflater = getTransformers<'model>()
170 | let connection = Debugger.connectViaExtension<'msg> options
171 | withDebuggerUsing deflater inflater connection program
172 | with ex ->
173 | Debugger.showError ["Unable to connect to the monitor, continuing w/o debugger"; ex.Message]
174 | program
175 |
176 | let inline withDebugger (program : Program<'a,'model,'msg,'view>) : Program<'a,'model,'msg,'view> =
177 | withDebuggerOptions (new ExtensionOptions()) program
178 |
--------------------------------------------------------------------------------