{"id":17997371,"url":"https://github.com/thma/threepennyelectron","last_synced_at":"2025-03-26T04:31:17.932Z","repository":{"id":51021597,"uuid":"211489064","full_name":"thma/ThreepennyElectron","owner":"thma","description":"Demo for using Threepenny GUI together with Electron to create a portable Desktop GUI Application in Haskell","archived":false,"fork":false,"pushed_at":"2023-12-15T17:49:22.000Z","size":1964,"stargazers_count":54,"open_issues_count":2,"forks_count":4,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-03-21T06:01:38.768Z","etag":null,"topics":["electron","gui","gui-programming","haskell","haskell-library","npm","standalone-gui-application","threepenny-gui"],"latest_commit_sha":null,"homepage":null,"language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thma.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2019-09-28T11:20:09.000Z","updated_at":"2024-11-30T05:57:56.000Z","dependencies_parsed_at":"2023-12-15T18:45:51.260Z","dependency_job_id":null,"html_url":"https://github.com/thma/ThreepennyElectron","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2FThreepennyElectron","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2FThreepennyElectron/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2FThreepennyElectron/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thma%2FThreepennyElectron/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thma","download_url":"https://codeload.github.com/thma/ThreepennyElectron/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245589255,"owners_count":20640252,"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":["electron","gui","gui-programming","haskell","haskell-library","npm","standalone-gui-application","threepenny-gui"],"created_at":"2024-10-29T21:18:15.564Z","updated_at":"2025-03-26T04:31:17.252Z","avatar_url":"https://github.com/thma.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Writing Haskell native GUI Applications with Threepenny GUI and Electron.\n\n[![Actions Status](https://github.com/thma/ThreepennyElectron/workflows/Haskell%20CI/badge.svg)](https://github.com/thma/ThreepennyElectron/actions)\n[![Known Vulnerabilities](https://snyk.io/test/github/thma/ThreepennyElectron/badge.svg?targetFile=package.json)](https://snyk.io/test/github/thma/ThreepennyElectron?targetFile=package.json)\n\n## tl;dr\n\nThreepenny is an awesome Haskell library for creating browser based applications running on localhost.\n\nBy combining it with the Electron.js framework you have a great toolset for writing cross-platform standalone GUI applications \u0026mdash; completely in Haskell with a great functional reactive programming API.\n\nSee it in action:\n\n```bash\ngit clone https://github.com/thma/ThreepennyElectron.git\ncd ThreepennyElectron\nstack init\nnpm install\nnpm start\n```\n\nPrerequisites:\n- [Git](https://git-scm.com/)\n- [Haskell Stack](https://docs.haskellstack.org/en/stable/README/)\n- [Nodejs](https://nodejs.org)\n\n## Immature support for writing Desktop Applications in Haskell ?\n\nSince reading The GUI chapter in [Real World Haskell](http://book.realworldhaskell.org/read/gui-programming-with-gtk-hs.html) \nI had the impression that Haskell does not excel in GUI programming.\nThe GUI examples in Real World Haskell are based on  [gtk2hs](https://github.com/gtk2hs/gtk2hs). Gtk2hs is a Haskell library that allows to write \nplatform-independent GUI applications based on the GTK library.\nThere are some large applications based on gtk2hs or its successor [gi-gtk-hs](https://github.com/haskell-gi/gi-gtk-hs) like the \nHaskell IDE [Leksah](http://leksah.org/). It's rock solid technology. But it's also quite dated and the imperative programming model \nis not an ideal fit for a purely functional language like Haskell.\n\nSo even though I'm a Haskell enthusiast I tended to agree with [Gabriel Gonzales \"Immature\" rating](https://github.com/Gabriel439/post-rfc/blob/master/sotu.md#standalone-gui-applications) \nof Haskell's support for standalone GUI Applications.  \n\n## Threepenny to the rescue\nA few weeks back I was asked to write a standalone GUI version of an existing Haskell commandline app. So I had to make up\nmy mind about choosing a GUI library with an optimal fit to my needs:\n\n- provide a multi-platform (Windows, MacOS, Linux) standalone GUI application.\n- use functional reactive programming instead of event handler callbacks\n- provide a modern look and feel e.g. material design\n\nI never was satisfied with the look and feel of GTK based applications.\nAnd I also wasn't keen on going back to callback based UI programming.\nSo I had a look at Gabriel Gonzalez great resource \n[State of the Haskell ecosystem](https://github.com/Gabriel439/post-rfc/blob/master/sotu.md).\n\nIn the [section on Standalone GUI applications](https://github.com/Gabriel439/post-rfc/blob/master/sotu.md#standalone-gui-applications) \nhe mainly mentions GTK and Qt bindings but also some other libraries.\nOne of them is [Threepenny GUI](http://wiki.haskell.org/Threepenny-gui) which caught my attention because it uses \nthe web browser as a display. And it comes with an (optional) functional reactive programming model!\n\n\u003e A program written with Threepenny is essentially a small web server that displays the user interface as a web \n\u003e page to any browser that connects to it. You can freely manipulate the HTML DOM and handle JavaScript events \n\u003e from your Haskell code.\n\u003e\n\u003e (Quoted from the  [hackage documentation](https://hackage.haskell.org/package/threepenny-gui))\n\nMy next thought was: It would be cool to use [Electron](https://electronjs.org/) to host the Threepenny HTML/Javascript \nfrontend against the Threepenny Haskell backend. By making use of the [electron packager](https://www.npmjs.com/package/electron-packager) \nthis would allow to package platform specific standalone GUI application for Windows, MacOS and Linux. \n\nI really got excited when I found out that Jeremy Barisch-Rooney already had already written a short \n[HOWTO document](https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/doc/electron.md) that \nexplains the required steps to glue an ELectron frontend to a Threepenny backend.\n\nBased on this Howto I was able to deliver a native windows GUI Application with an embed Haskell backend within just a few days.\nI received very good feedback from the endusers and my impression was:\n\n\u003e\"Thanks to Threepenny GUI support in Haskell has just become a bit more mature!\"\n\nAs I did not find much coverage of this specific Electron / Threepenny GUI combination in blogs or tutorials I thought it \nwould be a good idea to spread the word by writing a short tutorial featuring the basic building blocks of this approach.\n\nSo without further ado let's get started:\n\n## Writing a platform independent standalone calculator app\n\nIn order to provide a bit more than just a hello world example I'm showcasing a simple pocket calculator app. This allows to demonstrate basic features of writing real world UI applications. The calculator is based on an earlier [Threepenny GUI demo by Aleksey Pirogov](https://bitbucket.org/astynax/threep/src/default/).\n\nThe UI of the calculator is shown in the screenshot below. It features a display, a numeric block for entering digits and a decimal point, buttons for the four basic arithmetical operations, a **clear** button and a **clear error** button:\n\n![screenshot of the calculator](screenshot.png)\n\n\n### The calculator\n\nAt the heart of an application sits the model. In this case the [calculator](src/Calc.hs). It is implemented as a simple state machine. \nThe state machine knows five different states:\n\n1. Entering a number into the first register A\n2. Finishing the entry of the first number by entering an Operation (+, -, *, /)\n3. Entering a number into the second register B\n4. Finishing the Operation of the second number by entering **=** or another arithmetic operation\n5. an Error state in case of divison by zero or by entering a wrong sequence of buttons \n\nThis is reflected in the following data type declaration:\n\n```haskell\n-- | a data type representing all possible states of the calculator\ndata State = EnteringA     Entering                   -- ^ entering register A\n           | EnteredAandOp Double  Operation          -- ^ A, Op\n           | EnteringB     Double  Operation Entering -- ^ A, Op, entering register B\n           | Calculated    Double  Operation Double   -- ^ A, Op, B\n           | Error         Double  String             -- ^ A, Message\n           deriving (Show, Eq)\n\n-- | Entering is a tuple used while entering numbers. It consists of\ntype Entering = (String, Bool) -- A tuple of the String representation of the entered digits\n                               -- and a flag signalling that Dot was already pressed.\n```\n\nStarting with an initial state \n\n```haskell\ninitialState :: State\ninitialState = EnteringA (\"0\", False)\n```\n\nwe can operate the calculator by populating it with Button events:\n\n```haskell\n-- in GHCi:\n\u003e populate \"9\" initialState\nEnteringA (\"09\",False)\n\u003e populate \"9\" it\nEnteringA (\"099\",False)\n\u003e populate \"/\" it\nEnteredAandOp 99.0 Div\n\u003e populate \"7\" it\nEnteringB 99.0 Div (\"7\",False)\n\u003e populate \"=\" it\nCalculated 14.142857142857142 Div 7.0\n```\n\nThe `populate` function is defined as :\n\n```haskell\npopulate :: String -\u003e State -\u003e State\npopulate = processCommand . parseInput\n\n-- | process a calculator command. That is: compute a calculator state transition    \nprocessCommand :: Command -\u003e State -\u003e State\nprocessCommand cmd = case cmd of\n  Digit x      -\u003e addDigit x\n  Dot          -\u003e addDot\n  Operation op -\u003e applyOp op\n  command      -\u003e applyCmd command\n\n-- | parse a Command from an input string\nparseInput :: String -\u003e Command\nparseInput x = case x of\n  \"0\"  -\u003e Digit Zero\n  \"1\"  -\u003e Digit One\n  \"2\"  -\u003e Digit Two\n  \"3\"  -\u003e Digit Three\n  \"4\"  -\u003e Digit Four\n  \"5\"  -\u003e Digit Five\n  \"6\"  -\u003e Digit Six\n  \"7\"  -\u003e Digit Seven\n  \"8\"  -\u003e Digit Eight\n  \"9\"  -\u003e Digit Nine\n  \".\"  -\u003e Dot\n  \"+\"  -\u003e Operation Add\n  \"-\"  -\u003e Operation Sub\n  \"*\"  -\u003e Operation Mul\n  \"/\"  -\u003e Operation Div\n  \"=\"  -\u003e Flush\n  \"C\"  -\u003e Clear\n  \"CE\" -\u003e ClearError\n  _    -\u003e undefined\n```\n\nFirst the input is parsed to a Command. Based on the parsed Command \n(either a digit, a dot, an arithmetic operation or `=`, `C` or `CE`) the current state is modified\nby one of the functions `addDigit`, `addDot`, `applyOp` or `applyCmd`.\n\nI won't dive deeper into those functions, as you will easily grasp the mechanism by studying the [source code](src/Calc.hs).\n\n## The Threepenny GUI\n\nI will not give an introduction to the Threepenny GUI programming model here as \nThreepenny already ships with [plenty of samples](https://github.com/HeinrichApfelmus/threepenny-gui/tree/master/samples) and a good [getting started tutorial](https://github.com/HeinrichApfelmus/threepenny-gui/tree/master/doc/hal-2017). \nInstead I will focus on presenting only those parts that are necessary to understand the calculator GUI.   \n\nThe application Main module consists of a single function `main`. It reads a port number from the commandline an then call `Ui.start` to launch a WebServer hosting the Ui application on that port:\n\n```haskell\nmain :: IO ()\nmain = do\n  [port] \u003c- getArgs\n  Ui.start (read port)\n```\n\nThis function will either be called when starting the application with `stack exec ThreepennyElectron 8080` or by the electron launch script main.js (which we will discuss later).\n\nThe `Ui` module contains all code for rendering the HTML dom, setting up the event binding to GUI widgets and the respective interaction with application backend.\n\nLet's start with the main entry point `Ui.start` which is called on application launch:\n\n```haskell\nstart :: Int -\u003e IO ()\nstart port = startGUI defaultConfig\n    { jsPort   = Just port\n    , jsStatic = Just \"static\"\n    } setup\n```\n\nIt takes the port number as parameter and starts up a web server with the Threepenny `startGUI` function.\n`startGUI` has the folloing type signature: \n\n```haskell\n-- | Start server for GUI sessions.\nstartGUI\n    :: Config               -- ^ Server configuration.\n    -\u003e (Window -\u003e UI ())    -- ^ Action to run whenever a client browser connects.\n    -\u003e IO ()\n```\n\nWe build our server configuration by starting with the default configuration `defaultConfig` and \nthen modifying two properties:\n\n1. setting the port number to the one read from the command line. \n2. declaring the static content (i.e any html, JavaScript and CSS content) to reside in the directory `./static`.\n\nThe `(Window -\u003e UI ())` action parameter is filled with the function `setup`.\n\nObviously this function must have the following signature:\n\n```haskell\n-- | setup window layout and event handling\nsetup :: Window -\u003e UI ()\n```\n\nAs this function defines the whole layout and user interaction we will inspect it step by step.\n\n### Creating the Threepenny UI design\n\nThe first step is to define the UI elements the overall window layout:\n\n```haskell\nsetup win = void $ do\n  -- define page + stylesheet\n  return win # set title \"3PennyCalc\"\n  UI.addStyleSheet win \"semantic.css\"\n```\n\nWe start by assigning a title to the window `win` and adding a stylesheet. In our example we are using the [Semantic UI](https://semantic-ui.com/) stylesheet. (You could of course use any other css framework or roll your own.)\n\nNext we define the calculator display element `outputBox` as a `UI.input` element. These elements willbe rendered as HTML DOM elements in the browser. Threepenny provides combinators to define css classes and other html attributes. In this case we set the input field to readonly, make the text align to the right and set its width:\n\n```haskell\n  -- define UI controls\n  outputBox \u003c- UI.input\n                # set (attr \"readonly\") \"true\"\n                # set (attr \"style\") \"text-align: right; min-width: 324px\"\n```\n\nThis is resulting HTML DOM element:\n\n```html\n\u003cinput readonly=\"readonly\" style=\"text-align: right; min-width: 324px\"\u003e\n```\n\nIn the next step we define the calculator buttons for digits, operations and commands:\n\n```haskell\n  -- define the button grid\n  buttons   \u003c- mapM (mapM mkButton) buttonLabels\n\n  where\n    mkButton :: (Command, Color) -\u003e UI Element\n    mkButton (cmd, clr) =\n      let btnLabel = lbl cmd -- get the button text\n      in  UI.button #. (\"ui \" ++ color clr ++ \" button\")\n                    # set text btnLabel # set value btnLabel\n                    # set (attr \"type\")  \"button\"\n                    # set (attr \"style\") \"min-width: 60px\"\n\n    color :: Color -\u003e String\n    color = map toLower . show\n\n    buttonDefinitions :: [[(Command, Color)]]\n    buttonDefinitions =\n      [ [(Digit Seven, Grey), (Digit Eight, Grey), (Digit Nine,  Grey), (ClearError,   Orange), (Clear,        Orange)]\n      , [(Digit Four,  Grey), (Digit Five,  Grey), (Digit Six,   Grey), (Operation Add, Brown), (Operation Sub, Brown)]\n      , [(Digit One,   Grey), (Digit Two,   Grey), (Digit Three, Grey), (Operation Mul, Brown), (Operation Div, Brown)]\n      , [(Dot,  Grey),        (Digit Zero,  Grey), (Flush, Black)] ]\n\n-- | Button colors\ndata Color = Grey | Orange | Brown | Black deriving (Show)\n```\n\nTo understand this piece of code let's start with `buttonDefinitions :: [[(Command, Color)]]`: a list of lists of `(Command, Color)` tuples. \nThe outer list represents the rows, the inner list the columns in each row. \nThe tuples represent the button commands and colors we want to see on the calculator buttons.\n\nMapping the function `mkButton` over the `buttonDefinitions` is then used to create the `buttons :: [[UI Element]]`. \nWhere `mkButton` defines each button as a `UI.button`, assigns a semantic.ui css class `(\"ui \" ++ color c ++ \" button\")` \nto it (using the `#.` combinator) and sets text and other attributes by using the `# set` combinator. \n\nTo give an example the first element from `buttonDefinitions`: `(Digit Seven, Grey)` will be rendered in the HTML DOM as:\n\n```html\n\u003cbutton class=\"ui grey button\" value=\"7\" type=\"button\" style=\"min-width: 60px\"\u003e7\u003c/button\u003e\n```\n\nAs the last step of the layouting stage we glue everything together to a nice grid and place it as the HTML body into the DOM tree.\nAgain we use css classes from the Semantic UI framework to create a pleasant look and feel:\n\n```haskell\n  UI.getBody win # set (attr \"style\") \"overflow: hidden\" #+\n    [ UI.div #. \"ui raised very padded text container segment\" #+\n      [UI.table #+ [UI.row [UI.div #. \"ui input\" #+ [element outputBox]]] #+ \n                    map (UI.row . map element) buttons]\n    ]\n```\nThe resulting HTML looks like follows (for brevity I'm showing only everything up to the first row of buttons):\n\n```html\n\u003cbody style=\"overflow: hidden\"\u003e\n\u003cnoscript\u003ePlease enable JavaScript.\u003c/noscript\u003e\n\n\u003cdiv class=\"ui raised very padded text container segment\"\u003e\n    \u003ctable\u003e\n        \u003cdiv class=\"table\"\u003e\n            \u003cdiv class=\"table-row\"\u003e\n                \u003cdiv class=\"table-cell\"\u003e\n                    \u003cdiv class=\"ui input\"\u003e\u003cinput readonly=\"readonly\" style=\"text-align: right; min-width: 324px\"\u003e\n                    \u003c/div\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n        \u003cdiv class=\"table\"\u003e\n            \u003cdiv class=\"table-row\"\u003e\n                \u003cdiv class=\"table-cell\"\u003e\n                    \u003cbutton class=\"ui grey button\" value=\"7\" type=\"button\" style=\"min-width: 60px\"\u003e7\u003c/button\u003e\n                \u003c/div\u003e\n                \u003cdiv class=\"table-cell\"\u003e\n                    \u003cbutton class=\"ui grey button\" value=\"8\" type=\"button\" style=\"min-width: 60px\"\u003e8\u003c/button\u003e\n                \u003c/div\u003e\n                \u003cdiv class=\"table-cell\"\u003e\n                    \u003cbutton class=\"ui grey button\" value=\"9\" type=\"button\" style=\"min-width: 60px\"\u003e9\u003c/button\u003e\n                \u003c/div\u003e\n                \u003cdiv class=\"table-cell\"\u003e\n                    \u003cbutton class=\"ui orange button\" value=\"CE\" type=\"button\" style=\"min-width: 60px\"\u003eCE\u003c/button\u003e\n                \u003c/div\u003e\n                \u003cdiv class=\"table-cell\"\u003e\n                    \u003cbutton class=\"ui orange button\" value=\"C\" type=\"button\" style=\"min-width: 60px\"\u003eC\u003c/button\u003e\n                \u003c/div\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n\n        ...\n\n    \u003c/table\u003e\n\u003c/div\u003e\n\u003c/body\u003e\n```\n\nThis was everything we need to create the HTML UI as shown in the [screenshot](#writing-a-platform-independent-standalone-calculator-app) above.\n\n### Defining the application behaviour\n\nNow we come to the interesting part of UI interaction. Threepenny comes with support for functional reactive programming based on the concepts of [reactive banana](https://wiki.haskell.org/Reactive-banana), a cool FRP framework by Heinrich Apfelmus.\n\nSo I promise we will not see any old-school event-handling in the following code:\n\n```haskell\n  let  \n      -- map buttons to Command. (buttonMap :: [(Element, Command)] )\n      buttonMap = zip (concat buttons) (concatMap (map fst) buttonDefinitions)\n      -- register mouse click events to all buttons. (clicks :: Event Command )\n      clicks  = buttonClicks buttonMap\n      -- use (processCommand :: Command -\u003e State -\u003e State) to build a function that computes a\n      -- calculator state transition (commands :: Event (State -\u003e State))\n      commands  = fmap processCommand clicks\n\n  -- calculate behaviour by accumulating all commands, starting with the initial state    \n  calcBehaviour \u003c- accumB initialState commands\n  -- use Calc.toString to extract the display string from the calculator state\n  let outText  = fmap toString calcBehaviour\n  -- write outText to the outputBox UI element\n  element outputBox # sink value outText\n  where\n    buttonClicks :: [(Element, Command)] -\u003e Event Command\n    buttonClicks = foldr1 (UI.unionWith const) . map makeClick\n      where\n        makeClick (element, cmd) = UI.pure cmd \u003c@ UI.click element\n```\n\nWe'll walk through this code from top to bottom.\nFirst `buttonMap` is defined as an associative list mapping all calculator buttons to their respective `Command`s. \n\nNext we define `clicks :: Event Command`. Where `Event a` represents a stream of events as they occur in time.\n\nThis `clicks` event stream is generated by applying `buttonClicks` to the button map we created in the first step. \n\nSo effectively each time a calculator button is clicked we receive the `Command` represented by the button.\n\nIn the next step we use `processCommand` to generate calculator state transition functions based on each command in the stream. \n\n```haskell\ncommands  = fmap processCommand clicks\n```\n\nWe have to use `fmap` to access the command in the `Event` container.\nThe resulting type of `commands` is thus `commands :: Event (State -\u003e State)`.\n\nNow effectively `commands` is a stream of `(State -\u003e State)` calculator state transitions.\n\nIn the following step we define a `Behaviour` based on the `commands` stream of state transitions.\nIn Threepenny `Behavior a` represents a value that varies in time. Think of it as\n\n```haskell\ntype Behavior a = Time -\u003e a\n```\n\nThe Threepenny function `accumB` can be used to compute behaviour starting from an initial state and a stream of state transition events:\n\n```haskell\naccumB :: MonadIO m =\u003e a --^ the initial value\n       -\u003e Event (a -\u003e a) --^ the stream state transitions\n       -\u003e m (Behavior a) --^ the resulting Behaviour (that is a value varrying in time )\n```\n\nSo once `calcBehaviour \u003c- accumB initialState commands` did all the heavy lifting \nwe just have to extract the display text from the state and to render it in the outputBox:\n\n```haskell\n  -- use Calc.toString to extract the display string from the calculator state \n  let outText  = fmap toString calcBehaviour\n  -- write outText to the outputBox UI element\n  element outputBox # sink value outText\n```\n\n## Recap: what we've got so far\n\nUntil now we have written a calculator as a Threepenny GUI application. \nWe can build and execute it with the following stack commands: \n\n```bash\nstack init\nstack install\nstack exec ThreepennyElectron 8023\n```\n\nIf you now navigate your WebBrowser to `http://127.0.0.1:8023` you'll see the calculator in action.\n\nTo ease the usage of this basic Threepenny application when working in GHCi I have provided a short helper function `up` which will automatically\nopen the Threepenny application in your default web browser:\n\n````haskell\n-- | launch application in default web browser\nup :: IO ()\nup = do\n  let port = 8023\n  launchAppInBrowser port\n  start port\n\n-- | convenience function that opens the 3penny UI in the default web browser\nlaunchAppInBrowser:: Int -\u003e IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)\nlaunchAppInBrowser port = case os of\n  \"mingw32\" -\u003e createProcess  (shell $ \"start \"    ++ url)\n  \"darwin\"  -\u003e createProcess  (shell $ \"open \"     ++ url)\n  _         -\u003e createProcess  (shell $ \"xdg-open \" ++ url)\n  where url = \"http://localhost:\" ++ show port\n\n````\n\n## Electron Integration\n\nSo now we are able to execute our calculator as a local web application in our web browser.\nBut our aim was to have a local standalone application that does not rely on a browser.\n\nThat's where we bring in Electron to bundle the Threepenny GUI Haskell backend with a Chromium based fronted.\n\n### Providing a very straightforward wrapper script\n\n[Electron](https://electronjs.org/) is a popular JavaScript framework that allows to write cross platform desktop applications based on Chromium. \nReal world applications like [Atom](https://atom.io/), [Visual Studio Code](https://code.visualstudio.com/) or [Slack](https://slack.com/intl/de-de/downloads/windows) are good [examples of what can be achieved with it](https://electronjs.org/apps).\n\nAs I already mentioned in the introduction, Heinrich Apfelmus already [created a tutorial](https://github.com/HeinrichApfelmus/threepenny-gui/blob/master/doc/electron.md) on how to write an electron wrapper around a Threepenny GUI application.\n\nLet's start with a short look at the [Javascript code in ](main.js).\n\nThis script detects a free tcp/ip port on localhost and spawns the ThreepennyElectron applications as a separate processes. \nThe free port is handed over to the ThreepennyELectron app as a commandline parameter.\n\nOnce the ThreepennyElectron server is accepting connections we can safely open the application window and load the local url as it's content.\n\n````javascript\n// Relative path to the Threepenny binary.\nconst relBin = './build/ThreepennyElectron';\n\n// Assign a random port to run on.\nfreeport((err, port) =\u003e {\n  if (err) throw err;\n\n  const url = `http://localhost:${port}`;\n  let child = null; // the Threepenny Server process we will spawn\n\n  // Keep a global reference of the window object, if we don't, the window will\n  // be closed automatically when the JavaScript object is garbage collected.\n  let win;\n\n  // Called when Electron has finished initialization and is ready to create\n  // browser windows. Some APIs can only be used after this event occurs. We\n  // start the child process and wait before loading the web page.\n  app.on('ready', () =\u003e {\n    child = spawn(path.join(__dirname, relBin), [port]);\n    child.stdout.setEncoding('utf8');\n    child.stderr.setEncoding('utf8');\n    child.stdout.on('data', console.log);\n    child.stderr.on('data', console.log);\n    child.on('close', code =\u003e\n      console.log(`Threepenny app exited with code ${code}`));\n\n    // Wait until the Threepenny server is ready for connections.\n    waitOn({ resources: [url], timeout }, (err_) =\u003e {\n      if (err_) throw err_;\n      createWindow();\n    });\n  });\n\n  function createWindow() {\n      // Create the browser window.\n      win = new BrowserWindow({\n          width: 470,\n          height: 370,\n          maximizable: false,\n          resizable: false,\n          icon: 'calc.ico',\n          title: '3PennyCalc...'\n      });\n\n      win.removeMenu();\n      console.log(`Loading URL: ${url}`);\n      win.loadURL(url);\n\n      // Emitted when the window is closed.\n      win.on('closed', () =\u003e {\n          // Dereference the window object for garbage collection.\n          win = null;\n      });\n  }\n\n});\n````\n\n### NPM handling\n\nTo make things easier to handle I've improved the npm integration a bit. Once you have initialized stack with `stack init` you can build and run the calculator app (including the haskell backend) with just two npm commands:\n\n```bash\nnpm install\nnpm start\n```\n\nThe trick was to define a prestart script in [package.json](package.json)\n\n```javascript\n\"prestart\": \"node ./stack-install.js\",\n```\n\nThis script simply does a `stack install --local-bin-path build`. This guarantees that the ThreepennyElectron binary is residing under `./build/ThreepennyElectron` as expected by the `main.js` script.\n\nWith the following npm command you can create application package ready for deployment on your platform:\n\n```bash\nnpm run pack-app\n```\n\nYou can use the parameters `--platform` and `--arch` to create packages for other platforms as well.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthma%2Fthreepennyelectron","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthma%2Fthreepennyelectron","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthma%2Fthreepennyelectron/lists"}