Răsfoiți Sursa

added build configuration
fixed events duplicating
fixed neste fors

Matheus Giovani 2 ani în urmă
părinte
comite
ec3855642f

+ 4 - 0
package.json

@@ -12,6 +12,9 @@
     "./packages/webpack-loader"
   ],
   "scripts": {
+    "build": "cross-env NODE_ENV=production npm-run-all -s build:*",
+    "build:compiler": "cd packages/compiler && yarn build",
+    "build:renderer": "cd packages/renderer && yarn build",
     "watch": "npm-run-all -p watch:*",
     "watch:compiler": "cd packages/compiler && yarn watch",
     "watch:renderer": "cd packages/renderer && yarn watch",
@@ -19,6 +22,7 @@
   },
   "dependencies": {},
   "devDependencies": {
+    "cross-env": "^7.0.3",
     "source-map-loader": "^3.0.0",
     "source-map-support": "^0.5.21",
     "webpack": "^5.51.1",

+ 2 - 0
packages/compiler/package.json

@@ -7,6 +7,8 @@
   "private": false,
   "main": "./out/",
   "scripts": {
+    "build": "npm-run-all -s build:*",
+    "build:ts": "tsc -p \"tsconfig.prod.json\"",
     "watch": "npm-run-all -p -r watch:*",
     "watch:ts": "tsc -watch"
   },

+ 1 - 0
packages/compiler/tsconfig.json

@@ -3,6 +3,7 @@
     "compilerOptions": {
         "target": "esnext",
         "outDir": "./out",
+        "baseUrl": "./src",
         "declarationDir": "./types",
         "typeRoots": [
             "./src/typings/",

+ 9 - 0
packages/compiler/tsconfig.prod.json

@@ -0,0 +1,9 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+        "outFile": "out/index.js",
+        "declarationDir": null,
+        "module": "amd",
+        "removeComments": true
+    }
+}

+ 2 - 0
packages/renderer/package.json

@@ -7,6 +7,8 @@
   "private": false,
   "main": "./out/",
   "scripts": {
+    "build": "npm-run-all -s build:*",
+    "build:ts": "tsc -p \"tsconfig.prod.json\"",
     "watch": "npm-run-all -p -r watch:*",
     "watch:ts": "cross-env NODE_OPTIONS=\"-r tsconfig-paths/register\" tsc -watch"
   },

+ 5 - 36
packages/renderer/src/core/Component.ts

@@ -1,20 +1,6 @@
 import { reactive } from "../model/Reactivity";
 import { Renderer } from "./vdom/Renderer";
-
-/**
- * Represents a slot.
- */
-interface Slot {
-    /**
-     * The comment holding the slot position.
-     */
-    container: HTMLElement | Comment;
-
-    /**
-     * All fallback nodes
-     */
-    fallbackNodes: Node[]
-}
+import { Slot } from "./vdom/renderer/Slot";
 
 /**
  * Represents a component's data.
@@ -198,7 +184,6 @@ export class Component {
 
             // Find all slots, templates and references
             const slots = Array.from(renderContainer.querySelectorAll("slot"));
-            const templates = Array.from(renderContainer.querySelectorAll("template"));
             const refs = Array.from(renderContainer.querySelectorAll("[ref]"));
 
             // Iterate over all slots
@@ -209,24 +194,8 @@ export class Component {
                 // If it's a named slot
                 if (slot.hasAttribute("name")) {
                     // Save it
-                    this.$slots[slot.getAttribute("name")] = {
-                        container: comment,
-                        fallbackNodes: [...comment.childNodes]
-                    };
-                }
-            }
-
-            // Iterate over all templates
-            for(let childrenTemplate of templates) {
-                // If it's a named template
-                if (childrenTemplate.hasAttribute("name")) {
-                    // Remove it from the DOM
-                    childrenTemplate.parentElement.removeChild(childrenTemplate);
-
-                    // Save it
-                    this.$templates[childrenTemplate.getAttribute("name")] = () => {
-                        return [...childrenTemplate.content.children].map((node) => node.innerHTML);
-                    };
+                    this.$slots[slot.getAttribute("name")] = new Slot(comment.childNodes);
+                    this.$slots[slot.getAttribute("name")].container = comment;
                 }
             }
 
@@ -252,8 +221,8 @@ export class Component {
         const rendered = await this.render();
 
         // If it's targeting a slot
-        if (!(target instanceof HTMLElement)) {
-            target.container.replaceWith(rendered);
+        if (target instanceof Slot) {
+            target.replaceWith(rendered);
         } else {
             target.append(rendered);
         }

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

@@ -1,4 +1,4 @@
-import { RendererNode } from "@/model/vdom/RendererNode";
+import { RendererNode } from "../../../model/vdom/RendererNode";
 import { directive } from "../../../model/Directive";
 import { evaluateLater } from "../../../model/Evaluator";
 import { walk } from "../../../model/NodeWalker";
@@ -18,7 +18,7 @@ directive("if", async (node: ConditionalNode, { expression, scope }) => {
 
     let lastValue: boolean = null;
 
-    if (!node.hasConsequence()) {
+    if (!node.hasConsequent()) {
         Debugger.error("node %O has no consequence.", node);
     }
 
@@ -43,23 +43,26 @@ directive("if", async (node: ConditionalNode, { expression, scope }) => {
 
             // If the conditional matched
             if (value) {
-                cloned = await walk(node.cloneConsequence(), scope);
+                cloned = node.cloneConsequent();
             } else
             // If has an alternate
-            if (node.hasAlternative()) {
-                cloned = await walk(node.cloneAlternative(), scope);
+            if (node.hasAlternate()) {
+                cloned = node.cloneAlternate();
             }
 
             if (cloned) {
                 // Clone it into the DOM
                 node.append(
-                    ...cloned
+                    ...await walk(cloned, scope)
                 );
             }
 
+            node.setIgnored();
             node.setDirty().setChildrenDirty(true, false);
         } catch(e) {
-            Debugger.error("failed to evaluate conditional:");
+            Debugger.error("failed to evaluate conditional \"%s\"", expression);
+            Debugger.error("last scope was %O", scope);
+
             throw e;
         }
 

+ 0 - 1
packages/renderer/src/core/vdom/directives/EventHandler.ts

@@ -17,7 +17,6 @@ directive("on", async (node, { value, expression, scope }) => {
 
         debug("will handle event \"%s\" to %O", value, evaluate);
 
-        // @todo check why events are duplicating for the FIRST element inside the for-loop
         node.addEventListener(value, async ($event: any) => {
             debug("handled %s event", value);
             

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

@@ -16,8 +16,6 @@ directive("for", async (node: LoopNode, { expression, scope }) => {
     const loopData = parseForExpression(expression);
     const evaluate = evaluateLater(loopData.items);
 
-    console.warn(node.body);
-
     const removeEffect = await effect(async () => {        
         let loopScope;
 
@@ -76,6 +74,7 @@ directive("for", async (node: LoopNode, { expression, scope }) => {
                 }
             }
 
+            node.setIgnored();
             node.setDirty().setChildrenDirty(true, false);
         } catch(e) {
             Debugger.error("failed to evaluate for loop");

+ 25 - 17
packages/renderer/src/core/vdom/nodes/ConditionalNode.ts

@@ -36,33 +36,41 @@ export class ConditionalNode extends PupperNode {
     public clone() {
         const clone = new ConditionalNode(this.node, this.parent, this.renderer);
 
-        clone.consequent = this.cloneConsequence();
-        clone.alternate = this.cloneAlternative();
+        clone.consequent = this.cloneConsequent();
+        clone.alternate = this.cloneAlternate();
 
         return clone;
     }
 
-    public hasConsequence() {
+    /**
+     * Determines if has the conditional has a consequence (then).
+     * @returns 
+     */
+    public hasConsequent() {
         return this.consequent !== undefined;
     }
-
-    public getConsequence() {
-        return this.consequent;
-    }
-
-    public hasAlternative() {
+    
+    /**
+     * Determines if has the conditional has an alternative (else).
+     * @returns 
+     */
+    public hasAlternate() {
         return this.alternate !== undefined;
     }
 
-    public getAlternative() {
-        return this.alternate;
-    }
-
-    public cloneConsequence() {
-        return this.consequent.map((child) => child.clone());
+    /**
+     * Clone the consequent nodes.
+     * @returns 
+     */
+    public cloneConsequent() {
+        return this.consequent.map((child) => child.clone().setParent(this));
     }
 
-    public cloneAlternative() {
-        return this.alternate?.map((child) => child.clone());
+    /**
+     * Clone the alternate nodes.
+     * @returns 
+     */
+    public cloneAlternate() {
+        return this.alternate?.map((child) => child.clone().setParent(this));
     }
 }

+ 24 - 0
packages/renderer/src/core/vdom/renderer/Slot.ts

@@ -0,0 +1,24 @@
+export class Slot {
+    /**
+     * The comment holding the slot position.
+     */
+    public container: HTMLElement | Comment;
+
+    constructor(
+        /**
+         * All fallback nodes for this slot.
+         */
+        protected fallback: NodeListOf<Node>
+    ) {
+
+    }
+
+    public createComment() {
+        this.container = document.createComment("!");
+        return this.container;
+    }
+
+    public replaceWith(content: Element) {
+        this.container.replaceWith(content);
+    }
+}

+ 40 - 15
packages/renderer/src/model/vdom/RendererNode.ts

@@ -8,10 +8,6 @@ import VText from "virtual-dom/vnode/vtext";
 
 import Debugger from "../../util/Debugger";
 
-import { LoopNode } from "../../core/vdom/nodes/LoopNode";
-import { ConditionalNode } from "../../core/vdom/nodes/ConditionalNode";
-import VNode from "virtual-dom/vnode/vnode";
-
 const debug = Debugger.extend("vdom:node");
 
 interface IHookFn extends Function {
@@ -38,10 +34,24 @@ function Hook<TProps extends {
 };
 
 export class RendererNode<TNode extends VirtualDOM.VTree = any> {
+    /**
+     * The children nodes for this node.
+     */
     public children: RendererNode[] = [];
 
+    /**
+     * The properties for this node.
+     */
     public properties: Record<string, string | boolean | number | IHookFn> = {};
+
+    /**
+     * The attributes for this node.
+     */
     public attributes: Record<string, string | boolean | number> = {};
+
+    /**
+     * The event listeners for this node.
+     */
     public eventListeners: Record<string, EventListenerOrEventListenerObject[]> = {};
 
     /**
@@ -389,24 +399,24 @@ export class RendererNode<TNode extends VirtualDOM.VTree = any> {
     /**
      * Adds an event listener to this node.
      * @param event The event name to be added.
-     * @param listener The event callback.
+     * @param callback The event callback.
      */
-    public addEventListener(event: keyof DocumentEventMap | string, listener: EventListenerOrEventListenerObject) {
+    public addEventListener(event: keyof DocumentEventMap | string, callback: EventListener) {
         this.eventListeners[event] = this.eventListeners[event] || [];
 
-        if (!this.eventListeners[event].includes(listener)) {
-            this.eventListeners[event].push(listener);
+        if (!this.eventListeners[event].includes(callback)) {
+            this.eventListeners[event].push(callback);
         }
     }
 
     /**
      * Removes a callback from an event listener.
      * @param event The event name.
-     * @param listener The callback to be removed.
+     * @param callback The callback to be removed.
      */
-    public removeEventListener(event: keyof DocumentEventMap | string, listener: EventListenerOrEventListenerObject) {
+    public removeEventListener(event: keyof DocumentEventMap | string, callback: EventListenerOrEventListenerObject) {
         this.eventListeners[event].splice(
-            this.eventListeners[event].indexOf(listener),
+            this.eventListeners[event].indexOf(callback),
             1
         );
     }
@@ -642,12 +652,24 @@ export class RendererNode<TNode extends VirtualDOM.VTree = any> {
 
         for(let evt in this.eventListeners) {
             for(let handler of this.eventListeners[evt]) {
-                this.element.removeEventListener(evt, handler);
                 this.element.addEventListener(evt, handler);
             }
         }
     }
 
+    /**
+     * Called when the element has been removed from the DOM.
+     */
+    private onElementRemoved() {
+        for(let evt in this.eventListeners) {
+            for(let handler of this.eventListeners[evt]) {
+                this.element.removeEventListener(evt, handler);
+            }
+        }
+
+        this.element = null;
+    }
+
     /**
      * Converts the current node into a virtual DOM node.
      * @returns 
@@ -663,12 +685,15 @@ export class RendererNode<TNode extends VirtualDOM.VTree = any> {
             return this.node;
         }
 
-        // If has no $p_create hook yet
-        if (!("$p_create" in this.properties)) {
+        // If has no $pupper hook yet
+        if (!("$pupper" in this.properties)) {
             // Create the hook
-            this.properties["$p_create"] = Hook({
+            this.properties["$pupper"] = Hook({
                 hook: (node: Element) => {
                     this.onElementCreated(node);
+                },
+                unhook: () => {
+                    this.onElementRemoved();
                 }
             });
         }

+ 2 - 5
packages/renderer/tsconfig.json

@@ -3,11 +3,8 @@
     "compilerOptions": {
         "outDir": "./out",
         "baseUrl": "./src",
-        "declarationDir": "./types",
-        "paths": {
-            "@/*": ["./*"]
-        }
+        "declarationDir": "./types"
     },
-    "include": ["./src/*.ts", "./src/typings/**/*.d.ts"],
+    "include": ["./src/**/*.ts", "./src/typings/**/*.d.ts"],
     "exclude": ["node_modules"],
 }

+ 9 - 0
packages/renderer/tsconfig.prod.json

@@ -0,0 +1,9 @@
+{
+    "extends": "./tsconfig.json",
+    "compilerOptions": {
+        "outFile": "out/index.js",
+        "declarationDir": null,
+        "module": "amd",
+        "removeComments": true
+    }
+}

+ 2 - 2
test/templates/template.pupper

@@ -99,6 +99,6 @@ implementation
                 thumbnail: "https://media.istockphoto.com/photos/happy-shiba-inu-dog-on-yellow-redhaired-japanese-dog-smile-portrait-picture-id1197121742?k=20&m=1197121742&s=170667a&w=0&h=SDkUmO-JcBKWXl7qK2GifsYzVH19D7e6DAjNpAGJP2M=",
                 shibe: true
             });
-        });
 
-        //ImportedComponent.mount(this.$slots.slot);
+            ImportedComponent.mount(this.$slots.slot);
+        });