All files / packages/fds-uif/generator-lwc/src generateTemplate.ts

88.09% Statements 37/42
87.5% Branches 7/8
77.77% Functions 7/9
92.3% Lines 36/39

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112                      5x 5x       9x     9x     9x     9x     9x     9x     5x       6x   37x   14x           3x   11x         34x   34x 9x 57x         9x 9x   27x   19x       27x   27x   7x 8x 7x       7x 7x 7x 7x                   9x     9x     9x     9x       9x    
import {
  type ComponentMetadata,
  type StructureMetadata,
  type SlotMetadata,
  PropMetadata,
  ComposedComponentMetadata,
} from '@fds-uif/generator-base';
import { serialize, defaultTreeAdapter, html } from 'parse5';
import type { DefaultTreeAdapterMap, Token } from 'parse5';
import { uppercaseFirstChar } from './utils/uppercaseFirstChar.js';
 
const NS = html.NS;
const treeAdapter = defaultTreeAdapter;
 
function createDocument() {
  // Create an empty document
  const document = treeAdapter.createDocument();
 
  // Create template element
  const templateElement = treeAdapter.createElement('template', NS.HTML, []) as DefaultTreeAdapterMap['template'];
 
  // Create an empty content fragment for the template
  const templateContent = treeAdapter.createDocumentFragment();
 
  // Associate the content fragment with the template
  treeAdapter.setTemplateContent(templateElement, templateContent);
 
  // Build the document structure
  treeAdapter.appendChild(document, templateElement);
 
  // More intutiive template -> #document-fragment -> elements
  return { document, template: templateContent };
}
 
const kebabToCamel = (str: string) => str.replace(/-([a-z])/g, (group) => group.slice(-1).toUpperCase());
 
function createChildren(parent: DefaultTreeAdapterMap['parentNode'], parentNode: (null | SlotMetadata | StructureMetadata | ComposedComponentMetadata), children: (SlotMetadata | StructureMetadata | ComposedComponentMetadata)[], props: PropMetadata[]): void {
  if (!children || children.length === 0) {
    return;
  }
  children.forEach((child) => {
    if (child.hasOwnProperty('type') && (child as SlotMetadata).type === 'slot') {
      const slot = child as SlotMetadata;
      if (parent.nodeName === 'svg') {
        console.log('LWC does not support svg>slot nesting');
        throw new Error('LWC does not support svg>slot nesting');
      }
      if (slot.name === 'default') {
        treeAdapter.appendChild(parent, treeAdapter.createElement('slot', NS.HTML, []));
      } else {
        treeAdapter.appendChild(parent, treeAdapter.createElement('slot', NS.HTML, [
          { name: 'name', value: slot.name },
        ]));
      }
    } else {
      const node = child as StructureMetadata;
      // static attributes and binding
      const bound = (node.boundAttributes ?? []).map((api) => {
        const knownApi = props.find((prop) => {
          return prop.name === api.prop;
        });
        if (!knownApi) {
          return null;
        }
        return { name: api.attribute, value: `{${knownApi.name}}` };
      }).filter(x => x !== null);
      if (node.element) {
        const newElement = treeAdapter.createElement(node.element, NS.HTML, [
          ...Object.entries(node.attributes).map(([attribute, value]) => {
            return { name: attribute, value: value } as Token.Attribute;
          }),
          ...bound
        ]);
        treeAdapter.appendChild(parent, newElement);
        // recursively create children
        createChildren(newElement, node, node.children, props);
      } else if (node.conditionals && node.conditionals.length > 0) {
        node.conditionals.forEach((condition) => {
          const lwcCondition = condition.condition.split(' || ').map(x => uppercaseFirstChar(x)).join('Or');
          const templateElement = treeAdapter.createElement('template', NS.HTML, [{
            name: 'lwc:if',
            value: `{hasSlot${lwcCondition}}`
          }]) as DefaultTreeAdapterMap['template'];
          const templateContent = treeAdapter.createDocumentFragment();
          treeAdapter.setTemplateContent(templateElement, templateContent);
          treeAdapter.appendChild(parent, templateElement);
          createChildren(templateContent, condition.trueBranch, [condition.trueBranch], props);
        });
      }
    }
  });
}
 
export function generateTemplate(metadata: ComponentMetadata): string {
  //console.log(JSON.stringify(metadata, undefined, '  '));
  // create an empty document with a template element
  const { document, template } = createDocument();
 
  // loop metadata.structure.children and add them to the the template
  createChildren(template, null, [metadata.structure], metadata.props);
 
  // serialize the document to a string
  let raw = serialize(document);
 
  // ="{...}" to {} syntax
  raw = raw.replace(/="(\{\w+\})"/g, '=$1');
  // lwc:if="{...}" to {} syntax
  //raw = raw.replace(/lwc:(if|else)="(\{[^\}]+\})"/g, 'lwc:$1=$2');
 
  return raw;
}