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 🙏

© 2026 – Pkg Stats / Ryan Hefner

wc-template

v1.0.0

Published

generate custom element from html template

Readme

wc-template

Create custom elements using template and link elements.

Usage

The script should be added after template and link with wc-template, and after style and link stylesheet with wc-global-css attribute:

<script src='https://cdn.jsdelivr.net/npm/wc-template@1/wc-template.js'></script>

or copy it to your project:

<script src='path/to/wc-template.js'></script>

Examples

Go to the examples. Or run it locally by cloning this repository and run:

npm install
npm test

API

template

<!-- create custom element with name custom-element -->
<template wc-template='custom-element'>
</template>

link

<!-- link to external template -->
<link rel='preload' as='fetch' crossorigin wc-template href='templates.html'/>

observed-attributes

<!-- add observed attributes -->
<template wc-template='custom-element' observed-attributes='custom-attr-1 custom-attr-2 custom-attr-3'>
</template>

shadow-root

<!-- add shadow root -->
<template wc-template='custom-element'>
    <shadow-root>
        <p>Hello World</p>
    </shadow-root>
</template>
<!-- shadow root options
    mode: default value is 'open', available values are 'open' and 'closed'
    clonable: default value is false
    delegates-focus: default value is false
    serializable: default value is false
    slot-assignment: default to 'named', available values are 'named' and 'manual'
-->
<template wc-template='custom-element'>
    <shadow-root mode='closed' clonable delegates-focus serializable slot-assignment='manual'>
        <p>Hello World</p>
    </shadow-root>
</template>

CSS

<!-- attach wc-global-css styles to shadow root -->
<link rel='stylesheet' href='style.css' wc-global-css/>
<style wc-global-css>
    p { color: #f00; }
</style>
<template wc-template='custom-element'>
    <shadow-root wc-global-css>
        <p>Hello World</p>
    </shadow-root>
</template>
<!-- attach display style to shadow root, it also adds :host([hidden]) { display: none; } -->
<template wc-template='custom-element'>
    <shadow-root display='inline-block'>
        <p>Hello World</p>
    </shadow-root>
</template>
<!-- add styles -->
<template wc-template='custom-element'>
    <shadow-root display='inline-block'>
        <p>Hello World</p>
    </shadow-root>
    <style>
        :host {
            width: 300px;
        }

        p {
            color: #0f0;
        }
    </style>
    <!-- external css with same url will be optimized to only use one CSSStyleSheet instance across different wc-template -->
    <link rel='stylesheet' href='style.css'/>
</template>
<!-- order matters -->
<style wc-global-css>
    p {
        color: red;
    }
</style>

<!-- result: color: blue -->
<template wc-template='custom-element'>
    <shadow-root display='block' wc-global-css>
        <p>Hello World</p>
    </shadow-root>
    <style>
        p {
            color: blue;
        }
    </style>
</template>
<!-- order matters -->
<style wc-global-css>
    p {
        color: red;
    }
</style>

<!-- result: color: red -->
<template wc-template='custom-element'>
    <style>
        p {
            color: blue;
        }
    </style>
    <shadow-root display='block' wc-global-css>
        <p>Hello World</p>
    </shadow-root>
</template>

script

<template wc-template='custom-element' observed-attributes='customer'>
    <shadow-root display='block' wc-global-css mode='closed'>
        <p><span id='hello'></span>, <span id='name'></span></p>
        <p id='html'></p>
        <p id='htmlUnsafeString'></p>
    </shadow-root>
    <style>
        #hello { color: #f00; }
        #name { color: #7a3; }
    </style>

    <!-- script run on constructor -->
    <script>
        // available variables:
        // shadowRoot, id, $, html, htmlUnsafeString, baseURL, State, Static, PRIVATE

        // shadowRoot is available even when mode is closed
        for (const style of shadowRoot.adoptedStyleSheets)
            console.log(style);

        // use id to access elements
        id.hello.textContent = 'Hello World';

        // use $ to access State instance, attributes are automatically included
        // note that attributes are null at constructor
        $.LISTEN('customer', () => id.name.textContent = $.customer);

        // $.STARTED is false at constructor and become true when first connectedCallback is called
        console.log($.STARTED);

        // $.CONNECTED is false at constructor, become true when connectedCallback is called
        // and become false when disconnectedCallback is called
        console.log($.CONNECTED);

        // call INIT to run listener immediately
        $.LISTEN('STARTED', () => console.log('STARTED', $.STARTED)).INIT();
        $.LISTEN('CONNECTED', () => console.log('CONNECTED', $.CONNECTED)).INIT();

        // html literal
        // use baseURL to resolve relative path
        id.html.innerHTML = html`<img src='${String(new URL('image.png', baseURL))}' alt='img'/>`;

        // htmlUnsafeString: string is not escaped
        id.htmlUnsafeString.innerHTML = htmlUnsafeString('<span style="color: blue;">Hello World</span>');

        // State constructor
        console.log('$ instance of State', $ instanceof State); // true

        // Static constructor
        console.log('this instance of Static', this instanceof Static); // true
        console.log('this.constructor === Static', this.constructor === Static); // true

        // PRIVATE symbol to hold private data on this and Static
        this[PRIVATE].data = 10;

        // create state
        $.INIT('state0', 4);
        console.log('state0', $.state0);

        // listen to state change, state is changed when old value and new value differ according Object.is
        const listener0 = $.LISTEN('state0', () => console.log('state0', $.state0));
        $.state0 = 4; // listeners won't be called
        $.state0 = 5; // listeners will be called

        // unlisten
        listener0.UNLISTEN();
        $.state0 = 6;

        // create state with force, listeners will be called even when old value and new value are same
        $.INIT('state1', 1, 'force');
        const listener1 = $.LISTEN('state1', () => console.log('state1', $.state1)).INIT();
        $.state1 = 1;

        // listen to multiple states
        $.LISTEN('state0', 'state1', () => console.log('state0', $.state0, 'state1', $.state1));

        // listener called twice
        $.state0 = 9;
        $.state1 = 10;

        // batch multiple write, listener only called once
        $.BATCH();
        $.state0 = 11;
        $.state1 = 12;
        $.COMMIT();

        // attach state as property
        $.INIT('state2', 5).ATTACH(this);
        console.log(this.state2);

        // with different name
        $.INIT('state3', 7).ATTACH(this, '_state3');
        console.log(this._state3);

        // read only
        $.INIT('state4', 9).ATTACH(this, null, 'ro');
        try { this.state4 = 10; } catch(e) { console.error(e); }

        // attach to a property that already exists
        this.state5 = 19;
        $.INIT('state5', 0).ATTACH(this);
        console.log('state5', $.state5); // 19
    </script>

    <!-- static script, called and awaited before registering custom element -->
    <script static>
        // available variables
        // Static, PRIVATE, State, baseURL, html, htmlUnsafeString, styles, getGlobalCSS, updateGlobalCSS

        // create shared State instance
        const $ = Static[PRIVATE].state = new State();
        $.INIT('shared0', 'home');

        // await styles
        await styles.whenLoaded();

        console.log('wc-global-css', styles[0] === getGlobalCSS());
        // control wc-global-css
        document.head.insertAdjacentHTML('beforeend', html`
            <style wc-global-css>
                p { font-style: italic; }
            </style>`);
        updateGlobalCSS();

        // dynamically create custom element
        const template = document.createElement('template');
        template.setAttribute('wc-template', 'my-element');
        template.innerHTML = html`
            <shadow-root><p>Hello World</p></shadow-root>
            <script static>
                alert('my-element');
            </${'script'}>`;
        document.head.appendChild(template);
    </script>

    <!-- async script run on constructor -->
    <script async>
        // OK
        $.INIT('async0');

        await Promise.resolve();

        // throw Error, INIT can't be called after constructor return
        $.INIT('async1');
    </script>
</template>