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

sushi-element

v1.0.0-beta.3

Published

An expressive way to create web components.

Downloads

7

Readme

Sushi Element 🍣

Sushi Element offers an expressive way to create standards-based web components that work with (and without) your favorite framework. Notable features include:

  • Expressive API
  • Declarative templates
  • Reactive data binding via props + state
  • Fast, efficient rendering
  • Lifecycle hooks
  • Watchers
  • Open source
  • < 5 KB (minified + gzipped)

This experiment was created by Cory LaViska in New Hampshire.

How it works

At the core of Sushi Element is a class factory called createElement() that accepts an object and returns a custom element. You can use the resulting element in your HTML or with your favorite framework.

Sushi Element is designed to be lightweight and stick to the platform as closely as possible. It uses template literals powered by lit-html for fast, efficient rendering with no virtual DOM requirement.

Example syntax

Here's an obligatory counter example that increments a state variable and re-renders automatically on click.

// counter.js
import { createElement, html } from 'sushi-element';

export const Counter = createElement({
  tagName: 'my-counter',
  state: {
    count: 0
  },
  render() {
    return html` <button type="button" @click="${() => this.count++}">Count: ${this.count}</button> `;
  }
});

To use the counter in your HTML:

<script type="module">
  import { Counter } from './counter.js';
  Counter.register();
</script>

<my-counter></my-counter>

A note about "props" and state

In Sushi element, the term "props" refers to a form of state that the user controls by setting HTML attributes or JavaScript properties on the element. The term "state" refers to the element's internal state which the user has no control over. Changes to either will cause the element to re-render.

The concept of attributes and properties can be confusing, so Sushi Element abstracts them into "props." Internally, Sushi Element only looks at properties, but it will automatically sync attribute changes to their corresponding properties for better DX. This means that the color attribute in <my-element color="blue"> will translate to this.color = 'blue' on the element instance and, if the attribute changes, this.color will update to match.

By default, property changes will not reflect back to attributes. Thus, setting this.color = 'tomato' will update the property but not the attribute nor the DOM. You can modify this behavior by adding the reflect option to any prop, which will make this.color = 'tomato' reflect the attribute and result in <my-element color="tomato">. This can be useful if you intend to style your element with attribute selectors.

Attributes are always lower-snake-case and properties are always camelCase. For example, an attribute named primary-color will have a corresponding property of primaryColor. Sushi Element handles this conversion for you automatically.

API

Inspiration for the API was taken from Vue, Stencil, and React, but no framework nor compiler is required to use Sushi Element.

The following functions are exported by the library:

createElement(config)

Creates a custom element. The config argument can contain the following properties:

  • tagName: string - The tag name to use for the custom element. Must include at least one hyphen, per the custom elements spec.
  • shadow?: boolean - Whether or not to use shadow DOM. Default true.
  • styles?: string - The CSS to inject into the component.
  • props?: { [key: string]: Prop | any } - The element's props. The object key is the prop name and the value can be the prop's default value or a Prop object with options created by the prop() function. On init, the default value will be overridden if a corresponding attribute is set on the element.
  • state?: { [key: string]: any } - The element's state variables. The object key is the state name and the value is the initial value.
  • methods?: { [key: string] : () => void } - Public methods to assign to the element. The object key is the method name. All public methods are called in the context of the host element, so this will refer to the element instance.
  • connected?: () => void - Called when the element is first connected to the DOM.
  • disconnected?: () => void - Called when the element is disconnected from the DOM.
  • ready?: () => void - Called when the element has been connected and after the first render has completed.
  • beforeRender?: () => void - Called once before each render.
  • afterRender?: () => void - Called once after each render.

All lifecycle methods and custom methods are called in the context of the host element, so this will refer to the element's instance.

The resulting custom element will have the following properties and methods:

  • register() - Registers the element with the custom elements registry. Returns the element's tag name.

prop(options)

Defines a prop with additional options. The options argument can contain the following properties:

  • value?: any - The prop's default value.
  • reflect?: boolean - Reflects the prop back to the attribute when possible. The value must be a string, number, or boolean for the prop to reflect. Default false.
  • watch?: () => void - A function to call each time the prop changes.

Example:

const el = createElement({
  props: {
    // Prop with no options
    size: 'medium'

    // Prop with options
    color: prop({
      value: 'tomato',
      reflect: true,
      watch: (oldValue, newValue) => console.log(`color changed from ${oldValue} to ${newValue}`)
    }),
  }
})

html(strings, ...values)

Used only in the render() method to generate a template. This should always be called as a tagged template literal:

render() {
  return html`
    <h2>My Template</h2>
  `;
}

See the Templates section for more information about writing templates.

Templates

Templates are powered by lit-html, so all the rules and examples for writing templates apply.

This section of the docs will be expanded in the future, so please refer to the examples on the lit-html website for now.

Directives

Directives are functions that make writing templates even easier. For now, please refer to lit-html's directives page for details.

For convenience, Sushi Element exports lit-html's classMap, live, nothing, and styleMap directives. Additional directives can be imported from lit-html or from your own custom directive module.

Events

Each element will have an emit() method that lets you fire off custom events easily. The first argument is the event name and an optional second argument lets you provide details and additional options for the event (e.g. composed, bubbles, etc.)

Here's an example of how you can use it:

render() {
  return html`
    <button
      @focus="${() => this.emit('my-focus-event')}"
      @blur="${() => this.emit('my-blur-event', { detail: 'this will be available in event.detail' })}"
    >
      Click me
    </button>
  `;
}

To listen to events, use addEventListener() as you normally would.

Developers

  • Use npm start to build + watch
  • Use npm build to build
  • Use npm run sandbox to build + watch + launch a dev server that points to the examples directory

This is still somewhat experimental and there's still some work to do with it.

What relevance does the word "sushi" have to this project?

Probably none. I just needed a quick name to start hacking on it. But in hindsight, the concept of sushi seems to parallel many aspects of component-driven development. Just like apps, sushi is comprised of many smaller components (nori, fish, roe, wasabi) and can range from very simple (sashimi) to very complex (uramaki).

And, just like sushi, I don't expect that everyone will enjoy this library, but I do hope they at least try it and see how simple crafting lightweight, standards-based web components can be!