commit 34aaeaccc148820dec7fbbbbae08665050b4d40d Author: setop Date: Fri Sep 26 00:15:36 2025 +0200 first working implementation diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..b825aa9 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +elm 0.19.1 diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3378b4 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +A tool to generate HTML code from Elm source in the terminal using [QuickJS](https://bellard.org/quickjs/). + +# Design + +QuickJS (Qjs) is a [JavaScript runtime](https://en.wikipedia.org/wiki/List_of_JavaScript_engines), similar to V8 or SpiderMonkey, but lighter and faster. + +As any runtime, Qjs can interpret JavaScript code, but it is not a web browser. It has no concept of an HTML document. + +To bridge this gap, we add a minimal [DOM](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) implementation. + +Next, we concatenate this with the Elm JavaScript output and an app launcher snippet, then ask Qjs to interpret all of it. + +## Limitations + +* No event loop +* Hence, no [TEA](https://guide.elm-lang.org/architecture/); the `main` function must return a static view +* The Elm app module must be called "Main" +* Nodes can only have one parent (this should always be the case) + +# Usage + +1. Clone this repository and navigate into your local copy +2. Run `elm init`. +3. Create `src/Main.elm` with the following content: + +```elm +module Main exposing(main) + +import Html exposing (p, text) + +main = p [] [text "Hello World!"] +``` + +4. Run `./build.sh`; this generates the corresponding HTML code: + +```html +

+Hello World! +

+``` + +# Prior Work + +There are more complete tools for generating static sites with Elm: + +* [elm-pages](https://elm-pages.com/) +* [elmstatic](https://korban.net/elm/elmstatic) +* [elm-starter](https://github.com/lucamug/elm-starter) +* [siteelm](https://github.com/nikueater/siteelm) + +You should probably consider using one of them instead of this one. :) diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..ef8fab8 --- /dev/null +++ b/build.sh @@ -0,0 +1,17 @@ +#!/bin/sh -eu + +w1="$(mktemp out_$$_1_XXXX.js)" + +elm make --optimize --output=${w1} src/Main.elm 1>&2 + +w2="$(mktemp out_$$_XXXX.js)" + +cat dom.js ${w1} launch.js > ${w2} + +rm ${w1} + +qjs --std ${w2} + +# if qjs fails, output file will stay for debugging, else + +rm ${w2} diff --git a/dom.js b/dom.js new file mode 100644 index 0000000..7caac64 --- /dev/null +++ b/dom.js @@ -0,0 +1,58 @@ +function Node(parent, tag) { + this.parentNode = parent; // Node + this.pos = 0; // position in siblings list + this.tagName = tag; + this.text = null; + // attributes are stored directly on the node object + // see https://github.com/elm/virtual-dom/blob/master/src/Elm/Kernel/VirtualDom.js#L511 + this.children = []; // List of Node + this.replaceChild = (newNode, domNode) => { + this.children[domNode.pos-1] = newNode; + newNode.pos = domNode.pos; + }; + if (parent != null) { + this.pos = parent.children.push(this); + } + this.appendChild = (node) => { + node.pos = this.children.push(node); + } + this.dump = (d=0) => { + if (this.text != null) { + print(this.text); + return + } + std.printf("<%s", this.tagName) + // Set.difference(other) is not avalable in qjs + for (a of Object.keys(this)) { + if (!NodeKeys.has(a)) { + std.printf(' %s="%s"', a, this[a]) + } + } + if (this.children.length==0) { + print("/>") + } else { + print(">") + for (c of this.children) { + c.dump(d+1) + } + print(""); + } + } + return this; +} + +var document = new Node(null, "document"); +var target = new Node(document, "target"); +const NodeKeys = new Set(Object.keys(target)); + +// getElementById is only used once, to get node Elm must hook into. +// so we don't need a full fledge implementation with lookup facilities +// just to return the target node +document.getElementById = (_id) => { return target} + +document.createElement = (tag) => new Node(null, tag); +document.createTextNode = (text) => { t = new Node(null, "#text" ); t.text = text; return t } + +try { + +// here will come the Elm app code diff --git a/launch.js b/launch.js new file mode 100644 index 0000000..466b46a --- /dev/null +++ b/launch.js @@ -0,0 +1,13 @@ + + + // above is the DOM implementation + // and the Elm app code + + // here we lanch the app + var app = Elm.Main.init({ node: document.getElementById("elm") }); + + document.children[0].dump(); + +} catch(e) { + throw e; +}