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

@applicvision/frontend-friends

v1.16.1

Published

Build frontend easily

Readme

Frontend Friends     Tests NPM Version install size

Say hi to your new best friends for frontend development!

Frontend friends offer a lightweight alternative to massive SPA-libraries. The humble wish is to provide you with a great developer experience as you navigate between declarative views, easy-to-follow state management, two-way bindings, effective list rendering, component authoring and interactive islands. All this with type hints, but without any build step or extra dependencies.

Getting started

To get the best developer experience, install Frontend Friends as an npm package.

$ npm install @applicvision/frontend-friends

Then import it in your JavaScript.

import { island, html } from '@applicvision/frontend-friends'

const app = island(
  () => html`<section>
    ${['You', 'did', 'it!'].map(
      text => html`<p>${text}</p>`
    )}
  </section>`
)

app.mount(document.body)

Then to check it out in your browser you have two options; CDN or build tool.

CDN

The easiest way to get started is to let your web page get the code from a CDN which can deliver ES modules, such as JSDeliver, and their modules CDN, or esm.sh.

First, add an importmap to your html-file. This tells the browser to fetch all imports of @applicvision/frontend-friends from esm.sh.

<script type="importmap">
{
  "imports": {
    "@applicvision/frontend-friends": "https://esm.sh/@applicvision/frontend-friends",
    "@applicvision/frontend-friends/": "https://esm.sh/@applicvision/frontend-friends/"
  }
}
</script>

Then, add a script tag pointing to your application:

<script type="module" src="main.js"></script>

And that's it! Now serve your files any way you like, and start building your web application, using modern technologies without any intermediate build tool.

But why npm install if the code is fetched from CDN?

Good question! It is not needed for the application to run in the browser, but it is highly recommended to get the correct developer experience. Here is some more information.

Build tool

Since Frontend Friends is shipped as an npm package, it is naturally also possible to use in a build tool setup. For instance, using vite:

$ npm install -D vite

And then just start the dev server:

$ npx vite

Using this strategy, no <script type="importmap"> should be used.

For more information on using Vite, please consult their great documentation at https://vite.dev/

Creating your first component

Frontend Friends leverages the built-in way to work with components; Custom elements. So, as soon as the components are defined in the custom elements registry, they can be used anywhere on the page. Either straight in HTML, by dynamic creation document.createElement, or even in another view system such as Vue or React.

Let's say we want a greeting component. Here is a simple example:

import { DeclarativeElement, html } from '@applicvision/frontend-friends'

// Make a class which extends DeclarativeElement
class GreetingLabel extends DeclarativeElement {

  // This will make the element react to changes to the 'name' attribute
  static observedAttributes = ['name']

  // The render function is called whenever the view needs an update
  // It should return a tagged template string, using the imported html function from frontend-friends
  render() {
    
    // This simply returns markup for a header with text Hello plus the name passed as attribute
    return html`<h2>Hello ${this.getAttribute('name')}!</h2>`
  }

  // Lastly, add a static initialization block to define the class in the custom elements registry.
  static { customElements.define('greeting-label', this) }
}

The name of the element has to include a hyphen, and follow some other rules.

The html tag function in front of the backtick string literal might look a bit unfamiliar. But it has many advantages. Under the hood it is used both to determine which parts of the template are dynamic and which parts are not, and also to automatically cache pieces of rendered markup for potential reuse.

The tagged template strategy is also used in Lit.

And it actually brings one additional advantage: we can get context awareness in the string. In the example code blocks here we get syntax highlighting for HTML even though we are in a JavaScript context. And there are useful extensions for VS Code to improve the developer experience. Feel free to explore available extensions.

But now, back to our code. Our component is ready! We can now use the element directly in our HTML:

<greeting-label name="World"></greeting-label>
<!-- note that custom elements must have a closing tag, even if they are 'empty' and not designed to render any content -->

The element can also be created dynamically:

const greetingLabel = document.createElement('greeting-label')
greetingLabel.setAttribute('name', 'World')
document.body.appendChild(greetingLabel)
// Updating the attribute will re-render the element
greetingLabel.setAttribute('name', 'New world')

Creating an island

Now let's use our greeting-label in a context together with an input field where the user could enter the name to be passed to the greeting-label. To do that, we can define a little 'micro-app'. These small pieces of logic are sometimes referred to as islands.

import { island, html } from '@applicvision/frontend-friends'

const app = island(
  // The first argument to island can be thought of as a setup function. It is called once at island creation.
  // Put your initial state in a property called 'state' on the object returned.
  // Changes to that object will cause the island to update.
  function () { 
    return { state: { name: '' }}
  },
  // The second argument is the render function.
  // It is called with the current state of the island every time a visual update is needed.
  ({ state }) => html`
    <input
      placeholder="Enter your name"
      value=${state.name}
      oninput=${updateName}
    >
    <greeting-label name=${state.name || 'stranger'}></greeting-label>
  `
)

function updateName(event) {
	app.state.name = event.target.value
}

// Here we assume an element with id app somewhere in the document.
app.mount(document.getElementById('app'))

This is quite a common pattern, so there is an easier way; we can use a two-way binding. That means we let the state be changed also directly by the input field. To enable this, a special attribute is used, ff-share. This indicates a two-way binding. It corresponds to v-model in Vue.

So we rewrite the previous code slightly.

// twoway is a little helper that simply creates an object with a set and a get function, which is the shape required for the two-way binding.
import { island, html, twoway } from '@applicvision/frontend-friends'

const app = island(
  () => ({ state: { name: '' }}),
  ({ state }) => html`
    <input
      placeholder="Enter your name"
      ff-share=${twoway(state, 'name')}
    >
    <greeting-label name=${state.name || 'stranger'}></greeting-label>
  `
)

app.mount(document.getElementById('app'))

Examples

A more extended example is located in the docs folder of the repository. The JavaScript can be found here. And, to check out the result of the example, please visit https://applicvision.github.io/frontend-friends/examples/todo.

Typescript

Yes, you can use TypeScript. Frontend friends includes JSDoc annotation for most of the API. For instance, DeclarativeElement can be a generic class if using shared state:

class MyStatefulElement extends DeclarativeElement<boolean> {
  render() {
    // this.sharedState state is boolean here
    return html`active: ${this.sharedState}`
  }
}

API Documentation

API documentation is available here.