Sfoglia il codice sorgente

fixed text and HTML related things
added nodes are still being ignored

Matheus Giovani 2 anni fa
parent
commit
5daa55e05f

+ 1 - 1
nodemon.json

@@ -1,6 +1,6 @@
 {
     "$schema": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/nodemon.json",
-    "watch": ["./src/webpack-loader/**/*", "./src/compiler/out/**/*.js"],
+    "watch": ["./src/webpack-loader/**/*", "./src/**/*"],
     "ext": "js",
     "exec": "webpack",
     "env": {

+ 2 - 1
packages/compiler/src/core/compiler/HTMLToVirtualDOM.ts

@@ -156,7 +156,8 @@ export class PugToVirtualDOM {
 
         this.writeLn(`h("span", {`);
             this.ident(`attrs: {\n`);
-                this.ident(`"x-${node.mustEscape ? "text": "html"}": "${node.val.replace(/"/g, '\\"')}"\n`);
+                this.ident(`"x-html": "${node.val.replace(/"/g, '\\"')}",\n`);
+                this.ident(`"x-escape": ${node.mustEscape}\n`);
             this.outdent(`}\n`);
         this.outdent(`})`);
     }

+ 0 - 1
packages/compiler/src/core/plugin/hooks/PupperToAlpineHook.ts

@@ -5,7 +5,6 @@ export class PupperToAlpineHook extends Hook {
     public static Attributes: Record<string, string> = {
         "p-show": "x-show",
         "p-on": "x-on",
-        "p-text": "x-text",
         "p-html": "x-html",
         "p-model": "x-model",
         "p-modelable": "x-modelable",

+ 1 - 1
packages/dom2vdom/src/index.ts

@@ -27,7 +27,7 @@ class Converter {
     public convertNode(node: Node | Element) {
         // If it's not an element
         if (!(node instanceof Element)) {
-            return JSON.stringify(node.textContent);
+            return node.textContent;
         }
 
         const properties: Record<string, Record<string, Attr>> = {};

+ 38 - 7
packages/renderer/src/core/vdom/Node.ts

@@ -31,12 +31,12 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
     private dirty: boolean = true;
     private patching: boolean = false;
     private renderable: boolean = true;
+    public replacedWith: PupperNode[] = null;
 
-    public text: string = "";
     public element: Element = null;
 
     constructor(
-        protected node: TNode | string,
+        public node: TNode | string,
         public parent: PupperNode = null,
         public renderer: Renderer
     ) {
@@ -96,6 +96,22 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
         return this.element !== null;
     }
 
+    /**
+     * Checks if this node was replaced.
+     * @returns 
+     */
+    public wasReplaced() {
+        return this.replacedWith !== null;
+    }
+
+    /**
+     * Retrieves the nodes which this node was replaced with.
+     * @returns 
+     */
+    public getReplacement() {
+        return this.replacedWith;
+    }
+
     /**
      * Sets if this node is dirty (needs to be reparsed) or not.
      * @param dirty If it's dirty or not.
@@ -172,6 +188,14 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
         return this.renderable;
     }
 
+    /**
+     * Checks if it's a string node.
+     * @returns 
+     */
+    public isString() {
+        return typeof this.node === "string";
+    }
+
     /**
      * Retrieves an object containing all attributes and properties.
      * @returns 
@@ -267,13 +291,21 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
             return nodes;
         }
 
+        const replacement = nodes.map((node) =>
+            !(node instanceof PupperNode) ?
+                new PupperNode(node, this.parent, this.renderer) :
+                node
+        ) as PupperNode[];
+
         this.parent.children.splice(
             this.getIndex(),
             1,
             // @ts-ignore
-            ...nodes.map((node) => !(node instanceof PupperNode) ? new PupperNode(node, this.parent, this.renderer) : node)
+            ...replacement
         );
 
+        this.replacedWith = replacement;
+
         return nodes;
     }
 
@@ -282,7 +314,6 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
      * @returns 
      */
     public replaceWithComment() {
-        // @ts-ignore
         const comment = new PupperNode(h.c("!"), this.parent, this.renderer);
 
         this.replaceWith(comment);
@@ -402,13 +433,13 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
      * @returns 
      */
     public clone() {
-        const clonedNode = this.node === undefined ? this.text : h(this.tag, {
+        const clonedNode = this.isString() ? this.node : h(this.tag, {
             attrs: { ...this.attributes },
             props: { ...this.properties },
             on: {... this.eventListeners }
         }, []);
 
-        const clone = new PupperNode(clonedNode, this.parent, this.renderer);
+        const clone = new PupperNode(clonedNode as TNode, this.parent, this.renderer);
         clone.children = this.children.map((child) => child.clone());
 
         return clone;
@@ -481,7 +512,7 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
         }
 
         if (this.tag === "!") {
-            this.node = h.c(this.text) as TNode;
+            this.node = h.c("") as TNode;
             return this.node;
         }
 

+ 10 - 2
packages/renderer/src/core/vdom/directives/Conditional.ts

@@ -45,14 +45,22 @@ directive("if", async (node, { expression, scope }) => {
             // If the conditional matched
             if (value) {
                 // Clone them into the DOM
-                clones = await walk(children.map((child) => child.clone().setParent(node.parent)), scope);
+                clones = await walk(
+                    children.map((child) =>
+                        child.clone()
+                            .setParent(node.parent)
+                            .setDirty(true, false)
+                            .setChildrenDirty(true, false)
+                            .setChildrenIgnored(false)
+                    ), scope);
+                
                 node.insertBefore(...clones);
             }
 
             node.parent.setDirty();
         } catch(e) {
             console.warn("[pupper.js] failed to evaluate conditional:");
-            console.error(e);
+            throw e;
         }
     });
 });

+ 19 - 7
packages/renderer/src/core/vdom/directives/HTML.ts

@@ -5,6 +5,7 @@ import { PupperNode } from "../Node";
 import dom2vdom from "@pupperjs/dom2vdom";
 
 import h from "virtual-dom/h";
+import { VTree } from "virtual-dom";
 
 /**
  * @directive x-html
@@ -13,18 +14,29 @@ import h from "virtual-dom/h";
 directive("html", async (node, { expression, scope }) => {
     const evaluate = evaluateLater(expression);
 
+    let replacedNode: PupperNode = null;
+
+    const escape = node.getAttribute("x-escape");
+    node.removeAttribute("x-escape");
+
     await effect(async () => {
         try {
-            const html = await evaluate(scope) as string;
-            const evaluatedNode = dom2vdom(html, h) as VirtualDOM.VTree;
+            let content: string | VTree = await evaluate(scope) as string;
+            
+            if (!escape) {
+                const evaluatedNode = dom2vdom(content, h) as VirtualDOM.VTree;
+                content = evaluatedNode;
+            }
 
-            node.appendChild(
-                new PupperNode(evaluatedNode, node.parent, node.renderer)
-            );
+            if (replacedNode) {
+                replacedNode.delete();
+            }
 
-            node.removeAttribute("x-html");
+            replacedNode = new PupperNode(content, node.parent, node.renderer);
+            node.insertBefore(replacedNode);
+            node.delete();
 
-            node.setDirty();
+            node.parent.setDirty();
         } catch(e) {
             console.warn("[pupper.js] failed to set inner HTML:");
             console.error(e);

+ 4 - 1
packages/renderer/src/core/vdom/directives/Loop.ts

@@ -71,6 +71,9 @@ directive("for", async (node, { expression, scope }) => {
                         .setParent(node.parent)
                         .setDirty(true, false)
                         .setChildrenDirty(true, false)
+
+                        // @todo new added nodes are still being ignored because the comment is ignored
+                        // strange, bug the effect is never triggered for freshly reacted items
                         .setChildrenIgnored(false);
 
                     node.insertBefore(child);
@@ -88,7 +91,7 @@ directive("for", async (node, { expression, scope }) => {
         }
     });
 
-    node.addEventListener("removed", removeEffect);
+    node.addEventListener("DOMNodeRemoved", removeEffect);
 });
 
 /**

+ 0 - 43
packages/renderer/src/core/vdom/directives/Text.ts

@@ -1,43 +0,0 @@
-import { directive } from "../../../model/Directive";
-import { maybeEvaluateLater } from "../../../model/Evaluator";
-import { effect } from "../../../model/Reactivity";
-import { PupperNode } from "../Node";
-
-/**
- * @directive x-text
- * @description Sets an element inner text.
- * 
- * @todo When a buffered text is given, no text is displayed.
- *       Maybe the HTML to VDom is treating buffered text incorrectly.
- */
-directive("text", async (node, { expression, scope }) => {
-    const evaluate = maybeEvaluateLater(expression);
-
-    let replacedNode: PupperNode = null;
-
-    await effect(async () => {
-        try {
-            const text = await evaluate(scope) as string;
-
-            if (!text) {
-                console.warn(`pupper.js evaluated x-text expression "${expression}" as`, undefined);
-                return;
-            }
-
-            if (replacedNode) {
-                replacedNode = replacedNode.replaceWith(
-                    new PupperNode(text, node, node.renderer)
-                )[0];
-            } else {
-                replacedNode = new PupperNode(text, node, node.renderer);
-                node.appendChild(replacedNode);
-                node.removeAttribute("x-text");
-            }
-
-            node.setDirty();
-        } catch(e) {
-            console.warn("[pupper.js] failed to set inner text:");
-            console.error(e);
-        }
-    });
-});

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

@@ -7,7 +7,6 @@ import "./core/vdom/directives/Conditional";
 import "./core/vdom/directives/Loop";
 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";
 

+ 79 - 31
packages/renderer/src/model/NodeWalker.ts

@@ -3,64 +3,93 @@ import { directives } from "./Directive";
 
 import * as Debugger from "../util/Debugger";
 
+export enum ENodeWalkResult {
+    PREVIOUS,
+    NEXT,
+    REMOVE
+}
+
 export async function walk<TNode extends PupperNode | PupperNode[]>(nodes: TNode, scope: any = null): Promise<TNode> {
     if (!Array.isArray(nodes)) {
-        return await node(nodes as PupperNode, scope) as TNode;
+        return (await walkNode(nodes as PupperNode, scope)).node as TNode;
     }
 
     let count = nodes.length;
     let i = 0;
 
     while(i < count) {
-        const result = await node(nodes[i], scope);
-
-        if (result === undefined) {
-            nodes.splice(i++, 1);
-            count = nodes.length;
-            continue;
-        }
+        const { node, result } = await walkNode(nodes[i], scope);
 
-        if (Array.isArray(result)) {
-            nodes.splice(i++, 1, ...result);
+        // If the result is to remove the node
+        if (result === ENodeWalkResult.REMOVE) {
+            // Remove it and continue
+            nodes.splice(i, 1);
             count = nodes.length;
-            continue;
-        }
 
-        if (!result.exists() || result.isBeingIgnored()) {
-            i++;
             continue;
         }
 
-        nodes[i] = result;
-
-        i++;
+        // If it's an array
+        if (Array.isArray(node)) {
+            // Append it to the nodes array
+            nodes.splice(i, 1, ...await walk(node, scope));
+            count = nodes.length;
+        } else {
+            // If it's going back
+            if (result === ENodeWalkResult.PREVIOUS) {
+                // Parse it again
+                i--;
+                continue;
+            }
+
+            // If the node doesn't exists or is being ignored
+            if (!node.exists() || node.isBeingIgnored()) {
+                i++;
+                continue;
+            }
+
+            nodes[i] = node;
+            i++;          
+        }        
     }
 
     return nodes;
 }
 
-async function node(node: PupperNode | undefined, scope: any) {
-    Debugger.group(node.tag, node.getAttributesAndProps(), node);
-
+async function walkNode(node: PupperNode | undefined, scope: any): Promise<{
+    result: ENodeWalkResult,
+    node?: PupperNode | PupperNode[]
+}> {
     // If it's an invalid node
     if (!node) {
-        Debugger.endGroup();
         // Ignore it
-        return undefined;
+        return {
+            result: ENodeWalkResult.REMOVE
+        };
     }
 
+    Debugger.group(node.tag, node.getAttributesAndProps(), node);
+
     // Ignore if it's a string
-    if (typeof node === "string") {
-        Debugger.info("node is a string");
+    if (typeof node?.node === "string") {
+        Debugger.warn("node is a plain string");
         Debugger.endGroup();
-        return node;
+
+        return {
+            node,
+            result: ENodeWalkResult.NEXT
+        };
     }
 
     // Ignore if it's being ignored
     if (node.isBeingIgnored()) {
-        Debugger.info("node is being ignored");
+        Debugger.warn("node is being ignored");
         Debugger.endGroup();
-        return node;
+
+        return {
+            node,
+            result: ENodeWalkResult.NEXT
+        };
     }
 
     for(let handle of directives(node, scope)) {
@@ -70,11 +99,26 @@ async function node(node: PupperNode | undefined, scope: any) {
     // Set it as non-dirty.
     node.setDirty(false);
 
+    // If node was replaced, stop parsing
+    if (node.wasReplaced()) {
+        Debugger.warn("node was replaced with %O", node.getReplacement());
+        Debugger.endGroup();
+
+        return {
+            node: node.getReplacement(),
+            result: ENodeWalkResult.NEXT
+        };
+    }
+
     // If the node was removed, stop parsing
     if (!node.exists()) {
-        Debugger.info("node was removed");
+        Debugger.warn("node was removed");
         Debugger.endGroup();
-        return node;
+
+        return {
+            node,
+            result: ENodeWalkResult.NEXT
+        };
     }
 
     // Parse children if needed
@@ -84,12 +128,16 @@ async function node(node: PupperNode | undefined, scope: any) {
 
     // If it's non-renderable
     if (!node.isRenderable()) {
-        Debugger.info("node is not renderable");
+        Debugger.warn("node is not renderable");
+
         // Allow parsing its children but prevent itself from being rendered.
         return undefined;
     }
 
     Debugger.endGroup();
 
-    return node;
+    return {
+        node,
+        result: ENodeWalkResult.NEXT
+    };
 }

+ 14 - 4
packages/renderer/src/util/Debugger.ts

@@ -37,7 +37,17 @@ export function debug(message: string, ...args: any[]) {
  * @returns 
  */
 export function info(message: string, ...args: any[]) {
-    return debug("%c" + message, ...["color: aqua", ...args])
+    return logger("%c" + message, ...["color: aqua", ...args])
+}
+
+/**
+ * Prints a debug warning message to the console.
+ * @param message The message to be displayed, in sprintf format.
+ * @param args Any arguments to be passed to the message sprintf.
+ * @returns 
+ */
+ export function warn(message: string, ...args: any[]) {
+    return logger("%c" + message, ...["color: yellow", ...args])
 }
 
 /**
@@ -64,8 +74,7 @@ export function endGroup() {
  */
 export function toggleLogger() {
     if (enabled) {
-        debuggerModule.enable("pupper");
-        debuggerModule.enable("pupper:*");
+        debuggerModule.enable("pupper pupper:*");
     } else {
         debuggerModule.disable("pupper");
         debuggerModule.disable("pupper:*");
@@ -94,7 +103,8 @@ const Debugger = {
     toggleDebug,
     info,
     debug,
-    extend
+    extend,
+    warn
 } as const;
 
 export default Debugger;