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

@sff-ui/core

v0.1.6

Published

SFF - Small Frontend Framework

Readme

Small Frontend Framework Core

This package contains the core functionality provided by the SFF library as well as its detailed overview.

Core features

createApp() function

Once you initialized your first SFF app, after navigating to src/main.ts file you'll see the following line:

createApp(App).mount(document.getElementById('app'));

This is the entry point of your app.
The createApp() function can take two parameters: the RootComponent (called App in our example) and the optional config object, where you can describe your global state management rules.
For more info on the global state management refer to this section.
createApp() returns the App instance (don't mix up with the root component's name!) that exposes a single method - mount(). You can use this method to, well, mount your app into the real DOM in your browser. The mount() method accepts a single parameter — the DOM node to mount your components into.

Main building blocks or HyperScript

The SFF uses a special set of functions to describe the components structure — the HypeScript functions. HyperScript, in this case, means that these functions create Virtual DOM nodes. A list of the available functions is pretty short and simple as it contains only two of them:

  • h()
  • fragment()

h() accepts either a class component or a string as its first parameter. If a string is provided then h() tries to create a corresponding HTML element from it. For example, h("div") will create a div container. The second parameter — optional components props / HTML elements attributes. The third one — optional array of children. You can also pass a single node instead of an array.
fragment() is even simpler as it accepts a single parameter — an array of nodes / a single node. It serves as a container that can bind together multiple nodes without reflecting it (i.e., w/o creating an HTML element) in the real DOM.

See the usage examples of these functions in the Components section of this doc.

Components

To create your own components, you have to use the Component class.

import { Component } from "@sff-ui/core";

export class MyComponent extends Component {}

The component above is not very useful as it doesn't render anything. So let's add the render() to it. This method is special since it contains the components' structure — what will be created in the browser's DOM. Moreover, you are required to implement it to define a component.

import { Component, h, fragment } from "@sff-ui/core";

export class MyComponent extends Component {
  handleClick = () => console.log("Clicked!");
  
  render() {
    return fragment([
      h("div", {}, "Hello, World!"),
      h("button", { on: { click: this.handleClick } }, "Click me!")
    ]);
  }
}

createApp(MyComponent).mount(document.getElementById('app'));

In the example above, we created a component that renders a "Hello, World!" message along with a button, that, when clicked, logs "Clicked!" to the console.

Component's props

Every component can receive a set of external values from the parent components - props. To define the names and types of the available props you need to pass a generic parameter into the Component class like this:

import { Component } from "@sff-ui/core";

interface MyComponentProps {
  foo: string;
  bar: number;
}

export class MyComponent extends Component<MyComponentProps> {
  render() { return null; }
}

After that, you will be able to use these values inside your component via built-in props object as well as pass them from the parent components:

import { Component } from "@sff-ui/core";

interface MyComponentProps {
  foo: string;
  bar: number;
}

export class MyComponent extends Component<MyComponentProps> {
  render() {
    return h("div", {}, `${this.props.foo} ${this.props.bar}`);
  }
}

export class App extends Component {
  render() {
    return h(MyComponent, { foo: 'foo', bar: 42 })
  }
}

createApp(App).mount(document.getElementById('app'));

Component's local state

Every component has its own local state. By default, it's set to an empty object ({}) and is accessible through this.state property. To change the state, you can use a built-in method setState(), which accepts a new state object. You can also define a type of your state by overriding the derived state property.

import { Component, h, fragment } from "@sff-ui/core";

interface CounterState {
  count: number;
}

export class Counter extends Component {
  state: CounterState = { count: 0 };
  
  inc = () => this.setState({ count: this.state.count + 1 });
  dec = () => this.setState({ count: this.state.count - 1 });
  
  render() {
    return fragment([
      h("button", { on: { click: this.dec } }, "-"),
      h("div", {}, this.state.count),
      h("button", { on: { click: this.inc } }, "+"),
    ]);
  }
}

createApp(Counter).mount(document.getElementById('app'));

Above, we implemented a simple Counter app using component's local state.

Component's lifecycle

Each component has a set of built-in methods that are fired on the certain phases of the component's life. These methods are called lifecycle methods. SFF provides three of them:

  • afterMount()
  • afterUpdate()
  • beforeUnmount()

afterMount() is called only once right after the component has been mounted into the browser's DOM. It's an ideal place to set your event listeners and do network requests.
afterUpdate() is called everytime when:

  • parent component is updated (re-rendered)
  • local state changes
  • component's props change

To avoid unnecessary re-renders on parent component's updates, you can use useMemo() method.
Note: be sure to call it inside a constructor to avoid unexpected results!

import { Component } from "@sff-ui/core";

export class MyComponent extends Component<MyComponentProps> {
  constructor() {
    super();
    this.useMemo();
  }

  render() {
    return null;
  }
}

beforeUnmount() is called right before the component is going to be removed from the DOM. It's an ideal place to unsubscribe from the event listeners you've set up in afterMount().

Global state

To add global state to your app, you need to pass the corresponding configuration into createApp() function.

interface GlobalState {
  foo: string;
  bar: number;
}

createApp<App, GlobalState>(App, {
  state: { foo: 'foo', bar: 42 },
  reducers: {
    setFoo: (state, payload: string) => ({ ...state, foo: payload }),
    setBar: (state, payload: number) => ({ ...state, bar: payload }),
  },
});

createApp(App).mount(document.getElementById('app'));

Then, inside a component you can dispatch() your actions via this.props.store property. The global store data is accessible via this.props.store.data.

import { Component } from "@sff-ui/core";

export class MyComponent extends Component {
  afterMount() {
    this.props.store.dispatch('setFoo', 'foo2');
    this.props.store.dispatch('setBar', 99);
  }
  
  afterUpdate() {
    const { foo, bar } = this.props.store.data;
    console.log(foo, bar);
  }
  
  render() {
    return null;
  }
}

Router

Example of Router usage.

import { Link, h, createApp } from "@sff-ui/core";

class Navbar extends Component {
  render() {
    return h('div', {}, [
      h(Link, { to: '/' }, 'Homepage'),
      h(Link, { to: '/about' }, 'About'),
      h(Link, { to: '/contact-us' }, 'Contact us'),
    ])
  }
}

class Footer extends Component {
  render() {
    return null;
  }
}

class Homepage extends Component {
  render() {
    return null;
  }
}

class About extends Component {
  render() {
    return null;
  }
}

class ContactUs extends Component {
  render() {
    return null;
  }
}

export class App extends Component {
  render() {
    return h(Router, {}, [
      h(Navbar),
      h(Route, {to: '/'}, h(Homepage)),
      h(Route, {to: '/about'}, h(About)),
      h(Route, {to: '/contact-us'}, h(ContactUs)),
      h(Footer),
    ]);
  }
}

createApp(App).mount(document.getElementById('app'));

Styles

To apply styles to your components, just import CSS file at the top of your component source file.

/* style.css */

.content {
    display: flex;
    height: 100%;
    background-color: red;
}
import "./style.css";
import { h } from "@sff-ui/core";

class Homepage extends Component {
  render() {
    return h('div', { className: 'content' });
  }
}