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
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ examples/Tasks/main
examples/Tuples/main
examples/EncodeDecode/main
examples/SafeMath/main
examples/ElmWebApp/backend
examples/ElmWebApp/frontend/elm-stuff/*
examples/ElmWebApp/frontend/elm.js
examples/GoPlatform/platform/*.dylib
roc_nightly/

Expand Down
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/ElmWebApp/backend

cd ./examples/ElmWebApp/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
}
69 changes: 69 additions & 0 deletions examples/ElmWebApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Web App with Elm

A minimal web app 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:frontend/src/Main.elm
```

elm.json:
```json
file:frontend/elm.json
```

index.html:
```html
file:frontend/index.html
```

backend.roc:
```roc
file:backend.roc
```
## Running

### Roc

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

# development
roc backend.roc --linker=legacy

# production
roc build backend.roc --optimize --linker=legacy
./backend
```

### Elm

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

Compile elm code to javascript:
```
cd examples/ElmWebApp/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](localhost:8001/index.html) in your browser.
37 changes: 37 additions & 0 deletions examples/ElmWebApp/backend.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
app [Model, server] {
pf: platform "https://github.com/roc-lang/basic-webserver/releases/download/0.8.0/jz2EfGAtz_y06nN7f8tU9AvmzhKK-jnluXQQGa9rZoQ.tar.br",
}

import pf.Stdout
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

# Model is produced by `init`.
Model : {}

# With `init` you can set up a database connection once at server startup,
# generate css by running `tailwindcss`,...
# In this case we don't have anything to initialize, so it is just `Task.ok {}`.

server = { init: Task.ok {}, respond }

respond : Request, Model -> Task Response [ServerErr Str]_
respond = \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: [
# !!
# Change http://localhost:8001 to your domain for production usage
# !!
{ name: "Access-Control-Allow-Origin", value: "http://localhost:8001" },
],
body: Str.toUtf8 "Hi, Elm! This is from Roc: 🎁\n",
}
27 changes: 27 additions & 0 deletions examples/ElmWebApp/frontend/elm.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"type": "application",
"source-directories": [
"src"
],
"elm-version": "0.19.1",
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
"elm/json": "1.1.3"
},
"indirect": {
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/time": "1.0.0",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.3"
}
},
"test-dependencies": {
"direct": {},
"indirect": {}
}
}
13 changes: 13 additions & 0 deletions examples/ElmWebApp/frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html>
<head> </head>
<body>
<!-- app div is used by elm -->
<div id="app"></div>
<!-- elm is compiled to js -->
<script src="elm.js"></script>
<script>
Elm.Main.init({ node: document.getElementById("app") });
</script>
</body>
</html>
84 changes: 84 additions & 0 deletions examples/ElmWebApp/frontend/src/Main.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
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
, update = update
, subscriptions = subscriptions
, view = view
}

-- MODEL

-- Model represents the state of our application
type Model
= 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)

-- 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 errorMsg ->
text ("Is the Roc webserver running? I hit an error: " ++ errorMsg)
Loading ->
text "Loading..."
Success body ->
div [ id "app" ]
[
h1 [] [ text body ]
]

-- HTTP

-- 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
5 changes: 3 additions & 2 deletions examples/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ You can find the source code for all of these at [github.com/roc-lang/examples](
- [Parser](/Parser/README.html)
- [Arithmetic](/Arithmetic/README.html)
- [Looping Tasks](/TaskLoop/README.html)
- [Record Builder](/RecordBuilder/README.html)
- [Encoding & Decoding Abilities](/EncodeDecode/README.html)
- [Least Squares](/LeastSquares/README.html)
- [Towers of Hanoi](/TowersOfHanoi/README.html)
- [Record Builder](/RecordBuilder/README.html)
- [Multi-line Comments](/MultiLineComments/README.html)
- [Go Platform](/GoPlatform/README.html)
- [.NET Platform](/DotNetPlatform/README.html)
- [Encoding & Decoding Abilities](/EncodeDecode/README.html)
- [Elm Web App](/ElmWebApp/README.html)

## External examples

Expand Down
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
perl # for ci/update_basic_cli_url.sh
go # for GoPlatform example
dotnet-sdk_8 # for DotnetPlatform example
elmPackages.elm # for ElmWebApp example
] ++ linuxInputs;

# nix does not store libs in /usr/lib or /lib
Expand Down
Loading