pzavolinsky / elmx

A tiny precompiler that takes an Elm program with embedded HTML and desugars the HTML into elm-html syntax. Elmx is to Elm what React's JSX is to Javascript
MIT License
351 stars 11 forks source link

elmx

elmx is to Elm what React's JSX is to Javascript. That is, Elmx is a tiny precompiler that takes an Elm program with embedded HTML and desugars the HTML into elm-html syntax.

Atom integration

apm install language-elmx

If you also have the Elm Atom packages installed you will get additional functionality in your .elmx files:

See language-elmx for more details.

Emacs integration

Sorry but currently there is no Emacs integration for elmx. You could check the Gulp integration example for an alternative workflow that runs elmx independently of your editor.

On the flip side, if you are an Emacs fan then probably you are a hacker as well and could help with the integration. If you are up for it, check the TextMate grammar file for elmx. Also maybe you can hack (ehm, I mean compose) the existing linter and auto-complete Elm plugins like I did for the language-elm Atom package.

If you want to contribute with this or any other elmx integration let me know in an issue and I'll put the link here.

Webpack integration

Use elmx-webpack-preloader.

Gulp integration

This integration uses Gulp to monitor changes in .elmx files and pipes the file contents through the elmx parser to produce .elm files.

See the full integration example in: Gulp integration example

Library installation

npm install --save-dev elmx

Then:

const elmxParser = require('elmx');

const elmSource = elmxParser(elmxSource);

Syntax

In the same spirit of JSX, elmx syntax allows HTML tags embedded in the Elm code and uses { and } to interpolate Elm code into the HTML.

Check the live cheatsheet to play around with elmx directly in the browser

For example:

import Html
import Html.Attributes

main : Html.Html msg
main = <span>Hello, elmx!</span>

Translates to:

import Html
import Html.Attributes

main : Html.Html msg
main = Html.node "span" [] [Html.text "Hello, elmx!"]

Note that for elmx to work you need to import both Html and Html.Attributes.

Attributes

Attributes can be specified with:

showError : Html msg
showError = <span class="error">Oops!</span>

Or:

showError : String -> Html msg
showError errorClass = <span class={errorClass}>Oops!</span>

Or:

showError : Html.Attribute msg -> Html msg
showError errorAttr = <span {errorAttr}>Oops!</span>

Or:

import Html.Events exposing (onInput)
-- note the import above!

myInput : (String -> msg) -> Html msg
myInput tagFn = <input {onInput tagFn}/>

Or, for any of the following, onClick, onDoubleClick, onMouseDown, onMouseUp, onMouseEnter, onMouseLeave, onMouseOver, onMouseOut, onInput, onCheck, onSubmit, onSubmitOptions, onBlur, onFocus:

import Html.Events
-- note the import above!

myInput : (String -> msg) -> Html msg
myInput tagFn = <input onInput={tagFn} />

myButton : Html msg
myButton = <button onClick={Clicked}>Click Me!</button>

Or:

showError : List (Html.Attribute msg) -> Html msg
showError errorAttrs = <span {:errorAttrs}>Oops!</span>

(note the : in {:errorAttrs})

Children

Elm expressions can be interpolated into HTML with:

addBorder : Html msg -> Html msg
addBorder s = <div class="border">{s}</div>

Unlike JSX, elmx requires a few extensions to accommodate for Elm's types, namely:

Elm strings can be interpolated with:

showMessage : String -> Html msg
showMessage s = <span>{=s}</span>

(note the = in {=s})

Elm lists can be interpolated with:

makeList : List (Html msg) -> Html msg
makeList lis = <ul>{:lis}</ul>

(note the : in {:lis})

Keyed children

Keyed elements are supported in two flavours: explicit and implicit. Explicit keyed elements require that you specify the keyed attribute in the list container (e.g. 'ul', 'ol', etc.), for example:

import Html.Keyed -- remember this!

keyedList : List (String, Html.Html msg) -> Html.Html msg
keyedList items = <ul keyed>{:items}</ul>

(note the keyed attribute in <ul keyed>, also note the Html.Keyed import)

In some limited cases, elmx can deduce that an element is keyed because it contains at least one child with a key attribute, when this happens, the keyed attribute becomes optional:

import Html.Keyed -- remember this!

keyedList : List (String, Html.Html msg) -> Html.Html msg
keyedList items =
   <ul>
     <li key="i1">If one child has a key the parent is keyed</li>
     {:items}
   </ul>

In both cases (explicit and implicit), whenever elmx finds a tag with a key attribute, it will generate a keyed tuple instead of the normal element. That is:

<li key={toString id}>{=name}</li>

Translates to:

(toString id, Html.node "li" [] [Html.text name])

Summary

All together:

import Html
import Html.Attributes exposing (title, align)
import List exposing (map)

main : Html.Html msg
main =
  let
    hello = <h1>Hello</h1>
    name = "Homer"
    lis = map (\s -> <li>{=s}</li>) [ "Bart", "Lisa", "Maggie" ]
    commonAttrs =
      [ title "common title"
      , align "left"
      ]
  in
    <div class="container" {:commonAttrs}>
      {hello}
      {=name} is the father of:
      <ul>{:lis}</ul>
    </div>

Considerations, cool stuff, limitations and workarounds