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

feather-render

v1.1.9

Published

A feather light render framework

Downloads

5

Readme

Feather Render

gzip license version

✨ A feather light render framework ✨ 621 bytes minified and gzipped - no dependencies - SSR support

Companion frameworks:

Live examples:

coffee

Getting started

Package

npm i feather-render

...or inline

<head>
  <script src="feather-render.min.js"></script>
</head>
<body>
  <script>
    const { html, hydrate } = window.__feather__ || {};
  </script>
</body>

Index

Usage

Documentation

Examples

Usage

Basic syntax

import { html } from 'feather-render';

const TodoItem = ({ todo }) => {
  return html`
    <li>${todo.title}</li>
  `;
};

const TodoList = () => {
  return html`
    <ul>${todos.map(todo => TodoItem({ todo })).join('')}</ul>
  `;
};

const Document = () => html`
  <!DOCTYPE html>
  <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Feather</title>
      <script type="module" src="index.js"></script>
    </head>
    <body>
      ${TodoList()}
    </body>
  </html>
`;

Tip: Plugins for VSCode like lit-html or Inline HTML can be used for syntax highlighting.

Server-Side Rendering (SSR)

import express from 'express';
import { Document } from './components/Document';

const server = express();

server
    .get('/', (req, res) => {
        res.send(Document().toString());
    })
    .use(express.static('dist'));

server.listen(5000);

Client hydration

import { hydrate } from 'feather-render';
import { TodoList } from './components/TodoList.js';

hydrate(TodoList(), document.body);

Documentation

html()

const { refs, render, mount, unmount } = html`<div></div>`;

Parameters

  • string - html template string to render

Return value: Render

  • refs - list of id'ed elements
  • render - return of functional component
  • mount() - run callback on mount
  • unmount() - run callback on unmount

html().mount()

mount(() => {
  console.log('Component inserted in DOM');
});

Parameters

  • callback() - function called when component is inserted in DOM

Return value

  • void

html().unmount()

unmount(() => {
  console.log('Component removed from DOM');
});

Parameters

  • callback() - function called after component is removed from DOM

Return value

  • void

hydrate()

hydrate(App(), document.body);

Parameters

  • element - Render from html()
  • target - where to mount the DOM

Return value

  • void

Examples

Re-rendering

Primitive values

import { store } from 'feather-state';
import { html } from 'feather-render';

const { watch, ...state } = store({
  greeting: 'Hello, World'
});

const Component = () => {
  const { refs, render } = html`
    <p id="paragraph">${state.greeting}</p>
  `;

  // Watch greeting + update DOM
  watch(state, 'greeting', (next) => {
    refs.paragraph?.replaceChildren(next);
  });

  // Change greeting state
  setTimeout(() => {
    state.greeting = 'Hello, back!';
  }, 1000);

  return render;
};

Lists

import { store } from 'feather-state';
import { html } from 'feather-render';

const { watch, ...state  } = store({
  todos: ['Todo 1', 'Todo 2'];
});

const TodoItem = ({ todo }) => {
  return html`
    <li>${todo}</ul>
  `;
};

const TodoList = () => {
  const { refs, render, mount } = html`
    <ul id="todoList">
      ${state.todos.map(todo => (
        TodoItem({ todo })
      )).join('')}
    </ul>
  `;

  const reRenderTodos = () => {
    const fragment = new DocumentFragment();
    for (let todo of todoStore.todos) {
      const { element } = TodoItem({ todo });
      element && fragment.appendChild(element);
    }
    refs.todoList?.replaceChildren(fragment);
  };

  // Watch todos + update DOM
  watch(state, 'todos', () => {
    reRenderTodos();
  });

  // Hydrate TodoItems
  mount(() => {
    reRenderTodos();
  });

  // Append todo in state
  setTimeout(() => {
    state.todos = [...state.todos, 'Todo 3'];
  }, 1000);

  return render;
};

Event listeners

Form submission

import { html } from 'feather-render';

const Component = () => {
  const { refs, render, mount, unmount } = html`
    <form id="form">
      <p id="status">Fill in form</p>
      <input type="text" />
      <button type="submit">Submit</button>
    </form>
  `;

  const handleSubmit = (event) => {
    event.preventDefault();
    refs.status?.replaceChildren('Submitting');
  };

  mount(() => {
    refs.form?.addEventListener('submit', handleSubmit);
  });
  unmount(() => {
    refs.form?.removeEventListener('submit', handleSubmit);
  });

  return render;
};

Fetching

Server and client

const App = () => {
  const { render } = html``;

  fetch('http://localhost:5000/api/v1/user')
    .then(res => res.json())
    .then(res => console.log(res));

  return render;
};

Server or client

const isServer = () => typeof window === 'undefined';
const isClient = () => typeof window !== 'undefined';

const App = () => {  
  const { render } = html``;

  if (isServer()) {
    fetch('http://localhost:5000/api/v1/user')
      .then(res => res.json())
      .then(res => console.log(res));
  }

  if (isClient()) {
    fetch('http://localhost:5000/api/v1/user')
      .then(res => res.json())
      .then(res => console.log(res));
  }

  return render;
};

On mount

const App = () => {
  const { render, mount } = html``;

  mount(() => {
    fetch('http://localhost:5000/api/v1/user')
      .then(res => res.json())
      .then(res => console.log(res));
  });

  return render;
};

Other

Lazy / Suspense

const App = () => {
  const { render } = html`
    <div id="lazyParent"></div>
  `;

  import('./LazyComponent').then(({ LazyComponent }) => {
    const { element } = LazyComponent();
    element && refs.lazyParent?.replaceChildren(element);
  });

  return render;
};

Unique id's

let i = 0;
export function id(name: string) {
  return `${name}_${i++}`;
}
import { id } from '../helpers/id';

const App = () => {
  const uniqueId = id('unique');

  const { refs, render, mount } = html`
    <div id=${uniqueId}></div>
  `;

  mount(() => {
    refs[uniqueId]?.replaceChildren('Component mounted');
  });

  return render;
};

Roadmap 🚀

  • CLI tool
  • Automatically hydrate child components
  • Cleaner way of referencing values in html
  • Binding values, re-renders and listeners
  • CSS in JS examples