{"id":19210975,"url":"https://github.com/boudra/whistle","last_synced_at":"2025-05-12T19:26:24.190Z","repository":{"id":57556640,"uuid":"162187554","full_name":"boudra/whistle","owner":"boudra","description":"Experiment to build single page apps in Elixir","archived":false,"fork":false,"pushed_at":"2019-03-20T21:09:27.000Z","size":232,"stargazers_count":51,"open_issues_count":6,"forks_count":3,"subscribers_count":10,"default_branch":"master","last_synced_at":"2025-04-18T03:35:01.405Z","etag":null,"topics":["elixir","elm-architecture","virtual-dom","web-framework","websockets"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boudra.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-17T20:42:58.000Z","updated_at":"2024-02-03T14:24:48.000Z","dependencies_parsed_at":"2022-09-14T12:23:04.713Z","dependency_job_id":null,"html_url":"https://github.com/boudra/whistle","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boudra%2Fwhistle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boudra%2Fwhistle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boudra%2Fwhistle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boudra%2Fwhistle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boudra","download_url":"https://codeload.github.com/boudra/whistle/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253807206,"owners_count":21967316,"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":["elixir","elm-architecture","virtual-dom","web-framework","websockets"],"created_at":"2024-11-09T13:40:01.796Z","updated_at":"2025-05-12T19:26:24.166Z","avatar_url":"https://github.com/boudra.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Whistle\n[![Hex.pm](https://img.shields.io/hexpm/v/whistle.svg)](https://hex.pm/packages/whistle) [![Build Status](https://travis-ci.org/boudra/whistle.svg?branch=master)](https://travis-ci.org/boudra/whistle) [![Inline docs](http://inch-ci.org/github/boudra/whistle.svg)](http://inch-ci.org/github/boudra/whistle) [![Coverage Status](https://coveralls.io/repos/github/boudra/whistle/badge.svg)](https://coveralls.io/github/boudra/whistle)\n\n\u003cbr\u003e\n\nWhistle is a library for building interactive web apps or small components entirely in Elixir, it can render components in HTML and via WebSockets, all Whistle programs are able to do:\n\n- Server side rendering, like in any modern web framework\n- Client side interactivity using an architecture similar to Elm\n- Single page application routing with Plug, so that the page doesn't need to be fully reloaded\n\nFor an example Single Page Application including Server Side Rendering and routing, that uses most of Whistle's features, check out this chat application:\n\n- Code: [boudra/whistle-chat](https://github.com/boudra/whistle-chat)\n- Demo: [https://lumpy-some-piglet.gigalixirapp.com/](https://lumpy-some-piglet.gigalixirapp.com/)\n\nLinks:\n\n- Documentation: [https://hexdocs.pm/whistle](https://hexdocs.pm/whistle)\n- [Getting started](https://hexdocs.pm/whistle/readme.html#getting-started)\n\n\n**Please remember that this project is still in it's very early stages, some things might not work and the API will most definetly change. Also it is just a side project at the moment so development could be slow.**\n\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:whistle, git: \"https://github.com/boudra/whistle\", ref: \"master\"},\n    # {:whistle, \"~\u003e 0.1.0\"},\n\n    # optional\n    {:jason, \"~\u003e 1.0\"}, # for encoding and decoding JSON\n    {:horde, \"~\u003e 0.4.0\"} # for distributing the program processes\n  ]\nend\n```\n\n## Getting started\n\nThe router is where everything starts, it defines the path of the Websocket listener and what routes match to what programs.\n\nHere is an example:\n\n```elixir\n# lib/my_app_web/program_router.ex\n\ndefmodule MyAppWeb.ProgramRouter do\n  use Whistle.Router, path: \"/ws\"\n\n  match(\"counter\", MyAppWeb.ExampleProgram, %{})\nend\n```\n\nUse `Whistle.Router.match/3` to define program routes, the router will spawn a new program instance for every unique route.\n\nThe program is a module where we specify how to manage and render its state, here is a very simple example:\n\n```elixir\n# lib/my_app_web/programs/example_program.ex\n\ndefmodule MyAppWeb.ExampleProgram do\n  use Whistle.Program\n\n  def init(_params) do\n    {:ok, 0}\n  end\n\n  def update(:increment, state, session) do\n    {:ok, state + 1, session}\n  end\n\n  def update(:decrement, state, session) do\n    {:ok, state - 1, session}\n  end\n\n  def view(state, _session) do\n    ~H\"\"\"\n    \u003cdiv\u003e\n      \u003cbutton on-click={{ :increment }}\u003e+\u003c/button\u003e\n      \u003cspan\u003eThe current number is: {{ state }}\u003e\u003c/span\u003e\n      \u003cbutton on-click={{ :decrement }}\u003e-\u003c/button\u003e\n    \u003c/div\u003e\n    \"\"\"\n  end\nend\n```\n\nCheck out the docs for `Whistle.Program` to see all the callbacks available and the different ways to render the view.\n\nAll you need to do now is add the router in your supervision tree, a router will spawn a dynamic Supervisor and Registry to keep track of all the program instances, you can run as many different routers as you want:\n\n```elixir\n# lib/my_app/application.ex\n\nchildren = [\n  {MyAppWeb.ProgramRouter, []}\n]\n```\n\nNow that you have a router and a program set up, it's time to link everything up with a server, Whistle works with Plug so it doesn't need Phoenix to run, but you can integrate with an existing Phoenix project too:\n\n- [Running with Whistle and Plug](https://hexdocs.pm/whistle/setup.html)\n- [Integrate with your existing Phoenix project](https://hexdocs.pm/whistle/phoenix.html)\n\n\n## FAQs\n\n### What problems does Whistle address?\n\nWhistle is a web library that works a bit differently than normal MVC web frameworks. It is composed of stateful long-running components, allowing you to create interactive applications entirely in Elixir via WebSockets, while being also able to render an HTML page like any other web framework.\n\nIt aims to provide a more functional approach to building web apps in Elixir, while also taking more advantage of Erlang's actor model.\n\nIt also provides an extensive [Javascript API](https://hexdocs.pm/whistle/javascript.html) for when Elixir alone doesn't cut it and interop with existing front-end libraries like React is needed.\n\n### What is a Router?\n\nA Router is a module that defines what routes match what programs, every unique route string will spawn a unique program instance. The router also supervises the [Program Regsitry](https://hexdocs.pm/whistle/Whistle.Program.Registry.html), the Program Supervisor and the [HTTP server](https://hexdocs.pm/whistle/setup.html) if there is one.\n\n### What is a Program?\n\nA program is a stateful component that runs as an Erlang process, this is where we define how the state looks like, how it's updated and how it's rendered.\n\n### What is the difference between a Fullscreen Program and an Embeded Program?\n\nA fullscreen Program is when a Program renders the whole HTML document, including the `\u003chead\u003e` and the `\u003cbody\u003e`. Fullscreen programs normally take control of the routing too, you can make a whole web application with a Fullscreen program.\n\nEmbeded programs are normally small components that can be included in your web page, like a typeahead search widget or a chat.\n\n### How does the Virtual DOM work?\n\nThe Virtual DOM is an in-memory representation of the client's DOM that lives in the server, every time a Program's state changes, Whistle will render it compare the new Virtual DOM against the old one, and send minimal changes to the client via WebSockets.\n\nWhistle's Virtual DOM is represented as follows:\n\n```elixir\n# {key, {tag, {attributes, children}}}\n{0, {\"div\", {[class: \"red\"], [{0, \"first\"}, {1, \"second\"}]}}}\n```\nYou can use the macros to generate it (preferable):\n\n```elixir\nHtml.div([class: \"red\"], [\n  \"first\",\n  \"second\"\n])\n```\n\nOr the `~H` sigil (note that this is not EEx, it is a custom templating format similar to Mustache):\n\n```elixir\n~H\"\"\"\n\u003cdiv class=\"red\"\u003efirst second\u003c/div\u003e\n\"\"\"\n```\n\nThe Virtual DOM consists of pairs of tuples so that it can also be a valid Elixir AST and be able to generate most of it at compile time:\n\n```elixir\niex\u003e a = \"text\"\niex\u003e Html.span([], [a])\n{0, {\"span\", {[], [{0, {:a, [], nil}}]}}}\n```\n\nWhistle also provides functions to render a VDOM to string, this is used to render a HTML response when rendering Programs in Plug.\n\n```elixir\niex\u003e Html.span([], [\"text\"]) |\u003e Whistle.Html.Dom.node_to_string()\n\u003cspan\u003etext\u003c/span\u003e\n```\n\n## Roadmap\n\n\nWhat has been done so far:\n\n- [x] Program orchestrating, program error recovery, client auto-reconnections\n- [x] Program and client message communication and broadcasting\n- [x] Lazy Virtual DOM trees to reduce unecessary rendering and diffing\n- [x] Integrate Virtual DOM with Elixir's AST so it can be generated at compile-time\n- [x] Initial render via HTTP, then stream updates via WebSockets\n- [x] Full screen program mode with routing and browser history to build Single Page Applications with Server Side Rendering :rocket:\n- [x] HTML string template file to VDOM tree in the view\n- [x] \"Single Page Applications\" with built in routing and browser history support\n- [x] Code reloading for code Programs without having to restart\n\nCheckout the issues list to see what features are planned:\n\nhttps://github.com/boudra/whistle/issues\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboudra%2Fwhistle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboudra%2Fwhistle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboudra%2Fwhistle/lists"}