├── .config
└── dotnet-tools.json
├── .gitattributes
├── .github
├── CONTRIBUTING.md
├── ISSUE_TEMPLATE.md
└── workflows
│ ├── ci.yml
│ └── publish.yml
├── .gitignore
├── .npmrc
├── .paket
└── Paket.Restore.targets
├── .vscode
└── settings.json
├── Directory.Build.props
├── LICENSE.md
├── NuGet.Config
├── README.md
├── babel.config.json
├── docs
├── _partials
│ └── footer.jsx
├── index.md
├── release_notes.md
├── scss
│ └── fable-font.scss
├── static
│ ├── fonts
│ │ └── fable-font
│ │ │ ├── fable-font.eot
│ │ │ ├── fable-font.svg
│ │ │ ├── fable-font.ttf
│ │ │ └── fable-font.woff
│ └── img
│ │ ├── hmr_demo.gif
│ │ └── logo.png
├── style.scss
└── v1_and_v2.md
├── global.json
├── hmr.sln
├── nacara.config.json
├── package-lock.json
├── package.json
├── paket.dependencies
├── paket.lock
├── paket.references
├── scripts
├── release-core.js
└── release-nuget.js
├── src
├── Bundler.fs
├── CHANGELOG.md
├── Fable.Elmish.HMR.fsproj
├── HMR.Parcel.fs
├── HMR.Vite.fs
├── HMR.Webpack.fs
├── common.fs
├── hmr.fs
└── paket.references
└── tests
├── App.fs
├── Router.fs
├── Tests.fsproj
├── index.html
├── paket.references
├── parcel
└── index.html
└── webpack
├── index.html
└── webpack.config.js
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "paket": {
6 | "version": "5.257.0",
7 | "commands": [
8 | "paket"
9 | ]
10 | },
11 | "fable": {
12 | "version": "3.4.9",
13 | "commands": [
14 | "fable"
15 | ]
16 | }
17 | }
18 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 |
--------------------------------------------------------------------------------
/.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: CI
2 | on:
3 | pull_request:
4 | branches: [ master ]
5 | workflow_dispatch:
6 | jobs:
7 | build:
8 | runs-on: ${{ matrix.os }}
9 | matrix:
10 | os: [ ubuntu-latest, macOS-latest, windows-latest ]
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-dotnet@v1
14 | with:
15 | dotnet-version: '6.0.x'
16 | - name: Setup workspace
17 | run: dotnet tool restore
18 | - name: Build package
19 | run: dotnet build src
20 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: Publish
2 | on:
3 | push:
4 | branches:
5 | - master
6 | workflow_dispatch:
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v2
12 | - uses: actions/setup-dotnet@v1
13 | with:
14 | dotnet-version: '6.0.x'
15 | - uses: actions/setup-node@v2
16 | with:
17 | node-version: '16'
18 | - name: Install and use custom npm version
19 | run: npm i -g npm@7
20 | - name: Setup workspace
21 | run: npm install
22 | - name: Build site
23 | run: npm run docs:build
24 | - name: Deploy site
25 | uses: peaceiris/actions-gh-pages@v3
26 | with:
27 | personal_token: ${{ secrets.GITHUB_TOKEN }}
28 | publish_dir: ./docs_deploy
29 |
--------------------------------------------------------------------------------
/.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 | build/
15 | obj
16 | bin
17 | out
18 |
19 | # git
20 | *.orig
21 |
22 | .vs
23 |
24 | temp
25 | paket-files
26 |
27 | .ionide/
28 |
29 | .nacara/
30 | doc_deploy/
31 | fableBuild/
32 | tests/parcel/dist/
33 | .parcel-cache/
34 | dist/
35 | docs_deploy/
36 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.paket/Paket.Restore.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
8 |
9 | $(MSBuildVersion)
10 | 15.0.0
11 | false
12 | true
13 |
14 | true
15 | $(MSBuildThisFileDirectory)
16 | $(MSBuildThisFileDirectory)..\
17 | $(PaketRootPath)paket-files\paket.restore.cached
18 | $(PaketRootPath)paket.lock
19 | classic
20 | proj
21 | assembly
22 | native
23 | /Library/Frameworks/Mono.framework/Commands/mono
24 | mono
25 |
26 |
27 | $(PaketRootPath)paket.bootstrapper.exe
28 | $(PaketToolsPath)paket.bootstrapper.exe
29 | $([System.IO.Path]::GetDirectoryName("$(PaketBootStrapperExePath)"))\
30 |
31 | "$(PaketBootStrapperExePath)"
32 | $(MonoPath) --runtime=v4.0.30319 "$(PaketBootStrapperExePath)"
33 |
34 |
35 |
36 |
37 | true
38 | true
39 |
40 |
41 | True
42 |
43 |
44 | False
45 |
46 | $(BaseIntermediateOutputPath.TrimEnd('\').TrimEnd('\/'))
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | $(PaketRootPath)paket
56 | $(PaketToolsPath)paket
57 |
58 |
59 |
60 |
61 |
62 | $(PaketRootPath)paket.exe
63 | $(PaketToolsPath)paket.exe
64 |
65 |
66 |
67 |
68 |
69 | <_DotnetToolsJson Condition="Exists('$(PaketRootPath)/.config/dotnet-tools.json')">$([System.IO.File]::ReadAllText("$(PaketRootPath)/.config/dotnet-tools.json"))
70 | <_ConfigContainsPaket Condition=" '$(_DotnetToolsJson)' != ''">$(_DotnetToolsJson.Contains('"paket"'))
71 | <_ConfigContainsPaket Condition=" '$(_ConfigContainsPaket)' == ''">false
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | <_PaketCommand>dotnet paket
83 |
84 |
85 |
86 |
87 |
88 | $(PaketToolsPath)paket
89 | $(PaketBootStrapperExeDir)paket
90 |
91 |
92 | paket
93 |
94 |
95 |
96 |
97 | <_PaketExeExtension>$([System.IO.Path]::GetExtension("$(PaketExePath)"))
98 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(_PaketExeExtension)' == '.dll' ">dotnet "$(PaketExePath)"
99 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' AND '$(OS)' != 'Windows_NT' AND '$(_PaketExeExtension)' == '.exe' ">$(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)"
100 | <_PaketCommand Condition=" '$(_PaketCommand)' == '' ">"$(PaketExePath)"
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | true
122 | $(NoWarn);NU1603;NU1604;NU1605;NU1608
123 | false
124 | true
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)'))
134 |
135 |
136 |
137 |
138 |
139 |
141 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[0].Replace(`"`, ``).Replace(` `, ``))
142 | $([System.Text.RegularExpressions.Regex]::Split(`%(Identity)`, `": "`)[1].Replace(`"`, ``).Replace(` `, ``))
143 |
144 |
145 |
146 |
147 | %(PaketRestoreCachedKeyValue.Value)
148 | %(PaketRestoreCachedKeyValue.Value)
149 |
150 |
151 |
152 |
153 | true
154 | false
155 | true
156 |
157 |
158 |
162 |
163 | true
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 | $(PaketIntermediateOutputPath)\$(MSBuildProjectFile).paket.references.cached
183 |
184 | $(MSBuildProjectFullPath).paket.references
185 |
186 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references
187 |
188 | $(MSBuildProjectDirectory)\paket.references
189 |
190 | false
191 | true
192 | true
193 | references-file-or-cache-not-found
194 |
195 |
196 |
197 |
198 | $([System.IO.File]::ReadAllText('$(PaketReferencesCachedFilePath)'))
199 | $([System.IO.File]::ReadAllText('$(PaketOriginalReferencesFilePath)'))
200 | references-file
201 | false
202 |
203 |
204 |
205 |
206 | false
207 |
208 |
209 |
210 |
211 | true
212 | target-framework '$(TargetFramework)' or '$(TargetFrameworks)' files @(PaketResolvedFilePaths)
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 | false
224 | true
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',').Length)
236 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[0])
237 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[1])
238 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[4])
239 | $([System.String]::Copy('%(PaketReferencesFileLines.Identity)').Split(',')[5])
240 |
241 |
242 | %(PaketReferencesFileLinesInfo.PackageVersion)
243 | All
244 | runtime
245 | runtime
246 | true
247 | true
248 |
249 |
250 |
251 |
252 | $(PaketIntermediateOutputPath)/$(MSBuildProjectFile).paket.clitools
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[0])
262 | $([System.String]::Copy('%(PaketCliToolFileLines.Identity)').Split(',')[1])
263 |
264 |
265 | %(PaketCliToolFileLinesInfo.PackageVersion)
266 |
267 |
268 |
269 |
273 |
274 |
275 |
276 |
277 |
278 | false
279 |
280 |
281 |
282 |
283 |
284 | <_NuspecFilesNewLocation Include="$(PaketIntermediateOutputPath)\$(Configuration)\*.nuspec"/>
285 |
286 |
287 |
288 |
289 |
290 | $(MSBuildProjectDirectory)/$(MSBuildProjectFile)
291 | true
292 | false
293 | true
294 | false
295 | true
296 | false
297 | true
298 | false
299 | true
300 | $(PaketIntermediateOutputPath)\$(Configuration)
301 | $(PaketIntermediateOutputPath)
302 |
303 |
304 |
305 | <_NuspecFiles Include="$(AdjustedNuspecOutputPath)\*.$(PackageVersion.Split(`+`)[0]).nuspec"/>
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
363 |
364 |
407 |
408 |
450 |
451 |
492 |
493 |
494 |
495 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "search.exclude": {
3 | "**/node_modules": true,
4 | "**/bower_components": true,
5 | "**/packages": true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 5
5 |
6 |
7 |
8 | true
9 | https://github.com/elmish/hmr.git
10 | https://github.com/elmish/hmr
11 | README.md
12 | LICENSE.md
13 | Hot Module Replacement for Elmish apps
14 | fable;elmish;fsharp;hmr
15 | Maxime Mangel
16 |
17 |
18 |
19 | true
20 | true
21 | true
22 | true
23 | snupkg
24 | $(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Elmish-HMR: [Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement/) integration for [elmish](https://github.com/fable-compiler/elmish) applications.
2 | =======
3 |
4 | [](https://badge.fury.io/nu/Fable.Elmish.HMR)
5 |
6 | For more information see [the docs](https://elmish.github.io/hmr).
7 |
8 | ## Installation
9 |
10 | ```sh
11 | paket add nuget Fable.Elmish.HMR
12 | ```
13 |
14 | ## Demo
15 |
16 | 
17 |
18 | ## Development
19 |
20 | This repository use NPM scripts to control the build system here is a list of the main scripts available:
21 |
22 | | Script | Description |
23 | |---|---|
24 | | `npm run tests:watch` | To use when working on the tests suits |
25 | | `npm run docs:watch` | To use when working on the documentation, hosted on [http://localhost:8080](http://localhost:8080) |
26 | | `npm run docs:publish` | Build a new version of the documentation and publish it to Github Pages |
27 | | `npm run release` | Build a new version of the packages if needed and release it |
28 |
--------------------------------------------------------------------------------
/babel.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/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.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: standard
3 | toc: false
4 | ---
5 |
6 | ## Hot Module Replacement
7 |
8 | Elmish applications can benefit from Hot Module Replacement (known as HMR).
9 |
10 | This allow us to modify the application while it's running, without a full reload. Your application will now maintain its state between two changes.
11 |
12 | 
13 |
14 | ## Installation
15 | Add Fable package with paket:
16 |
17 | ```sh
18 | paket add nuget Fable.Elmish.HMR
19 | ```
20 |
21 | ## Webpack configuration
22 |
23 | Add `hot: true` and `inline: true` (only for webpack < v5.0.0) to your `devServer` node.
24 |
25 | Example:
26 |
27 | ```js
28 | // ...
29 | devServer: {
30 | // ...
31 | hot: true
32 | }
33 | // ...
34 | ```
35 |
36 | ## Parcel and Vite
37 |
38 | Parcel and Vite, are supported since version 4.2.0. They don't require any specific configuration.
39 |
40 | ## Usage
41 |
42 | :::warning{title="Limitation"}
43 | Currently, Elmish.HMR only works when running a **single** Elmish instance with HMR enabled.
44 |
45 | If you need supports for multiple Elmish instances, please contribute it to Elmish.HMR via a PR.
46 | :::
47 |
48 | The package will include the HMR support only if you are building your program with `DEBUG` set in your compilation conditions. Fable adds it by default when in watch mode.
49 |
50 | You need to always include `open Elmish.HMR` after your others `open Elmish.XXX` statements. This is needed to shadow the supported APIs.
51 |
52 | For example, if you use `Elmish.Program.run` it will be shadowed as `Elmish.HMR.Program.run`.
53 |
54 | ```fs
55 | open Elmish
56 | open Elmish.React
57 | open Elmish.HMR // See how this is the last open statement
58 |
59 | Program.mkProgram init update view
60 | |> Program.withReactSynchronous "elmish-app"
61 | |> Program.run
62 | ```
63 |
64 | You can also use `Elmish.Program.runWith` if you need to pass custom arguments, `runWith` will also be shadowed as `Elmish.HMR.Program.runWith`:
65 |
66 | ```fs
67 | Program.mkProgram init update view
68 | |> Program.withReactSynchronous "elmish-app"
69 | |> Program.runWith ("custom argument", 42)
70 | ```
71 |
--------------------------------------------------------------------------------
/docs/release_notes.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: changelog
3 | changelog_path: ./../src/CHANGELOG.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/hmr/af6e63d51a9191670295dadc55d49b0307e38bfb/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/hmr/af6e63d51a9191670295dadc55d49b0307e38bfb/docs/static/fonts/fable-font/fable-font.ttf
--------------------------------------------------------------------------------
/docs/static/fonts/fable-font/fable-font.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elmish/hmr/af6e63d51a9191670295dadc55d49b0307e38bfb/docs/static/fonts/fable-font/fable-font.woff
--------------------------------------------------------------------------------
/docs/static/img/hmr_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elmish/hmr/af6e63d51a9191670295dadc55d49b0307e38bfb/docs/static/img/hmr_demo.gif
--------------------------------------------------------------------------------
/docs/static/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elmish/hmr/af6e63d51a9191670295dadc55d49b0307e38bfb/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 |
--------------------------------------------------------------------------------
/docs/v1_and_v2.md:
--------------------------------------------------------------------------------
1 | ---
2 | layout: standard
3 | toc: false
4 | ---
5 |
6 | :::warning
7 |
8 | This page is an archive for v1.x and v2.x of Fable.Elmish.HMR package
9 |
10 | :::
11 |
12 | ## Hot Module Replacement
13 |
14 | Elmish applications can benefit from Hot Module Replacement (known as HMR).
15 |
16 | This allow us to modify the application while it's running, without a full reload. Your application will now maintain its state between two changes.
17 |
18 | 
19 |
20 |
21 | ## Installation
22 | Add Fable package with paket:
23 |
24 | ```sh
25 | paket add nuget Fable.Elmish.HMR
26 | ```
27 |
28 | ## Webpack configuration
29 |
30 | Add `hot: true` and `inline: true` to your `devServer` node.
31 |
32 | Example:
33 |
34 | ```js
35 | devServer: {
36 | contentBase: resolve('./public'),
37 | port: 8080,
38 | hot: true,
39 | inline: true
40 | }
41 | ```
42 |
43 | You also need to add this two plugins when building in development mode:
44 |
45 | - `webpack.HotModuleReplacementPlugin`
46 | - `webpack.NamedModulesPlugin`
47 |
48 | Example:
49 |
50 | ```js
51 | plugins : isProduction ? [] : [
52 | new webpack.HotModuleReplacementPlugin(),
53 | new webpack.NamedModulesPlugin()
54 | ]
55 | ```
56 |
57 | You can find a complete `webpack.config.js` [here](https://github.com/elmish/templates/blob/master/src/react-demo/Content/webpack.config.js).
58 |
59 | ## Program module functions
60 |
61 | Augment your program instance with HMR support.
62 |
63 | *IMPORTANT*: Make sure to add HMR support before `Program.withReact` or `Program.withReactNative` line.
64 |
65 | Usage:
66 |
67 | ```fs
68 | open Elmish.HMR
69 |
70 | Program.mkProgram init update view
71 | |> Program.withHMR // Add the HMR support
72 | |> Program.withReact "elmish-app"
73 | |> Program.run
74 | ```
75 |
76 | and if you use React Native:
77 |
78 |
79 | ```fs
80 | open Elmish.HMR
81 |
82 | Program.mkProgram init update view
83 | |> Program.withHMR // Add the HMR support
84 | |> Program.withReactNative "elmish-app"
85 | |> Program.run
86 | ```
87 |
88 | ## Conditional compilation
89 |
90 | If don't want to include the Hot Moduple Replacement in production builds surround it with `#if DEBUG`/`#endif` and define the symbol conditionally in your build system.
91 |
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "6.0.0",
4 | "rollForward": "minor"
5 | }
6 | }
--------------------------------------------------------------------------------
/hmr.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30114.105
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Fable.Elmish.HMR", "src\Fable.Elmish.HMR.fsproj", "{E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}"
7 | EndProject
8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Tests", "tests\Tests.fsproj", "{F1C11895-3C0A-4AF9-BC27-326D928A9EB3}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|x64 = Debug|x64
14 | Debug|x86 = Debug|x86
15 | Release|Any CPU = Release|Any CPU
16 | Release|x64 = Release|x64
17 | Release|x86 = Release|x86
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Debug|x64.ActiveCfg = Debug|Any CPU
26 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Debug|x64.Build.0 = Debug|Any CPU
27 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Debug|x86.ActiveCfg = Debug|Any CPU
28 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Debug|x86.Build.0 = Debug|Any CPU
29 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Release|x64.ActiveCfg = Release|Any CPU
32 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Release|x64.Build.0 = Release|Any CPU
33 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Release|x86.ActiveCfg = Release|Any CPU
34 | {E6AD8367-AB78-4A3C-A6F7-146D3A6341E6}.Release|x86.Build.0 = Release|Any CPU
35 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Debug|x64.ActiveCfg = Debug|Any CPU
38 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Debug|x64.Build.0 = Debug|Any CPU
39 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Debug|x86.ActiveCfg = Debug|Any CPU
40 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Debug|x86.Build.0 = Debug|Any CPU
41 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
42 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Release|Any CPU.Build.0 = Release|Any CPU
43 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Release|x64.ActiveCfg = Release|Any CPU
44 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Release|x64.Build.0 = Release|Any CPU
45 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Release|x86.ActiveCfg = Release|Any CPU
46 | {F1C11895-3C0A-4AF9-BC27-326D928A9EB3}.Release|x86.Build.0 = Release|Any CPU
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/nacara.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "siteMetadata": {
3 | "title": "Elmish.HMR",
4 | "url": "https://elmish.github.io",
5 | "baseUrl": "/hmr/",
6 | "editUrl" : "https://github.com/elmish/hmr/edit/master/docs",
7 | "favIcon": "static/img/logo.png"
8 | },
9 | "navbar": {
10 | "start": [
11 | {
12 | "pinned": true,
13 | "label": "Docs",
14 | "items": [
15 | {
16 | "label": "Latest",
17 | "url": "/hmr/index.html"
18 | },
19 | {
20 | "label": "v1.x & v2.x",
21 | "url": "/hmr/v1_and_v2.html"
22 | }
23 | ]
24 | }
25 | ],
26 | "end": [
27 | {
28 | "url": "https://github.com/elmish/hmr",
29 | "icon": "fab fa-2x fa-github",
30 | "label": "GitHub"
31 | }
32 | ]
33 | },
34 | "remarkPlugins": [
35 | {
36 | "resolve": "gatsby-remark-vscode",
37 | "property": "remarkPlugin",
38 | "options": {
39 | "theme": "Atom One Light",
40 | "extensions": [
41 | "vscode-theme-onelight"
42 | ]
43 | }
44 | }
45 | ],
46 | "layouts": [
47 | "nacara-layout-standard"
48 | ]
49 | }
50 |
--------------------------------------------------------------------------------
/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 | "directories": {
10 | "test": "tests"
11 | },
12 | "scripts": {
13 | "postinstall": "dotnet tool restore",
14 | "tests:watch-webpack": "cd tests/webpack && webpack serve --mode development --port 3000",
15 | "tests:watch-parcel": "cd tests/parcel && parcel serve index.html --port 3001",
16 | "tests:watch-vite": "cd tests && vite --port 3002",
17 | "tests:watch-fable": "cd tests && dotnet fable --watch --outDir fableBuild",
18 | "pretests:watch": "shx rm -rf tests/fableBuild",
19 | "tests:watch": "concurrently -p none 'npm:tests:watch-*'",
20 | "docs:watch": "nacara watch",
21 | "docs:build": "nacara build",
22 | "docs:publish": "nacara build && gh-pages -d docs_deploy",
23 | "release": "node ./scripts/release-nuget.js src Fable.Elmish.HMR.fsproj"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/elmish/hmr.git"
28 | },
29 | "devDependencies": {
30 | "@babel/preset-react": "^7.18.6",
31 | "chalk": "^5.1.2",
32 | "changelog-parser": "^2.8.1",
33 | "concurrently": "^7.5.0",
34 | "gatsby-remark-vscode": "^3.3.1",
35 | "html-webpack-plugin": "^5.5.0",
36 | "nacara": "^1.7.0",
37 | "nacara-layout-standard": "^1.8.0",
38 | "parcel": "^2.8.0",
39 | "process": "^0.11.10",
40 | "shelljs": "^0.8.5",
41 | "shx": "^0.3.4",
42 | "vite": "^3.2.3",
43 | "vscode-theme-onelight": "github:akamud/vscode-theme-onelight",
44 | "webpack": "^5.75.0",
45 | "webpack-cli": "^4.10.0",
46 | "webpack-dev-server": "^4.11.1"
47 | },
48 | "dependencies": {
49 | "react": "^18.2.0",
50 | "react-dom": "^18.2.0"
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/paket.dependencies:
--------------------------------------------------------------------------------
1 | source https://nuget.org/api/v2
2 |
3 | nuget FSharp.Core ~> 4.7.2 redirects:force, content:none
4 | nuget Fable.Elmish >= 4
5 | nuget Fable.Elmish.React >= 4
6 | nuget Microsoft.SourceLink.GitHub copy_local: true
7 |
8 | group Tests
9 | source https://nuget.org/api/v2
10 |
11 | nuget FSharp.Core ~> 4.7.2 redirects:force, content:none
12 |
13 | nuget Fable.Core
14 | nuget Fable.Elmish >= 4
15 | nuget Fable.Elmish.Browser >= 4
16 | nuget Fable.Elmish.React >= 4
17 | nuget Fable.Promise
18 | nuget Feliz >= 2 prerelease
--------------------------------------------------------------------------------
/paket.lock:
--------------------------------------------------------------------------------
1 | NUGET
2 | remote: https://www.nuget.org/api/v2
3 | Fable.Browser.Blob (1.2) - restriction: >= netstandard2.0
4 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
5 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
6 | Fable.Browser.Dom (2.10.1) - restriction: >= netstandard2.0
7 | Fable.Browser.Blob (>= 1.2) - restriction: >= netstandard2.0
8 | Fable.Browser.Event (>= 1.5) - restriction: >= netstandard2.0
9 | Fable.Browser.WebStorage (>= 1.1) - restriction: >= netstandard2.0
10 | Fable.Core (>= 3.2.8) - restriction: >= netstandard2.0
11 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
12 | Fable.Browser.Event (1.5) - restriction: >= netstandard2.0
13 | Fable.Browser.Gamepad (>= 1.1) - restriction: >= netstandard2.0
14 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
15 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
16 | Fable.Browser.Gamepad (1.1) - restriction: >= netstandard2.0
17 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
18 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
19 | Fable.Browser.WebStorage (1.1) - restriction: >= netstandard2.0
20 | Fable.Browser.Event (>= 1.5) - restriction: >= netstandard2.0
21 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
22 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
23 | Fable.Core (3.7.1) - restriction: >= netstandard2.0
24 | Fable.Elmish (4.0)
25 | Fable.Core (>= 3.7.1) - restriction: >= netstandard2.0
26 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
27 | Fable.Elmish.React (4.0)
28 | Fable.Elmish (>= 4.0) - restriction: >= netstandard2.0
29 | Fable.ReactDom.Types (>= 18.0) - restriction: >= netstandard2.0
30 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
31 | Fable.React.Types (18.1) - restriction: >= netstandard2.0
32 | Fable.Browser.Dom (>= 2.4.4) - restriction: >= netstandard2.0
33 | Fable.Core (>= 3.2.7) - restriction: >= netstandard2.0
34 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
35 | Fable.ReactDom.Types (18.0) - restriction: >= netstandard2.0
36 | Fable.React.Types (>= 18.1) - restriction: >= netstandard2.0
37 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
38 | FSharp.Core (4.7.2) - content: none, redirects: force
39 | Microsoft.Build.Tasks.Git (1.1.1) - copy_local: true
40 | Microsoft.SourceLink.Common (1.1.1) - copy_local: true
41 | Microsoft.SourceLink.GitHub (1.1.1) - copy_local: true
42 | Microsoft.Build.Tasks.Git (>= 1.1.1)
43 | Microsoft.SourceLink.Common (>= 1.1.1)
44 |
45 | GROUP Tests
46 | NUGET
47 | remote: https://www.nuget.org/api/v2
48 | Fable.AST (4.2.1) - restriction: >= netstandard2.0
49 | Fable.Browser.Blob (1.2) - restriction: >= netstandard2.0
50 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
51 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
52 | Fable.Browser.Dom (2.10.1) - restriction: >= netstandard2.0
53 | Fable.Browser.Blob (>= 1.2) - restriction: >= netstandard2.0
54 | Fable.Browser.Event (>= 1.5) - restriction: >= netstandard2.0
55 | Fable.Browser.WebStorage (>= 1.1) - restriction: >= netstandard2.0
56 | Fable.Core (>= 3.2.8) - restriction: >= netstandard2.0
57 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
58 | Fable.Browser.Event (1.5) - restriction: >= netstandard2.0
59 | Fable.Browser.Gamepad (>= 1.1) - restriction: >= netstandard2.0
60 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
61 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
62 | Fable.Browser.Gamepad (1.1) - restriction: >= netstandard2.0
63 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
64 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
65 | Fable.Browser.WebStorage (1.1) - restriction: >= netstandard2.0
66 | Fable.Browser.Event (>= 1.5) - restriction: >= netstandard2.0
67 | Fable.Core (>= 3.0) - restriction: >= netstandard2.0
68 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
69 | Fable.Core (3.7.1)
70 | Fable.Elmish (4.0)
71 | Fable.Core (>= 3.7.1) - restriction: >= netstandard2.0
72 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
73 | Fable.Elmish.Browser (4.0)
74 | Fable.Browser.Dom (>= 2.10.1) - restriction: >= netstandard2.0
75 | Fable.Elmish (>= 4.0) - restriction: >= netstandard2.0
76 | Fable.Elmish.UrlParser (>= 1.0) - restriction: >= netstandard2.0
77 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
78 | Fable.Elmish.React (4.0)
79 | Fable.Elmish (>= 4.0) - restriction: >= netstandard2.0
80 | Fable.ReactDom.Types (>= 18.0) - restriction: >= netstandard2.0
81 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
82 | Fable.Elmish.UrlParser (1.0) - restriction: >= netstandard2.0
83 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
84 | Fable.Promise (3.2)
85 | Fable.Core (>= 3.7.1) - restriction: >= netstandard2.0
86 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
87 | Fable.React.Types (18.1) - restriction: >= netstandard2.0
88 | Fable.Browser.Dom (>= 2.4.4) - restriction: >= netstandard2.0
89 | Fable.Core (>= 3.2.7) - restriction: >= netstandard2.0
90 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
91 | Fable.ReactDom.Types (18.0) - restriction: >= netstandard2.0
92 | Fable.React.Types (>= 18.1) - restriction: >= netstandard2.0
93 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
94 | Feliz (2.0.0-prerelease-003)
95 | Fable.Core (>= 3.2.7) - restriction: >= netstandard2.0
96 | Fable.React.Types (>= 18.0) - restriction: >= netstandard2.0
97 | Feliz.CompilerPlugins (>= 2.0.0-prerelease-003) - restriction: >= netstandard2.0
98 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
99 | Feliz.CompilerPlugins (2.0.0-prerelease-003) - restriction: >= netstandard2.0
100 | Fable.AST (>= 4.0) - restriction: >= netstandard2.0
101 | FSharp.Core (>= 4.7.2) - restriction: >= netstandard2.0
102 | FSharp.Core (4.7.2) - content: none, redirects: force
103 |
--------------------------------------------------------------------------------
/paket.references:
--------------------------------------------------------------------------------
1 | group FakeBuild
2 | dotnet-fake
3 |
--------------------------------------------------------------------------------
/scripts/release-core.js:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path'
2 | import { existsSync, readFileSync, writeFileSync } from 'fs'
3 | import chalk from 'chalk'
4 | import parseChangelog from 'changelog-parser'
5 |
6 | const log = console.log
7 |
8 | /**
9 | * @typedef Options
10 | * @type {object}
11 | * @property {string} baseDirectory - Path to the project directory the project file and the CHANGELOG.md should be at the root of this repository
12 | * @property {string} projectFileName - Name of the project file when we need to check/change the version information
13 | * @property {RegExp} versionRegex - Regex used to detect the current version it should have 3 groups: 1. Text before the version - 2. The version - 3. Rest after the function
14 | * @property {function} publishFn - The function to execute in order to publish the package
15 | */
16 |
17 | /**
18 | *
19 | * @param {Options} options
20 | */
21 | export const release = async ( options ) => {
22 |
23 | // checks if the package.json and CHANGELOG exist
24 | const changelogPath = resolve(options.baseDirectory, "CHANGELOG.md")
25 | const packageJsonPath = resolve(options.baseDirectory, options.projectFileName)
26 |
27 | if (!existsSync(changelogPath)) {
28 | log(chalk.red(`CHANGELOG.md not found in ${options.baseDirectory}`))
29 | }
30 |
31 | if (!existsSync(packageJsonPath)) {
32 | log(chalk.red(`${options.projectFileName} not found in ${options.baseDirectory}`))
33 | }
34 |
35 | // read files content
36 | const changelogContent = readFileSync(changelogPath).toString().replace("\r\n", "\n")
37 | const packageJsonContent = readFileSync(packageJsonPath).toString()
38 |
39 | const changelog = await parseChangelog({ text: changelogContent })
40 |
41 | let versionInfo = undefined;
42 |
43 | // Find the first version which is not Unreleased
44 | for (const version of changelog.versions) {
45 | if (version.title.toLowerCase() !== "unreleased") {
46 | versionInfo = version;
47 | break;
48 | }
49 | }
50 |
51 | if (versionInfo === undefined) {
52 | log(chalk.red(`No version ready to be released found in the CHANGELOG.md`))
53 | process.exit(1)
54 | }
55 |
56 | const m = options.versionRegex.exec(packageJsonContent)
57 |
58 | if (m === null) {
59 | log(chalk.red(`No version property found in the ${options.projectFileName}`))
60 | process.exit(1)
61 | }
62 |
63 | const lastPublishedVersion = m[2];
64 |
65 | if (versionInfo.version === lastPublishedVersion) {
66 | log(chalk.blue(`Last version has already been published. Skipping...`))
67 | process.exit(0)
68 | }
69 |
70 | log(chalk.blue("New version detected"))
71 |
72 | const newPackageJsonContent = packageJsonContent.replace(options.versionRegex, `$1${versionInfo.version}$3`)
73 |
74 | // Update fsproj file on the disk
75 | writeFileSync(packageJsonPath, newPackageJsonContent)
76 |
77 | try {
78 | await options.publishFn(versionInfo)
79 | } catch (e) {
80 | writeFileSync(packageJsonPath, packageJsonContent)
81 | log(chalk.red("An error occured while publishing the new package"))
82 | log(chalk.blue("The files have been reverted to their original state"))
83 | process.exit(1)
84 | }
85 |
86 | log(chalk.green(`Package published`))
87 | process.exit(0)
88 | }
89 |
--------------------------------------------------------------------------------
/scripts/release-nuget.js:
--------------------------------------------------------------------------------
1 | import { resolve, basename } from 'path'
2 | import chalk from 'chalk'
3 | import shelljs from 'shelljs'
4 |
5 | const log = console.log
6 |
7 | import { release } from "./release-core.js"
8 |
9 | const getEnvVariable = function (varName) {
10 | const value = process.env[varName];
11 | if (value === undefined) {
12 | log(error(`Missing environnement variable ${varName}`))
13 | process.exit(1)
14 | } else {
15 | return value;
16 | }
17 | }
18 |
19 | // Check that we have enought arguments
20 | if (process.argv.length < 4) {
21 | log(chalk.red("Missing arguments"))
22 | process.exit(1)
23 | }
24 |
25 | const cwd = process.cwd()
26 | const baseDirectory = resolve(cwd, process.argv[2])
27 | const projectFileName = process.argv[3]
28 |
29 | const NUGET_KEY = getEnvVariable("NUGET_KEY")
30 |
31 | release({
32 | baseDirectory: baseDirectory,
33 | projectFileName: projectFileName,
34 | versionRegex: /(^\s*)(.*)(<\/Version>\s*$)/gmi,
35 | publishFn: async (versionInfo) => {
36 |
37 | const packResult =
38 | shelljs.exec(
39 | "dotnet pack -c Release",
40 | {
41 | cwd: baseDirectory
42 | }
43 | )
44 |
45 | if (packResult.code !== 0) {
46 | throw "Dotnet pack failed"
47 | }
48 |
49 | const fileName = basename(projectFileName, ".fsproj")
50 |
51 | const pushNugetResult =
52 | shelljs.exec(
53 | `dotnet nuget push bin/Release/${fileName}.${versionInfo.version}.nupkg -s nuget.org -k ${NUGET_KEY}`,
54 | {
55 | cwd: baseDirectory
56 | }
57 | )
58 |
59 | if (pushNugetResult.code !== 0) {
60 | throw "Dotnet push failed"
61 | }
62 | }
63 | })
64 |
--------------------------------------------------------------------------------
/src/Bundler.fs:
--------------------------------------------------------------------------------
1 | namespace Elmish.HMR
2 |
3 | module Bundler =
4 |
5 | type T =
6 | | Vite
7 | | WebpackESM
8 | | WebpackCJS_and_Parcel
9 |
10 | let current =
11 | // This code is "special" to not call it ugly
12 | // But we need to protect against ReferenceError exception
13 | // when accessing non existing variables
14 | // By doing this detection only once here, we can avoid propagating
15 | // this "special" code everywhere else
16 |
17 | let mutable result = None
18 |
19 | try
20 | if HMR.Webpack.active then
21 | result <- Some WebpackESM
22 | with
23 | | _ ->
24 | ()
25 |
26 | if result.IsNone then
27 | try
28 | if HMR.Parcel.active then
29 | result <- Some WebpackCJS_and_Parcel
30 | with
31 | | _ ->
32 | ()
33 |
34 | if result.IsNone then
35 | try
36 | if HMR.Vite.active then
37 | result <- Some Vite
38 | with
39 | | _ ->
40 | ()
41 |
42 | result
43 |
--------------------------------------------------------------------------------
/src/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ## 7.0.0 - 2022-12-22
10 |
11 | * Breaking: support for v4 elmish
12 |
13 | ## 6.0.0-beta-003 - 2022-11-09
14 |
15 | * Support newest version of Elmish v4 with the new subscription system (by @kspeakman)
16 |
17 | ## 6.0.0-beta-002 - 2021-11-30
18 |
19 | * Remove Fable.Elmish.Browser dependency
20 | * Fix HMR support, it was not working in beta-001
21 |
22 | ## 6.0.0-beta-001 - 2021-11-29
23 |
24 | * Support for elmish v4
25 | * Remove `Program.toNavigable` shadowing because `Elmish.Browser` can now remove its listeners by itself
26 |
27 | ## 6.0.0 - 2021-11-29
28 |
29 | * UNLISTED
30 | * Support for elmish v4
31 | * Remove `Program.toNavigable` shadowing because `Elmish.Browser` can now remove its listeners by itself
32 |
33 | ## 5.2.0 - 2021-11-30
34 |
35 | * PR #37: Mitigate problem where `hot.dispose` run late compared to `hot.accept` (Vite seems to be doing that sometimes...)
36 |
37 | ## 5.1.0 - 2021-11-26
38 |
39 | * PR #36: Add a comment on Webpack HMR support detection to redirect the user to the issue explaining what is happening.
40 |
41 | ## 5.0.0 - 2021-11-25
42 |
43 | * Make a new major released which include un-listed version 4.2.0, 4.2.1, 4.3.0, and 4.3.1
44 | * Add a comment on Vite HMR support detection to redirect the user to the issue explaining what is happening.
45 | * Fix `Program.toNavigable` version when a Bundler is detected. It was not registering the listener for the location change.
46 |
47 | ## 4.3.1 - 2021-11-15
48 |
49 | * Fix #34: Lower FSharp.Core requirement
50 |
51 | ## 4.3.0 - 2021-11-09
52 |
53 | * Rework bundler detection adding `try ... with` when detecting the bundler used to avoid `RefrenceError` exception
54 |
55 | ## 4.2.1 - 2021-11-08
56 |
57 | * Fix GIF url in the README
58 |
59 | ## 4.2.0 - 2021-11-08
60 |
61 | * Fix #29: Add support for Webpack, Parcel and Vite
62 |
63 | ## 4.1.0
64 |
65 | * Upgrade dependency Fable.Elmish.Browser to 3.0.4 (see https://github.com/elmish/hmr/issues/27)
66 |
67 | ## 4.0.1
68 |
69 | * Fix #25: Fix React shadowing calls to use the `LazyView` from `HMR`
70 |
71 | ## 4.0.0
72 |
73 | * Release stable version
74 |
75 | ## 4.0.0-beta-6
76 |
77 | * Upgrade Fable.Elmish.Browser (by @alfonsogarciacaro)
78 | * Re-add the non debuggin Helpers so people can just `open Elmish.HMR` (by @alfonsogarciacaro)
79 |
80 | ## 4.0.0-beta-5
81 |
82 | * Inline functions (by @alfonsogarciacaro)
83 |
84 | ## 4.0.0-beta-4
85 |
86 | * Update to Elmish 3.0.0-beta-7 (by @alfonsogarciacaro)
87 |
88 | ## 4.0.0-beta-3
89 |
90 | * Update to `Fable.Core` v3-beta
91 |
92 | ## 4.0.0-beta-2
93 |
94 | * Includes: Fix ReactNative
95 | * Includes: Add shadowing function for `runWith` by @theimowski
96 |
97 | ## 4.0.0-beta-1
98 |
99 | * Elmish 3.0 compat
100 |
101 | ## 3.2.0
102 |
103 | * Fix ReactNative
104 |
105 | ## 3.1.0
106 |
107 | * Add shadowing function for `runWith` by @theimowski
108 |
109 | ## 3.0.2
110 |
111 | * Fix HMR by including `Elmish_HMR_Count` variable in window namespace
112 |
113 | ## 3.0.1
114 |
115 | * Fix `Release` version of `lazyView`, `lazyView2`, `lazyView3` and `lazyViewWith`
116 |
117 | ## 3.0.0
118 |
119 | * Fix HMR support for Elmish apps and shadow more API
120 |
121 | ## 2.1.0
122 |
123 | * Release stable
124 |
125 | ## 2.1.0-beta-1
126 |
127 | * Add shadow version of `Program.toNavigable`
128 |
129 | ## 2.0.0
130 |
131 | * Release 2.0.0
132 | * Use Fable.Import.HMR instead of local binding
133 |
134 | ## 2.0.0-beta-4
135 |
136 | * Re-releasing v1.x for Fable 2
137 |
138 | ## 1.0.1
139 |
140 | * Backporting build
141 |
142 | ## 1.0.0
143 |
144 | * Release 1.0.0
145 |
146 | ## 1.0.0-beta-1
147 |
148 | * Release in beta
149 |
150 | ## 0.1.0-alpha.1
151 |
152 | * Initial hmr release
153 |
--------------------------------------------------------------------------------
/src/Fable.Elmish.HMR.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | 7.0.0
4 | netstandard2.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/HMR.Parcel.fs:
--------------------------------------------------------------------------------
1 | module HMR.Parcel
2 |
3 | open Fable.Core
4 |
5 | // I wasn't able to find Parcel HMR API definition
6 | // So this bindings is minimal and not complete
7 |
8 | []
9 | type IHot =
10 |
11 | /// Optional data coming from disposed module
12 | abstract data : obj option
13 |
14 | /// **Description**
15 | /// Accept updates for itself.
16 | /// **Parameters**
17 | /// * `errorHandler` - parameter of type `(obj -> unit) option` - Function to fire when the dependencies are updated
18 | ///
19 | /// **Output Type**
20 | /// * `unit`
21 | ///
22 | /// **Exceptions**
23 | ///
24 | abstract accept: ?errorHandler: (obj -> unit) -> unit
25 |
26 | /// **Description**
27 | /// Add a handler which is executed when the current module code is replaced.
28 | /// This should be used to remove any persistent resource you have claimed or created.
29 | /// If you want to transfer state to the updated module, add it to given `data` parameter.
30 | /// This object will be available at `module.hot.data` after the update.
31 | /// **Parameters**
32 | /// * `data` - parameter of type `obj`
33 | ///
34 | /// **Output Type**
35 | /// * `unit`
36 | ///
37 | /// **Exceptions**
38 | ///
39 | abstract dispose: data: obj -> unit
40 |
41 | []
42 | let hot : IHot = jsNative
43 |
44 | []
45 | let active : bool = jsNative
46 |
--------------------------------------------------------------------------------
/src/HMR.Vite.fs:
--------------------------------------------------------------------------------
1 | module HMR.Vite
2 |
3 | open Fable.Core
4 |
5 | []
6 | type Status =
7 | /// The process is waiting for a call to check (see below)
8 | | [] Idle
9 | /// The process is checking for updates
10 | | [] Check
11 | /// The process is getting ready for the update (e.g. downloading the updated module)
12 | | [] Prepare
13 | /// The update is prepared and available
14 | | [] Ready
15 | /// The process is calling the dispose handlers on the modules that will be replaced
16 | | [] Dispose
17 | /// The process is calling the accept handlers and re-executing self-accepted modules
18 | | [] Apply
19 | /// An update was aborted, but the system is still in it's previous state
20 | | [] Abort
21 | /// An update has thrown an exception and the system's state has been compromised
22 | | [] Fail
23 |
24 | type ApplyOptions =
25 | /// Ignore changes made to unaccepted modules.
26 | abstract ignoreUnaccepted : bool option with get, set
27 | /// Ignore changes made to declined modules.
28 | abstract ignoreDeclined : bool option with get, set
29 | /// Ignore errors throw in accept handlers, error handlers and while reevaluating module.
30 | abstract ignoreErrored : bool option with get, set
31 | /// Notifier for declined modules
32 | abstract onDeclined : (obj -> unit) option with get, set
33 | /// Notifier for unaccepted modules
34 | abstract onUnaccepted : (obj -> unit) option with get, set
35 | /// Notifier for accepted modules
36 | abstract onAccepted : (obj -> unit) option with get, set
37 | /// Notifier for disposed modules
38 | abstract onDisposed : (obj -> unit) option with get, set
39 | /// Notifier for errors
40 | abstract onErrored : (obj -> unit) option with get, set
41 |
42 |
43 | // Vite seems to be very strict on how import.meta.hot is invoked
44 | // so make sure Fable doesn't surround it with parens
45 | []
46 | type IHot =
47 |
48 | []
49 | abstract active: bool
50 |
51 | []
52 | abstract data: obj option
53 |
54 | []
55 | abstract getData: key: string -> obj
56 |
57 | []
58 | abstract setData: key: string * obj -> unit
59 |
60 | /// **Description**
61 | /// Accept updates for itself.
62 | /// **Parameters**
63 | /// * `errorHandler` - parameter of type `(obj -> unit) option` - Function to fire when the dependencies are updated
64 | ///
65 | /// **Output Type**
66 | /// * `unit`
67 | ///
68 | /// **Exceptions**
69 | ///
70 | []
71 | abstract accept: ?handler: (obj -> unit) -> unit
72 |
73 | /// Add a handler which is executed when the current module code is replaced.
74 | /// This should be used to remove any persistent resource you have claimed or created.
75 | /// If you want to transfer state to the updated module, add it to given `data` parameter.
76 | /// This object will be available at `module.hot.data` after the update.
77 | []
78 | abstract dispose: (obj -> unit) -> unit
79 |
80 | /// Add a handler which is executed when the current module code is replaced.
81 | /// This should be used to remove any persistent resource you have claimed or created.
82 | /// If you want to transfer state to the updated module, add it to given `data` parameter.
83 | /// This object will be available at `module.hot.data` after the update.
84 | []
85 | abstract dispose: obj -> unit
86 |
87 | []
88 | abstract invalidate: unit -> unit
89 |
90 | []
91 | let hot : IHot = jsNative
92 |
93 | []
94 | let active : bool = jsNative
95 |
--------------------------------------------------------------------------------
/src/HMR.Webpack.fs:
--------------------------------------------------------------------------------
1 | module HMR.Webpack
2 |
3 | open Fable.Core
4 |
5 | []
6 | type Status =
7 | /// The process is waiting for a call to check (see below)
8 | | [] Idle
9 | /// The process is checking for updates
10 | | [] Check
11 | /// The process is getting ready for the update (e.g. downloading the updated module)
12 | | [] Prepare
13 | /// The update is prepared and available
14 | | [] Ready
15 | /// The process is calling the dispose handlers on the modules that will be replaced
16 | | [] Dispose
17 | /// The process is calling the accept handlers and re-executing self-accepted modules
18 | | [] Apply
19 | /// An update was aborted, but the system is still in it's previous state
20 | | [] Abort
21 | /// An update has thrown an exception and the system's state has been compromised
22 | | [] Fail
23 |
24 | type ApplyOptions =
25 | /// Ignore changes made to unaccepted modules.
26 | abstract ignoreUnaccepted : bool option with get, set
27 | /// Ignore changes made to declined modules.
28 | abstract ignoreDeclined : bool option with get, set
29 | /// Ignore errors throw in accept handlers, error handlers and while reevaluating module.
30 | abstract ignoreErrored : bool option with get, set
31 | /// Notifier for declined modules
32 | abstract onDeclined : (obj -> unit) option with get, set
33 | /// Notifier for unaccepted modules
34 | abstract onUnaccepted : (obj -> unit) option with get, set
35 | /// Notifier for accepted modules
36 | abstract onAccepted : (obj -> unit) option with get, set
37 | /// Notifier for disposed modules
38 | abstract onDisposed : (obj -> unit) option with get, set
39 | /// Notifier for errors
40 | abstract onErrored : (obj -> unit) option with get, set
41 |
42 |
43 | []
44 | type IHot =
45 |
46 | /// Optional data coming from disposed module
47 | abstract data : obj option
48 |
49 | /// **Description**
50 | /// Retrieve the current status of the hot module replacement process.
51 | /// **Parameters**
52 | ///
53 | ///
54 | /// **Output Type**
55 | /// * `Status`
56 | ///
57 | /// **Exceptions**
58 | ///
59 | abstract status: unit -> Status
60 |
61 | /// **Description**
62 | /// Accept updates for the given dependencies and fire a callback to react to those updates.
63 | /// **Parameters**
64 | /// * `dependencies` - parameter of type `U2` - Either a string or an array of strings
65 | /// * `errorHandler` - parameter of type `(obj -> unit) option` - Function to fire when the dependencies are updated
66 | /// **Output Type**
67 | /// * `unit`
68 | ///
69 | /// **Exceptions**
70 | ///
71 | abstract accept: dependencies: U2 * ?errorHandler: (obj -> unit) -> unit
72 |
73 | /// **Description**
74 | /// Accept updates for itself.
75 | /// **Parameters**
76 | /// * `errorHandler` - parameter of type `(obj -> unit) option` - Function to fire when the dependencies are updated
77 | ///
78 | /// **Output Type**
79 | /// * `unit`
80 | ///
81 | /// **Exceptions**
82 | ///
83 | abstract accept: ?errorHandler: (obj -> unit) -> unit
84 |
85 | /// **Description**
86 | /// Reject updates for the given dependencies forcing the update to fail with a 'decline' code.
87 | /// **Parameters**
88 | /// * `dependencies` - parameter of type `U2` - Either a string or an array of strings
89 | ///
90 | /// **Output Type**
91 | /// * `unit`
92 | ///
93 | /// **Exceptions**
94 | ///
95 | abstract decline: dependencies: U2 -> unit
96 |
97 | /// **Description**
98 | /// Reject updates for itself.
99 | /// **Parameters**
100 | ///
101 | ///
102 | /// **Output Type**
103 | /// * `unit`
104 | ///
105 | /// **Exceptions**
106 | ///
107 | abstract decline: unit -> unit
108 |
109 | /// **Description**
110 | /// Add a handler which is executed when the current module code is replaced.
111 | /// This should be used to remove any persistent resource you have claimed or created.
112 | /// If you want to transfer state to the updated module, add it to given `data` parameter.
113 | /// This object will be available at `module.hot.data` after the update.
114 | /// **Parameters**
115 | /// * `data` - parameter of type `obj`
116 | ///
117 | /// **Output Type**
118 | /// * `unit`
119 | ///
120 | /// **Exceptions**
121 | ///
122 | abstract dispose: data: obj -> unit
123 |
124 | /// **Description**
125 | /// Add a handler which is executed when the current module code is replaced.
126 | /// This should be used to remove any persistent resource you have claimed or created.
127 | /// If you want to transfer state to the updated module, add it to given `data` parameter.
128 | /// This object will be available at `module.hot.data` after the update.
129 | /// **Parameters**
130 | /// * `handler` - parameter of type `obj -> unit`
131 | ///
132 | /// **Output Type**
133 | /// * `unit`
134 | ///
135 | /// **Exceptions**
136 | ///
137 | abstract addDisposeHandler: handler: (obj -> unit) -> unit
138 |
139 | /// **Description**
140 | /// Remove the callback added via `dispose` or `addDisposeHandler`.
141 | /// **Parameters**
142 | /// * `callback` - parameter of type `obj -> unit`
143 | ///
144 | /// **Output Type**
145 | /// * `unit`
146 | ///
147 | /// **Exceptions**
148 | ///
149 | abstract removeDisposeHandler: callback: (obj -> unit) -> unit
150 |
151 | /// **Description**
152 | /// Test all loaded modules for updates and, if updates exist, `apply` them.
153 | /// **Parameters**
154 | /// * `autoApply` - parameter of type `U2`
155 | ///
156 | /// **Output Type**
157 | /// * `JS.Promise`
158 | ///
159 | /// **Exceptions**
160 | ///
161 | abstract check: autoApply: U2 -> JS.Promise
162 |
163 | /// **Description**
164 | /// Continue the update process (as long as `module.hot.status() === 'ready'`).
165 | /// **Parameters**
166 | /// * `options` - parameter of type `U2`
167 | ///
168 | /// **Output Type**
169 | /// * `JS.Promise`
170 | ///
171 | /// **Exceptions**
172 | ///
173 | abstract apply: options : ApplyOptions -> JS.Promise
174 |
175 | /// **Description**
176 | /// Register a function to listen for changes in `status`.
177 | /// **Parameters**
178 | ///
179 | ///
180 | /// **Output Type**
181 | /// * `unit`
182 | ///
183 | /// **Exceptions**
184 | ///
185 | abstract addStatusHandler: (obj -> unit) -> unit
186 |
187 | /// **Description**
188 | /// Remove a registered status handler.
189 | /// **Parameters**
190 | /// * `callback` - parameter of type `obj -> unit`
191 | ///
192 | /// **Output Type**
193 | /// * `unit`
194 | ///
195 | /// **Exceptions**
196 | ///
197 | abstract removeStatusHandler: callback: (obj -> unit) -> unit
198 |
199 | // Webpack can support HMR via `import.meta.webpackHot` and `module.hot`
200 | // But in order to make this module Webpack specific we only bind to `import.meta.webpackHot`
201 | // Indeed, Parcel is using `module.hot`
202 | // In case, someone use Elmish.HMR with an old Webpack module which doesn't support
203 | // `import.meta.webpackHot` the HMR support will be done indirectly via HMR.Parcel module
204 | // This is to try keep a clean written and generated code
205 |
206 | []
207 | let hot : IHot = jsNative
208 |
209 | []
210 | let active : bool = jsNative
211 |
--------------------------------------------------------------------------------
/src/common.fs:
--------------------------------------------------------------------------------
1 | namespace Elmish.HMR
2 |
3 | open Fable.Core.JsInterop
4 | open Fable.React
5 | open Browser
6 | open Elmish
7 | open Elmish.React.Internal
8 |
9 | []
10 | module Common =
11 |
12 | []
13 | type LazyProps<'model> = {
14 | model: 'model
15 | render: unit -> ReactElement
16 | equal: 'model -> 'model -> bool
17 | }
18 |
19 | type LazyState =
20 | { HMRCount : int }
21 |
22 | module Components =
23 | type LazyView<'model>(props) =
24 | inherit Component,LazyState>(props)
25 |
26 | let hmrCount =
27 | if isNull window?Elmish_HMR_Count then
28 | 0
29 | else
30 | unbox window?Elmish_HMR_Count
31 |
32 | do base.setInitState({ HMRCount = hmrCount})
33 |
34 | override this.shouldComponentUpdate(nextProps, _nextState) =
35 | // Note: It seems like if the tabs is not focus
36 | // It can happen that the re-render doesn't happen
37 | // I am not sure why
38 | // In theory, this should not be a problem most of the times
39 | match Bundler.current with
40 | | Some _ ->
41 | let currentHmrCount : int = window?Elmish_HMR_Count
42 | if currentHmrCount > this.state.HMRCount then
43 | this.setState(fun _prevState _props ->
44 | { HMRCount = currentHmrCount }
45 | )
46 | // An HMR call has been triggered between two frames we force a rendering
47 | true
48 | else
49 | not <| this.props.equal this.props.model nextProps.model
50 |
51 | | None ->
52 | not <| this.props.equal this.props.model nextProps.model
53 |
54 | override this.render () =
55 | this.props.render ()
56 |
57 | #if DEBUG
58 | /// Avoid rendering the view unless the model has changed.
59 | /// equal: function to compare the previous and the new states
60 | /// view: function to render the model
61 | /// state: new state to render
62 | let lazyViewWith (equal:'model->'model->bool)
63 | (view:'model->ReactElement)
64 | (state:'model) =
65 | ofType,_,_>
66 | { render = fun () -> view state
67 | equal = equal
68 | model = state }
69 | []
70 | #else
71 | /// Avoid rendering the view unless the model has changed.
72 | /// equal: function to compare the previous and the new states
73 | /// view: function to render the model
74 | /// state: new state to render
75 | let inline lazyViewWith (equal:'model->'model->bool)
76 | (view:'model->ReactElement)
77 | (state:'model) =
78 | Elmish.React.Common.lazyViewWith equal view state
79 | #endif
80 |
81 | #if DEBUG
82 | /// Avoid rendering the view unless the model has changed.
83 | /// equal: function to compare the previous and the new states
84 | /// view: function to render the model using the dispatch
85 | /// state: new state to render
86 | /// dispatch: dispatch function
87 | let lazyView2With (equal:'model->'model->bool)
88 | (view:'model->'msg Dispatch->ReactElement)
89 | (state:'model)
90 | (dispatch:'msg Dispatch) =
91 | ofType,_,_>
92 | { render = fun () -> view state dispatch
93 | equal = equal
94 | model = state }
95 | []
96 | #else
97 | /// Avoid rendering the view unless the model has changed.
98 | /// equal: function to compare the previous and the new states
99 | /// view: function to render the model using the dispatch
100 | /// state: new state to render
101 | /// dispatch: dispatch function
102 | let inline lazyView2With (equal:'model->'model->bool)
103 | (view:'model->'msg Dispatch->ReactElement)
104 | (state:'model)
105 | (dispatch:'msg Dispatch) =
106 | Elmish.React.Common.lazyView2With equal view state dispatch
107 | #endif
108 |
109 | #if DEBUG
110 | /// Avoid rendering the view unless the model has changed.
111 | /// equal: function to compare the previous and the new model (a tuple of two states)
112 | /// view: function to render the model using the dispatch
113 | /// state1: new state to render
114 | /// state2: new state to render
115 | /// dispatch: dispatch function
116 | let lazyView3With (equal:_->_->bool) (view:_->_->_->ReactElement) state1 state2 (dispatch:'msg Dispatch) =
117 | ofType,_,_>
118 | { render = fun () -> view state1 state2 dispatch
119 | equal = equal
120 | model = (state1,state2) }
121 | []
122 | #else
123 | /// Avoid rendering the view unless the model has changed.
124 | /// equal: function to compare the previous and the new model (a tuple of two states)
125 | /// view: function to render the model using the dispatch
126 | /// state1: new state to render
127 | /// state2: new state to render
128 | /// dispatch: dispatch function
129 | let inline lazyView3With (equal:_->_->bool) (view:_->_->_->ReactElement) state1 state2 (dispatch:'msg Dispatch) =
130 | Elmish.React.Common.lazyView3With equal view state1 state2 dispatch
131 | #endif
132 |
133 |
134 | #if DEBUG
135 | /// Avoid rendering the view unless the model has changed.
136 | /// view: function of model to render the view
137 | let inline lazyView (view:'model->ReactElement) =
138 | lazyViewWith (=) view
139 | #else
140 | /// Avoid rendering the view unless the model has changed.
141 | /// view: function of model to render the view
142 | let inline lazyView (view:'model->ReactElement) =
143 | Elmish.React.Common.lazyView view
144 | #endif
145 |
146 | #if DEBUG
147 | /// Avoid rendering the view unless the model has changed.
148 | /// view: function of two arguments to render the model using the dispatch
149 | let lazyView2 (view:'model->'msg Dispatch->ReactElement) =
150 | lazyView2With (=) view
151 | #else
152 | /// Avoid rendering the view unless the model has changed.
153 | /// view: function of two arguments to render the model using the dispatch
154 | let inline lazyView2 (view:'model->'msg Dispatch->ReactElement) =
155 | Elmish.React.Common.lazyView2 view
156 | #endif
157 |
158 | #if DEBUG
159 | /// Avoid rendering the view unless the model has changed.
160 | /// view: function of three arguments to render the model using the dispatch
161 | let lazyView3 (view:_->_->_->ReactElement) =
162 | lazyView3With (=) view
163 | #else
164 | /// Avoid rendering the view unless the model has changed.
165 | /// view: function of three arguments to render the model using the dispatch
166 | let inline lazyView3 (view:_->_->_->ReactElement) =
167 | Elmish.React.Common.lazyView3 view
168 | #endif
169 |
--------------------------------------------------------------------------------
/src/hmr.fs:
--------------------------------------------------------------------------------
1 | namespace Elmish.HMR
2 |
3 | open Fable.Core.JsInterop
4 | open Browser
5 | open Elmish
6 |
7 | []
8 | module Program =
9 |
10 | type Msg<'msg> =
11 | | UserMsg of 'msg
12 | | Stop
13 |
14 | module Internal =
15 |
16 | type Platform =
17 | | Browser
18 | | ReactNative
19 |
20 | let platform =
21 | match window?navigator?product with
22 | | "ReactNative" -> ReactNative
23 | | _ -> Browser
24 |
25 | let tryRestoreState (hmrState : 'model ref) (data : obj) =
26 | match platform with
27 | | ReactNative ->
28 | let savedHmrState = window?react_native_elmish_hmr_state
29 | if not (isNull (box savedHmrState)) then
30 | hmrState.Value <- savedHmrState
31 |
32 | | Browser ->
33 | if not (isNull data) && not (isNull data?hmrState) then
34 | hmrState.Value <- data?hmrState
35 |
36 | let saveState (data : obj) (hmrState : 'model) =
37 | match platform with
38 | | ReactNative ->
39 | window?react_native_elmish_hmr_state <- hmrState
40 | | Browser ->
41 | data?hmrState <- hmrState
42 |
43 |
44 | let inline private updateElmish_HMR_Count () =
45 | window?Elmish_HMR_Count <-
46 | if isNull window?Elmish_HMR_Count then
47 | 0
48 | else
49 | window?Elmish_HMR_Count + 1
50 |
51 | /// Start the dispatch loop with `'arg` for the init() function.
52 | let inline runWith (arg: 'arg) (program: Program<'arg, 'model, 'msg, 'view>) =
53 | #if !DEBUG
54 | Program.runWith arg program
55 | #else
56 | let hmrState : 'model ref = ref (unbox null)
57 |
58 | match Bundler.current with
59 | | Some current ->
60 | updateElmish_HMR_Count ()
61 |
62 | let hmrDataObject =
63 | match current with
64 | | Bundler.Vite ->
65 | HMR.Vite.hot.accept()
66 | HMR.Vite.hot.data
67 |
68 | | Bundler.WebpackESM ->
69 | HMR.Webpack.hot.accept()
70 | HMR.Webpack.hot.data
71 |
72 | | Bundler.WebpackCJS_and_Parcel ->
73 | HMR.Parcel.hot.accept()
74 | HMR.Parcel.hot.data
75 |
76 | Internal.tryRestoreState hmrState hmrDataObject
77 |
78 | | None ->
79 | ()
80 |
81 | let mapUpdate userUpdate (msg : Msg<'msg>) (model : 'model) =
82 | let newModel,cmd =
83 | match msg with
84 | | UserMsg userMsg ->
85 | userUpdate userMsg model
86 |
87 | | Stop ->
88 | model, Cmd.none
89 |
90 | hmrState.Value <- newModel
91 |
92 | newModel
93 | , Cmd.map UserMsg cmd
94 |
95 | let createModel (model, cmd) =
96 | model, cmd
97 |
98 | let mapInit userInit args =
99 | if isNull (box hmrState.Value) then
100 | let (userModel, userCmd) = userInit args
101 |
102 | userModel
103 | , Cmd.map UserMsg userCmd
104 | else
105 | hmrState.Value, Cmd.none
106 |
107 | let mapSetState userSetState (userModel : 'model) dispatch =
108 | userSetState userModel (UserMsg >> dispatch)
109 |
110 | let hmrSubscription =
111 | let handler dispatch =
112 | match Bundler.current with
113 | | Some current ->
114 | match current with
115 | | Bundler.Vite ->
116 | HMR.Vite.hot.dispose(fun data ->
117 | Internal.saveState data hmrState.Value
118 |
119 | dispatch Stop
120 | )
121 |
122 | | Bundler.WebpackESM ->
123 | HMR.Webpack.hot.dispose(fun data ->
124 | Internal.saveState data hmrState.Value
125 |
126 | dispatch Stop
127 | )
128 |
129 | | Bundler.WebpackCJS_and_Parcel ->
130 | HMR.Parcel.hot.dispose(fun data ->
131 | Internal.saveState data hmrState.Value
132 |
133 | dispatch Stop
134 | )
135 |
136 | | None ->
137 | ()
138 | // nothing to cleanup
139 | {new System.IDisposable with member _.Dispose () = ()}
140 |
141 | [["Hmr"], handler ]
142 |
143 | let mapSubscribe subscribe model =
144 | Sub.batch [
145 | subscribe model |> Sub.map "HmrUser" UserMsg
146 | hmrSubscription
147 | ]
148 |
149 | let mapView userView model dispatch =
150 | userView model (UserMsg >> dispatch)
151 |
152 | let mapTermination (predicate, terminate) =
153 | let mapPredicate =
154 | function
155 | | UserMsg msg -> predicate msg
156 | | Stop -> true
157 |
158 | mapPredicate, terminate
159 |
160 | program
161 | |> Program.map mapInit mapUpdate mapView mapSetState mapSubscribe mapTermination
162 | |> Program.runWith arg
163 | #endif
164 |
165 | /// Start the dispatch loop with `unit` for the init() function.
166 | let inline run (program: Program) =
167 | #if !DEBUG
168 | Program.run program
169 | #else
170 | runWith () program
171 | #endif
172 |
173 | (*
174 | Shadow: Fable.Elmish.React
175 | *)
176 |
177 | let inline withReactBatched placeholderId (program: Program<_,_,_,_>) =
178 | #if !DEBUG
179 | Elmish.React.Program.withReactBatched placeholderId program
180 | #else
181 | Elmish.React.Program.Internal.withReactBatchedUsing lazyView2With placeholderId program
182 | #endif
183 |
184 | let inline withReactSynchronous placeholderId (program: Program<_,_,_,_>) =
185 | #if !DEBUG
186 | Elmish.React.Program.withReactSynchronous placeholderId program
187 | #else
188 | Elmish.React.Program.Internal.withReactSynchronousUsing lazyView2With placeholderId program
189 | #endif
190 |
191 | let inline withReactHydrate placeholderId (program: Program<_,_,_,_>) =
192 | #if !DEBUG
193 | Elmish.React.Program.withReactHydrate placeholderId program
194 | #else
195 | Elmish.React.Program.Internal.withReactHydrateUsing lazyView2With placeholderId program
196 | #endif
197 |
198 | []
199 | let inline withReact placeholderId (program: Program<_,_,_,_>) =
200 | #if !DEBUG
201 | Elmish.React.Program.withReactBatched placeholderId program
202 | #else
203 | Elmish.React.Program.Internal.withReactBatchedUsing lazyView2With placeholderId program
204 | #endif
205 |
206 | []
207 | let inline withReactUnoptimized placeholderId (program: Program<_,_,_,_>) =
208 | #if !DEBUG
209 | Elmish.React.Program.withReactSynchronous placeholderId program
210 | #else
211 | Elmish.React.Program.Internal.withReactSynchronousUsing lazyView2With placeholderId program
212 | #endif
213 |
--------------------------------------------------------------------------------
/src/paket.references:
--------------------------------------------------------------------------------
1 | FSharp.Core
2 | Fable.Core
3 | Fable.Elmish.React
4 |
--------------------------------------------------------------------------------
/tests/App.fs:
--------------------------------------------------------------------------------
1 | module App
2 |
3 | open Elmish
4 | open Feliz
5 | open Fable.Core
6 | open Browser
7 | open Elmish.UrlParser
8 | open Elmish.HMR
9 | open Elmish.Navigation
10 |
11 | type Bundler =
12 | | Webpack
13 | | Vite
14 | | Parcel
15 | | Unknown
16 |
17 | []
18 | type Page =
19 | | Home
20 | | About
21 |
22 | type Model =
23 | {
24 | CurrentRoute : Router.Route option
25 | ActivePage : Page
26 | Value : int
27 | Reloads : int
28 | Bundler : Bundler
29 | }
30 |
31 | type Msg =
32 | | Tick
33 | | Loaded
34 |
35 | let private setRoute (result: Option) (model : Model) =
36 | let model = { model with CurrentRoute = result }
37 |
38 | match result with
39 | | None ->
40 | let requestedUrl = Browser.Dom.window.location.href
41 |
42 | JS.console.error("Error parsing url: " + requestedUrl)
43 |
44 | { model with
45 | ActivePage = Page.Home
46 | }
47 | , Cmd.none
48 |
49 | | Some route ->
50 | match route with
51 | | Router.Home ->
52 | { model with
53 | ActivePage = Page.Home
54 | }
55 | , Cmd.none
56 |
57 | | Router.About ->
58 | { model with
59 | ActivePage = Page.About
60 | }
61 | , Cmd.none
62 |
63 |
64 | let private init (optRoute : Router.Route option) =
65 | let bundler =
66 | match int window.location.port with
67 | | 3000 -> Webpack
68 | | 3001 -> Parcel
69 | | 3002 -> Vite
70 | | _ -> Unknown
71 |
72 | {
73 | CurrentRoute = None
74 | ActivePage = Page.Home
75 | Value = 0
76 | Reloads = -1 // initial load doesn't count
77 | Bundler = bundler
78 | }
79 | |> setRoute optRoute
80 |
81 | let private update (msg : Msg) (model : Model) =
82 | match msg with
83 | | Tick ->
84 | { model with
85 | Value = model.Value + 1
86 | }
87 | , Cmd.none
88 | | Loaded ->
89 | { model with
90 | Reloads = model.Reloads + 1
91 | }
92 | , Cmd.none
93 |
94 | // We are not using Hooks for the Tick system so Elmish HMR
95 | // support is tested and not React HMR or React fast refresh
96 |
97 | module Sub =
98 | let tick intervalMs onTick onStarted =
99 | let subId = ["tick"]
100 | let start dispatch =
101 | let intervalId = JS.setInterval (fun () -> dispatch onTick) intervalMs
102 | // setTimeout needed: https://github.com/elmish/elmish/issues/256
103 | JS.setTimeout (fun () -> dispatch onStarted) 1 |> ignore
104 | { new System.IDisposable with
105 | member _.Dispose() = JS.clearInterval intervalId }
106 | subId, start
107 |
108 | let private subscribe model =
109 | // sub doesn't change if model changes, is running as long as app runs
110 | // so we can use it to know when app reloads
111 | [ Sub.tick 1000 Tick Loaded ]
112 |
113 | let private liveCounter model =
114 | Html.section [
115 | prop.classes ["section"]
116 | prop.children [
117 | Html.div [
118 | prop.classes ["has-text-centered"]
119 | prop.children [
120 | Html.div [
121 | Html.text "Application is running since "
122 | Html.text (string model.Value)
123 | Html.text " seconds with "
124 | Html.text (string model.Reloads)
125 | Html.text " reloads"
126 | ]
127 |
128 | Html.br [ ]
129 |
130 | Html.text "Change me and check that the timer has not been reset"
131 | ]
132 | ]
133 | ]
134 | ]
135 |
136 | let private navbar (model : Model) =
137 | Html.nav [
138 | prop.classes ["navbar"; "is-info"]
139 | prop.children [
140 | Html.div [
141 | prop.classes ["navbar-menu"]
142 | prop.children [
143 | Html.div [
144 | prop.classes ["navbar-start"]
145 | prop.children [
146 | Html.a [
147 | prop.classes ["navbar-item"]
148 | Router.href Router.Home
149 |
150 | prop.text "Home"
151 | ]
152 |
153 | Html.a [
154 | prop.classes ["navbar-item"]
155 | Router.href Router.About
156 |
157 | prop.text "About"
158 | ]
159 | ]
160 | ]
161 | ]
162 | ]
163 | ]
164 | ]
165 |
166 | let private renderActivePage (page : Page) =
167 | let content =
168 | match page with
169 | | Page.Home ->
170 | Html.p [
171 | prop.classes ["has-text-centered"]
172 |
173 | prop.children [
174 | Html.text "This is the home page"
175 | ]
176 | ]
177 |
178 | | Page.About ->
179 | Html.p [
180 | prop.classes ["has-text-centered"]
181 |
182 | prop.children [
183 | Html.text "This is the about page"
184 | ]
185 | ]
186 |
187 | Html.section [
188 | prop.classes ["section"]
189 | prop.children [
190 | content
191 | ]
192 | ]
193 |
194 | let private renderBundlerInformation (bundler : Bundler) =
195 | let bundlerText =
196 | match bundler with
197 | | Webpack -> "Webpack"
198 | | Parcel -> "Parcel"
199 | | Vite -> "Vite"
200 | | Unknown -> "Unknown"
201 |
202 | Html.section [
203 | prop.classes ["section"]
204 | prop.children [
205 | Html.p [
206 | prop.classes ["has-text-centered"]
207 |
208 | prop.children [
209 | Html.text "This is page is running using: "
210 | Html.text bundlerText
211 | ]
212 | ]
213 | ]
214 | ]
215 |
216 | let private lazyViewTest (_value : int) =
217 | Html.section [
218 | prop.classes ["section"]
219 | prop.children [
220 | Html.div [
221 | prop.classes ["has-text-centered"]
222 |
223 | prop.children [
224 | Html.p "Change this text and see that it has been changed after HMR being applied"
225 | Html.br [ ]
226 | Html.p "If HMR was not supported this text would not change until a full refresh of the page"
227 | ]
228 | ]
229 | ]
230 | ]
231 |
232 | let private view (model : Model) (dispatch : Dispatch) =
233 | Html.div [
234 | navbar model
235 |
236 | renderBundlerInformation model.Bundler
237 |
238 | Html.hr [ ]
239 |
240 | renderActivePage model.ActivePage
241 |
242 | Html.hr [ ]
243 |
244 | Html.div [
245 | prop.classes ["container"]
246 | prop.children [
247 | liveCounter model
248 | ]
249 | ]
250 |
251 | Html.hr [ ]
252 |
253 | // We are passing a stable value
254 | // because we want to test that this is the HMR counter
255 | // increment which cause the changes and not the passed value
256 | lazyView lazyViewTest 2
257 |
258 | Html.hr [ ]
259 | ]
260 |
261 |
262 |
263 | Program.mkProgram init update view
264 | |> Program.withSubscription subscribe
265 | |> Program.toNavigable (parseHash Router.pageParser) setRoute
266 | |> Program.withReactBatched "root"
267 | |> Program.run
268 |
--------------------------------------------------------------------------------
/tests/Router.fs:
--------------------------------------------------------------------------------
1 | []
2 | module Router
3 |
4 | open Browser
5 | open Elmish.Navigation
6 | open Elmish.UrlParser
7 | open Feliz
8 |
9 | type Route =
10 | | Home
11 | | About
12 |
13 | let private segment (pathA : string) (pathB : string) =
14 | pathA + "/" + pathB
15 |
16 | let private toHash page =
17 | match page with
18 | | Home -> "home"
19 | | About -> "about"
20 |
21 | |> segment "#"
22 |
23 | let pageParser : Parser Route, Route> =
24 | oneOf
25 | [
26 | map Home (s "home")
27 | map About (s "about")
28 |
29 | map Home top
30 | ]
31 |
32 |
33 | let href route =
34 | prop.href (toHash route)
35 |
36 | let modifyUrl route =
37 | route |> toHash |> Navigation.modifyUrl
38 |
39 | let newUrl route =
40 | route |> toHash |> Navigation.newUrl
41 |
42 | let modifyLocation route =
43 | window.location.href <- toHash route
44 |
--------------------------------------------------------------------------------
/tests/Tests.fsproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | netstandard2.0
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Elmish HMR - demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/paket.references:
--------------------------------------------------------------------------------
1 | group Tests
2 | Fable.Core
3 | Fable.Elmish
4 | Fable.Elmish.Browser
5 | Fable.Elmish.React
6 | Fable.Promise
7 | Feliz
--------------------------------------------------------------------------------
/tests/parcel/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Elmish HMR - demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tests/webpack/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Elmish HMR - demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/tests/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | import HtmlWebpackPlugin from 'html-webpack-plugin';
2 | import {fileURLToPath} from 'node:url';
3 | import path from 'node:path';
4 |
5 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
6 |
7 | function resolve(filePath) {
8 | return path.join(__dirname, filePath)
9 | }
10 |
11 | export default (_env, options) => {
12 |
13 | var isDevelopment = options.mode === "development";
14 |
15 | return {
16 | // In development, bundle styles together with the code so they can also
17 | // trigger hot reloads. In production, put them in a separate CSS file.
18 | entry:
19 | {
20 | app: "./../fableBuild/App.js"
21 | },
22 | // Add a hash to the output file name in production
23 | // to prevent browser caching if code changes
24 | output: {
25 | path: resolve("./temp"),
26 | filename: "app.js"
27 | },
28 | devtool: isDevelopment ? 'eval-source-map' : false,
29 | watchOptions: {
30 | ignored: /node_modules/,
31 | },
32 | plugins:
33 | [
34 | // In production, we only need the bundle file
35 | isDevelopment && new HtmlWebpackPlugin({
36 | filename: "./index.html",
37 | template: "./index.html"
38 | })
39 | ].filter(Boolean),
40 | // Configuration for webpack-dev-server
41 | devServer: {
42 | hot: true
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------