Browse Source

set up a typescript pipeline for interpreter

Jonathan Kelley 1 năm trước cách đây
mục cha
commit
7f60010c1e

+ 65 - 0
packages/interpreter/build.rs

@@ -0,0 +1,65 @@
+use std::{
+    fs::read_to_string,
+    hash::{DefaultHasher, Hash, Hasher},
+    process::Command,
+};
+
+fn main() {
+    // If any TS changes, re-run the build script
+    println!("cargo:rerun-if-changed=src/*.ts");
+
+    for entry in ["common", "form", "interpreter"].iter() {
+        gen_bindings(entry);
+    }
+}
+
+// okay...... so tsc might fail if the user doesn't have it installed
+// we don't really want to fail if that's the case
+// but if you started *editing* the .ts files, you're gonna have a bad time
+// so.....
+// we need to hash each of the .ts files and add that hash to the JS files
+// if the hashes don't match, we need to fail the build
+// that way we also don't need
+fn gen_bindings(name: &str) {
+    let contents = read_to_string(&format!("src/{name}.ts")).unwrap();
+    let generated = read_to_string(&format!("src/gen/{name}.js")).unwrap_or_default();
+    let hashed = hash_file(&contents);
+
+    // If the file is generated, and the hash is the same, we're good, don't do anything
+    if generated
+        .lines()
+        .next()
+        .unwrap_or_default()
+        .starts_with(&format!("// DO NOT EDIT THIS FILE. HASH: {}", hashed))
+    {
+        return;
+    }
+
+    // If the file is generated, and the hash is different, we need to generate it
+    let status = Command::new("tsc")
+        .arg(format!("src/{name}.ts"))
+        .arg("--outDir")
+        .arg("gen")
+        .arg("--target")
+        .arg("es6")
+        .status()
+        .unwrap();
+
+    if !status.success() {
+        panic!(
+            "Failed to generate bindings for {}. Make sure you have tsc installed",
+            name
+        );
+    }
+
+    // The file should exist, and now we need write the TS hash to the file
+    let generated = read_to_string(&format!("gen/{name}.js")).unwrap();
+    let generated = format!("// DO NOT EDIT THIS FILE. HASH: {}\n{}", hashed, generated);
+    std::fs::write(&format!("src/gen/{name}.js"), generated).unwrap();
+}
+
+fn hash_file(obj: &str) -> u64 {
+    let mut hasher = DefaultHasher::new();
+    obj.hash(&mut hasher);
+    hasher.finish()
+}

+ 3 - 0
packages/interpreter/gen/README.md

@@ -0,0 +1,3 @@
+temporary generated code directory since tsc doesn't have a crossplatform way of generating typescript to stdout
+
+https://github.com/microsoft/TypeScript/issues/1226#issuecomment-523544134

+ 1 - 0
packages/interpreter/src/common.js → packages/interpreter/src/common.ts

@@ -1,4 +1,5 @@
 export function setAttributeInner(node, field, value, ns) {
+
   const name = field;
   if (ns === "style") {
     // ????? why do we need to do this

+ 0 - 0
packages/interpreter/src/form.ts


+ 81 - 0
packages/interpreter/src/gen/common.js

@@ -0,0 +1,81 @@
+// DO NOT EDIT THIS FILE. HASH: 9578489549991746027
+export function setAttributeInner(node, field, value, ns) {
+    const name = field;
+    if (ns === "style") {
+        // ????? why do we need to do this
+        if (node.style === undefined) {
+            node.style = {};
+        }
+        node.style[name] = value;
+    }
+    else if (!!ns) {
+        node.setAttributeNS(ns, name, value);
+    }
+    else {
+        switch (name) {
+            case "value":
+                if (value !== node.value) {
+                    node.value = value;
+                }
+                break;
+            case "initial_value":
+                node.defaultValue = value;
+                break;
+            case "checked":
+                node.checked = truthy(value);
+                break;
+            case "initial_checked":
+                node.defaultChecked = truthy(value);
+                break;
+            case "selected":
+                node.selected = truthy(value);
+                break;
+            case "initial_selected":
+                node.defaultSelected = truthy(value);
+                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 (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
+                    node.removeAttribute(name);
+                }
+                else {
+                    node.setAttribute(name, value);
+                }
+        }
+    }
+}
+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,
+    webkitdirectory: true,
+};
+function truthy(val) {
+    return val === "true" || val === true;
+}

+ 1 - 0
packages/interpreter/src/gen/form.js

@@ -0,0 +1 @@
+// DO NOT EDIT THIS FILE. HASH: 3476900567878811119

+ 0 - 0
packages/interpreter/src/gen/interpreter.js


+ 42 - 39
packages/interpreter/src/interpreter.js → packages/interpreter/src/interpreter.ts

@@ -1,5 +1,7 @@
 class InterpreterConfig {
-  constructor(intercept_link_redirects) {
+  intercept_link_redirects: boolean;
+
+  constructor(intercept_link_redirects: boolean) {
     this.intercept_link_redirects = intercept_link_redirects;
   }
 }
@@ -21,6 +23,42 @@ async function handler(event, name, bubbles, config) {
 
   let contents = await serialize_event(event);
 
+  if (
+    target.tagName === "FORM" &&
+    (event.type === "submit" || event.type === "input")
+  ) {
+    const formData = new FormData(target);
+
+    for (let name of formData.keys()) {
+      const fieldType = target.elements[name].type;
+
+      switch (fieldType) {
+        case "select-multiple":
+          contents.values[name] = formData.getAll(name);
+          break;
+
+        // add cases for fieldTypes that can hold multiple values here
+        default:
+          contents.values[name] = formData.get(name);
+          break;
+      }
+    }
+  }
+
+  if (
+    target.tagName === "SELECT" &&
+    event.type === "input"
+  ) {
+    const selectData = target.options;
+    contents.values["options"] = [];
+    for (let i = 0; i < selectData.length; i++) {
+      let option = selectData[i];
+      if (option.selected) {
+        contents.values["options"].push(option.value.toString());
+      }
+    }
+  }
+
   // TODO: this should be liveview only
   if (
     target.tagName === "INPUT" &&
@@ -61,42 +99,6 @@ async function handler(event, name, bubbles, config) {
     }
   }
 
-  if (
-    target.tagName === "FORM" &&
-    (event.type === "submit" || event.type === "input")
-  ) {
-    const formData = new FormData(target);
-
-    for (let name of formData.keys()) {
-      const fieldType = target.elements[name].type;
-
-      switch (fieldType) {
-        case "select-multiple":
-          contents.values[name] = formData.getAll(name);
-          break;
-
-        // add cases for fieldTypes that can hold multiple values here
-        default:
-          contents.values[name] = formData.get(name);
-          break;
-      }
-    }
-  }
-
-  if (
-    target.tagName === "SELECT" &&
-    event.type === "input"
-  ) {
-    const selectData = target.options;
-    contents.values["options"] = [];
-    for (let i = 0; i < selectData.length; i++) {
-      let option = selectData[i];
-      if (option.selected) {
-        contents.values["options"].push(option.value.toString());
-      }
-    }
-  }
-
   window.ipc.postMessage(
     window.interpreter.serializeIpcMessage("user_event", {
       name: name,
@@ -324,7 +326,7 @@ window.interpreter.setFocus = function (id, focus) {
   return true;
 }
 
-function get_mouse_data(event) {
+function get_mouse_data(event: MouseEvent) {
   const {
     altKey,
     button,
@@ -447,7 +449,8 @@ async function serialize_event(event) {
     case "dragover":
     case "dragstart":
     case "drop": {
-      let files = null;
+      let files = [];
+
       if (event.dataTransfer && event.dataTransfer.files) {
         files = ["a", "b", "c"];
         // files = await serializeFileList(event.dataTransfer.files);

+ 3 - 3
packages/interpreter/src/lib.rs

@@ -2,8 +2,8 @@
 #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
 #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
 
-pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
-pub static COMMON_JS: &str = include_str!("./common.js");
+pub static INTERPRETER_JS: &str = include_str!("./gen/interpreter.js");
+pub static COMMON_JS: &str = include_str!("./gen/common.js");
 
 #[cfg(feature = "sledgehammer")]
 mod sledgehammer_bindings;
@@ -20,7 +20,7 @@ pub use write_native_mutations::*;
 #[cfg(all(feature = "minimal_bindings", feature = "webonly"))]
 pub mod minimal_bindings {
     use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
-    #[wasm_bindgen(module = "/src/common.js")]
+    #[wasm_bindgen(module = "/src/gen/common.js")]
     extern "C" {
         pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
     }

+ 0 - 2
packages/web/src/dom.rs

@@ -36,8 +36,6 @@ pub struct UiEvent {
     pub data: PlatformEventData,
 }
 
-//fn get_document(elem: &web_sys::Element) ->
-
 impl WebsysDom {
     pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender<UiEvent>) -> Self {
         let (document, root) = match cfg.root {

+ 1 - 0
packages/web/src/event.rs

@@ -441,6 +441,7 @@ impl HasFileData for WebFormData {
     fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
         #[cfg(not(feature = "file_engine"))]
         let files = None;
+
         #[cfg(feature = "file_engine")]
         let files = self
             .element