npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

xtal-element

v0.0.623

Published

Some opinionated web component base classes.

Downloads

1,911

Readme

How big is this package in your project? NPM version Playwright Tests

Development of a xtal-element consists of checks and balances between these mental "branches" of development:

  1. The "Majestic Definitive Branch". An mjs file that is used to build an html file. Developers can skip this step, if they prefer to edit the html file directly. Developing xtal-element elements via *.mjs works best in combination with the be-importing compiler, which for now is limited to providing tagged template literal support.
  2. The "Concessional Branch": CSS styling, imported via CSS Modules or via a style tag within the html file. There are two strong reasons to keep the CSS in a separate file (but this is not a doctrinaire rule).
    1. Multiple components share the same CSS.
    2. "Dependency injection": with the help of the link preload tag (and/or, possibly, import maps), allow the consumer of the web component to define their own theme, with no penalty for the consumer from the original default css theme which the developer chose to forgo.
  3. "The "Ecmascript Branch". The client-side Javascript that must ship and execute in the browser's main thread to achieve the desired functionality. Minimal JS boilerplate to "tie the knot" as far as registering the custom element, and specialized methods made available to the custom element class, as a last resort. If github autopilot is accurately guessing all your next moves when writing JS, maybe it's time to encapsulate that as a declarative web component or behavior/decorator. With current standards, we are pushed quite hard to make this file serve as the entry point for our custom element. The JS file can then import the other two files in parallel (especially if link rel=preload is used). However, with the help of a few key behaviors / decorators built with xtal-element, it is possible to circumvent the need for the boilerplate JS. Use of other xtal-element built components allows the JSON and CSS files to be replaced with alternative custom files for ultimate flexibility / customizability / extensibility, with no additional payload. This is configured via optional (but highly encouraged) link rel=preload tags.

Additional files that are optional, but definitely helpful / expected for an xtal-element-based custom element:

  1. A TypeScript types file. Note that using build-less, ts-check mjs files, rather than global warming inducing typescript building, is encouraged!
  2. A custom element manifest file (auto-generated.)

A potentially fourth branch of development involves JavaScript that influences the HTML markup of the declarative HTML web component -- either in an HTMLRewriter (supported by Cloudflare), and/or in a service worker, which, w3c willing, could have a similar api, which would make life good.

Web components from streamed HTML content

xtal-element is a web component that allows us to manufacture other web components declaratively, conveniently, and without having to repeat ourselves by separately downloading a template. Instead, the definition of the web component can be inferred from the live HTML stream, as part of the original payload of the web page, or, where it makes sense to lazy load, from an HTML stream delivered via fetch. This has a number of benefits -- it is less complex for the developer to manage, and it reduces the payload / time to interactivity.

The analogy is defining a variable, and assigning a value to the variable at the same time, when writing a program.

Part I - no ShadowDOM custom elements

Pre-rendered live DOM that is reused, with manual CSR attribute

<div>
    <div>Hello, <span>world</span></div>
    <xtal-element aka=hello-world></xtal-element>
</div>

...

<hello-world csr></hello-world>

Here, xtal-element turns the content of the parent element into a web component. So the hello-world tag would render as follows:

<div>
    <div>Hello, <span>world</span></div>
    <xtal-element aka=hello-world></xtal-element>
</div>
...
<hello-world csr>
    <div>Hello, <span>world</span></div>
</hello-world>

[!NOTE] Shadow DOM is bypassed in this instance. It makes sense in this case not to use Shadow DOM for consistency between the original, defining element, and subsequent instances, for styling consistency.

[!NOTE] It is best to place the xtal-element as the last child of the parent element, to support streaming most effectively.

In fact, the following may make more sense from a styling perspective, and also works:

Example 1b -- Pre-rendered live DOM specifies the name of the web component:

<hello-world>
    <div>Hello, <span>world</span></div>
    <xtal-element></xtal-element>
</hello-world>
<hello-world csr></hello-world>

Renders:

<hello-world>
  <div>Hello, <span>world</span></div>
  <xtal-element></xtal-element>
</hello-world>
<hello-world csr>
  <div>Hello, <span>world</span></div>
</hello-world>

[!NOTE] Why is the csr attribute necessary? csr stands for "client-side-rendering" for the initial render. Isn't it obvious we want to client side render if we are creating a web component? It is only necessary for web components that don't use declarative shadow DOM and don't use server-side-rendering, or server-side-generated rendering, or click-clackity keyboard typed rendering for the initial view. Here's the thinking:

  1. We don't want to do any unnecessary rendering. So some sort of "message" protocol is required to reduce unnecessary rendering.
  2. The presence of declarative shadow DOM markup sends a strong signal that server side rendering was used. Why go through the trouble of adding the template element if not?
  3. In the absence of declarative shadow DOM clues, what can we use if ShadowDOM is not used? I was tempted to say "if children are found assume server side rendering" but then there's this masterclass in Hamlet-style indecision. So in the absence of mercy from the platform (a recurring pattern it seems) we are opting to err on the side of encouraging server-side rendering.
  4. I want to be clear, though, that it isn't obvious in my mind if server-side rendering will always win out over csr for the second, and subsequent instances of a web component. Yes, as far as the first instance it will be better performing. However, it's been a while since I've measured this, but I've seen instances where template cloning actually surpasses server-side rendering in some instances (without the help of service workers, at least). So "do your own research", basically.
  5. Next, we will see an option to configure the web component so that the "default" assumption is reversed, so that "no-csr" is needed to block the (unnecessary?) initial render.

The assume-csr option

To address the concern above, add the following attribute to indicate that subsequent instances should apply client side rendering to the initial rendering:

<div>
    <div>Hello, <span>world</span></div>
    <xtal-element aka=hello-world assume-csr></xtal-element>
</div>

<hello-world></hello-world>

With inline, WHATWG-approved binding

We can add implicit inline binding using microdata attributes:

<hello-world place=Earth>
    <div itemscope>Hello, <span itemprop=place>world</span></div>
    <xtal-element infer-props></xtal-element>
</hello-world>
<hello-world place=Venus></hello-world>
<hello-world place=Mars></hello-world>

...renders:

<hello-world>
    <div itemscope>Hello, <span itemprop=place>Earth</span></div>
    <xtal-element infer-props></xtal-element>
</hello-world>
<hello-world place=Venus>
    <div itemscope>Hello, <span itemprop=place>Venus</span></div>
</hello-world>
<hello-world place=Mars>
    <div itemscope>Hello, <span itemprop=place>Mars</span></div>
</hello-world>

So the first instance of the pattern displays without a single byte of Javascript being downloaded.

Subsequent instances take less bandwidth to download, and generate quite quickly due to use of templates. It does require the xtal-element web component library to be loaded once.

With "binding from a distance"

<div>
  <div>Hello, <span>world</span></div>
  <xtal-element 
    aka=hello-world 
    prop-defaults='{
        "place": "Venus"
    }' 
    xform='{
        "span": "place"
    }'
  ></xtal-element>
</div>
<hello-world place=Mars></hello-world>
<hello-world></hello-world>

... generates:

<div>
    <div>Hello, <span>world</span></div>
    <xtal-element ...></xtal-element>
</div>
<hello-world place=Mars>
    <div>
        <div>Hello, <span>Mars</span></div>
    </div>
</hello-world>
<hello-world place=Mars>
    <div>
        <div>Hello, <span>Venus</span></div>
    </div>
</hello-world>

Again, using Shadow DOM is somewhat iffy, as styling is fundamentally different between the "defining" element and subsequent elements, thus Shadow DOM is not used by default.

To enable ShadowDOM, use the "shadowRootMode" setting:

With "binding from a distance" and with shadow DOM

<div>
  <div>Hello, <span>world</span></div>
  <xtal-element 
    aka=hello-world 
    shadow-root-mode=open
    prop-defaults='{
        "place": "Venus"
    }' 
    xform='{
        "span": "place"
    }'
  ></xtal-element>
</div>
<hello-world place=Mars></hello-world>

Editing JSON-in-html can be rather error prone. A VS Code extension is available to help with that, and is compatible with web versions of VSCode.

And in practice, it is also quite ergonomic to edit these declarative web components in a *.mjs file that executes in node as the file changes, and compiles to an html file via the may-it-be compiler. This allows the attributes to be editable with JS-like syntax. Typescript >4.6 supports compiling mts to mjs files, which then allows typing of the attributes. Examples of this in practice are:

  1. up-down-counter
  2. xtal-side-nav
  3. xtal-editor
  4. cotus
  5. plus-minus
  6. scratch-box

Anyway.

The "binding from a distance" refers to the xform property (which stands for "transform").

The "xform" setting uses Mount-observing transforms, or trans-rendering syntax, similar to CSS, in order to bind the template "from a distance", but xtal-element eagerly awaits inline binding with Template Instantiation being built into the platform as well, so the two approaches can collaborate.

Example 3a -- Pre-rendered web components that use streaming declarative Shadow DOM.

This syntax also works:

<hello-world place=Earth>
  <template shadowrootmode=open>
      <div itemscope>Hello, <span itemprop=place>world</span></div>
        <style adopt>
            span {
                color: green;
            }
        </style>
        <xtal-element infer-props></xtal-element>
  </template>
</hello-world>
<hello-world place=Mars></hello-world>
<hello-world place=Venus></hello-world>

All the browsers support this now!

Server-side rendering

A large swath of useful web components, for example web components that wrap some of the amazing codepens we see, don't (or shouldn't, anyway) require a single line of custom Javascript. The slot mechanism supported by web components can go a long way towards weaving in dynamic content.

In that scenario, the CDN server of the (pre-built) static HTML file (or a local file inclusion, imported into the solution via npm) is the SSR solution, as long as the HTML file can either be

  1. Embedded in the server stream for the entire page, or
  2. Client-side included, via a solution like Jquery's load method, k-fetch, include-fragment-element, sl-include, templ-mount, xtal-fetch, html-includes, wc-include, ng-include, html-include-element or countless other ought-to-be-built-into-the-platform-already-but-isn't options (sigh).
  3. On the client-side include side, be-importing is specifically tailored for this scenario.

The good people of github, in particular, earn a definitive stamp of approval from xtal-element. They are definitely onto something quite significant, with their insightful comment:

This declarative approach is very similar to SSI or ESI directives. In fact, an edge implementation could replace the markup before it's actually delivered to the client.

<include-fragment src="/github/include-fragment/commit-count" timeout="100">
  <p>Counting commits…</p>
</include-fragment>

A proxy may attempt to fetch and replace the fragment if the request finishes before the timeout. Otherwise the tag is delivered to the client. This library only implements the client side aspect.

Music to my ears!

The client-side approach is more conducive to fine-grained caching, while the server-side stream approach better for above-the-fold initial view metrics.

If going with the server-side route, there are certainly scenarios where weaving in dynamic content in the server is useful, beyond what can be done with slots, in order to provide a better initial view.

One solution being pursued for this functionality is the xodus cloudflare helper classes project/edge-of-tomorrow. Eventually, w3c willing.

Its goal is to apply the "transform(s)" specified above, but in the cloud (or service worker) for the initial render (or pre-render?).

Example 4a -- Referencing non-JSON serializable entities.

<xtal-element
    onload=doEval 
    aka=hello-world 
    prop-defaults="{
        place: 'Venus'
    }" 
    xform="{
        span: {
            d: ({place}) => `What a beautiful world you are, ${place}`
        }
    }"
></xtal-element>

To evaluate dynamic expressions with full access to the JavaScript runtime engine, set attribute onload=doEval, as shown above.

The Ecmascript branch

The preferred mechanism to incorporate custom JavaScript, though, is not to use the onload=eval, but rather to "use the platform" and to define a JavaScript class. The JavaScript class must extend (at the "bottom" of the hierarchy) trans-render/Mount.js, and be registered as a web component if using the declarative support of this package.

The nice thing is this allows us to reuse the same web component base with different UI definitions, something I've found to be quite useful, personally.

The inherits attribute / property with a string value:

<xtal-element
    inherits=plus-minus-base
>
    ...
</xtal-element>

This assumes the document imports a js file, for example, that registers a custom element with name "plus-minus-base".

Example 4b -- Support for inner script tag

<hello-world place=Earth>
    <template shadowrootmode=open>
    <div></div>
    <xtal-element
        prop-defaults='{
            "place": "Earth"
        }'
        prop-info='{
            "greeting": {}
        }'
        xform='{
            "div": "greeting"
        }'
    >
        <script nomodule>[
            ({place}) => ({greeting: `Hello, ${place}`})
        ]</script>
    </xtal-element>
  </template>
</hello-world>
<hello-world place=Mars></hello-world>
<hello-world place=Venus></hello-world>

Real world examples

Example 1 up-down-counter

Streaming HTML definition

How to reference it locally with no build step.