소스 검색

added HTML string to virtual dom parser

Matheus Giovani 3 년 전
부모
커밋
340f2234e3

+ 2 - 0
packages/dom2vdom/.gitignore

@@ -0,0 +1,2 @@
+/out/
+/types/

+ 20 - 0
packages/dom2vdom/package.json

@@ -0,0 +1,20 @@
+{
+  "name": "@pupperjs/dom2vdom",
+  "version": "1.0.0",
+  "description": "A HTML to virtual-dom converter.",
+  "main": "./out/index.js",
+  "repository": "https://github.com/pupperjs/core/packages/dom2vdom",
+  "author": "Matheus Giovani <matheus@ad3com.com.br>",
+  "license": "MIT",
+  "private": false,
+  "types": "./types",
+  "scripts": {
+    "build": "tsc"
+  },
+  "dependencies": {},
+  "devDependencies": {
+    "@types/virtual-dom": "^2.1.1",
+    "tsc": "^2.0.4",
+    "typescript": "^4.7.3"
+  }
+}

+ 90 - 0
packages/dom2vdom/src/index.ts

@@ -0,0 +1,90 @@
+/// <reference types="virtual-dom" />
+
+/**
+ * Represents a hyperscript function.
+ */
+type FHyperScript = (
+    tagNameOrText: string,
+    properties?: Record<string, any>,
+    children?: (VirtualDOM.VTree | string)[]
+) => VirtualDOM.VTree;
+
+/**
+ * Used to convert nodes using a given hypescript function.
+ */
+class Converter {
+    constructor(
+        protected h: FHyperScript
+    ) {
+
+    }
+
+    /**
+     * Converts a single node into a virtual DOM node.
+     * @param node The node to be converted.
+     * @returns
+     */
+    public convertNode(node: Node | Element) {
+        // If it's not an element
+        if (!(node instanceof Element)) {
+            return JSON.stringify(node.textContent);
+        }
+
+        const properties: Record<string, Record<string, Attr>> = {};
+        const children: (VirtualDOM.VTree | string)[] = [];
+
+        // If has attributes
+        if (node.attributes) {
+            properties.attrs = {};
+    
+            // Save over all attributes
+            for(let key in node.attributes) {
+                properties.attrs[key === "class" ? "className" : key] = node.attributes[key];    
+            }
+        }
+
+        // If has children
+        if (node.childNodes.length > 0) {
+            for(let child of node.childNodes) {
+                children.push(this.convertNode(child));
+            }
+        }
+    
+        return this.h(node.tagName, properties, children);
+    }
+
+    /**
+     * Converts an element or a HTML string into virtual DOM nodes.
+     * @param node The element or string to be converted.
+     * @returns
+     */
+    public convertTree(node: Element | string) {
+        const template = document.createElement("template");
+        
+        if (node instanceof Element) {
+            template.content.appendChild(node);
+        } else {
+            template.innerHTML = node;
+        }
+
+        if (template.content.childElementCount > 1) {
+            let tree = [];
+
+            // Iterate over all nodes
+            for(let node of template.children) {
+                tree.push(this.convertNode(node));
+            }
+
+            return tree;
+        }
+
+        return this.convertNode(template.content.firstChild);
+    }
+}
+
+function dom2vdom(element: Element | string, h: FHyperScript) {
+    return new Converter(h).convertTree(element);
+}
+
+module.exports = dom2vdom;
+export default dom2vdom;

+ 9 - 0
packages/dom2vdom/tsconfig.json

@@ -0,0 +1,9 @@
+{
+    "extends": "../../tsconfig.json",
+    "compilerOptions": {
+        "outDir": "./out",
+        "declarationDir": "./types"
+    },
+    "include": ["./src/index.ts"],
+    "exclude": ["node_modules"],
+}

+ 1 - 2
packages/renderer/package.json

@@ -11,8 +11,7 @@
     "watch:ts": "cross-env NODE_OPTIONS=\"-r tsconfig-paths/register\" tsc -watch"
   },
   "dependencies": {
-    "dom2hscript": "^0.2.3",
-    "pug": "^3.0.2",
+    "@pupperjs/dom2vdom": "file:./../dom2vdom",
     "virtual-dom": "file:./../virtual-dom"
   },
   "types": "./types/",

+ 34 - 0
packages/renderer/src/core/vdom/directives/Component.ts

@@ -0,0 +1,34 @@
+import { directive, mapAttributes, replaceWith } from "../../../model/Directive";
+import { evaluateLater } from "../../../model/Evaluator";
+import { effect } from "../../../model/Reactivity";
+
+const debug = require("debug")("pupper:vdom:component");
+
+mapAttributes(replaceWith(":", "x-bind:"));
+
+/**
+ * @directive x-component
+ * @description Handles a component.
+ */
+directive("component", async (node, { value, expression, scope }) => {
+    const evaluate = expression ? evaluateLater(expression) : () => {};
+
+    await effect(async () => {
+        try {
+            const evaluated = await evaluate(scope);
+
+            // Bind the evaluated value to it
+            node.setAttribute(value, evaluated);
+        
+            debug("binding prop \"%s\" to \"%s\"", value, evaluated);
+        
+            // Remove the original attribute from the node
+            node.removeAttribute("x-bind:" + value);
+            
+            node.setDirty();
+        } catch(e) {
+            console.warn("[pupperjs] failed to bind property:");
+            console.error(e);
+        }
+    });
+});

+ 5 - 1
packages/renderer/src/core/vdom/directives/HTML.ts

@@ -2,6 +2,9 @@ import { directive } from "../../../model/Directive";
 import { evaluateLater } from "../../../model/Evaluator";
 import { effect } from "../../../model/Reactivity";
 import { PupperNode } from "../Node";
+import dom2vdom from "@pupperjs/dom2vdom";
+
+import h from "virtual-dom/h";
 
 /**
  * @directive x-html
@@ -13,9 +16,10 @@ directive("html", async (node, { expression, scope }) => {
     await effect(async () => {
         try {
             const html = await evaluate(scope) as string;
+            const evaluatedNode = dom2vdom(html, h) as VirtualDOM.VTree;
 
             node.appendChild(
-                new PupperNode(html, node.parent, node.renderer)
+                new PupperNode(evaluatedNode, node.parent, node.renderer)
             );
 
             node.removeAttribute("x-html");

+ 1 - 0
packages/renderer/src/index.ts

@@ -9,6 +9,7 @@ import "./core/vdom/directives/Bind";
 import "./core/vdom/directives/EventHandler";
 import "./core/vdom/directives/Text";
 import "./core/vdom/directives/HTML";
+import "./core/vdom/directives/Component";
 
 export default class Pupper {
     /**

+ 1 - 2
packages/renderer/src/model/Directive.ts

@@ -163,11 +163,10 @@ const DEFAULT = "DEFAULT"
 const directiveOrder = [
     "ref",
     "id",
+    "component",
     "bind",
     "if",
     "for",
-    "transition",
-    "show",
     "on",
     "text",
     "html",