瀏覽代碼

added dom parser

Matheus Giovani 3 年之前
父節點
當前提交
0f0ef28e5d

+ 68 - 18
packages/compiler/src/core/Plugin.ts

@@ -1,11 +1,10 @@
-
 import type PugLexer from "pug-lexer";
 import type { PugPlugin, PugToken, PugAST, PugNode, PugNodes, PugNodeAttribute, LexerPlugin, Options } from "pug";
 import type PupperCompiler from "..";
 
 import { Hook } from "./plugin/Hook";
 
-import { IfHook } from "./plugin/hooks/IfHook";
+import { ConditionalHook } from "./plugin/hooks/ConditionalHook";
 import { ForEachHook } from "./plugin/hooks/ForEachHook";
 import { ComponentHook } from "./plugin/hooks/ComponentHook";
 import { PropertyHook } from "./plugin/hooks/PropertyHook";
@@ -20,7 +19,9 @@ import { TagNode } from "./plugin/nodes/TagNode";
 import { NodeModel } from "../model/core/NodeModel";
 import { MixinNode } from "./plugin/nodes/MixinNode";
 import { ConditionalNode } from "./plugin/nodes/ConditionalNode";
-import { InspectNode } from "../util/NodeUtil";
+import pugError from "pug-error";
+import { Pug } from "../typings/pug";
+import { TemplateTagNode } from "./plugin/nodes/tags/TemplateTagNode";
 
 type THookArray = { new(plugin: Plugin): Hook }[];
 
@@ -63,8 +64,8 @@ export { PugToken, PugAST, PugNode, PugNodeAttribute, PugNodes, CompilerNode as
  */
 export default class Plugin implements PugPlugin {
     public static Hooks: THookArray = [
-        IfHook,
-        ForEachHook,
+        ConditionalHook,
+        //ForEachHook,
         ComponentHook,
         PropertyHook,
         PupperToAlpineHook,
@@ -92,7 +93,7 @@ export default class Plugin implements PugPlugin {
                 return new EachNode(node, parent);
 
             case "Tag":
-                return new TagNode(node, parent);
+                return this.makeTagNode(node, parent);
 
             case "Mixin":
                 return new MixinNode(node, parent);
@@ -102,6 +103,22 @@ export default class Plugin implements PugPlugin {
         }
     }
 
+    /**
+     * Creates a compiler tag node.
+     * @param node The pug node related to this new node.
+     * @param parent The parent node related to this node.
+     * @returns 
+     */
+    public static makeTagNode(node: Pug.Nodes.TagNode, parent: NodeModel): TagNode {
+        switch(node.name) {
+            default:
+                return new TagNode(node, parent);
+
+            case "template":
+                return new TemplateTagNode(node, parent);
+        }
+    }
+
     /**
      * A handler for the plugin filters.
      */
@@ -216,25 +233,27 @@ export default class Plugin implements PugPlugin {
      * @param node The node or node array to be parsed.
      * @returns 
      */
-    public parseChildren(node: NodeModel|NodeModel[]) {
+    public parseChildren<TInput extends NodeModel | NodeModel[], TResult>(node: TInput) {
         if (Array.isArray(node)) {
             this.applyFilters("parse", node);
 
             node.forEach((node) => {
                 this.parseChildren(node);
             });
-        } else {
-            node.setChildren(
-                this.applyFilters("parse", node.getChildren())
-            );
-
-            node.getChildren().forEach((child) => {
-                if (child.hasChildren()) {
-                    this.parseChildren(child);
-                }
-            });
+
+            return node;
         }
 
+        node.setChildren(
+            this.applyFilters("parse", node.getChildren())
+        );
+
+        node.getChildren().forEach((child) => {
+            if (child.hasChildren()) {
+                this.parseChildren(child);
+            }
+        });
+
         return node;
     }
 
@@ -245,7 +264,7 @@ export default class Plugin implements PugPlugin {
      */
     public parseNodes(ast: PugAST) {
         try {
-            const astNode = new AstNode(ast);
+            const astNode = new AstNode(ast, this);
 
             // Parse the AST children
             this.parseChildren(astNode);
@@ -281,4 +300,35 @@ export default class Plugin implements PugPlugin {
     public postCodeGen(code: string): string {
         return this.applyFilters("postCodeGen", code);
     }
+
+    /**
+     * Makes a compilation error.
+     * @param code The error code.
+     * @param message The error message.
+     * @param data The error data.
+     * @returns 
+     */
+    public makeError(code: string, message: string, data: {
+        line?: number;
+        column?: number;
+    } = {}) {
+        return pugError(code, message, {
+            ...data,
+            filename: this.options.filename,
+            src: this.options.contents
+        } as any);
+    }
+
+    /**
+     * Makes an error with "COMPILATION_ERROR" code.
+     * @param message The error message.
+     * @param data The error data.
+     * @returns 
+     */
+    public makeParseError(message: string, data: {
+        line?: number;
+        column?: number;
+    } = {}) {
+        return this.makeError("COMPILATION_ERROR", message, data);
+    }
 }

+ 1 - 5
packages/compiler/src/core/plugin/Hook.ts

@@ -97,10 +97,6 @@ export abstract class Hook {
         line?: number;
         column?: number;
     } = {}) {
-        return PugError(code, message, {
-            ...data,
-            filename: this.plugin.options.filename,
-            src: this.plugin.options.contents
-        } as any);
+        return this.plugin.makeError(code, message, data);
     }
 }

+ 2 - 2
packages/compiler/src/core/plugin/hooks/ComponentHook.ts

@@ -2,7 +2,7 @@ import { IPluginNode } from "../../Plugin";
 import { Hook } from "../Hook";
 import { TagNode } from "../nodes/TagNode";
 import { ScriptParser } from "./component/ScriptParser";
-import { IfHook } from "./IfHook";
+import { ConditionalHook } from "./ConditionalHook";
 import { StyleAndScriptHook } from "./StyleAndScriptHook";
 
 const DefaultExportSymbol = Symbol("ExportedComponent");
@@ -17,7 +17,7 @@ export interface IComponent {
 }
 
 export class ComponentHook extends Hook {
-    public $after = [IfHook, StyleAndScriptHook];
+    public $after = [ConditionalHook, StyleAndScriptHook];
 
     /**
      * The imports that will later be putted into the template header

+ 1 - 4
packages/compiler/src/core/plugin/hooks/IfHook.ts → packages/compiler/src/core/plugin/hooks/ConditionalHook.ts

@@ -2,7 +2,7 @@ import { Hook } from "../Hook";
 import { ConditionalNode } from "../nodes/ConditionalNode";
 import { TagNode } from "../nodes/TagNode";
 
-export class IfHook extends Hook {
+export class ConditionalHook extends Hook {
     public parse(nodes: ConditionalNode[]) {
         for(let node of nodes) {
             // Check if it's a conditional
@@ -11,9 +11,6 @@ export class IfHook extends Hook {
                 const alternate = node.getElse();
 
                 // Replace with an if <div x-if>
-                // @todo this is actually buggy and not working.
-                // For some reason, the children are not being parsed correctly and
-                // are leaving as pug Javascript inside (like each iteration)
                 const conditional = node.replaceWith({
                     type: "Tag",
                     name: "template",

+ 10 - 3
packages/compiler/src/core/plugin/hooks/PupperToAlpineHook.ts

@@ -18,11 +18,15 @@ export class PupperToAlpineHook extends Hook {
         "ref": "x-ref",
         "p-cloak": "x-cloak",
         "p-if": "x-if",
-        "p-id": "x-id"
+        "p-id": "x-id",
+
+        "p-render:to" : "x-teleport",
+        "p-render:before": "x-teleport",
+        "p-render:after": "x-teleport"
     };
 
     public lex(tokens: PugToken[]) {
-        return tokens.map((token, index) => {
+        return tokens.map((token) => {
             // We want only attribute tokens
             if (token.type !== "attribute") {
                 return token;
@@ -34,7 +38,10 @@ export class PupperToAlpineHook extends Hook {
                 token.name = PupperToAlpineHook.Attributes[token.name];
             }
 
-            // If it's a p-insert
+            // If it's a p-render
+            if (token.name.startsWith("p-render:")) {
+                console.log(token);
+            }
 
             return token;
         });

+ 3 - 3
packages/compiler/src/core/plugin/hooks/component/ScriptParser.ts

@@ -67,7 +67,7 @@ export class ScriptParser {
             componentPropsComponents.addPropertyAssignment({
                 name: alias,
                 initializer: alias
-            })
+            });
         }
     }
 
@@ -78,7 +78,7 @@ export class ScriptParser {
         let exportedComponents = componentProps.getProperty("components");
 
         if (exportedComponents) {
-            return exportedComponents;
+            return exportedComponents.getFirstChildByKindOrThrow(SyntaxKind.ObjectLiteralExpression);
         }
 
         return componentProps.addPropertyAssignment({
@@ -108,7 +108,7 @@ export class ScriptParser {
             // Add them to the components
             remainingComponents.forEach((component) => {
                 importedComponents.addPropertyAssignment({
-                    name: component.name,
+                    name: String(component.name),
                     initializer: component.name as string
                 });
             });

+ 6 - 1
packages/compiler/src/core/plugin/nodes/AstNode.ts

@@ -3,7 +3,12 @@ import Plugin, { PugAST } from "../../Plugin";
 
 export class AstNode extends NodeModel {
     constructor(
-        protected node: PugAST
+        protected node: PugAST,
+
+        /**
+         * The plugin related to this AST node.
+         */
+        public plugin: Plugin
     ) {
         super();
         

+ 28 - 4
packages/compiler/src/core/plugin/nodes/EachNode.ts

@@ -7,15 +7,19 @@ export class EachNode extends CompilerNode<Pug.Nodes.EachNode> {
      * @returns 
      */
     public getObjectName() {
-        return this.pugNode.obj;
+        return this.pugNode.obj?.trim();
+    }
+
+    public hasKeyName() {
+        return this.pugNode.key !== null;
     }
 
     /**
      * Retrieves the variable name for the iteration index.
      * @returns 
      */
-    public getIndexName() {
-        return this.pugNode.index;
+    public getKeyName() {
+        return this.hasKeyName() ? this.pugNode.val?.trim() : this.pugNode.key?.trim();
     }
 
     /**
@@ -23,6 +27,26 @@ export class EachNode extends CompilerNode<Pug.Nodes.EachNode> {
      * @returns 
      */
     public getValueName() {
-        return this.pugNode.val;
+        return this.hasKeyName() ? this.pugNode.key?.trim() : this.pugNode.val?.trim();
+    }
+
+    public toPugNode(): Pug.Nodes.TagNode {
+        let parsedConditional = /*js*/`${this.getValueName()} in ${this.getObjectName()}`;
+        let children = this.plugin.parseChildren(this.getChildren()).map((child) => child.toPugNode());
+
+        if (this.hasKeyName()) {
+            // In pug, key and value are inverted when a key is informed
+            // We are de-inverting it here
+            parsedConditional = /*js*/`(${this.getValueName()}, ${this.getKeyName()}) in ${this.getObjectName()}`;
+        }
+
+        return CompilerNode.parseNodeIntoPugNode({
+            type: "Tag",
+            name: "Template",
+            attributes: {
+                "x-for": parsedConditional
+            },
+            children: children
+        });
     }
 }

+ 8 - 0
packages/compiler/src/core/plugin/nodes/TagNode.ts

@@ -2,6 +2,14 @@ import { Pug } from "../../../typings/pug";
 import { BlockedCompilerNode } from "../../../model/core/nodes/BlockedCompilerNode";
 
 export class TagNode extends BlockedCompilerNode<Pug.Nodes.TagNode> {
+    /**
+     * Retrieves the tag name.
+     * @returns 
+     */
+    public getName() {
+        return this.getProp("name");
+    }
+
     /**
      * Checks if the node has the given attribute.
      * @param name The attribute name.

+ 16 - 0
packages/compiler/src/core/plugin/nodes/tags/TemplateTagNode.ts

@@ -0,0 +1,16 @@
+import { TagNode } from "../TagNode";
+
+export class TemplateTagNode extends TagNode {
+    public getName() {
+        return "template";
+    }
+
+    public toPugNode() {
+        // Template tags can only have one children
+        if (this.getChildren().length > 1) {
+            throw this.makeParseError("Template tags should only have one children");
+        }
+
+        return super.toPugNode();
+    }
+}

+ 39 - 6
packages/compiler/src/model/core/nodes/CompilerNode.ts

@@ -1,4 +1,5 @@
-import Plugin, { PugNodes, PugNodeAttribute, TPugNodeTypes, TCompilerNode } from "../../../core/Plugin";
+import Plugin, { PugNodes, PugNodeAttribute, TPugNodeTypes, TCompilerNode, TPugNodesWithTypes } from "../../../core/Plugin";
+import { AstNode } from "../../../core/plugin/nodes/AstNode";
 import { NodeModel } from "../NodeModel";
 
 export interface IParserNode {
@@ -13,7 +14,7 @@ export interface IParserNode {
 
 export type TNodes = PugNodes | CompilerNode | IParserNode;
 
-export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<CompilerNode> {
+export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<CompilerNode<any>> {
     /**
      * Makes a pug attribute node.
      * @param key The attribute name.
@@ -22,6 +23,7 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
      */
     public static makePugNodeAttribute(key: string, value: string | boolean | number): PugNodeAttribute {
         if (typeof value === "string") {
+            value = value.replace(/"/g, "\\\"");
             value = `"${value}"`;
         }
 
@@ -37,7 +39,7 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
      * @param node The node to be parsed.
      * @returns 
      */
-    public static parseNodeIntoPugNode(node: IParserNode) {
+    public static parseNodeIntoPugNode<TNode extends IParserNode>(node: TNode): TPugNodesWithTypes[TNode["type"]] {
         if (!("type" in node)) {
             throw new Error("No node type was given.");
         }
@@ -80,7 +82,7 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
             delete finalNode.children;
         }
 
-        return finalNode;
+        return finalNode as any;
     }
 
     /**
@@ -97,7 +99,12 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
         /**
          * The parent array that contains this node.
          */ 
-        public parent?: NodeModel
+        public parent?: NodeModel,
+
+        /**
+         * The plugin related to this compiler node.
+         */
+        protected plugin?: Plugin
     ) {
         super(parent);
 
@@ -110,6 +117,20 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
                 )
             });
         }
+
+        // If no plugin was given
+        if (!plugin) {
+            // Try retrieving the parent plugin
+            let parent = this.parent;
+
+            do {
+                if (parent?.parent) {
+                    parent = parent.parent;
+                }
+            } while(!(parent instanceof AstNode));
+
+            this.plugin = parent.plugin;
+        }
     }
 
     /**
@@ -249,11 +270,23 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
         return this;
     }
 
+    /**
+     * Makes a parsing error for this node.
+     * @param message The error message.
+     * @returns 
+     */
+    public makeParseError(message: string) {
+        return this.plugin.makeParseError(message, {
+            line: this.getLine(),
+            column: this.getColumn()
+        });
+    }
+
     /**
      * Converts the node back into a pug node.
      * @returns 
      */
-    public toPugNode() {
+    public toPugNode(): TNode | PugNodes {
         const finalNode = { ...this.pugNode };
 
         if (this.hasChildren() || this.pugNode.block) {        

+ 1 - 1
packages/compiler/src/typings/pug.d.ts

@@ -193,7 +193,7 @@ export declare namespace Pug {
             type: "Each";
             val: string;
             obj: string;
-            index: string;
+            key: string;
         }
 
         export declare interface MixinNode extends PugNode {

+ 1 - 1
packages/compiler/src/util/NodeUtil.ts

@@ -1,4 +1,4 @@
-import { appendFileSync, writeFileSync } from "fs";
+import { appendFileSync } from "fs";
 import { inspect } from "util";
 import { NodeModel } from "../model/core/NodeModel";
 

+ 2 - 0
packages/renderer/package.json

@@ -12,11 +12,13 @@
   },
   "dependencies": {
     "alpinejs": "^3.10.2",
+    "morphdom": "^2.6.1",
     "pug": "^3.0.2"
   },
   "types": "./types/",
   "devDependencies": {
     "@types/alpinejs": "^3.7.0",
+    "@types/morphdom": "^2.4.2",
     "@types/node": "^16.7.6",
     "debug": "^4.3.2",
     "npm-run-all": "^4.1.5",

+ 23 - 14
packages/renderer/src/core/Component.ts

@@ -1,4 +1,5 @@
 import Alpine from "alpinejs";
+import { DOMParser } from "./DomParser";
 
 /**
  * Represents a slot.
@@ -7,7 +8,7 @@ interface Slot {
     /**
      * The comment holding the slot position.
      */
-    container: Element | Comment;
+    container: HTMLElement | Comment;
 
     /**
      * All fallback nodes
@@ -89,6 +90,8 @@ export class PupperComponent {
      */
     public $refs: Record<string, HTMLElement> = {};
 
+    protected parser: DOMParser;
+
     constructor(
         /**
          * The component properties.
@@ -181,13 +184,15 @@ export class PupperComponent {
     }
 
     /**
-     * Renders a template string into a template tag with a div with [data-rendered-template] attribute.
+     * Renders a template string into a template tag with a div with [pup] attribute.
      * @param string The template string to be rendered.
      * @returns 
      */
     private renderStringToTemplate(string: string): HTMLTemplateElement {
         const renderContainer = document.createElement("template");
-        renderContainer.innerHTML = `<div data-rendered-template>${string}</div>`;
+
+        // @todo this div needs to be removed
+        renderContainer.innerHTML = `<div pup>${string}</div>`;
 
         return renderContainer;
     }
@@ -244,7 +249,7 @@ export class PupperComponent {
             ref.removeAttribute("ref");
         }
 
-        const container = renderContainer.content.children[0];
+        const container = renderContainer.content.children[0] as HTMLElement;
 
         return container;
     }
@@ -254,7 +259,7 @@ export class PupperComponent {
      * @param target The target element where the element will be mounted.
      * @returns 
      */
-    public mount(target: HTMLElement | Slot) {
+    public async mount(target: HTMLElement | Slot) {
         this.$identifier = "p_" + String((Math.random() + 1).toString(36).substring(2));
 
         const rendered = this.render();
@@ -272,21 +277,25 @@ export class PupperComponent {
             };
         });
 
+        // If it's targeting a slot
         if (!(target instanceof HTMLElement)) {
-            target.container.replaceWith(rendered);
-            target.container = rendered;
+            const replaced = document.createElement("div");
+            replaced.setAttribute("p-slot", "1");
+
+            target.container.replaceWith(replaced);
+            this.parser = new DOMParser(replaced);
+            
         } else {
-            target.appendChild(rendered);
+            // Append it to the virtual DOM
+            this.parser = new DOMParser(target);
         }
 
-        // Initialize Alpine for it
-        Alpine.initTree(rendered);
-
-        // Remove the identifier
-        //rendered.removeAttribute("x-data");
+        const mounted = await this.parser.appendChild(rendered);
 
         // Save a reference to the internal Alpine data proxy
         // @ts-ignore
-        this.$data = rendered._x_dataStack[0];      
+        this.$data = mounted._x_dataStack[0];
+
+        return mounted;
     }
 }

+ 135 - 0
packages/renderer/src/core/DomParser.ts

@@ -0,0 +1,135 @@
+import Alpine from "alpinejs";
+import morphdom from "morphdom";
+
+const AlpineNames = ["x-data", "x-teleport", "x-text", "x-html"];
+
+/**
+ * DOM parser is everything a virtual DOM wants to be.
+ * 
+ * It parses the Alpine reactivity inside a template tag
+ * and then removes everything related to Alpine like
+ * the attributes starting with "@" and ":", and also
+ * remove the Alpine-related tags like "x-data" or "x-html".
+ * 
+ * @todo would be useful in the future to use a real virtual DOM
+ * by the pug-code-gen thing.
+ */
+export class DOMParser {
+    /**
+     * The virtual dom where Alpine will work on.
+     */
+    public template = document.createElement("template");
+
+    protected templateObserver: MutationObserver;
+    observer: MutationObserver;
+
+    constructor(
+        /**
+         * The container where the application is hosted.
+         * If none is given, will target the document body.
+         */
+        protected container: HTMLElement = document.body
+    ) {
+        this.observer = new MutationObserver(this.observeMutations.bind(this));
+        this.observer.observe(this.container, {
+            childList: true,
+            subtree: true
+        });
+    }
+
+    protected observeMutations(mutations: MutationRecord[]) {
+        const queue: HTMLElement[] = [];
+
+        for(let mutation of mutations) {
+            if (!(mutation.target instanceof HTMLElement)) {
+                continue;
+            }
+
+            if (queue.includes(mutation.target)) {
+                continue;
+            }
+
+            queue.push(mutation.target);
+        }
+
+        queue.forEach((node) => {
+            if (!(node instanceof HTMLElement)) {
+                return;
+            }
+
+            this.filterChildrenAlpineAttributes(node);
+        });
+    }
+
+    /**
+     * Initializes a virtual dom node like Alpine does in start().
+     * @param node The node to be initialized.
+     */
+    protected initNode(node: HTMLElement) {
+        Alpine.initTree(node);
+    }
+
+    /**
+     * Appends a child to the virtual DOM.
+     * @param child The child to be appended.
+     * @returns 
+     */
+    public async appendChild(child: HTMLElement) {
+        // Append it to the virtual DOM
+        child = this.template.content.appendChild(child);
+
+        this.initNode(child);
+
+        // Wait for the next tick
+        await this.nextTick();
+
+        this.flush();
+
+        return child;
+    }
+
+    /**
+     * Waits for the next parser tick.
+     */
+    public nextTick() {
+        return new Promise<void>((resolve) => Alpine.nextTick(resolve));
+    }
+
+    protected flush() {
+        morphdom(this.container, this.template.content, {
+            childrenOnly: true,
+            onNodeAdded: (node) => {
+                if (node instanceof HTMLElement) {
+                    if (
+                        (node instanceof HTMLTemplateElement) ||
+                        (node.tagName === "SLOT")
+                    ) {
+                        node.remove();
+                        return node;
+                    }
+
+                    this.filterAlpineAttributes(node);
+                }
+
+                return node;
+            }
+        });
+    }
+
+    protected filterAlpineAttributes(el: Element) {
+        Array.from(el.attributes).forEach(({ name }) => {
+            if (name.startsWith("@") || name.startsWith(":") || AlpineNames.includes(name)) {
+                el.removeAttribute(name);
+            }
+        });
+    }
+
+    protected filterChildrenAlpineAttributes(node: Element) {
+        node.querySelectorAll("template, slot").forEach((node) => node.remove());
+        
+        return Array.from(node.querySelectorAll("*"))
+            .forEach((el) => 
+                this.filterAlpineAttributes(el)
+            );
+    }
+}

+ 29 - 5
packages/renderer/src/index.ts

@@ -1,8 +1,32 @@
+import Alpine from "alpinejs";
+
 import { PupperComponent as Component } from "./core/Component";
 
-const defineComponent = Component.create;
+export default class Pupper {
+    /**
+     * The default component class
+     */
+    public static Component = Component;
+
+    public static defineComponent = Component.create;
+
+    /**
+     * Sets a state in the global store.
+     * @param name The state key.
+     * @param value If set, will change the key value.
+     * @returns 
+     */
+    public static store(name: string, value?: any) {
+        return Alpine.store(name, value);
+    };
+    
+    /**
+     * The Pupper global state.
+     */
+    public static $global = Alpine.store("__GLOBAL__") as Record<string, any>;
+};
+
+// Sets the global magic
+Alpine.magic("global", () => Pupper.$global);
 
-export = {
-    Component,
-    defineComponent
-}
+module.exports = Pupper;

+ 6 - 4
packages/renderer/types/core/Component.d.ts

@@ -1,3 +1,4 @@
+import { DOMParser } from "./DomParser";
 /**
  * Represents a slot.
  */
@@ -5,7 +6,7 @@ interface Slot {
     /**
      * The comment holding the slot position.
      */
-    container: Element | Comment;
+    container: HTMLElement | Comment;
     /**
      * All fallback nodes
      */
@@ -68,6 +69,7 @@ export declare class PupperComponent {
      * Any component references.
      */
     $refs: Record<string, HTMLElement>;
+    protected parser: DOMParser;
     constructor(
     /**
      * The component properties.
@@ -93,7 +95,7 @@ export declare class PupperComponent {
      */
     renderTemplate(template: string): NodeListOf<ChildNode>;
     /**
-     * Renders a template string into a template tag with a div with [data-rendered-template] attribute.
+     * Renders a template string into a template tag with a div with [pup] attribute.
      * @param string The template string to be rendered.
      * @returns
      */
@@ -101,12 +103,12 @@ export declare class PupperComponent {
     /**
      * Renders the template function into a div tag.
      */
-    render(data?: Record<string, any>): Element;
+    render(data?: Record<string, any>): HTMLElement;
     /**
      * Renders and mounts the template into a given element.
      * @param target The target element where the element will be mounted.
      * @returns
      */
-    mount(target: HTMLElement | Slot): void;
+    mount(target: HTMLElement | Slot): Promise<HTMLElement>;
 }
 export {};

+ 18 - 5
packages/renderer/types/index.d.ts

@@ -1,6 +1,19 @@
 import { PupperComponent as Component } from "./core/Component";
-declare const _default: {
-    Component: typeof Component;
-    defineComponent: typeof Component.create;
-};
-export = _default;
+export default class Pupper {
+    /**
+     * The default component class
+     */
+    static Component: typeof Component;
+    static defineComponent: typeof Component.create;
+    /**
+     * Sets a state in the global store.
+     * @param name The state key.
+     * @param value If set, will change the key value.
+     * @returns
+     */
+    static store(name: string, value?: any): void;
+    /**
+     * The Pupper global state.
+     */
+    static $global: Record<string, any>;
+}

+ 43 - 43
test/index.js

@@ -4,51 +4,51 @@ import { defineComponent } from "@pupperjs/renderer";
 import ImportedComponent from "./templates/ImportedComponent.pupper";
 import ExportedComponent from "./templates/ExportedComponent.pupper";
 
-console.log("Imported", ImportedComponent);
-console.log("Exported", ExportedComponent);
-
-const pupper = defineComponent({
-    render: Template,
-    methods: {
-        onClickPuppy(puppy) {
-            alert("You clicked puppy " + puppy.id + "! :D");
-        }
-    },
-    data: {
-        page: {
-            title: "pupper.js is awesome!",
-            description: "I use pupper.js because I love puppies!",
-            lead: "Also we love puppers, shiberinos and other doggos too! 🐶"
+    (async function() {
+    const pupper = defineComponent({
+        render: Template,
+        methods: {
+            onClickPuppy(puppy) {
+                alert("You clicked puppy " + puppy.id + "! :D");
+            }
         },
-        puppies: [
-            {
-                id: 1,
-                title: "A cutie pup",
-                description: "Look at this cutie",
-                thumbnail: "https://placedog.net/800",
-                properties: ["beautiful", "doge"]
+        data: {
+            page: {
+                title: "pupper.js is awesome!",
+                description: "I use pupper.js because I love puppies!",
+                lead: "Also we love puppers, shiberinos and other doggos too! 🐶"
             },
-            {
-                id: 2,
-                title: "Another cute pup",
-                description: "Isn't it a cute doggo?!",
-                thumbnail: "https://placedog.net/400",
-                properties: ["wow", "much woof"]
-            }
-        ]
-    }
-});
+            puppies: [
+                {
+                    id: 1,
+                    title: "A cutie pup",
+                    description: "Look at this cutie",
+                    thumbnail: "https://placedog.net/800",
+                    properties: ["beautiful", "doge"]
+                },
+                {
+                    id: 2,
+                    title: "Another cute pup",
+                    description: "Isn't it a cute doggo?!",
+                    thumbnail: "https://placedog.net/400",
+                    properties: ["wow", "much woof"]
+                }
+            ]
+        }
+    });
 
-window.component = pupper;
-pupper.mount(document.getElementById("app"));
+    window.component = pupper;
+    
+    await pupper.mount(document.getElementById("app"));
 
-pupper.puppies.push({
-    id: 3,
-    title: "Wow, a shibe!",
-    description: "Cute shiberino!!!",
-    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
-});
+    pupper.puppies.push({
+        id: 3,
+        title: "Wow, a shibe!",
+        description: "Cute shiberino!!!",
+        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(pupper.$slots.slot);
-ImportedComponent.mount(pupper.$slots.slot2);
+    ExportedComponent.mount(pupper.$slots.slot);
+    ImportedComponent.mount(pupper.$slots.slot2);
+}());

+ 2 - 2
test/templates/template.pupper

@@ -19,9 +19,9 @@ link(href="https://getbootstrap.com/docs/4.0/examples/cover/cover.css", rel="sty
                     |Oh noe! No puppies to show :(
                 else
                     //- Render the puppies and share the onClickPuppy method with it
-                    each puppy in puppies
+                    each index, puppy in puppies
                         .col-5.mb-5
-                            .puppy.card.px-0(:data-id="puppy.id", @click="onClickPuppy(puppy)").text-dark
+                            .puppy.card.px-0(:data-pop="index", :data-id="puppy.id", @click="onClickPuppy(puppy)").text-dark
                                 img.card-img-top(:src="puppy.thumbnail", crossorigin="auto")
 
                                 .card-header