liamfiddler/eleventy-plugin-transformdom

add example: transform custom element / xml to html

milahu opened this issue · 2 comments

function customElementTransformer(nameCustom, options = {}) {
  const defaultOptions = {
    name: 'div',
    class: nameCustom,
  };
  options = Object.assign(defaultOptions, options);
  const domTransformer = {
    selector: nameCustom,
    transform: ({ elements, document }) => {
      for (let e = 0; e < elements.length; e++) {
        const element = elements[e];
        const elementNew = document.createElement(options.name);
        for (let a = 0; a < element.attributes.length; a++) {
           const attr = element.attributes[a];
           elementNew.setAttribute(attr.name, attr.value); // copy attribute
        }
        elementNew.innerHTML = element.innerHTML; // copy content
        if (options.class) elementNew.classList.add(options.class);
        //element.parentNode.replaceChild(elementNew, element);
        element.replaceWith(elementNew);
      }
    },
  };
  return domTransformer;
}

eleventyConfig.addPlugin(transformDomPlugin, [
  customElementTransformer('page'),
  customElementTransformer('nw', { name: 'span', class: 'nowrap-element' }),
]);

sample input

<page>
  hello world, here are <nw>four non wrapping words</nw>
</page>

sample output

<div class="page">
  hello world, here are <span class="nowrap-element">four non wrapping words</span>
</div>

benefits:

  • the class is also visible in the closing tag
  • shorter open-tags

more general

function getElementTransformer(selector, options = {}) {
  const defaultOptions = {
    name: 'div',
    attributes: (e => Object.fromEntries(Array.from(e.attributes).map(a => [a.name, a.value]))),
    class: (e => e.localName),
  };
  options = Object.assign(defaultOptions, options);
  const getName = (typeof options.name == 'function') ? (e => options.name(e)) : (() => options.name);
  const getAttributes = (
    (typeof options.attributes == 'function') ? (e => options.attributes(e)) :
    (typeof options.attributes == 'object') ? (() => options.attributes) :
    (() => false)
  );
  const getExtraClass = (
    (typeof options.class == 'function') ? (e => options.class(e)) :
    (typeof options.class == 'string') ? (() => options.class) :
    (() => false)
  );
  const domTransformer = {
    selector,
    transform: ({ elements, document }) => {
      for (let e = 0; e < elements.length; e++) {
        const element = elements[e];
        const nameNew = getName(element);
        if (!nameNew) continue; // no replace
        const attributesNew = getAttributes(element) || {};
        const extraClassNew = getExtraClass(element);
        const elementNew = document.createElement(nameNew);
        for (const [name, value] of Object.entries(attributesNew)) {
          elementNew.setAttribute(name, value);
        }
        elementNew.innerHTML = element.innerHTML;
        if (extraClassNew) elementNew.classList.add(extraClassNew);
        //element.parentNode.replaceChild(elementNew, element);
        element.replaceWith(elementNew);
      }
    },
  };
  return domTransformer;
}

eleventyConfig.addPlugin(transformDomPlugin, [
  getElementTransformer('page', { class: 'page-element' }),
  getElementTransformer('nw', { name: 'span', class: 'nowrap-element' }),
  ...(['de', 'en']).map(langKey => 
    getElementTransformer(langKey,
      { name: 'span', attributes: (e => ({ lang: e.localName })), class: false })
  )
]);

Wow! I hadn't considered the plugin being used in this way, but it's very cool!

Thanks for the detailed code too! I think this would be a really interesting example of how the plugin can be used 😃

I'm flat out at the moment - any chance you'd be willing to fork the repo, add your example to the "examples" directory (feel free to use one of the existing example directories as a template), and submit a PR?