Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ElmWithRoc #196

Merged
merged 9 commits into from
Aug 26, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -19,8 +19,9 @@ examples/Tasks/main
examples/Tuples/main
examples/EncodeDecode/main
examples/SafeMath/main
examples/ElmWithRoc/webserver
examples/ElmWithRoc/frontend/elm-stuff/*
examples/ElmFrontendRocBackend/webserver
examples/ElmFrontendRocBackend/frontend/elm-stuff/*
examples/ElmFrontendRocBackend/frontend/elm.js
examples/GoPlatform/platform/*.dylib
roc_nightly/

25 changes: 25 additions & 0 deletions ci_scripts/expect_scripts/ElmFrontendRocBackend.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/expect

# uncomment line below for debugging
# exp_internal 1

set timeout 7

spawn ./examples/ElmFrontendRocBackend/backend

cd ./examples/ElmFrontendRocBackend/frontend
spawn elm reactor --port 8001
cd ../../..

# wait for elm to start up
sleep 3

set curlOutput [exec curl -sS localhost:8001/index.html]

# We don't actually run the elm js here, selenium or puppeteer could be used in the future for a complete test
if {[string match "*Elm.Main.init*" $curlOutput]} {
exit 0
} else {
puts "Error: curl output was different than expected: $curlOutput"
exit 1
}
70 changes: 70 additions & 0 deletions examples/ElmFrontendRocBackend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Roc Web Server with an Elm Frontend

A minimal project with an Elm [frontend](https://chatgpt.com/share/93575daf-49ef-48ba-b39d-ab04672e4019) and Roc [backend](https://chatgpt.com/share/7ac35a32-dab5-46d0-bb17-9d584469556f). The Roc backend uses the [basic-webserver](https://github.com/roc-lang/basic-webserver) platform.

## Why Elm + Roc?

Roc was inspired by Elm, so it's nice to be able to use a similar language for the frontend. Elm also has a mature collection of re-usable packages.

## Alternatives

We've also enjoyed using [htmx with Roc](https://github.com/lukewilliamboswell/roc-htmx-playground). It allows you to use Roc for the frontend and the backend.

## Full Code

src/Main.elm:
```elm
file:src/Main.elm
```

elm.json:
```json
file:elm.json
```

index.html:
```html
file:index.html
```

webserver.roc:
```roc
file:webserver.roc
```
## Running

### Roc

You can change the port on which the Roc server runs with ROC_BASIC_WEBSERVER_PORT.
```
cd examples/ElmFrontendRocBackend/

# development
roc backend.roc

# production
roc build backend.roc --optimize
./backend
```

### Elm

> Note: for non-trivial Elm development we recommend using [elm-live](https://github.com/wking-io/elm-live).

Compile elm code to javascript.

```
cd examples/ElmFrontendRocBackend/frontend
# development
elm make src/Main.elm --output elm.js
# production
elm make src/Main.elm --output elm.js --optimize
```

Serve the frontend:
```
elm reactor --port 8001 # Roc backend will be on 8000
```
For production; use a [battle-tested HTTP server](https://chatgpt.com/share/5809a606-10ea-4ee6-b821-732465016254) instead of elm reactor.

Open localhost:8001/index.html in your browser.
Binary file added examples/ElmFrontendRocBackend/backend
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -5,15 +5,20 @@ import pf.Task exposing [Task]
import pf.Http exposing [Request, Response]
import pf.Utc

# [backend](https://chatgpt.com/share/7ac35a32-dab5-46d0-bb17-9d584469556f) Roc server

main : Request -> Task Response []
main = \req ->
# Log request datetime, method and url
datetime = Utc.now! |> Utc.toIso8601Str

Stdout.line! "$(datetime) $(Http.methodToStr req.method) $(req.url)"

Task.ok {
status: 200,
headers: [
# TODO check if this header is a good idea
{ name: "Access-Control-Allow-Origin", value: Str.toUtf8 "*" },
],
body: Str.toUtf8 "Hello, world! This is Roc Picasso\n",
body: Str.toUtf8 "Hi, Elm! This is from Roc: 🎁\n",
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -2,8 +2,10 @@
<html>
<head> </head>
<body>
<!-- app div is used by elm -->
<div id="app"></div>
<script src="main.js"></script>
<!-- elm is compiled to js -->
<script src="elm.js"></script>
<script>
Elm.Main.init({ node: document.getElementById("app") });
</script>
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
module Main exposing (..)

-- Importing necessary modules
import Browser
import Html exposing (Html, div, text, h1)
import Html.Attributes exposing (..)
import Http


-- MAIN

-- The main function is the entry point of an Elm application
main =
Browser.element
{ init = init
@@ -16,67 +17,68 @@ main =
, view = view
}


-- MODEL

-- Model represents the state of our application
type Model
= Failure
= Failure String
| Loading
| Success String


-- init function sets up the initial state and any commands to run on startup
init : () -> (Model, Cmd Msg)
init _ =
( Loading
, fetchData
)


-- UPDATE

-- Msg represents the different types of messages our app can receive
type Msg
= GotResponse (Result Http.Error String)


-- update function handles how the model changes in response to messages
update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
GotResponse result ->
case result of
Ok body ->
(Success body, Cmd.none)
Err error ->
(Failure (Debug.toString error), Cmd.none)

Err _ ->
(Failure, Cmd.none)

-- VIEW

-- view function determines what to display in the browser based on the current model
view : Model -> Html Msg
view model =
case model of
Failure ->
text "I was unable to load your response."

Failure errorMsg ->
text ("Is the Roc webserver running? I hit an error: " ++ errorMsg)
Loading ->
text "Loading..."

Success body ->
div [ id "app" ]
[
h1 [] [ text body ]
]

-- HTTP

-- HTTP REQUEST

-- fetchData sends an HTTP GET request to the Roc backend
fetchData : Cmd Msg
fetchData =
Http.get
{ url = "http://localhost:8000/"
, expect = Http.expectString GotResponse
}


-- SUBSCRIPTIONS

-- subscriptions allow the app to listen for external input (e.g., time, websockets)
-- In this case, we're not using any subscriptions
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
Sub.none
34 changes: 0 additions & 34 deletions examples/ElmWithRoc/README.md

This file was deleted.

Loading