Bladeren bron

added style pre-processor and scoped styles

Matheus Giovani 2 jaren geleden
bovenliggende
commit
b34035f18e

+ 2 - 0
packages/compiler/package.json

@@ -13,11 +13,13 @@
     "watch:ts": "tsc -watch"
   },
   "dependencies": {
+    "postcss": "^8.4.14",
     "pug": "^3.0.2",
     "pug-code-gen": "^2.0.3",
     "pug-error": "^2.0.0",
     "pug-linker": "^4.0.0",
     "pug-parser": "^6.0.0",
+    "sass": "^1.52.3",
     "ts-morph": "^15.1.0"
   },
   "types": "./types/",

+ 2 - 2
packages/compiler/src/core/Plugin.ts

@@ -50,12 +50,12 @@ interface INodeModelPugNodeTypeRelationship extends Record<TPugNodeTypes, TCompi
 /**
  * Retrieves a node model by the pug node type.
  */
-type TNodeModelByPugNodeType<TNode extends TPugNodeTypes> = Pick<INodeModelPugNodeTypeRelationship, TNode>;
+export type TNodeModelByPugNodeType<TNode extends TPugNodeTypes> = INodeModelPugNodeTypeRelationship[TNode];
 
 /**
  * Retrieves the node model by the pug node.
  */
-type TNodeModelByPugNode<TNode extends PugNodes, TNodeType extends TPugNodeTypes = TNode["type"]> = TNodeModelByPugNodeType<TNodeType>;
+export type TNodeModelByPugNode<TNode extends PugNodes, TNodeType extends TPugNodeTypes = TNode["type"]> = TNodeModelByPugNodeType<TNodeType>;
 
 export { PugToken, PugAST, PugNode, PugNodeAttribute, PugNodes, CompilerNode as IPluginNode };
 

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

@@ -63,9 +63,31 @@ export class ScriptParser {
             this.processComponentCustomEvents();
         }
 
+        if (this.component.style?.length > 0) {
+            this.processComponentStyle();
+        }
+
         return this.sourceFile.getText();
     }
 
+    private processComponentStyle() {
+        const mounted = this.findOrCreateComponentMethod("mounted");
+
+        mounted.setBodyText(
+            (mounted.hasBody() ? mounted.getBodyText() + "\n" : "") +
+            /*js*/`this.$p__style = document.createElement("style");` +
+            /*js*/`this.$p__style.innerHTML = "${this.component.style.replace(/"/g, "\\\"")}";` +
+            /*js*/`document.head.appendChild(this.$p__style);`
+        );
+
+        const unmounted = this.findOrCreateComponentMethod("unmounted");
+
+        unmounted.setBodyText(
+            (unmounted.hasBody() ? unmounted.getBodyText() + "\n" : "") +
+            /*js*/`this.$p__style.remove();`
+        );
+    }
+
     private processComponentData() {
         // Create the data parser
         const data = this.project.createSourceFile("data.js", this.component.data);

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

@@ -53,14 +53,14 @@ export class TagNode extends BlockedCompilerNode<Pug.Nodes.TagNode> {
         if (!this.hasAttribute(name)) {
             attr = {
                 name,
-                val: String(value),
+                val: value,
                 mustEscape: false
             };
 
             this.pugNode.attrs.push(attr);
         } else {
             attr = this.getRawAttribute(name);
-            attr.val = String(value);
+            attr.val = value;
         }
 
         return attr;

+ 44 - 6
packages/compiler/src/core/plugin/phases/PrepareComponentsHook.ts

@@ -1,3 +1,6 @@
+import sass from "sass";
+import postcss, { Rule } from "postcss";
+
 import { IPluginNode } from "../../Plugin";
 import { Hook } from "../Hook";
 import { TagNode } from "../nodes/TagNode";
@@ -31,6 +34,7 @@ export interface IComponent {
 
     template: string;
     script?: string;
+    scope?: string;
     style?: string;
     data?: string;
 
@@ -191,10 +195,6 @@ export class PrepareComponents extends Hook {
             ).parse();
 
             code = `${parsedScript}\n`;
-
-            if (exportedComponent.style) {
-                code += `\n${exportedComponent.style}\n`;
-            }
         }
 
         return code;
@@ -230,6 +230,7 @@ export class PrepareComponents extends Hook {
             },
             template: null,
             script: null,
+            scope: style?.hasAttribute("scoped") ? (Math.random() + 1).toString(36).substring(2) : null,
             style: null,
             data: null,
             exported: isRootComponent
@@ -259,7 +260,27 @@ export class PrepareComponents extends Hook {
 
         // If has a style
         if (style) {
-            console.log(style);
+            component.style = ConsumeChildrenAsString(style);
+
+            // If it's sass or scss
+            if (style.getAttribute("lang") === "sass" || style.getAttribute("lang") === "scss") {
+                component.style = sass.compileString(component.style, {
+                    style: this.compiler.options.debug ? "expanded" : "compressed"
+                }).css;
+            }
+
+            // If it's scoped
+            if (component.scope !== null) {
+                const css = postcss.parse(component.style);
+
+                css.nodes.forEach((node) => {
+                    if (node instanceof Rule) {
+                        node.selector = "[data-" + component.scope + "] " + node.selector;
+                    }
+                });
+
+                component.style = css.toResult().css;
+            }
         }
 
         // If has data
@@ -267,7 +288,7 @@ export class PrepareComponents extends Hook {
             component.data = ConsumeChildrenAsString(data);
         }
 
-        // If has methods
+        // If has implementation
         if (implementation) {
             component.implementation = this.consumeAsImplementation(implementation);
         }
@@ -295,6 +316,23 @@ export class PrepareComponents extends Hook {
             const compiler = new PupperCompiler(this.plugin.options);
             compiler.setSharedData(this.plugin.sharedData);
 
+            // If the component is scoped
+            if (component.scope !== null) {
+                let isFirstNode = true;
+
+                // Add the scope to the first component child
+                compiler.plugin.addFilter("parse", (nodes: IPluginNode[]) => {
+                    if (isFirstNode) {
+                        const node = nodes.find((node) => node instanceof TagNode) as TagNode;
+
+                        node.setAttribute("data-" + component.scope, true);
+                        isFirstNode = false;
+                    }
+
+                    return nodes;
+                }, null);
+            }
+
             const templateAsString = compiler.compileTemplate(contents);
             component.template = templateAsString;
         }

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

@@ -1,4 +1,4 @@
-import Plugin, { PugNodes, PugNodeAttribute, TPugNodeTypes, TCompilerNode, TPugNodesWithTypes } from "../../../core/Plugin";
+import Plugin, { PugNodes, PugNodeAttribute, TPugNodeTypes, TCompilerNode, TPugNodesWithTypes, TNodeModelByPugNode, TNodeModelByPugNodeType } from "../../../core/Plugin";
 import { AstNode } from "../../../core/plugin/nodes/AstNode";
 import { TagNode } from "../../../core/plugin/nodes/TagNode";
 import { NodeModel } from "../NodeModel";
@@ -155,8 +155,8 @@ export class CompilerNode<TNode extends PugNodes = any> extends NodeModel<Compil
      * @param type The children node type.
      * @returns 
      */
-    public findFirstChildByType(type: PugNodes["type"]) {
-        return this.children.find((child) => child.isType(type));
+    public findFirstChildByType<TType extends TPugNodeTypes>(type: TType): TNodeModelByPugNodeType<TType> {
+        return this.children.find((child) => child.isType(type)) as any;
     }
 
     /**

+ 7 - 0
test/templates/template.pupper

@@ -77,6 +77,13 @@ data
         }
     ]
 
+style(lang="scss", scoped)
+    .row {
+        .puppy {
+            box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.3);
+        }
+    }
+
 implementation
     //- Declaring methods
     #onClickPuppy(