{"id":16337041,"url":"https://github.com/amesgen/jsaddle-wasm","last_synced_at":"2025-03-20T23:30:55.599Z","repository":{"id":215809412,"uuid":"737391165","full_name":"amesgen/jsaddle-wasm","owner":"amesgen","description":"JSaddle integration for the GHC WASM backend","archived":false,"fork":false,"pushed_at":"2024-10-03T11:53:02.000Z","size":30,"stargazers_count":11,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-10-11T23:45:42.043Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"cc0-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amesgen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-30T21:29:31.000Z","updated_at":"2024-10-09T17:21:08.000Z","dependencies_parsed_at":"2024-01-06T18:26:35.560Z","dependency_job_id":"99699734-3446-4281-b195-6f69310bf01b","html_url":"https://github.com/amesgen/jsaddle-wasm","commit_stats":null,"previous_names":["amesgen/jsaddle-wasm"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amesgen%2Fjsaddle-wasm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amesgen%2Fjsaddle-wasm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amesgen%2Fjsaddle-wasm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amesgen%2Fjsaddle-wasm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amesgen","download_url":"https://codeload.github.com/amesgen/jsaddle-wasm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244710527,"owners_count":20497254,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-10T23:45:42.058Z","updated_at":"2025-03-20T23:30:55.592Z","avatar_url":"https://github.com/amesgen.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# jsaddle-wasm\n[![CI](https://github.com/amesgen/jsaddle-wasm/workflows/CI/badge.svg)](https://github.com/amesgen/jsaddle-wasm/actions)\n[![Hackage](https://img.shields.io/hackage/v/jsaddle-wasm)](https://hackage.haskell.org/package/jsaddle-wasm)\n[![Haddocks](https://img.shields.io/badge/documentation-Haddocks-purple)](https://hackage.haskell.org/package/jsaddle-wasm/docs/Language-Javascript-JSaddle-Wasm.html)\n\nRun [JSaddle][] `JSM` actions with the [GHC Wasm backend][].\n\nThis can for example be used to compile and run [Miso][] or [Reflex][] apps in the browser.\n\n\u003e [!IMPORTANT]\n\u003e This project is in an early stage.\n\n## Examples\n\n - Miso examples: https://github.com/tweag/ghc-wasm-miso-examples\n\n - Reflex examples: https://github.com/tweag/ghc-wasm-reflex-examples\n\n - Ormolu Live: https://github.com/tweag/ormolu/tree/master/ormolu-live\n   (uses the web worker approach described below)\n\n## How to use\n\nInstall a Wasm-enabled GHC with support for the Wasm JSFFI (including [synchronous JSFFI exports][sync-jsffi-exports][^missing-sync-jsffi]) from [ghc-wasm-meta][] (GHC 9.10 or newer).\n\n\nAssuming you built your application as an `app :: JSM ()`:\n\n```haskell\nimport Language.Javascript.JSaddle.Wasm qualified as JSaddle.Wasm\n\nforeign export javascript \"hs_start\" main :: IO ()\n\nmain :: IO ()\nmain = JSaddle.Wasm.run app\n```\n\nBuild the Wasm binary with the following GHC options:\n```cabal\nghc-options: -no-hs-main -optl-mexec-model=reactor \"-optl-Wl,--export=hs_start\"\n```\n\nNow, run the post-linker script as described in the [GHC User's Guide][ghc-users-guide-js-api]; we will call the resulting JavaScript file `ghc_wasm_jsffi.js`.\n\nThen, following the [GHC User's Guide][ghc-users-guide-js-api], you can run the Wasm binary in the browser via e.g. [browser_wasi_shim][]:\n```javascript\nimport { WASI, OpenFile, File, ConsoleStdout } from \"@bjorn3/browser_wasi_shim\";\nimport ghc_wasm_jsffi from \"./ghc_wasm_jsffi.js\";\n\nconst fds = [\n  new OpenFile(new File([])), // stdin\n  ConsoleStdout.lineBuffered((msg) =\u003e console.log(`[WASI stdout] ${msg}`)),\n  ConsoleStdout.lineBuffered((msg) =\u003e console.warn(`[WASI stderr] ${msg}`)),\n];\nconst options = { debug: false };\nconst wasi = new WASI([], [], fds, options);\n\nconst instance_exports = {};\nconst { instance } = await WebAssembly.instantiateStreaming(fetch(\"app.wasm\"), {\n  wasi_snapshot_preview1: wasi.wasiImport,\n  ghc_wasm_jsffi: ghc_wasm_jsffi(instance_exports),\n});\nObject.assign(instance_exports, instance.exports);\n\nwasi.initialize(instance);\nawait instance.exports.hs_start();\n```\n\n### Separating execution environments\n\nIt is also possible to run the Wasm worker in a different execution environment (e.g. a web worker) than the JSaddle JavaScript code that dispatches the JSaddle command messages.\n\nAn advantage of this approach is that computationally expensive operations in Wasm do not block the UI thread. A disadvantage is that there is some overhead for copying the data back and forth, and everything relying on synchronous callbacks (e.g. `stopPropagation`/`preventDefault`) definitely no longer works.\n\n - Instead of the `run` function above, you need to use `runWorker` (again assuming `app :: JSM ()`):\n\n   ```haskell\n   import Language.Javascript.JSaddle.Wasm qualified as JSaddle.Wasm\n\n   foreign export javascript \"hs_runWorker\" runWorker :: JSVal -\u003e IO ()\n\n   runWorker :: JSVal -\u003e IO ()\n   runWorker = JSaddle.Wasm.runWorker app\n   ```\n\n   The argument to `runWorker` here can be any message port in the sense of the [Channel Messaging API][]. In particular, it must provide a `postMessage` function and a `message` event.\n\n   For example, in a web worker, you can initialize the Wasm module as above, and then run\n   ```javascript\n   await instance.exports.hs_runWorker(globalThis);\n   ```\n   as `globalThis` (or `self`) in a web worker is a message port.\n\n - Additionally, you need to run the JSaddle command dispatching logic on the other end of the message port.\n\n   The necessary chunk of JavaScript is available as `jsaddleScript` both from `Language.Javascript.JSaddle.Wasm` from the main library, and also from `Language.Javascript.JSaddle.Wasm.JS` from the `js` public sublibrary, where the latter has the advantage to not depend on any JSFFI, so you can build a normal WASI command module or even a native executable while still depending on it.\n\n   It provides a function `runJSaddle` taking a single argument, a message port.\n\n   One way to invoke it is to save `jsaddleScript` to some file, include it via a `script` tag in your HTML file, and then run\n   ```javascript\n   const worker = new Worker(\"my-worker.js\");\n   runJSaddle(worker);\n   ```\n\n## Potential future work\n\n - Testing (e.g. via Selenium).\n - Add logging/stats.\n - Performance/benchmarking (not clear that this is actually a bottleneck for most applications).\n    - Optimize existing command-based implementation.\n       - Reuse buffers\n       - Use a serialization format more efficient than JSON.\n    - Patch `jsaddle` to not go through commands, by using the Wasm JS FFI.\n    - Implement `ghcjs-dom` API directly via the Wasm JS FFI.\n\n      This would involve creating a `ghcjs-dom-wasm` package by adapting the FFI import syntax from `ghcjs-dom-jsffi`/`ghcjs-dom-javascript` appropriately.\n\n      Currently, the generic `ghcjs-dom-jsaddle` seems to work fine, so it seems sensible to wait with this until benchmarks or other concerns motivate this.\n\n## Related projects\n\n - [WebGHC/jsaddle-wasm](https://github.com/WebGHC/jsaddle-wasm) for the analogue for [WebGHC][] instead of the [GHC Wasm backend][].\n\n[^missing-sync-jsffi]: Otherwise, you will see errors involving `unknown type name 'HsFUN'`.\n\n[JSaddle]: https://github.com/ghcjs/jsaddle\n[GHC Wasm backend]: https://www.tweag.io/blog/2022-11-22-wasm-backend-merged-in-ghc\n[Miso]: https://github.com/dmjio/miso\n[Reflex]: https://github.com/reflex-frp/reflex\n[ghc-wasm-meta]: https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta\n[browser_wasi_shim]: https://github.com/bjorn3/browser_wasi_shim\n[ghc-users-guide-js-api]: https://ghc.gitlab.haskell.org/ghc/doc/users_guide/wasm.html#the-javascript-api\n[WebGHC]: https://webghc.github.io\n[Channel Messaging API]: https://developer.mozilla.org/en-US/docs/Web/API/Channel_Messaging_API\n[sync-jsffi-exports]: https://gitlab.haskell.org/ghc/ghc/-/merge_requests/13994\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famesgen%2Fjsaddle-wasm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famesgen%2Fjsaddle-wasm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famesgen%2Fjsaddle-wasm/lists"}