Skip to content

Latest commit

 

History

History
787 lines (710 loc) · 28 KB

to-infinity-and-beyond.org

File metadata and controls

787 lines (710 loc) · 28 KB

Javascripting, With Style

Life in a post-js world

Why ALT-JS

Levels of Alt

  • Industrial Strength
  • DSLs
  • Experiments

Industrial Strength Languages

  • Coffeescript
  • Dart
  • Typescript
  • Clojurescript

Coffeescript

  • http://coffeescript.org
  • Macro language for js
  • Dynamic, functional/OO hybrid
  • Makes js a little nicer by eliminating boilerplate
  • Seamless js interop
  • Compiled

   arr = [
     1
     2
     3
     4
     5
     6
   ]
   count = (num) -> "#{num}! #{num} wonderful stars! Ah, ah, ah."
   (count stars for stars in arr when star % 2 is 0).join ""
   

Dart

  • https://www.dartlang.org/
  • Optionally typed
  • Java/JS syntax hybrid
  • Modular
  • HOFs and Collections
  • Annotations
  • Interpreted and Compiled

import 'dart:math' show Random;        // Import a class from a library.

void main() {                          // The app starts executing here.
  print(new Die(n: 12).roll());       // Print a new object's value. Chain method calls.
}

class Die {                            // Define a class.
  static Random shaker = new Random(); // Define a class variable.
  int sides, value;                    // Define instance variables.

  String toString() => '$value';      // Define a method using shorthand syntax.

  Die({int n: 6}) {                   // Define a constructor.
    if (4 <= n && n <= 20) {
      sides = n;
    } else {
      throw new ArgumentError(/* */);  // Support for errors and exceptions.
    }
  }
  int roll() {                         // Define an instance method.
    return value = shaker.nextInt(sides); // Get a random number.
  }
}

Typescript


export class Greeter
{
    element: HTMLElement;
    span: HTMLElement;
    timerToken: number;

    constructor (element: HTMLElement)
    {
        this.element = element;
        this.element.innerText += "The time is: ";
        this.span = document.createElement('span');
        this.element.appendChild(this.span);
        this.span.innerText = new Date().toUTCString();
    }

    start()
    {
        this.timerToken = setInterval(() => this.span.innerText = new Date().toUTCString(), 500);
    }

    stop()
    {
        clearTimeout(this.timerToken);
    }
}

Clojurescript

  • Lisp variant
  • JS Interop
  • Based on Clojure - http://clojure.org/
  • Functional
  • Nice async libraries
  • Optimizing compiler
  • Compiled

(ns dom.test
  (:require [clojure.browser.event :as event]
            [clojure.browser.dom   :as dom]))

(defn log [& args]
  (.log js/console (apply pr-str args)))

(defn log-obj [obj]
  (.log js/console obj))

(defn log-listener-count []
  (log "listener count: " (event/total-listener-count)))

(def source      (dom/get-element "source"))
(def destination (dom/get-element "destination"))

(dom/append source
            (dom/element "Testing me ")
            (dom/element "out!"))

(def success-count (atom 0))

(log-listener-count)

(event/listen source
              :click
              (fn [e]
                (let [i (swap! success-count inc)
                      e (dom/element :li
                                     {:id "testing"
                                      :class "test me out please"}
                                     "It worked!")]
                  (log-obj e)
                  (log i)
                  (dom/append destination
                              e))))

(log-obj (dom/element "Text node"))
(log-obj (dom/element :li))
(log-obj (dom/element :li {:class "foo"}))
(log-obj (dom/element :li {:class "bar"} "text node"))
(log-obj (dom/element [:ul [:li :li :li]]))
(log-obj (dom/element :ul [:li :li :li]))
(log-obj (dom/element :li {} [:ul {} [:li :li :li]]))
(log-obj (dom/element [:li {:class "baz"} [:li {:class "quux"}]]))

(log-obj source)
(log-listener-count)

DSLs

  • Templating – Handlebars, etc.
  • Frameworks – Opa and Angular

Handlebars

  • http://handlebarsjs.com/
  • Templating language that compiles to js functions
  • Complete grammar
  • Extensible via js functions called Helpers

By {{fullName author}}

{{body}}

Comments

{{#each comments}}

By {{fullName author}}

{{body}}
{{/each}}

Opa

  • Typesafe, Inferred
  • Dual runtime – server & client
  • Rich
  • Functional/OO Hybrid

function start(url) {
  match (url) {
    case {path: {nil} ... } :
      { display("Hello") };
    case {path: path ...} :
      { display(String.concat("::", path)) };
  }
}

Experiments

  • Completely different languages than JS
  • ASMjs
  • GHCjs
  • Scal

ASMjs

  • http://asmjs.org/spec/latest/
  • Type-safe, type annotated optimizable subset of js
  • REALLY FAST
  • Intended to be a better compile target for ALT-JS langs (C++ via llvm for example)

function DiagModule(stdlib) {
    "use asm";

    var sqrt = stdlib.Math.sqrt;

    function square(x) {
        x = +x;
        return +(x*x);
    }

    function diag(x, y) {
        x = +x;
        y = +y;
        return +sqrt(square(x) + square(y));
    }

    return { diag: diag };
}
var fast = DiagModule(window);     // produces AOT-compiled version
console.log(fast.diag(3, 4));        

GHC Js

  • Haskell compiled to Javascript
  • Yes, Haskell
  • JS interop using FFI (Foreign Function Interface)
  • Just write haskell

{-# LANGUAGE CPP, TemplateHaskell, QuasiQuotes, ScopedTypeVariables, NoMonomorphismRestriction, Rank2Types, DeriveDataTypeable #-}
module Main (
    main, lazyLoad_freecell
) where

import Prelude hiding ((!!))
import Control.Monad.Trans ( liftIO )
import System.IO (stderr, hPutStrLn, stdout, hFlush)
import GHCJS.DOM (runWebGUI, postGUISync, postGUIAsync, webViewGetDomDocument)
import GHCJS.DOM.Document
       (documentCreateElement, documentGetElementById, documentGetBody)
import GHCJS.DOM.HTMLElement
       (htmlElementSetInnerText, htmlElementSetInnerHTML)
import Data.Text.Lazy (Text, unpack)
import Text.Blaze.Html.Renderer.Text (renderHtml)
import Text.Hamlet (shamlet)
import Text.Blaze.Html (Html)
import GHCJS.DOM.Types
       (Node(..), castToHTMLElement, castToHTMLDivElement,
        castToHTMLInputElement)
import Control.Applicative ((<$>))
import GHCJS.DOM.Element
       (elementGetStyle, elementSetAttribute, elementOnclick,
        elementOnkeypress, elementOnkeyup, elementOnkeydown, elementFocus)
import GHCJS.DOM.HTMLInputElement
       (htmlInputElementGetValue)
import Control.Concurrent
       (tryTakeMVar, takeMVar, threadDelay, putMVar, forkIO, newEmptyMVar, forkIOWithUnmask)
import Control.Monad (when, forever)
import GHCJS.DOM.EventM
       (mouseShiftKey, mouseCtrlKey)
import GHCJS.DOM.Node
       (nodeInsertBefore, nodeAppendChild)
import GHCJS.DOM.CSSStyleDeclaration
       (cssStyleDeclarationSetProperty)
import Language.Javascript.JSaddle
       (strToText, valToStr, JSNull(..), deRefVal, valToObject, js, JSF(..), js1, js4, jsg,
        valToNumber, (!), (!!), (#), (<#), global, eval, fun, val, array, new, runJSaddle_,
        valToText, MakeValueRef(..), JSValue(..), call, JSM(..), JSValueRef)
import Control.Monad.Reader (ReaderT(..))
import qualified Data.Text as T (unpack, pack)
import FRP.Sodium
import Engine
import Freecell -- What could this be for ? :-)
#ifdef jmacro_MIN_VERSION
import Language.Javascript.JSC
       (evalJME, evalJM)
import Language.Javascript.JMacro
       (jmacroE, jLam, jmacro, renderJs, ToJExpr(..), JStat(..))
#endif
import Language.Haskell.TH (Exp(..), Lit(..))
import System.IO.Unsafe (unsafePerformIO)
import Control.Lens ((^.))
import Control.Exception (throwTo, catch, SomeException, Exception)
import Data.Typeable (Typeable)

data NewValueException = NewValueException deriving (Show, Typeable)

instance Exception NewValueException

main = do
  -- Running a GUI creates a WebKitGtk window in native code,
  -- but just returns the browser window when compiled to JavaScript
  runWebGUI $ \ webView -> do
    -- WebKitGtk provides the normal W3C DOM functions
    Just doc <- webViewGetDomDocument webView
    Just body <- documentGetBody doc

    -- Lets use some Hamlet to replace HTerm with some HTML
    Just div <- fmap castToHTMLDivElement <$> documentCreateElement doc "div"
    htmlElementSetInnerHTML div . unpack $ renderHtml [shamlet|$newline always
    ....

Scalajs

  • http://www.scala-js.org/
  • Scala in javascript!
  • Good interop with js
  • Write Scala, call js using js.Dynamic
  • Type-safe, rich

package example

import org.scalajs.dom
import scalatags.all._
import scalatags.Tags2.section
import scalatags.ExtendedString
import rx._
import rx.core.Propagator
import scala.scalajs.js.annotation.JSExport


case class Task(txt: Var[String], done: Var[Boolean])
@JSExport
object ScalaJSExample {

  import Framework._

  val editing = Var[Option[Task]](None)

  val tasks = Var(
    Seq(
      Task(Var("TodoMVC Task A"), Var(true)),
      Task(Var("TodoMVC Task B"), Var(false)),
      Task(Var("TodoMVC Task C"), Var(false))
    )
  )

  val filters: Map[String, Task => Boolean] = Map(
    ("All", t => true),
    ("Active", !_.done()),
    ("Completed", _.done())
  )

  val filter = Var("All")

  val inputBox = new DomRef[dom.HTMLInputElement](input(
    id:="new-todo",
    placeholder:="What needs to be done?",
    autofocus:=true
  ))

  @JSExport
  def main(): Unit = {
    dom.document.body.innerHTML = Seq(
      section(id:="todoapp")(
        header(id:="header")(
          h1("todos"),
          form(
            inputBox,
            onsubmit <~ {
              tasks() = Task(Var(inputBox.value), Var(false)) +: tasks()
              inputBox.value = ""
            }
          )
        ),
        section(id:="main")(
          input(
            id:="toggle-all",
            `type`:="checkbox",
            cursor:="pointer",
            onclick <~ {
              val target = tasks().exists(_.done() == false)
              Var.set(tasks().map(_.done -> target): _*)
            }
          ),
          label(`for`:="toggle-all", "Mark all as complete"),
          Rx{
            dom.console.log("A")
            ul(id:="todo-list")(
              for(task <- tasks() if filters(filter())(task)) yield {
                dom.console.log("B", task.txt())
                val inputRef = new DomRef[dom.HTMLInputElement](
                  input(`class`:="edit", value:=task.txt())
                )
                li(if(task.done()) `class`:="completed" else (), if(editing() == Some(task)) `class`:="editing" else ())(
                  div(`class`:="view")(
                    "ondblclick".attr <~ {editing() = Some(task)},
                    input(
                      `class`:="toggle",
                      `type`:="checkbox",
                      cursor:="pointer",
                      onchange <~ {task.done() = !task.done()},
                      if(task.done()) checked:=true else ()
                    ),
                    label(task.txt()),
                    button(
                      `class`:="destroy",
                      cursor:="pointer",
                      onclick <~ (tasks() = tasks().filter(_ != task))
                    )
                  ),
                  form(
                    onsubmit <~ {
                      task.txt() = inputRef.value
                      editing() = None
                    },
                    inputRef
                  )
                )
              }
            )
          },
          footer(id:="footer")(
            span(id:="todo-count")(strong(Rx(tasks().count(!_.done()).toString)), " item left"),
            Rx{
              ul(id:="filters")(
                for ((name, pred) <- filters.toSeq) yield {
                  li(a(
                    if(name == filter()) `class`:="selected" else (),
                    name,
                    href:="#",
                    onclick <~ (filter() = name)
                  ))
                }
              )
            },
            button(
              id:="clear-completed",
              onclick <~ {tasks() = tasks().filter(!_.done())},
              "Clear completed (", Rx(tasks().count(_.done()).toString), ")"
            )
          )
        ),
        footer(id:="info")(
          p("Double-click to edit a todo"),
          p(a(href:="https://github.com/lihaoyi/workbench-example-app/blob/todomvc/src/main/scala/example/ScalaJSExample.scala")("Source Code")),
          p("Created by ", a(href:="http://github.com/lihaoyi")("Li Haoyi"))
        )
      )
    ).mkString
  }
}

Javascript Can run Anything

  • JS or ASMJS can be the basis for any language you like
  • No more WAT
  • Have fun