Преглед изворни кода

feat: setup a typescript build

Jonathan Kelley пре 3 година
родитељ
комит
5bf6c96f9f

+ 3 - 2
packages/desktop/src/index.html

@@ -12,7 +12,8 @@
     <div id="main">
     </div>
 </body>
-<script type="text/javascript" src="index.js">
-</script>
+
+<script>var exports = {};</script>
+<script type="text/javascript" src="index.js" type="module"></script>
 
 </html>

+ 0 - 446
packages/desktop/src/index.js

@@ -1,446 +0,0 @@
-const bool_attrs = {
-  allowfullscreen: true,
-  allowpaymentrequest: true,
-  async: true,
-  autofocus: true,
-  autoplay: true,
-  checked: true,
-  controls: true,
-  default: true,
-  defer: true,
-  disabled: true,
-  formnovalidate: true,
-  hidden: true,
-  ismap: true,
-  itemscope: true,
-  loop: true,
-  multiple: true,
-  muted: true,
-  nomodule: true,
-  novalidate: true,
-  open: true,
-  playsinline: true,
-  readonly: true,
-  required: true,
-  reversed: true,
-  selected: true,
-  truespeed: true,
-};
-
-function serialize_event(event) {
-  switch (event.type) {
-    case "copy":
-    case "cut":
-    case "past":
-      return {};
-
-    case "compositionend":
-    case "compositionstart":
-    case "compositionupdate":
-      return {
-        data: event.data,
-      };
-
-    case "keydown":
-    case "keypress":
-    case "keyup":
-      return {
-        char_code: event.charCode,
-        key: event.key,
-        alt_key: event.altKey,
-        ctrl_key: event.ctrlKey,
-        meta_key: event.metaKey,
-        key_code: event.keyCode,
-        shift_key: event.shiftKey,
-        locale: "locale",
-        location: event.location,
-        repeat: event.repeat,
-        which: event.which,
-        // locale: event.locale,
-      };
-
-    case "focus":
-    case "blur":
-      return {};
-
-    case "change":
-      let target = event.target;
-      let value;
-      if (target.type === "checkbox" || target.type === "radio") {
-        value = target.checked ? "true" : "false";
-      } else {
-        value = target.value ?? target.textContent;
-      }
-
-      return {
-        value: value,
-      };
-
-    case "input":
-    case "invalid":
-    case "reset":
-    case "submit": {
-      let target = event.target;
-      let value = target.value ?? target.textContent;
-
-      if (target.type == "checkbox") {
-        value = target.checked ? "true" : "false";
-      }
-
-      return {
-        value: value,
-      };
-    }
-
-    case "click":
-    case "contextmenu":
-    case "doubleclick":
-    case "drag":
-    case "dragend":
-    case "dragenter":
-    case "dragexit":
-    case "dragleave":
-    case "dragover":
-    case "dragstart":
-    case "drop":
-    case "mousedown":
-    case "mouseenter":
-    case "mouseleave":
-    case "mousemove":
-    case "mouseout":
-    case "mouseover":
-    case "mouseup":
-      return {
-        alt_key: event.altKey,
-        button: event.button,
-        buttons: event.buttons,
-        client_x: event.clientX,
-        client_y: event.clientY,
-        ctrl_key: event.ctrlKey,
-        meta_key: event.metaKey,
-        page_x: event.pageX,
-        page_y: event.pageY,
-        screen_x: event.screenX,
-        screen_y: event.screenY,
-        shift_key: event.shiftKey,
-      };
-
-    case "pointerdown":
-    case "pointermove":
-    case "pointerup":
-    case "pointercancel":
-    case "gotpointercapture":
-    case "lostpointercapture":
-    case "pointerenter":
-    case "pointerleave":
-    case "pointerover":
-    case "pointerout":
-      return {
-        alt_key: event.altKey,
-        button: event.button,
-        buttons: event.buttons,
-        client_x: event.clientX,
-        client_y: event.clientY,
-        ctrl_key: event.ctrlKey,
-        meta_key: event.metaKey,
-        page_x: event.pageX,
-        page_y: event.pageY,
-        screen_x: event.screenX,
-        screen_y: event.screenY,
-        shift_key: event.shiftKey,
-        pointer_id: event.pointerId,
-        width: event.width,
-        height: event.height,
-        pressure: event.pressure,
-        tangential_pressure: event.tangentialPressure,
-        tilt_x: event.tiltX,
-        tilt_y: event.tiltY,
-        twist: event.twist,
-        pointer_type: event.pointerType,
-        is_primary: event.isPrimary,
-      };
-
-    case "select":
-      return {};
-
-    case "touchcancel":
-    case "touchend":
-    case "touchmove":
-    case "touchstart":
-      return {
-        alt_key: event.altKey,
-        ctrl_key: event.ctrlKey,
-        meta_key: event.metaKey,
-        shift_key: event.shiftKey,
-
-        // changed_touches: event.changedTouches,
-        // target_touches: event.targetTouches,
-        // touches: event.touches,
-      };
-
-    case "scroll":
-      return {};
-
-    case "wheel":
-      return {
-        delta_x: event.deltaX,
-        delta_y: event.deltaY,
-        delta_z: event.deltaZ,
-        delta_mode: event.deltaMode,
-      };
-
-    case "animationstart":
-    case "animationend":
-    case "animationiteration":
-      return {
-        animation_name: event.animationName,
-        elapsed_time: event.elapsedTime,
-        pseudo_element: event.pseudoElement,
-      };
-
-    case "transitionend":
-      return {
-        property_name: event.propertyName,
-        elapsed_time: event.elapsedTime,
-        pseudo_element: event.pseudoElement,
-      };
-
-    case "abort":
-    case "canplay":
-    case "canplaythrough":
-    case "durationchange":
-    case "emptied":
-    case "encrypted":
-    case "ended":
-    case "error":
-    case "loadeddata":
-    case "loadedmetadata":
-    case "loadstart":
-    case "pause":
-    case "play":
-    case "playing":
-    case "progress":
-    case "ratechange":
-    case "seeked":
-    case "seeking":
-    case "stalled":
-    case "suspend":
-    case "timeupdate":
-    case "volumechange":
-    case "waiting":
-      return {};
-
-    case "toggle":
-      return {};
-
-    default:
-      return {};
-  }
-}
-
-class Interpreter {
-  constructor(root) {
-    this.root = root;
-    this.stack = [root];
-    this.listeners = {
-      onclick: {},
-    };
-    this.lastNodeWasText = false;
-    this.nodes = [root];
-  }
-
-  top() {
-    return this.stack[this.stack.length - 1];
-  }
-
-  pop() {
-    return this.stack.pop();
-  }
-
-  PushRoot(edit) {
-    const id = edit.root;
-    const node = this.nodes[id];
-    this.stack.push(node);
-  }
-
-  AppendChildren(edit) {
-    let root = this.stack[this.stack.length - (1 + edit.many)];
-
-    let to_add = this.stack.splice(this.stack.length - edit.many);
-
-    for (let i = 0; i < edit.many; i++) {
-      root.appendChild(to_add[i]);
-    }
-  }
-
-  ReplaceWith(edit) {
-    let root = this.nodes[edit.root];
-    let els = this.stack.splice(this.stack.length - edit.m);
-
-    root.replaceWith(...els);
-  }
-
-  InsertAfter(edit) {
-    let old = this.nodes[edit.root];
-    let new_nodes = this.stack.splice(this.stack.length - edit.n);
-    old.after(...new_nodes);
-  }
-
-  InsertBefore(edit) {
-    let old = this.nodes[edit.root];
-    let new_nodes = this.stack.splice(this.stack.length - edit.n);
-    old.before(...new_nodes);
-  }
-
-  Remove(edit) {
-    let node = this.nodes[edit.root];
-    if (node !== undefined) {
-      node.remove();
-    }
-  }
-
-  CreateTextNode(edit) {
-    const node = document.createTextNode(edit.text);
-    this.nodes[edit.root] = node;
-    this.stack.push(node);
-  }
-
-  CreateElement(edit) {
-    const tagName = edit.tag;
-    const el = document.createElement(tagName);
-    this.nodes[edit.root] = el;
-    el.setAttribute("dioxus-id", edit.root);
-    this.stack.push(el);
-  }
-
-  CreateElementNs(edit) {
-    let el = document.createElementNS(edit.ns, edit.tag);
-    this.stack.push(el);
-    this.nodes[edit.root] = el;
-  }
-
-  CreatePlaceholder(edit) {
-    let el = document.createElement("pre");
-    el.hidden = true;
-    this.stack.push(el);
-    this.nodes[edit.root] = el;
-  }
-
-  RemoveEventListener(edit) {}
-
-  NewEventListener(edit) {
-    const event_name = edit.event_name;
-    const mounted_node_id = edit.root;
-    const scope = edit.scope;
-
-    const element = this.nodes[edit.root];
-    element.setAttribute(
-      `dioxus-event-${event_name}`,
-      `${scope}.${mounted_node_id}`
-    );
-
-    if (this.listeners[event_name] === undefined) {
-      this.listeners[event_name] = true;
-
-      this.root.addEventListener(event_name, (event) => {
-        const target = event.target;
-        const real_id = target.getAttribute(`dioxus-id`);
-
-        const should_prevent_default = target.getAttribute(
-          `dioxus-prevent-default`
-        );
-
-        let contents = serialize_event(event);
-
-        if (should_prevent_default === `on${event.type}`) {
-          event.preventDefault();
-        }
-
-        if (real_id == null) {
-          return;
-        }
-
-        rpc.call("user_event", {
-          event: event_name,
-          mounted_dom_id: parseInt(real_id),
-          contents: contents,
-        });
-      });
-    }
-  }
-
-  SetText(edit) {
-    this.nodes[edit.root].textContent = edit.text;
-  }
-
-  SetAttribute(edit) {
-    // console.log("setting attr", edit);
-    const name = edit.field;
-    const value = edit.value;
-    const ns = edit.ns;
-    const node = this.nodes[edit.root];
-
-    if (ns == "style") {
-      node.style[name] = value;
-    } else if (ns != null || ns != undefined) {
-      node.setAttributeNS(ns, name, value);
-    } else {
-      switch (name) {
-        case "value":
-          if (value != node.value) {
-            node.value = value;
-          }
-          break;
-        case "checked":
-          node.checked = value === "true";
-          break;
-        case "selected":
-          node.selected = value === "true";
-          break;
-        case "dangerous_inner_html":
-          node.innerHTML = value;
-          break;
-        default:
-          // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
-          if (value == "false" && bool_attrs.hasOwnProperty(name)) {
-            node.removeAttribute(name);
-          } else {
-            node.setAttribute(name, value);
-          }
-      }
-    }
-  }
-  RemoveAttribute(edit) {
-    const name = edit.field;
-    const node = this.nodes[edit.root];
-    node.removeAttribute(name);
-
-    if (name === "value") {
-      node.value = null;
-    }
-    if (name === "checked") {
-      node.checked = false;
-    }
-    if (name === "selected") {
-      node.selected = false;
-    }
-  }
-
-  handleEdits(edits) {
-    this.stack.push(this.root);
-
-    for (let x = 0; x < edits.length; x++) {
-      let edit = edits[x];
-      let f = this[edit.type];
-      f.call(this, edit);
-    }
-  }
-}
-
-function main() {
-  let root = window.document.getElementById("main");
-  window.interpreter = new Interpreter(root);
-  rpc.call("initialize");
-}
-
-main();

+ 1 - 1
packages/desktop/src/lib.rs

@@ -203,7 +203,7 @@ pub fn launch_with_props<P: 'static + Send>(
                         } else if path.trim_end_matches('/') == "index.html/index.js" {
                             wry::http::ResponseBuilder::new()
                                 .mimetype("text/javascript")
-                                .body(include_bytes!("./index.js").to_vec())
+                                .body(include_bytes!("../../jsinterpreter/interpreter.js").to_vec())
                         } else {
                             wry::http::ResponseBuilder::new()
                                 .status(wry::http::status::StatusCode::NOT_FOUND)

+ 461 - 0
packages/jsinterpreter/interpreter.js

@@ -0,0 +1,461 @@
+"use strict";
+exports.__esModule = true;
+function serialize_event(event) {
+    var _a, _b;
+    switch (event.type) {
+        case "copy":
+        case "cut":
+        case "past":
+            return {};
+        case "compositionend":
+        case "compositionstart":
+        case "compositionupdate":
+            var data = event.data;
+            return {
+                data: data
+            };
+        case "keydown":
+        case "keypress":
+        case "keyup":
+            var _c = event, charCode = _c.charCode, key = _c.key, altKey = _c.altKey, ctrlKey = _c.ctrlKey, metaKey = _c.metaKey, keyCode = _c.keyCode, shiftKey = _c.shiftKey, location_1 = _c.location, repeat = _c.repeat, which = _c.which;
+            return {
+                char_code: charCode,
+                key: key,
+                alt_key: altKey,
+                ctrl_key: ctrlKey,
+                meta_key: metaKey,
+                key_code: keyCode,
+                shift_key: shiftKey,
+                location: location_1,
+                repeat: repeat,
+                which: which,
+                locale: "locale"
+            };
+        case "focus":
+        case "blur":
+            return {};
+        case "change":
+            var target = event.target;
+            var value = void 0;
+            if (target.type === "checkbox" || target.type === "radio") {
+                value = target.checked ? "true" : "false";
+            }
+            else {
+                value = (_a = target.value) !== null && _a !== void 0 ? _a : target.textContent;
+            }
+            return {
+                value: value
+            };
+        case "input":
+        case "invalid":
+        case "reset":
+        case "submit": {
+            var target_1 = event.target;
+            var value_1 = (_b = target_1.value) !== null && _b !== void 0 ? _b : target_1.textContent;
+            if (target_1.type == "checkbox") {
+                value_1 = target_1.checked ? "true" : "false";
+            }
+            return {
+                value: value_1
+            };
+        }
+        case "click":
+        case "contextmenu":
+        case "doubleclick":
+        case "drag":
+        case "dragend":
+        case "dragenter":
+        case "dragexit":
+        case "dragleave":
+        case "dragover":
+        case "dragstart":
+        case "drop":
+        case "mousedown":
+        case "mouseenter":
+        case "mouseleave":
+        case "mousemove":
+        case "mouseout":
+        case "mouseover":
+        case "mouseup": {
+            var _d = event, altKey_1 = _d.altKey, button = _d.button, buttons = _d.buttons, clientX = _d.clientX, clientY = _d.clientY, ctrlKey_1 = _d.ctrlKey, metaKey_1 = _d.metaKey, pageX = _d.pageX, pageY = _d.pageY, screenX_1 = _d.screenX, screenY_1 = _d.screenY, shiftKey_1 = _d.shiftKey;
+            return {
+                alt_key: altKey_1,
+                button: button,
+                buttons: buttons,
+                client_x: clientX,
+                client_y: clientY,
+                ctrl_key: ctrlKey_1,
+                meta_key: metaKey_1,
+                page_x: pageX,
+                page_y: pageY,
+                screen_x: screenX_1,
+                screen_y: screenY_1,
+                shift_key: shiftKey_1
+            };
+        }
+        case "pointerdown":
+        case "pointermove":
+        case "pointerup":
+        case "pointercancel":
+        case "gotpointercapture":
+        case "lostpointercapture":
+        case "pointerenter":
+        case "pointerleave":
+        case "pointerover":
+        case "pointerout": {
+            var _e = event, altKey_2 = _e.altKey, button = _e.button, buttons = _e.buttons, clientX = _e.clientX, clientY = _e.clientY, ctrlKey_2 = _e.ctrlKey, metaKey_2 = _e.metaKey, pageX = _e.pageX, pageY = _e.pageY, screenX_2 = _e.screenX, screenY_2 = _e.screenY, shiftKey_2 = _e.shiftKey, pointerId = _e.pointerId, width = _e.width, height = _e.height, pressure = _e.pressure, tangentialPressure = _e.tangentialPressure, tiltX = _e.tiltX, tiltY = _e.tiltY, twist = _e.twist, pointerType = _e.pointerType, isPrimary = _e.isPrimary;
+            return {
+                alt_key: altKey_2,
+                button: button,
+                buttons: buttons,
+                client_x: clientX,
+                client_y: clientY,
+                ctrl_key: ctrlKey_2,
+                meta_key: metaKey_2,
+                page_x: pageX,
+                page_y: pageY,
+                screen_x: screenX_2,
+                screen_y: screenY_2,
+                shift_key: shiftKey_2,
+                pointer_id: pointerId,
+                width: width,
+                height: height,
+                pressure: pressure,
+                tangential_pressure: tangentialPressure,
+                tilt_x: tiltX,
+                tilt_y: tiltY,
+                twist: twist,
+                pointer_type: pointerType,
+                is_primary: isPrimary
+            };
+        }
+        case "select":
+            return {};
+        case "touchcancel":
+        case "touchend":
+        case "touchmove":
+        case "touchstart": {
+            var _f = event, altKey_3 = _f.altKey, ctrlKey_3 = _f.ctrlKey, metaKey_3 = _f.metaKey, shiftKey_3 = _f.shiftKey;
+            return {
+                // changed_touches: event.changedTouches,
+                // target_touches: event.targetTouches,
+                // touches: event.touches,
+                alt_key: altKey_3,
+                ctrl_key: ctrlKey_3,
+                meta_key: metaKey_3,
+                shift_key: shiftKey_3
+            };
+        }
+        case "scroll":
+            return {};
+        case "wheel": {
+            var _g = event, deltaX = _g.deltaX, deltaY = _g.deltaY, deltaZ = _g.deltaZ, deltaMode = _g.deltaMode;
+            return {
+                delta_x: deltaX,
+                delta_y: deltaY,
+                delta_z: deltaZ,
+                delta_mode: deltaMode
+            };
+        }
+        case "animationstart":
+        case "animationend":
+        case "animationiteration": {
+            var _h = event, animationName = _h.animationName, elapsedTime = _h.elapsedTime, pseudoElement = _h.pseudoElement;
+            return {
+                animation_name: animationName,
+                elapsed_time: elapsedTime,
+                pseudo_element: pseudoElement
+            };
+        }
+        case "transitionend": {
+            var _j = event, propertyName = _j.propertyName, elapsedTime = _j.elapsedTime, pseudoElement = _j.pseudoElement;
+            return {
+                property_name: propertyName,
+                elapsed_time: elapsedTime,
+                pseudo_element: pseudoElement
+            };
+        }
+        case "abort":
+        case "canplay":
+        case "canplaythrough":
+        case "durationchange":
+        case "emptied":
+        case "encrypted":
+        case "ended":
+        case "error":
+        case "loadeddata":
+        case "loadedmetadata":
+        case "loadstart":
+        case "pause":
+        case "play":
+        case "playing":
+        case "progress":
+        case "ratechange":
+        case "seeked":
+        case "seeking":
+        case "stalled":
+        case "suspend":
+        case "timeupdate":
+        case "volumechange":
+        case "waiting":
+            return {};
+        case "toggle":
+            return {};
+        default:
+            return {};
+    }
+}
+var bool_attrs = {
+    allowfullscreen: true,
+    allowpaymentrequest: true,
+    async: true,
+    autofocus: true,
+    autoplay: true,
+    checked: true,
+    controls: true,
+    "default": true,
+    defer: true,
+    disabled: true,
+    formnovalidate: true,
+    hidden: true,
+    ismap: true,
+    itemscope: true,
+    loop: true,
+    multiple: true,
+    muted: true,
+    nomodule: true,
+    novalidate: true,
+    open: true,
+    playsinline: true,
+    readonly: true,
+    required: true,
+    reversed: true,
+    selected: true,
+    truespeed: true
+};
+var Interpreter = /** @class */ (function () {
+    function Interpreter(root) {
+        this.root = root;
+        this.stack = [root];
+        this.listeners = {};
+        this.lastNodeWasText = false;
+        this.nodes = [root];
+    }
+    Interpreter.prototype.top = function () {
+        return this.stack[this.stack.length - 1];
+    };
+    Interpreter.prototype.pop = function () {
+        return this.stack.pop();
+    };
+    Interpreter.prototype.PushRoot = function (edit) {
+        var id = edit.root;
+        var node = this.nodes[id];
+        this.stack.push(node);
+    };
+    Interpreter.prototype.AppendChildren = function (edit) {
+        var root = this.stack[this.stack.length - (1 + edit.many)];
+        var to_add = this.stack.splice(this.stack.length - edit.many);
+        for (var i = 0; i < edit.many; i++) {
+            root.appendChild(to_add[i]);
+        }
+    };
+    Interpreter.prototype.ReplaceWith = function (edit) {
+        var root = this.nodes[edit.root];
+        var els = this.stack.splice(this.stack.length - edit.m);
+        root.replaceWith.apply(root, els);
+    };
+    Interpreter.prototype.InsertAfter = function (edit) {
+        var old = this.nodes[edit.root];
+        var new_nodes = this.stack.splice(this.stack.length - edit.n);
+        old.after.apply(old, new_nodes);
+    };
+    Interpreter.prototype.InsertBefore = function (edit) {
+        var old = this.nodes[edit.root];
+        var new_nodes = this.stack.splice(this.stack.length - edit.n);
+        old.before.apply(old, new_nodes);
+    };
+    Interpreter.prototype.Remove = function (edit) {
+        var node = this.nodes[edit.root];
+        if (node !== undefined) {
+            node.remove();
+        }
+    };
+    Interpreter.prototype.CreateTextNode = function (edit) {
+        // todo: make it so the types are okay
+        var node = document.createTextNode(edit.text);
+        this.nodes[edit.root] = node;
+        this.stack.push(node);
+    };
+    Interpreter.prototype.CreateElement = function (edit) {
+        var el = document.createElement(edit.tag);
+        el.setAttribute("dioxus-id", "".concat(edit.root));
+        this.nodes[edit.root] = el;
+        this.stack.push(el);
+    };
+    Interpreter.prototype.CreateElementNs = function (edit) {
+        var el = document.createElementNS(edit.ns, edit.tag);
+        this.stack.push(el);
+        this.nodes[edit.root] = el;
+    };
+    Interpreter.prototype.CreatePlaceholder = function (edit) {
+        var el = document.createElement("pre");
+        el.hidden = true;
+        this.stack.push(el);
+        this.nodes[edit.root] = el;
+    };
+    Interpreter.prototype.RemoveEventListener = function (edit) { };
+    Interpreter.prototype.NewEventListener = function (edit, handler) {
+        var event_name = edit.event_name;
+        var mounted_node_id = edit.root;
+        var scope = edit.scope;
+        console.log('new event listener', event_name, mounted_node_id, scope);
+        var element = this.nodes[edit.root];
+        element.setAttribute("dioxus-event-".concat(event_name), "".concat(scope, ".").concat(mounted_node_id));
+        if (!this.listeners[event_name]) {
+            this.listeners[event_name] = handler;
+            this.root.addEventListener(event_name, handler);
+        }
+    };
+    Interpreter.prototype.SetText = function (edit) {
+        this.nodes[edit.root].textContent = edit.text;
+    };
+    Interpreter.prototype.SetAttribute = function (edit) {
+        // console.log("setting attr", edit);
+        var name = edit.field;
+        var value = edit.value;
+        var ns = edit.ns;
+        var node = this.nodes[edit.root];
+        if (ns == "style") {
+            // @ts-ignore
+            node.style[name] = value;
+        }
+        else if (ns != null || ns != undefined) {
+            node.setAttributeNS(ns, name, value);
+        }
+        else {
+            switch (name) {
+                case "value":
+                    if (value != node.value) {
+                        node.value = value;
+                    }
+                    break;
+                case "checked":
+                    node.checked = value === "true";
+                    break;
+                case "selected":
+                    node.selected = value === "true";
+                    break;
+                case "dangerous_inner_html":
+                    node.innerHTML = value;
+                    break;
+                default:
+                    // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
+                    if (value == "false" && bool_attrs.hasOwnProperty(name)) {
+                        node.removeAttribute(name);
+                    }
+                    else {
+                        node.setAttribute(name, value);
+                    }
+            }
+        }
+    };
+    Interpreter.prototype.RemoveAttribute = function (edit) {
+        var name = edit.name;
+        var node = this.nodes[edit.root];
+        node.removeAttribute(name);
+        if (name === "value") {
+            node.value = "";
+        }
+        if (name === "checked") {
+            node.checked = false;
+        }
+        if (name === "selected") {
+            node.selected = false;
+        }
+    };
+    Interpreter.prototype.handleEdits = function (edits) {
+        console.log("handling edits ", edits);
+        this.stack.push(this.root);
+        var _loop_1 = function (edit) {
+            switch (edit.type) {
+                case "AppendChildren":
+                    this_1.AppendChildren(edit);
+                    break;
+                case "ReplaceWith":
+                    this_1.ReplaceWith(edit);
+                    break;
+                case "InsertAfter":
+                    this_1.InsertAfter(edit);
+                    break;
+                case "InsertBefore":
+                    this_1.InsertBefore(edit);
+                    break;
+                case "Remove":
+                    this_1.Remove(edit);
+                    break;
+                case "CreateTextNode":
+                    this_1.CreateTextNode(edit);
+                    break;
+                case "CreateElement":
+                    this_1.CreateElement(edit);
+                    break;
+                case "CreateElementNs":
+                    this_1.CreateElementNs(edit);
+                    break;
+                case "CreatePlaceholder":
+                    this_1.CreatePlaceholder(edit);
+                    break;
+                case "RemoveEventListener":
+                    this_1.RemoveEventListener(edit);
+                    break;
+                case "NewEventListener":
+                    // todo: only on desktop should we make our own handler
+                    var handler = function (event) {
+                        var target = event.target;
+                        console.log("event", event);
+                        if (target != null) {
+                            var real_id = target.getAttribute("dioxus-id");
+                            var should_prevent_default = target.getAttribute("dioxus-prevent-default");
+                            var contents = serialize_event(event);
+                            if (should_prevent_default === "on".concat(event.type)) {
+                                event.preventDefault();
+                            }
+                            if (real_id == null) {
+                                return;
+                            }
+                            window.rpc.call("user_event", {
+                                event: edit.event_name,
+                                mounted_dom_id: parseInt(real_id),
+                                contents: contents
+                            });
+                        }
+                    };
+                    this_1.NewEventListener(edit, handler);
+                    break;
+                case "SetText":
+                    this_1.SetText(edit);
+                    break;
+                case "SetAttribute":
+                    this_1.SetAttribute(edit);
+                    break;
+                case "RemoveAttribute":
+                    this_1.RemoveAttribute(edit);
+                    break;
+            }
+        };
+        var this_1 = this;
+        for (var _i = 0, edits_1 = edits; _i < edits_1.length; _i++) {
+            var edit = edits_1[_i];
+            _loop_1(edit);
+        }
+    };
+    return Interpreter;
+}());
+function main() {
+    var root = window.document.getElementById("main");
+    if (root != null) {
+        window.interpreter = new Interpreter(root);
+        window.rpc.call("initialize");
+    }
+}
+main();

+ 636 - 0
packages/jsinterpreter/interpreter.ts

@@ -0,0 +1,636 @@
+
+
+function serialize_event(event: Event) {
+  switch (event.type) {
+    case "copy":
+    case "cut":
+    case "past":
+      return {};
+
+    case "compositionend":
+    case "compositionstart":
+    case "compositionupdate":
+      let { data } = (event as CompositionEvent);
+      return {
+        data,
+      };
+
+    case "keydown":
+    case "keypress":
+    case "keyup":
+      let {
+        charCode,
+        key,
+        altKey,
+        ctrlKey,
+        metaKey,
+        keyCode,
+        shiftKey,
+        location,
+        repeat,
+        which,
+      } = (event as KeyboardEvent);
+
+      return {
+        char_code: charCode,
+        key: key,
+        alt_key: altKey,
+        ctrl_key: ctrlKey,
+        meta_key: metaKey,
+        key_code: keyCode,
+        shift_key: shiftKey,
+        location: location,
+        repeat: repeat,
+        which: which,
+        locale: "locale",
+      };
+
+    case "focus":
+    case "blur":
+      return {};
+
+    case "change":
+      let target = event.target as HTMLInputElement;
+      let value;
+      if (target.type === "checkbox" || target.type === "radio") {
+        value = target.checked ? "true" : "false";
+      } else {
+        value = target.value ?? target.textContent;
+      }
+
+      return {
+        value: value,
+      };
+
+    case "input":
+    case "invalid":
+    case "reset":
+    case "submit": {
+      let target = event.target as HTMLFormElement;
+      let value = target.value ?? target.textContent;
+
+      if (target.type == "checkbox") {
+        value = target.checked ? "true" : "false";
+      }
+
+      return {
+        value: value,
+      };
+    }
+
+    case "click":
+    case "contextmenu":
+    case "doubleclick":
+    case "drag":
+    case "dragend":
+    case "dragenter":
+    case "dragexit":
+    case "dragleave":
+    case "dragover":
+    case "dragstart":
+    case "drop":
+    case "mousedown":
+    case "mouseenter":
+    case "mouseleave":
+    case "mousemove":
+    case "mouseout":
+    case "mouseover":
+    case "mouseup": {
+      const {
+        altKey,
+        button,
+        buttons,
+        clientX,
+        clientY,
+        ctrlKey,
+        metaKey,
+        pageX,
+        pageY,
+        screenX,
+        screenY,
+        shiftKey,
+      } = event as MouseEvent;
+
+      return {
+        alt_key: altKey,
+        button: button,
+        buttons: buttons,
+        client_x: clientX,
+        client_y: clientY,
+        ctrl_key: ctrlKey,
+        meta_key: metaKey,
+        page_x: pageX,
+        page_y: pageY,
+        screen_x: screenX,
+        screen_y: screenY,
+        shift_key: shiftKey,
+      };
+    }
+
+    case "pointerdown":
+    case "pointermove":
+    case "pointerup":
+    case "pointercancel":
+    case "gotpointercapture":
+    case "lostpointercapture":
+    case "pointerenter":
+    case "pointerleave":
+    case "pointerover":
+    case "pointerout": {
+      const {
+        altKey,
+        button,
+        buttons,
+        clientX,
+        clientY,
+        ctrlKey,
+        metaKey,
+        pageX,
+        pageY,
+        screenX,
+        screenY,
+        shiftKey,
+        pointerId,
+        width,
+        height,
+        pressure,
+        tangentialPressure,
+        tiltX,
+        tiltY,
+        twist,
+        pointerType,
+        isPrimary,
+      } = event as PointerEvent;
+      return {
+        alt_key: altKey,
+        button: button,
+        buttons: buttons,
+        client_x: clientX,
+        client_y: clientY,
+        ctrl_key: ctrlKey,
+        meta_key: metaKey,
+        page_x: pageX,
+        page_y: pageY,
+        screen_x: screenX,
+        screen_y: screenY,
+        shift_key: shiftKey,
+        pointer_id: pointerId,
+        width: width,
+        height: height,
+        pressure: pressure,
+        tangential_pressure: tangentialPressure,
+        tilt_x: tiltX,
+        tilt_y: tiltY,
+        twist: twist,
+        pointer_type: pointerType,
+        is_primary: isPrimary,
+      };
+    }
+
+    case "select":
+      return {};
+
+    case "touchcancel":
+    case "touchend":
+    case "touchmove":
+    case "touchstart": {
+      const {
+        altKey,
+        ctrlKey,
+        metaKey,
+        shiftKey,
+      } = event as TouchEvent;
+      return {
+        // changed_touches: event.changedTouches,
+        // target_touches: event.targetTouches,
+        // touches: event.touches,
+        alt_key: altKey,
+        ctrl_key: ctrlKey,
+        meta_key: metaKey,
+        shift_key: shiftKey,
+      };
+    }
+
+    case "scroll":
+      return {};
+    case "wheel": {
+      const {
+        deltaX,
+        deltaY,
+        deltaZ,
+        deltaMode,
+      } = event as WheelEvent;
+      return {
+        delta_x: deltaX,
+        delta_y: deltaY,
+        delta_z: deltaZ,
+        delta_mode: deltaMode,
+      };
+    }
+
+    case "animationstart":
+    case "animationend":
+    case "animationiteration": {
+      const {
+        animationName,
+        elapsedTime,
+        pseudoElement,
+      } = event as AnimationEvent;
+      return {
+        animation_name: animationName,
+        elapsed_time: elapsedTime,
+        pseudo_element: pseudoElement,
+      };
+    }
+
+    case "transitionend": {
+      const {
+        propertyName,
+        elapsedTime,
+        pseudoElement,
+      } = event as TransitionEvent;
+      return {
+        property_name: propertyName,
+        elapsed_time: elapsedTime,
+        pseudo_element: pseudoElement,
+      };
+    }
+
+    case "abort":
+    case "canplay":
+    case "canplaythrough":
+    case "durationchange":
+    case "emptied":
+    case "encrypted":
+    case "ended":
+    case "error":
+    case "loadeddata":
+    case "loadedmetadata":
+    case "loadstart":
+    case "pause":
+    case "play":
+    case "playing":
+    case "progress":
+    case "ratechange":
+    case "seeked":
+    case "seeking":
+    case "stalled":
+    case "suspend":
+    case "timeupdate":
+    case "volumechange":
+    case "waiting":
+      return {};
+
+    case "toggle":
+      return {};
+
+    default:
+      return {};
+  }
+}
+
+const bool_attrs = {
+  allowfullscreen: true,
+  allowpaymentrequest: true,
+  async: true,
+  autofocus: true,
+  autoplay: true,
+  checked: true,
+  controls: true,
+  default: true,
+  defer: true,
+  disabled: true,
+  formnovalidate: true,
+  hidden: true,
+  ismap: true,
+  itemscope: true,
+  loop: true,
+  multiple: true,
+  muted: true,
+  nomodule: true,
+  novalidate: true,
+  open: true,
+  playsinline: true,
+  readonly: true,
+  required: true,
+  reversed: true,
+  selected: true,
+  truespeed: true,
+};
+
+class Interpreter {
+  root: Element;
+  stack: Element[];
+  listeners: { [key: string]: (event: Event) => void };
+  lastNodeWasText: boolean;
+  nodes: Element[];
+
+
+  constructor(root: Element) {
+    this.root = root;
+    this.stack = [root];
+    this.listeners = {
+    };
+    this.lastNodeWasText = false;
+    this.nodes = [root];
+  }
+
+  top() {
+    return this.stack[this.stack.length - 1];
+  }
+
+  pop() {
+    return this.stack.pop();
+  }
+
+  PushRoot(edit: PushRoot) {
+    const id = edit.root;
+    const node = this.nodes[id];
+    this.stack.push(node);
+  }
+
+  AppendChildren(edit: AppendChildren) {
+    let root = this.stack[this.stack.length - (1 + edit.many)];
+
+    let to_add = this.stack.splice(this.stack.length - edit.many);
+
+    for (let i = 0; i < edit.many; i++) {
+      root.appendChild(to_add[i]);
+    }
+  }
+
+  ReplaceWith(edit: ReplaceWith) {
+    let root = this.nodes[edit.root] as Element;
+    let els = this.stack.splice(this.stack.length - edit.m);
+
+    root.replaceWith(...els);
+  }
+
+  InsertAfter(edit: InsertAfter) {
+    let old = this.nodes[edit.root] as Element;
+    let new_nodes = this.stack.splice(this.stack.length - edit.n);
+    old.after(...new_nodes);
+  }
+
+  InsertBefore(edit: InsertBefore) {
+    let old = this.nodes[edit.root] as Element;
+    let new_nodes = this.stack.splice(this.stack.length - edit.n);
+    old.before(...new_nodes);
+  }
+
+  Remove(edit: Remove) {
+    let node = this.nodes[edit.root] as Element;
+    if (node !== undefined) {
+      node.remove();
+    }
+  }
+
+  CreateTextNode(edit: CreateTextNode) {
+    // todo: make it so the types are okay
+    const node = document.createTextNode(edit.text) as any as Element;
+    this.nodes[edit.root] = node;
+    this.stack.push(node);
+  }
+
+  CreateElement(edit: CreateElement) {
+    const el = document.createElement(edit.tag);
+    el.setAttribute("dioxus-id", `${edit.root}`);
+
+    this.nodes[edit.root] = el;
+    this.stack.push(el);
+  }
+
+  CreateElementNs(edit: CreateElementNs) {
+    let el = document.createElementNS(edit.ns, edit.tag);
+    this.stack.push(el);
+    this.nodes[edit.root] = el;
+  }
+
+  CreatePlaceholder(edit: CreatePlaceholder) {
+    let el = document.createElement("pre");
+    el.hidden = true;
+    this.stack.push(el);
+    this.nodes[edit.root] = el;
+  }
+
+  RemoveEventListener(edit: RemoveEventListener) { }
+
+  NewEventListener(edit: NewEventListener, handler: (event: Event) => void) {
+    const event_name = edit.event_name;
+    const mounted_node_id = edit.root;
+    const scope = edit.scope;
+    console.log('new event listener', event_name, mounted_node_id, scope);
+
+    const element = this.nodes[edit.root];
+    element.setAttribute(
+      `dioxus-event-${event_name}`,
+      `${scope}.${mounted_node_id}`
+    );
+
+    if (!this.listeners[event_name]) {
+      this.listeners[event_name] = handler;
+      this.root.addEventListener(event_name, handler);
+    }
+  }
+
+  SetText(edit: SetText) {
+    this.nodes[edit.root].textContent = edit.text;
+  }
+
+  SetAttribute(edit: SetAttribute) {
+    // console.log("setting attr", edit);
+    const name = edit.field;
+    const value = edit.value;
+    const ns = edit.ns;
+    const node = this.nodes[edit.root];
+
+    if (ns == "style") {
+
+      // @ts-ignore
+      (node as HTMLElement).style[name] = value;
+
+    } else if (ns != null || ns != undefined) {
+      node.setAttributeNS(ns, name, value);
+    } else {
+      switch (name) {
+        case "value":
+          if (value != (node as HTMLInputElement).value) {
+            (node as HTMLInputElement).value = value;
+          }
+          break;
+        case "checked":
+          (node as HTMLInputElement).checked = value === "true";
+          break;
+        case "selected":
+          (node as HTMLOptionElement).selected = value === "true";
+          break;
+        case "dangerous_inner_html":
+          node.innerHTML = value;
+          break;
+        default:
+          // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
+          if (value == "false" && bool_attrs.hasOwnProperty(name)) {
+            node.removeAttribute(name);
+          } else {
+            node.setAttribute(name, value);
+          }
+      }
+    }
+  }
+  RemoveAttribute(edit: RemoveAttribute) {
+    const name = edit.name;
+
+    const node = this.nodes[edit.root];
+    node.removeAttribute(name);
+
+    if (name === "value") {
+      (node as HTMLInputElement).value = "";
+    }
+
+    if (name === "checked") {
+      (node as HTMLInputElement).checked = false;
+    }
+
+    if (name === "selected") {
+      (node as HTMLOptionElement).selected = false;
+    }
+  }
+
+  handleEdits(edits: DomEdit[]) {
+    console.log("handling edits ", edits);
+    this.stack.push(this.root);
+
+    for (let edit of edits) {
+      switch (edit.type) {
+        case "AppendChildren":
+          this.AppendChildren(edit);
+          break;
+        case "ReplaceWith":
+          this.ReplaceWith(edit);
+          break;
+        case "InsertAfter":
+          this.InsertAfter(edit);
+          break;
+        case "InsertBefore":
+          this.InsertBefore(edit);
+          break;
+        case "Remove":
+          this.Remove(edit);
+          break;
+        case "CreateTextNode":
+          this.CreateTextNode(edit);
+          break;
+        case "CreateElement":
+          this.CreateElement(edit);
+          break;
+        case "CreateElementNs":
+          this.CreateElementNs(edit);
+          break;
+        case "CreatePlaceholder":
+          this.CreatePlaceholder(edit);
+          break;
+        case "RemoveEventListener":
+          this.RemoveEventListener(edit);
+          break;
+        case "NewEventListener":
+          // todo: only on desktop should we make our own handler
+          let handler = (event: Event) => {
+            const target = event.target as Element | null;
+            console.log("event", event);
+            if (target != null) {
+
+              const real_id = target.getAttribute(`dioxus-id`);
+
+              const should_prevent_default = target.getAttribute(
+                `dioxus-prevent-default`
+              );
+
+              let contents = serialize_event(event);
+
+              if (should_prevent_default === `on${event.type}`) {
+                event.preventDefault();
+              }
+
+              if (real_id == null) {
+                return;
+              }
+
+              window.rpc.call("user_event", {
+                event: (edit as NewEventListener).event_name,
+                mounted_dom_id: parseInt(real_id),
+                contents: contents,
+              });
+            }
+          };
+          this.NewEventListener(edit, handler);
+          break;
+        case "SetText":
+          this.SetText(edit);
+          break;
+        case "SetAttribute":
+          this.SetAttribute(edit);
+          break;
+        case "RemoveAttribute":
+          this.RemoveAttribute(edit);
+          break;
+      }
+    }
+  }
+}
+
+function main() {
+  let root = window.document.getElementById("main");
+  if (root != null) {
+    window.interpreter = new Interpreter(root);
+    window.rpc.call("initialize");
+  }
+}
+
+
+type PushRoot = { type: "PushRoot", root: number };
+type AppendChildren = { type: "AppendChildren", many: number };
+type ReplaceWith = { type: "ReplaceWith", root: number, m: number };
+type InsertAfter = { type: "InsertAfter", root: number, n: number };
+type InsertBefore = { type: "InsertBefore", root: number, n: number };
+type Remove = { type: "Remove", root: number };
+type CreateTextNode = { type: "CreateTextNode", text: string, root: number };
+type CreateElement = { type: "CreateElement", tag: string, root: number };
+type CreateElementNs = { type: "CreateElementNs", tag: string, root: number, ns: string };
+type CreatePlaceholder = { type: "CreatePlaceholder", root: number };
+type NewEventListener = { type: "NewEventListener", root: number, event_name: string, scope: number };
+type RemoveEventListener = { type: "RemoveEventListener", event_name: string, scope: number, root: number };
+type SetText = { type: "SetText", root: number, text: string };
+type SetAttribute = { type: "SetAttribute", root: number, field: string, value: string, ns: string | undefined };
+type RemoveAttribute = { type: "RemoveAttribute", root: number, name: string };
+
+
+type DomEdit =
+  PushRoot |
+  AppendChildren |
+  ReplaceWith |
+  InsertAfter |
+  InsertBefore |
+  Remove |
+  CreateTextNode |
+  CreateElement |
+  CreateElementNs |
+  CreatePlaceholder |
+  NewEventListener |
+  RemoveEventListener |
+  SetText |
+  SetAttribute |
+  RemoveAttribute;
+
+
+export { };
+declare global {
+  interface Window {
+    interpreter: Interpreter;
+    rpc: { call: (method: string, args?: any) => void };
+  }
+}
+
+
+type Edits = DomEdit[];
+
+main();

+ 10 - 0
packages/jsinterpreter/tsconfig.json

@@ -0,0 +1,10 @@
+{
+    "compilerOptions": {
+        "target": "es5",
+        // "module": "None",
+        "module": "commonjs",
+        "strict": true,
+        "outDir": "dist",
+        "sourceMap": true
+    }
+}