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

scouter

v0.0.8

Published

700 byte framework agnostic router.

Downloads

2

Readme

scouter

800 byte framework agnostic universal router.

Features

scouter is an attempt to take the magic out of routing front-end applications. Specifically, React. It's Not Components™, and hopefully that's a good thing.

  1. Data first
  2. Async-by-default
  3. Nestable
  4. Middleware
  5. Universal, works in Node

scouter can be used with most UI building libraries, but the examples below will use React.

Install

npm i scouter --save

Usage

import React from 'react'
import { render } from 'react-dom'
import { router, route, use } from 'scouter'

const app = router(
  use(context => {
    window.history.pushState({}, '', context.location)
  }),
  route({
    path: '/',
    component: props => <h1>Home</h1>
  }),
  route({
    path: '*',
    component: props => <h1>404 Not Found</h1>
  })
)

app.resolve('/').then(({ component: Comp }) => {
  render(<Comp />, document.body)
})

Defining routes

Routes accept an object as their only parameter. The route object can contain the following properties:

  • path - a path representing the route, including named parameters (required)
  • component - a component to be returned when the route is matched (required)
  • load - called when route matches, useful for loading data, can return a Promise
    • any returned data will be passed to child-route load functions and output from router.resolve()
  • options - an object with extra data and parameters
    • not used internally (at the moment), but can be useful to library users
import { route } from 'scouter'

const path = '/'

const component = props => (
  <h1>Home page</h1>
)

const load = context => {
  return new Promise((res, rej) => {
    resolve({})
  })
}

const options = {
  pageTitle: 'Home'
}

export default route({ path, component, load, options })

Asynchronous routes

When a load handler is defined on a route, scouter will wait to resolve the route until the routes's load function resolves. During this time, the developer can show a loading animation or what-have-you.

Named route parameters are passed to the load function on the context object.

import { route } from 'scouter'

const path = '/posts/:slug'

const component = post => (
  <h1>{post.slug}</h1>
)

const load = context => {
  return getPostFromAPI(context.params.slug)
}

export default route({ path, component, load })

Nested routes

Routes can be nested by passing a route to the function returned from the parent route. Nested routes build on the routes of their parents.

This means that certain generic routes can be reused in different locations. The code below results in four separate routes: /about, /about/:slug, /posts, and /posts/:slug.

import { router, route } from 'scouter'

const About = route({
  path: '/about',
  component: About
})

const Posts = route({
  path: '/posts',
  component: Posts
})

const Lightbox = route({
  path: ':slug',
  component: Lightbox
})

export default router(
  About(
    Lightbox
  ),
  Posts(
    Lightbox
  )
)

Nested loading

Nested routes will wait for their parent's load functions to resolve before calling their own. This is useful for cases where child routes depend on data from their parents.

import { router, route } from 'scouter'
import store from './my-store.js'

const About = route({
  path: '/about',
  component: props => (
    <div>
      <h1>About page</h1>

      <ul>
        {props.teamMembers.map(person => (
          <li>{person.name}</li>
        ))}
      </ul>
    </div>
  ),
  load: context => {
    store.setState({
      teamMembers: [
        {
          name: 'Person One',
          slug: 'person-one'
        },
        {
          name: 'Person Two',
          slug: 'person-two'
        }
      ]
    })
  }
})

const AboutMember = route({
  path: ':slug',
  component: props => {
    const person = store.getState().teamMembers.filter(person => (
      person.slug === props.params.slug
    ))[0]
    return (
      <h1>{person.name}</h1>
    )
  }
})

const app = router (
  About(
    AboutMember
  )
)

app.resolve('/about/person-two').then(({ component: Comp, data, params }) => {
  render(<Comp params={params} />, document.body) // => <h1>Person Two</h1>
})

Middleware

scouter implements very simple middleware. Middleware are applied for the route scope in which they are defined, and all nested scopes.

Below, middleware1 will be called when the / route resolves. When the /posts/:slug route resolves, both middleware1 and middleware2 will be called.

const middleware1 = use(context => console.log('Top level!'))
const middleware2 = use(context => console.log('Nested!'))

export default router(
  middleware1,
  Home,
  Posts(
    middleware2,
    Post
  )
)

Using context

You can optionally pass an initial context object when calling router.resolve(). This is useful when dealing with API keys, authorization tokens, etc.

router.resolve('/', { auth_token: 'abcde12345' }).then(props => ...)

The context object is passed to all load functions, as well as any middleware you've defined along the way.

const About = route({
  path: '/about',
  component: About,
  load: context => {
    console.log(context.auth_token) // => abcde12345
  }
})

Server Side Rendering

const app = require('express')()
const { renderToString } = require('react-dom/server')

app.get('*', (req, res) => {
  router.resolve(req.originalUrl).then(({ component: Comp }) => {
    res.send(`
      <!doctype html>
      <html>
        <head>
        </head>
        <body>
          ${renderToString(<Comp />)}
        </body>
      </html>
    `)
  })
})

app.listen(8080)

Recipes TODO

  1. document.title using options.title utility
  2. Incremental rendering using picostate
  3. Redirect example

MIT