Skip to content

Repository files navigation

Presto

Presto is an Elixir library for creating Elm-like or React-like single page applications (SPAs) completely in Elixir.

It was presented at ElixirConfEU 2018. You can find the slides here.

Installation

Add this to mix.exs:

{:presto, "~> 0.1.2"}

Philosophy

Web development is too complciated. Front-ends, back-ends, multiple languages, markup, it's all too complicated. Things can be simpler.

We want:

  1. the feel and data model (mostly) of React.
  2. views to be a projection of the data.
  3. the simplicity of Elm's model/update/view functions.
  4. all of this in Elixir.

Model -> Update -> View
State -> Message -> Response

This is a GenServer.

How It Works

  1. A GenServer keeps the state for the user. It’s all on the server.
  2. For a single component root, there is one GenServer that comes to life when it gets a message.
  3. It receives DOM events from the browser over a channel, updating the GenServer state.
  4. UI updates are returned via the channel.

The GenServers are managed by a DynamicSupervisor.

Components are scoped to a visitor_id, which is unique to each browser.

Add Presto to mix.exs

mix.exs

defpdepsdo[...{:presto,"~> 0.1.2"},...]end

Create a component

lib/presto/single_counter.ex

defmodulePrestoDemoWeb.Presto.SingleCounterdousePresto.ComponentuseTaggart.HTMLrequireLogger@implPresto.Componentdefinitial_model(_model)do0end@implPresto.Componentdefupdate(message,model)docasemessagedo%{"event"=>"click","id"=>"inc"}->model+1%{"event"=>"click","id"=>"dec"}->model-1endend@implPresto.Componentdefrender(model)dodivdo"Counter is: #{inspect(model)}"button(id: "inc",class: "presto-click")do"More"endbutton(id: "dec",class: "presto-click")do"Less"endendendend

Add the component to a view

index.html.eex

<%=Presto.render(Presto.component(PrestoDemoWeb.Presto.SingleCounter,assigns[:visitor_id]))%>

Wire up the javascript

assets/package.json

 ...
"dependencies": {
...
"presto": "file:../deps/presto"},
...

app.js

import{Presto}from"presto"importunpolyfrom"unpoly/dist/unpoly.js"letpresto=newPresto(channel,up);

Wire Up A Presto Channel

user_socker.ex

defmodulePrestoDemoWeb.UserSocketdousePhoenix.Socketchannel("presto:*",PrestoDemoWeb.CounterChannel)defconnect(%{"token"=>token}=_params,socket)docasePrestoDemoWeb.Session.decode_socket_token(token)do{:ok,visitor_id}->{:ok,assign(socket,:visitor_id,visitor_id)}{:error,_reason}->:errorendend...

component_channel.ex

defmodulePrestoDemoWeb.CounterChanneldo...defhandle_in("presto",payload,socket)do%{visitor_id: visitor_id}=socket.assigns# send event to presto component{:ok,dispatch}=Presto.dispatch(PrestoDemoWeb.Presto.SingleCounter,visitor_id,payload)casedispatchdo[]->nil_->push(socket,"presto",dispatch)end{:reply,{:ok,payload},socket}end...end

Setup user_token and visitor_id plugs

router.ex

pipeline:browserdoplug(:accepts,["html"])plug(:fetch_session)plug(:fetch_flash)plug(:protect_from_forgery)plug(:put_secure_browser_headers)plug(PrestoDemoWeb.Plugs.VisitorIdPlug)plug(PrestoDemoWeb.Plugs.UserTokenPlug)end

user_token_plug.ex

defmodulePrestoDemoWeb.Plugs.UserTokenPlugdoimportPlug.Conndefinit(default),do: defaultdefcall(conn,_default)doifvisitor_id=conn.assigns[:visitor_id]douser_token=PrestoDemoWeb.Session.encode_socket_token(visitor_id)assign(conn,:user_token,user_token)elseconnendendend

visitor_id_plug.ex

defmodulePrestoDemoWeb.Plugs.VisitorIdPlugdoimportPlug.Conn@key:visitor_iddefinit(default),do: defaultdefcall(conn,_default)dovisitor_id=get_session(conn,@key)ifvisitor_iddoassign(conn,@key,visitor_id)elsevisitor_id=Base.encode64(:crypto.strong_rand_bytes(32))conn|>put_session(@key,visitor_id)|>assign(@key,visitor_id)endendend

Testing

Testing is easy. It’s just a GenServer. Spin them up, update, test the response. Done.

Growing Your Application

Use the language. Growing your app is very simple with this approach. If your render() method gets too big, you just split it up in to helpers and modules and whatnot. If your update() method gets too big, you just split it up in to helpers and modules and whatnot.

Demos

Simple Counter

Here is the code for a simple counter demo

PrestoChange.io

This is a real application using Presto.

The code is here.

This is running on the West Coast of the USA:

www.prestochange.io

This is running in Central Europe:

eu.prestochange.io

About

Elixir-based SPA sites without the SPA

Resources

Stars

10 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages