Skip to content

p2js/xenon

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Xenon: Static HTML Components in 650 Bytes

Xenon is a small library that implements declarative, reusable components into static HTML, with no JavaScript interaction or build step.

The entire library uses exactly 627 bytes of JavaScript.

Motivation

Take, for the sake of example, my own personal portfolio website.

Besides a little easter egg that couldn't be done using just CSS, it uses precisely 4 lines of javascript to insert my correct age.

Although I love using JS frameworks for web apps, static websites often do not need to implement any sort of reactivity or server-side interaction, and a build step slows down development and adds unnecessary complexity.

However, even a small static webpage like this could benefit from having some sort of reusable component system: my webpage's showcase projects all follow the same exact structure for uniformity, but the markup for each of them has to be defined individually, making them harder to read and maintain. Unfortunately, HTML does not provide any no-JS way to reuse parts of the webpage.

Xenon addresses this issue; an extremely small drop-in library with no dependencies that offers a flexible way to compose easily reusable and maintainable markup.

See the demo.html file in this repository to see the library in action, with a recreation of my website's projects page using Xenon components.

Basic Usage

Adding to a project

To add Xenon to a HTML document, add the following script tag to the head of the document:

<script defer src="https://cdn.jsdelivr.net/gh/p2js/xenon@latest/xenon.min.js"></script>

Alternatively, for faster loading, you can inline Xenon by placing the following script into your document:

<script defer>
// https://github.com/p2js/xenon
(e=>{let t=e=>e.querySelectorAll("template[_]").forEach((e=>{document.querySelectorAll(e.getAttribute("_")).forEach((t=>{let r=e.innerHTML;for(let l of e.getAttributeNames().filter((e=>"_"!=e))){let o=t.getAttribute(l)||e.getAttribute(l);r=r.replaceAll("{"+l+"}",o)}r=r.replaceAll("{$children}",t.innerHTML),t.innerHTML=r,t.querySelectorAll("if").forEach((e=>{e.getAttributeNames().some((e=>t.hasAttribute(e)))?e.replaceWith(...e.childNodes):e.remove()})),t.outerHTML=t.innerHTML})),e.remove()}));t(document),document.querySelectorAll("iframe.template-import").forEach((e=>{e.onload=r=>{t(e.contentDocument),e.remove()}}))})();
</script>

Declaring/Using component templates

To add a component to a HTML document, start by declaring its template: insert a HTML template element with the _ attribute set to the component's name:

<template _="greeting">
    <p>Hello, World!</p>
</template>

Standard practice is adding them to the head of your HTML, as they are unrendered fragments to be used elsewhere.

Then, just add an element with the specified name, and it will be transformed into the template's inner HTML when the document loads:

<greeting></greeting> <!-- Will render as: <p>Hello, World!</p> -->

Attributes

In your template declaration, you can add any parameters you want by passing them as parameters to the template element, optionally assigning a default value to them.

Parameters can be referenced by inserting their name in curly braces. They can be used to insert values in text or attribute names/values:

<template _="joke" noun wink="<i>;)</i>">
    <p>So you're telling me a {noun} fried this rice?</p>
    {wink}
</template>

<joke noun="shrimp"></joke>
<!-- Will render as: <p>So you're telling me a shrimp fried this rice?</p> <i>;)</i> -->

Component children

Any instance of {$children} inside a component template will be replaced with the component instances' inner HTML:

<template _="double">
    {$children}
    {$children}
</template>

<double>
    <p>Am I seeing double?</p> 
</double>
<!-- Will render as: <p>Am I seeing double?</p> <p>Am I seeing double?</p> -->

Children can also be used as a partial fallback for if the component fails to load (such as if JavaScript is disabled).

Conditional rendering

Sometimes, it's useful to want to only display something if an instance includes an attribute, such as a summary component that only shows a thumbnail if the instance provides a source link.

To do this, use an <if> element inside your template, passing the attribute that needs to be included as an attribute:

<template _="conditional" name>
    <if name>
        <p>You're here, {name}!</p>
    </if>
</template>

These elements will render their inner HTML if one of the provided parameters is present in the element (acting as logical OR), otherwise rendering nothing:

<conditional name="HTML lover"></conditional> 
<!-- Renders as: <p>You're here, HTML Lover!</p> -->

<conditional></conditional>                   
<!-- Renders nothing -->

If blocks can be nested to act as logical AND.

Template Imports

To share components between files or on the web, or simply store them in their own files, you can use component template imports.

Store any number of components inside a separate HTML file:

<!-- speech.template.html -->
<template _="speech" say><pre>
&lt;{say}&gt;
\
 O
-|-
/ \
</pre></template>

You can then import templates, either from a local file or from anywhere on the web, using an iframe element with its class attribute set to template-import. They can be used just like inline templates, and will render once the iframes load:

<iframe hidden class="template-import" src="speech.template.html"></iframe>

<speech say="Ahoy there!"></speech> <!--Will render once loaded!-->

About

Static HTML Components in 650 Bytes

Resources

License

Stars

Watchers

Forks