mirror of
https://codeberg.org/setop/elm-scripting
synced 2025-11-08 21:49:57 +00:00
first working implementation
This commit is contained in:
1
.tool-versions
Normal file
1
.tool-versions
Normal file
@@ -0,0 +1 @@
|
|||||||
|
elm 0.19.1
|
||||||
51
README.md
Normal file
51
README.md
Normal file
@@ -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
|
||||||
|
<p>
|
||||||
|
Hello World!
|
||||||
|
</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
# 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. :)
|
||||||
17
build.sh
Executable file
17
build.sh
Executable file
@@ -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}
|
||||||
58
dom.js
Normal file
58
dom.js
Normal file
@@ -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("</"+this.tagName+">");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
Reference in New Issue
Block a user