浏览代码

adds a debugger
fixes a bug in implementation functions parameters
fixes event binding (still buggy, but at least it works now)

Matheus Giovani 3 年之前
父节点
当前提交
6db466da83

+ 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/**/*"],
+    "watch": ["./src/webpack-loader/**/*", "./src/compiler/out/**/*.js"],
     "ext": "js",
     "exec": "webpack",
     "env": {

+ 1 - 1
package.json

@@ -1,5 +1,5 @@
 {
-  "name": "pupperjs",
+  "name": "pupper.js",
   "version": "1.0.0",
   "description": "A reactive template engine based in Pug and Alpine.js",
   "author": "Matheus Giovani <matheus@ad3com.com.br>",

+ 4 - 1
packages/compiler/src/core/Compiler.ts

@@ -71,7 +71,10 @@ export class PupperCompiler {
         }
 
         // Create the debug logger
-        this.debugger = new Console(createWriteStream(process.cwd() + "/.logs/log.log"), createWriteStream(process.cwd() + "/.logs/error.log"));
+        this.debugger = new Console(
+            createWriteStream(process.cwd() + "/.logs/log.log", { flags: "a" }),
+            createWriteStream(process.cwd() + "/.logs/error.log", { flags: "a" })
+        );
     }
 
     /**

+ 7 - 2
packages/compiler/src/core/plugin/phases/PrepareComponentsHook.ts

@@ -125,6 +125,8 @@ export class PrepareComponents extends Hook {
                     // Extract all single params
                     const singleParams = matchedParams.matchAll(singleParamRegExp);
 
+                    let attributes = tagData.attributes;
+
                     // Iterate over all params
                     for(let param of singleParams) {
                         // If it doesn't have a initializer
@@ -133,9 +135,12 @@ export class PrepareComponents extends Hook {
                         }
 
                         // Strictly add an "undefined" initializer to it
-                        implContent = implContent.replace(param[0].trim(), param[0].trim() + " = undefined");
+                        attributes = attributes.replace(param[0].trim(), param[0].trim() + " = undefined");
                     }
 
+                    // Replace the attributes with the new ones
+                    implContent = implContent.replace(tagData.attributes, attributes);
+
                     // Skip the params lines
                     implLine += matchedParams.split("\n").length;
                 }
@@ -148,7 +153,7 @@ export class PrepareComponents extends Hook {
                 ...implContent.split("\n")
             );
 
-            this.compiler.debugger.log(lines);
+            this.compiler.debugger.log(lines.join("\n"));
 
             break;
         }

+ 0 - 1
packages/renderer/package.json

@@ -17,7 +17,6 @@
   "types": "./types/",
   "devDependencies": {
     "@types/node": "^16.7.6",
-    "@types/virtual-dom": "^2.1.1",
     "cross-env": "^7.0.3",
     "debug": "^4.3.4",
     "tsc": "^2.0.3",

+ 4 - 0
packages/renderer/src/core/Component.ts

@@ -249,6 +249,10 @@ export class Component {
             target.append(rendered);
         }
 
+        if ("mounted" in this.$component) {
+            this.$component.mounted.call(this);
+        }
+
         return rendered;
     }
 }

+ 56 - 28
packages/renderer/src/core/vdom/Node.ts

@@ -4,8 +4,11 @@ import h from "virtual-dom/h";
 import diff from "virtual-dom/diff";
 import patch from "virtual-dom/patch";
 import VComment from "virtual-dom/vnode/vcomment";
+import VText from "virtual-dom/vnode/vtext";
 
-const debug = require("debug")("pupper:vdom:node");
+import Debugger from "../../util/Debugger";
+
+const debug = Debugger.extend("vdom:node");
 
 const Hook = (callback: CallableFunction) => {
     const hook = function() {};
@@ -37,25 +40,41 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
         public parent: PupperNode = null,
         public renderer: Renderer
     ) {
-        if (typeof node !== "string") {
-            // Initialize the properties
-            this.tag = "tagName" in node ? node.tagName : "TEXT";
+        this.initNode();
+    }
 
-            if (node instanceof VComment) {
-                this.tag = "!";
-            }
+    /**
+     * Initializes the node data.
+     * @returns 
+     */
+    private initNode() {
+        if (typeof this.node === "string") {
+            return;
+        }
+
+        // If it's a text node
+        if (this.node instanceof VText) {
+            this.node = this.node.text;
+            return;
+        }
 
-            if ("properties" in node) {
-                if ("attrs" in node.properties) {
-                    this.attributes = Object.assign(this.attributes, node.properties.attrs);
+        // Initialize the properties
+        this.tag = "tagName" in this.node ? this.node.tagName : "TEXT";
+
+        if (this.node instanceof VComment) {
+            this.tag = "!";
+        } else {
+            if ("properties" in this.node) {
+                if ("attrs" in this.node.properties) {
+                    this.attributes = Object.assign(this.attributes, this.node.properties.attrs);
                 }
 
-                if ("props" in node.properties) {
-                    this.properties = Object.assign(this.properties, node.properties.props);
+                if ("props" in this.node.properties) {
+                    this.properties = Object.assign(this.properties, this.node.properties.props);
                 }
 
-                if ("on" in node.properties) {
-                    this.eventListeners = Object.assign(this.eventListeners, node.properties.on as any);
+                if ("on" in this.node.properties) {
+                    this.eventListeners = Object.assign(this.eventListeners, this.node.properties.on as any);
                 }
             } else {
                 this.attributes = {};
@@ -63,12 +82,9 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
                 this.eventListeners = {};
             }
 
-            if ("children" in node) {
-                this.children = node.children.map((child) => new PupperNode(child, this, renderer));
+            if ("children" in this.node) {
+                this.children = this.node.children.map((child) => new PupperNode(child, this, this.renderer));
             }
-        } else {
-            this.tag = "TEXT";
-            this.text = node;
         }
     }
 
@@ -269,8 +285,6 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
         // @ts-ignore
         const comment = new PupperNode(h.c("!"), this.parent, this.renderer);
 
-        console.log(comment);
-
         this.replaceWith(comment);
 
         return comment;
@@ -283,7 +297,10 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
      */
     public addEventListener(event: keyof DocumentEventMap | string, listener: EventListenerOrEventListenerObject) {
         this.eventListeners[event] = this.eventListeners[event] || [];
-        this.eventListeners[event].push(listener);
+
+        if (!this.eventListeners[event].includes(listener)) {
+            this.eventListeners[event].push(listener);
+        }
     }
 
     /**
@@ -439,6 +456,21 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
         this.dirty = false;
     }
 
+    /**
+     * Called when the element has been added to the DOM.
+     * @param node The node element.
+     */
+    private onElementCreated(node: Element) {
+        this.element = node;
+
+        for(let evt in this.eventListeners) {
+            for(let handler of this.eventListeners[evt]) {
+                this.element.removeEventListener(evt, handler);
+                this.element.addEventListener(evt, handler);
+            }
+        }
+    }
+
     /**
      * Converts the current node into a virtual DOM node.
      * @returns 
@@ -449,7 +481,7 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
         }
 
         if (this.tag === "!") {
-            this.node = new VComment(this.text);
+            this.node = h.c(this.text) as TNode;
             return this.node;
         }
 
@@ -457,7 +489,7 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
             ...this.attributes,
             ...this.properties,
             $p_create: Hook((node: Element) => {
-                this.element = node;
+                this.onElementCreated(node);
             })
         };
 
@@ -467,10 +499,6 @@ export class PupperNode<TNode extends VirtualDOM.VTree = any> {
             delete properties.class;
         }
 
-        for(let evt in this.eventListeners) {
-            properties["on" + evt] = this.eventListeners[evt];
-        }
-
         this.node = h(
             this.tag,
             properties,

+ 3 - 1
packages/renderer/src/core/vdom/Renderer.ts

@@ -8,7 +8,9 @@ import { PupperNode } from "./Node";
 import { diff, patch, create } from "virtual-dom";
 import h from "virtual-dom/h";
 
-const debug = require("debug")("pupper:vdom");
+import Debugger from "../../util/Debugger";
+
+const debug = Debugger.extend("vdom");
 
 /**
  * Most of the evaluation functions were taken from alpine.js

+ 4 - 2
packages/renderer/src/core/vdom/directives/Bind.ts

@@ -2,7 +2,9 @@ import { directive, mapAttributes, replaceWith } from "../../../model/Directive"
 import { evaluateLater } from "../../../model/Evaluator";
 import { effect } from "../../../model/Reactivity";
 
-const debug = require("debug")("pupper:vdom:on");
+import Debugger from "../../../util/Debugger";
+
+const debug = Debugger.extend("vdom:on");
 
 mapAttributes(replaceWith(":", "x-bind:"));
 
@@ -27,7 +29,7 @@ directive("bind", async (node, { value, expression, scope }) => {
             
             node.setDirty();
         } catch(e) {
-            console.warn("[pupperjs] failed to bind property:");
+            console.warn("[pupper.js] failed to bind property:");
             console.error(e);
         }
     });

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

@@ -2,7 +2,9 @@ import { directive, mapAttributes, replaceWith } from "../../../model/Directive"
 import { evaluateLater } from "../../../model/Evaluator";
 import { effect } from "../../../model/Reactivity";
 
-const debug = require("debug")("pupper:vdom:component");
+import Debugger from "../../../util/Debugger";
+
+const debug = Debugger.extend("vdom:component");
 
 mapAttributes(replaceWith(":", "x-bind:"));
 
@@ -20,14 +22,12 @@ directive("component", async (node, { value, expression, 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.removeAttribute("x-component:" + value);
             
             node.setDirty();
         } catch(e) {
-            console.warn("[pupperjs] failed to bind property:");
+            console.warn("[pupper.js] failed to bind property:");
             console.error(e);
         }
     });

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

@@ -4,7 +4,9 @@ import { walk } from "../../../model/NodeWalker";
 import { effect } from "../../../model/Reactivity";
 import { PupperNode } from "../Node";
 
-const debug = require("debug")("pupper:vdom:directives:conditional");
+import Debugger from "../../../util/Debugger";
+
+const debug = Debugger.extend("vdom:directives:conditional");
 
 /**
  * @directive x-if
@@ -49,7 +51,7 @@ directive("if", async (node, { expression, scope }) => {
 
             node.parent.setDirty();
         } catch(e) {
-            console.warn("[pupperjs] failed to evaluate conditional:");
+            console.warn("[pupper.js] failed to evaluate conditional:");
             console.error(e);
         }
     });

+ 10 - 3
packages/renderer/src/core/vdom/directives/EventHandler.ts

@@ -1,7 +1,9 @@
 import { directive, mapAttributes, replaceWith } from "../../../model/Directive";
 import { evaluateLater } from "../../../model/Evaluator";
 
-const debug = require("debug")("pupper:vdom:on");
+import Debugger from "../../../util/Debugger";
+
+const debug = Debugger.extend("vdom:on");
 
 mapAttributes(replaceWith("@", "x-on:"));
 
@@ -13,15 +15,20 @@ directive("on", async (node, { value, expression, scope }) => {
     try {
         const evaluate = expression ? evaluateLater(expression) : () => {};
 
+        debug("will handle event \"%s\" to %O", value, evaluate);
+
         node.addEventListener(value, async ($event: any) => {
             debug("handled %s event", value);
-            evaluate(scope);
+            
+            const evScope = { ...scope, $event };
+
+            evaluate(evScope);
         });
 
         // Remove the prop from the node
         node.removeAttribute("x-on:" + value);
     } catch(e) {
-        console.warn("[pupperjs] failed to evaluate event handler:");
+        console.warn("[pupper.js] failed to evaluate event handler:");
         console.error(e);
     }
 });

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

@@ -26,7 +26,7 @@ directive("html", async (node, { expression, scope }) => {
 
             node.setDirty();
         } catch(e) {
-            console.warn("[pupperjs] failed to set inner HTML:");
+            console.warn("[pupper.js] failed to set inner HTML:");
             console.error(e);
         }
     });

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

@@ -82,7 +82,7 @@ directive("for", async (node, { expression, scope }) => {
 
             node.parent.setDirty();
         } catch(e) {
-            console.warn("[pupperjs] The following information can be useful for debugging:");
+            console.warn("[pupper.js] The following information can be useful for debugging:");
             console.warn("last scope:", loopScope);
             console.error(e);
         }

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

@@ -36,7 +36,7 @@ directive("text", async (node, { expression, scope }) => {
 
             node.setDirty();
         } catch(e) {
-            console.warn("[pupperjs] failed to set inner text:");
+            console.warn("[pupper.js] failed to set inner text:");
             console.error(e);
         }
     });

+ 12 - 10
packages/renderer/src/model/NodeWalker.ts

@@ -1,6 +1,8 @@
 import { PupperNode } from "../core/vdom/Node";
 import { directives } from "./Directive";
 
+import * as Debugger from "../util/Debugger";
+
 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;
@@ -38,26 +40,26 @@ export async function walk<TNode extends PupperNode | PupperNode[]>(nodes: TNode
 }
 
 async function node(node: PupperNode | undefined, scope: any) {
-    console.group(node.tag, node.getAttributesAndProps(), node);
+    Debugger.group(node.tag, node.getAttributesAndProps(), node);
 
     // If it's an invalid node
     if (!node) {
-        console.groupEnd();
+        Debugger.endGroup();
         // Ignore it
         return undefined;
     }
 
     // Ignore if it's a string
     if (typeof node === "string") {
-        console.info("node is a string");
-        console.groupEnd();
+        Debugger.info("node is a string");
+        Debugger.endGroup();
         return node;
     }
 
     // Ignore if it's being ignored
     if (node.isBeingIgnored()) {
-        console.info("node is being ignored");
-        console.groupEnd();
+        Debugger.info("node is being ignored");
+        Debugger.endGroup();
         return node;
     }
 
@@ -70,8 +72,8 @@ async function node(node: PupperNode | undefined, scope: any) {
 
     // If the node was removed, stop parsing
     if (!node.exists()) {
-        console.info("node was removed");
-        console.groupEnd();
+        Debugger.info("node was removed");
+        Debugger.endGroup();
         return node;
     }
 
@@ -82,12 +84,12 @@ async function node(node: PupperNode | undefined, scope: any) {
 
     // If it's non-renderable
     if (!node.isRenderable()) {
-        console.info("node is not renderable");
+        Debugger.info("node is not renderable");
         // Allow parsing its children but prevent itself from being rendered.
         return undefined;
     }
 
-    console.groupEnd();
+    Debugger.endGroup();
 
     return node;
 }

+ 5 - 2
packages/renderer/src/model/Reactivity.ts

@@ -4,7 +4,9 @@ type TReactiveObj = Record<string | number | symbol, any>;
 const effects = new Map<TReactiveObj, Record<string | symbol, TEffect[]>>();
 let currentEffect: TEffect = null;
 
-const debug = require("debug")("pupper:reactivity");
+import Debugger from "../util/Debugger";
+
+const debug = Debugger.extend("reactivity");
 
 const ProxySymbol = Symbol("$Proxy");
 
@@ -87,8 +89,9 @@ export function reactive(obj: TReactiveObj) {
             if (typeof value === null) {
                 target[property] = null;
             } else
-            // Only objects can be reactive
+            // Only objects / arrays can be reactive
             if (typeof value === "object") {
+                // If it's not proxied yet
                 if (value[ProxySymbol] === undefined) {
                     target[property] = reactive(value);
                 }

+ 0 - 19
packages/renderer/src/typings/virtual-dom.d.ts

@@ -1,19 +0,0 @@
-namespace VirtualDOM {
-    interface VComment {
-        comment: string;
-        version: string;
-        type: string;
-    }
-
-    function isVComment(x: any): boolean;
-    
-    interface VCommentConstructor {
-        new (comment: string): VComment;
-    }
-
-    type VTree = VText | VComment | VNode | Widget | Thunk;
-
-    declare interface h {
-        c(comment: string): VComment;
-    }
-}

+ 0 - 10
packages/renderer/src/typings/virtual-dom/vcomment.d.ts

@@ -1,10 +0,0 @@
-declare module "virtual-dom/vnode/vcomment" {
-    import VCommentConstructor = VirtualDOM.VCommentConstructor;
-    const VComment: VCommentConstructor;
-    export = VComment;
-}
-
-declare module "virtual-dom/vnode/is-vcomment" {
-    import isVComment = VirtualDOM.isVComment;
-    export = isVComment;
-}

+ 115 - 0
packages/renderer/src/util/Debugger.ts

@@ -0,0 +1,115 @@
+const debuggerModule = require("debug");
+
+type FLogger = (message: string, ...args: any[]) => void;
+
+export let enabled = localStorage.getItem("pupperjs:debug") === "1";
+
+/**
+ * The base pupper logger instance.
+ */
+export const logger = debuggerModule("pupper") as (FLogger & {
+    extend: (namespace: string) => FLogger
+});
+
+/**
+ * Returns an extended debugger instance.
+ * @param namespace The namespace to extend to.
+ * @returns 
+ */
+export function extend(namespace: string) {
+    return logger.extend(namespace);
+}
+
+/**
+ * Prints a debug 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 debug(message: string, ...args: any[]) {
+    return logger(message, ...args);
+}
+
+/**
+ * Prints a debug information 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 info(message: string, ...args: any[]) {
+    return debug("%c" + message, ...["color: aqua", ...args])
+}
+
+/**
+ * Opens a new group
+ * @param args Any arguments to be passed to console.log()
+ */
+export function group(...args: any[]) {
+    if (enabled) {
+        console.group(...args);
+    }
+}
+
+/**
+ * Ends the last opened group
+ */
+export function endGroup() {
+    if (enabled) {
+        console.groupEnd();
+    }
+}
+
+/**
+ * Toggles the logger (if enabled or not).
+ */
+export function toggleLogger() {
+    if (enabled) {
+        debuggerModule.enable("pupper");
+        debuggerModule.enable("pupper:*");
+    } else {
+        debuggerModule.disable("pupper");
+        debuggerModule.disable("pupper:*");
+    }
+}
+
+/**
+ * Toggles the debug mode (if enabled or not).
+ */
+export function toggleDebug() {
+    enabled = !enabled;
+
+    if (enabled) {
+        localStorage.setItem("pupperjs:debug", "1");
+    } else {
+        localStorage.removeItem("pupperjs:debug");
+    }
+
+    toggleLogger();
+}
+
+const Debugger = {
+    group,
+    endGroup,
+    enabled,
+    toggleDebug,
+    info,
+    debug,
+    extend
+} as const;
+
+export default Debugger;
+
+declare global {
+    interface Window {
+        pDebugger: typeof Debugger
+    }
+}
+
+if (process.env.NODE_ENV !== "production") {
+    toggleLogger();
+
+    window.pDebugger = Debugger;
+
+    console.warn("pupper.js detected a non-production environment.");
+    console.warn("The debugger object has been exposed to the window. You can access it by using window.pDebugger (", window.pDebugger, ")");
+}

+ 1 - 1
packages/renderer/tsconfig.json

@@ -8,6 +8,6 @@
             "@/*": ["./*"]
         }
     },
-    "include": ["./src/*.ts", "src/typings/**/*.d.ts"],
+    "include": ["./src/*.ts", "./src/typings/**/*.d.ts"],
     "exclude": ["node_modules"],
 }

+ 0 - 6
test/index.js

@@ -1,8 +1,5 @@
 import Template from "./templates/template.pupper";
 
-import ImportedComponent from "./templates/ImportedComponent.pupper";
-import ExportedComponent from "./templates/ExportedComponent.pupper";
-
 (async function() {
     window.component = Template;
     await Template.mount(document.getElementById("app"));
@@ -14,7 +11,4 @@ import ExportedComponent from "./templates/ExportedComponent.pupper";
         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
     });
-
-    ExportedComponent.mount(Template.$slots.slot);
-    ImportedComponent.mount(Template.$slots.slot2);
 }());

+ 6 - 10
test/templates/ExportedComponent.pupper

@@ -2,14 +2,10 @@ template
     div
         |This component was exported and loaded by another component!
 
-        div
-            a.text-primary(href="#", @click="alert('Hello world!')")="Testing a link with an event"
+        div.my-3
+            a.text-primary(href="#", @click="onClickLink($event)")="Testing a link with an event"
 
-        hr
-
-script(lang="ts", type="text/javascript")
-    import Pupper from "@pupperjs/renderer";
-
-    export default Pupper.defineComponent({
-
-    });
+implementation
+    #onClickLink(e)
+        e.preventDefault();
+        alert("Hello world!");

+ 5 - 10
test/templates/ImportedComponent.pupper

@@ -6,16 +6,11 @@ template
             |This component has an imported component!
             ExportedComponent()
 
-        div
+        div.my-3
             ="Also mounted into a slot:"
             slot(name="slot")
 
-script(lang="ts", type="text/javascript")
-    import Pupper from "@pupperjs/renderer";
-
-    export default Pupper.defineComponent({
-        mounted() {
-            console.log("Rendering the exported component into slot \"slot\"");
-            ExportedComponent.mount(this.$slots.slot);
-        }
-    });
+implementation
+    when#mounted
+        console.log("Rendering", ExportedComponent, "into named slot \"slot\"", this.$slots.slot);
+        ExportedComponent.mount(this.$slots.slot);

+ 2 - 7
test/templates/template.pupper

@@ -46,10 +46,7 @@ template
                         slot(name="slot")
                         slot(name="slot2")
 
-                footer.mastfoot.mt-auto(p-listener="footerListener")
-                    .inner
-                        p
-                            |Cover template for <a href="https://getbootstrap.com/">Bootstrap</a>, by <a href="https://twitter.com/mdo">@mdo</a>
+import ImportedComponent(from="./ImportedComponent.pupper")
 
 data
     page = {
@@ -88,6 +85,4 @@ implementation
 
     when#mounted
         console.log("The component was mounted.");
-
-    //- Listening to multiple events with one ID'ed listener.
-    listener#footerListener.keyup.keypress
+        ImportedComponent.mount(this.$slots.slot);