UNPKG

@11ty/webc

Version:

Single File Web Components

260 lines (222 loc) 6.6 kB
import { AstSerializer } from "./ast.js"; class AstQuery { // List from the parse5 serializer // https://github.com/inikulin/parse5/blob/3955dcc158031cc773a18517d2eabe8b17107aa3/packages/parse5/lib/serializer/index.ts static voidElements = { area: true, base: true, basefont: true, bgsound: true, br: true, col: true, embed: true, frame: true, hr: true, img: true, input: true, keygen: true, link: true, meta: true, param: true, source: true, track: true, wbr: true, }; /* Tag Names */ static getTagName(node) { let is = AstQuery.getAttributeValue(node, AstSerializer.attrs.IS); if(is) { return is; } return node.tagName; } static isVoidElement(tagName) { return AstQuery.voidElements[tagName] || false; } /* Specific queries */ static getSlotTargets(node) { let targetNodes = AstQuery.findAllElements(node, "slot"); let map = {}; for(let target of targetNodes) { let name = AstQuery.getAttributeValue(target, "name") || "default"; map[name] = true; } return map; } static isLinkStylesheetNode(tagName, node) { return tagName === "link" && AstQuery.getAttributeValue(node, "rel") === "stylesheet"; } // filter out webc:setup static isScriptNode(tagName, node) { return tagName === "script" && !AstQuery.hasAttribute(node, AstSerializer.attrs.SETUP); } static getExternalSource(tagName, node) { if(AstQuery.isLinkStylesheetNode(tagName, node)) { return AstQuery.getAttributeValue(node, "href"); } if(AstQuery.isScriptNode(tagName, node)) { return AstQuery.getAttributeValue(node, "src"); } } /* Attributes */ static hasAttribute(node, attributeName) { return (node.attrs || []).find(({name}) => name === attributeName) !== undefined; } static hasAnyAttribute(node, attributes) { let lookup = {}; for(let name of attributes) { lookup[name] = true; } return (node.attrs || []).find(({name}) => lookup[name]) !== undefined; } static getAttributeValue(node, attributeName) { let nameAttr = (node.attrs || []).find(({name}) => name === attributeName); if(!nameAttr) { // Same as Element.getAttribute // https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute return null; } return nameAttr?.value; } static getRootNodeMode(node) { // override is when child component definitions override the host component tag let rootAttributeValue = AstQuery.getAttributeValue(node, AstSerializer.attrs.ROOT); if(rootAttributeValue) { return rootAttributeValue; } // merge is when webc:root attributes flow up to the host component (and the child component tag is ignored) if(rootAttributeValue === "") { return "merge"; } return false; } static getRootAttributes(component, scopedStyleHash) { let attrs = []; // webc:root Attributes let tops = AstQuery.getTopLevelNodes(component, [], [AstSerializer.attrs.ROOT]); for(let root of tops) { for(let attr of root.attrs) { if(attr.name !== AstSerializer.attrs.ROOT) { attrs.push({ name: attr.name, value: attr.value }); } } } if(scopedStyleHash) { // it’s okay if there are other `class` attributes, we merge them later attrs.push({ name: "class", value: scopedStyleHash }); } return attrs; } /* Declarative Shadow DOM */ static isDeclarativeShadowDomNode(node) { let tagName = AstQuery.getTagName(node); return tagName === "template" && (AstQuery.hasAttribute(node, "shadowroot") || AstQuery.hasAttribute(node, "shadowrootmode")) } static hasDeclarativeShadowDomChild(node) { return AstQuery.findElement(node, "template", ["shadowroot", "shadowrootmode"]); } /* Content */ static getTextContent(node) { // used for style hashes let content = []; for(let child of node.childNodes || []) { if(child.nodeName === "#text") { content.push(child.value); } } return content; } static hasTextContent(node) { return AstQuery.getTextContent(node).find(entry => entry.trim().length > 0) !== undefined; } /* Shallow element finds */ static getImplicitRootNodes(node) { return [ AstQuery.findElement(node, "body"), AstQuery.findElement(node, "head") ].filter(node => !!node); } static getTopLevelNodes(node, tagNames = [], webcAttrs = []) { let roots = AstQuery.getImplicitRootNodes(node); if(roots.length === 0) { throw new Error("Unable to find component root, expected an implicit <head> or <body>"); } let children = []; for(let root of roots) { for(let child of AstQuery.getChildren(root, tagNames, webcAttrs)) { children.push(child); } } return children; } static getChildren(parentNode, tagNames = [], attrCheck = []) { if(!parentNode) { return []; } if(typeof tagNames === "string") { tagNames = [tagNames]; } if(!tagNames || Array.isArray(tagNames)) { tagNames = new Set(tagNames); } let results = []; for(let child of parentNode.childNodes || []) { let tagName = AstQuery.getTagName(child); if(tagNames.size === 0 || tagNames.has(tagName)) { if(attrCheck.length === 0 || attrCheck.find(attr => AstQuery.hasAttribute(child, attr))) { results.push(child); } } } return results; } static getFirstTopLevelNode(node, tagName, attrName) { let roots = AstQuery.getImplicitRootNodes(node); if(roots.length === 0) { throw new Error("Unable to find component root, expected an implicit <head> or <body>"); } for(let root of roots) { let match = AstQuery.findFirstChild(root, tagName, attrName); if(match) { return match; } } } static findFirstChild(parentNode, tagName, attrName) { for(let child of parentNode?.childNodes || []) { if(!tagName || tagName === AstQuery.getTagName(child)) { if(!attrName || AstQuery.hasAttribute(child, attrName)) { return child; } } } } /* Deep element finds */ static findAllElements(root, tagName) { let results = []; let rootTagName = AstQuery.getTagName(root); if(rootTagName === tagName) { results.push(root); } for(let child of root.childNodes || []) { for(let node of AstQuery.findAllElements(child, tagName)) { results.push(node); } } return results; } static findElement(root, tagName, attrCheck = []) { let rootTagName = AstQuery.getTagName(root); if(rootTagName === tagName) { if(attrCheck.length === 0 || attrCheck.find(attr => AstQuery.hasAttribute(root, attr))) { return root; } } for(let child of root.childNodes || []) { let node = AstQuery.findElement(child, tagName, attrCheck); if(node) { return node; } } } } export { AstQuery };