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

featherweight

v3.0.0

Published

featherweight application pattern

Downloads

9

Readme

featherweight

PLEASE NOTE: This is all highly experimental, I've not built anything major with this and there are holes in functionality and unsolved problems related to events. Also, just because I'm open sourcing doesn't mean I'll maintain it for you or do anything else with it, ever. This is a pure experiment at this point. I'm simply sharing it in the spirit of open source in case anyone finds it interesting.

No Maintenance Intended

The basic idea of featherweight is that you as a developer only do this:

  1. Configure redux however you want it (all your application state will live in Redux)
  2. Write a single, main, pure, synchronous view function that takes the state from redux (including the current URL of your app) and returns a virtual dom to represent what it should look like at that exact moment.

That's it!

Featherweight then manages everything else. It's a simple abstraction to manage communication between the worker thread (where Redux and your reducers will run) and the main thread:**

  1. 99% of your app code runs in a web worker
  2. No fancy router, the url is just another piece of state
  3. You can super easily pre-render as much HTML as possible before sending to the client either on the server or to static HTML files at build time.
  4. JS "takes over" on the clientside as soon as it's loaded.
  5. All code necessary to do the above should be < 10kb min + gzipped

understanding the application pattern

Featherweight lets you do all the heavy application work in a web worker. This means rendering new UI, virtual dom diffing, data fetching, etc. all happens in a Web Worker.

This leaves the main thread free to focus entirely on efficient DOM updates and listening for user interractions.

It consists of two primary components:

  1. The worker code: import { worker } from 'featherweight'
  2. The main thread (ui thread) code: import { ui } from 'featherweight'

Setting up the main thread

Featherweight's ui handles most of the main thread for you. The only custom code you need here, may be to import whatever styles or 3rd party scripts.

Typically, it would just look like this:

// import the ui module
import { ui } from 'featherweight'

// import your worker code (with webpack-worker-loader this Just Works™)
import WorkerThread from './worker.thread'

// possibly import styles if you're using some sort of css/style loader with webpack
import './styles/main.styl'

ui({
  // pass it the worker thread
  worker: new WorkerThread(),

  // Pass in the root element where you
  // want the app to live. It's recommended
  // that this be something other than the
  // <body> in case other libraries or
  // browser plugins need to insert elements, etc,
  rootElement: document.body.firstChild
})

setting up the worker thread

Here's where all the work happens, but again, most of your efforts will go into writing the Redux reducers and UI code.

The boilerplate inside the worker is pretty straight forward:

// worker.thread.js
import { worker } from 'featherweight'
import appView from './views/app'
import configureRedux from './configureRedux'

worker({
  // pass it your redux main store
  // here we assume we're setting up
  // redux and it's reducers in another module
  // that exports a function we can use to
  // get the configured redux instance
  redux: configureRedux(),

  // This is the main application UI component that
  // you'll write. It should be a pure function returning
  // a new virtual DOM when passed the main application state
  // object we get from Redux. 
  view: appView,

  // To keep things easy to trace we also want to 
  // explicitily  pass in the reference to the worker
  // context. This will always just be `self`. 
  // 
  // If you're unfamiliar with this concept, it's just 
  // something that exists within all Web Workers. 
  // 
  // featherweight needs this in order to be able to listen for
  // and send DOM updates back to the ui thread.
  workerContext: self
})

Writing the UI components

The assumption is that components don't have state. All state lives in Redux, including the current URL of the app.

This means the entire UI needs to be a single pure function that takes the application state object and return a new virtual dom.

It's signature is simple:

virtualDom = ui(state)

Of course, you still have to be able to break your application into small, modular components and show different things based on different urls.

To address this, here's a super simple example:

import home from './home'
import about from './about'
import pageNotFound from './pageNotFound'

export default (state) => {
  // we just grab the `.url` property of state
  const { url } = state
  let page

  // We then grab a `page` component
  // conditionally based on that url
  if (url === '/') {
  	// here we can pass the state through
  	// if we'd like
    page = home(state)
  } else if (url === '/about') {
    // this page is just text content
    // so passing in state isn't necessary
    page = about()
  }

  // we could also handle URLs our app isn't
  // aware of with a fallback page
  if (!page) {
    page = pageNotFound()
  }

  // here we simply return the JSX and include
  // the `{page}` content as part of our layout
  return (
    // note that `main` here is just an HTML5 element
    // nothing special
    <main>
      <h1>Feather POC App</h1>
      <nav>
        <a href='/'>home</a> | <a href='/about'>about</a>
      </nav>
      {page}
    </main>
  )
}

handling state

For cleanliness, I suggest setting up your Redux in a separate file.

That file may look something like this:

import { createStore } from 'redux'
import * as reducers from './reducers/index'

export default () => {
  return createStore(reducers)
}

pre-rendering at build time or as part of a server response

Because your app UI is a pure function, turning your view into HTML is quite simple.

Simply call your apps main UI function passing in whatever state you'd like to render to generate virtual DOM.

Then you can use the vdom-to-html module from npm to generate an HTML string.

import toHtml from 'vdom-to-html'
import app from './views/app'

// wherever you want to create the HTML string
const renderedHtml = toHtml(app({url: '/about'}))

featherweight patterns

  1. Under no circumstances should your complete app weigh more than 60 kb min+gzip JS. Ideally, much less.
  2. every. single. piece. of. state. lives. in. redux
  3. Leave the main thread alone
  4. Never touch the DOM directly
  5. The UI thread does nothing other than apply DOM updates and post events back to the worker thread
  6. Never use this
  7. Never use function use () => {} for everything
  8. The UI is a pure function
  9. There are no stateful components
  10. Pretty much everything is a pure function
  11. Name all your modules with camelCase file names
  12. Use standard for code style
  13. The build step should turn your app into a set of static HTML, CSS, and JS files.

install

npm install featherweight

credits

If you like this follow @HenrikJoreteg on twitter.

license

MIT