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

buttermilk

v2.0.1

Published

Beautifully simple isomorphic routing for React projects.

Downloads

112

Readme

npm version build status codecov downloads

buttermilk

installation

Grab the buttermilk NPM module with your favorite package manager.

npm i buttermilk

migrating from v1

  1. Upgrade all react dependencies (and buttermilk, of course):
npm i react@latest react-dom@latest react-is@latest buttermilk@latest
  1. If you are dynamically-importing components for any routes, wrap the import in React.lazy() (note that this only works in the browser right now because React.Suspense doesn't work server-side yet.)

routes: [
  {
    path: '/',
    render: () => React.lazy(() => import('./Home')),
  },
  {
    path: '*',
    render: () => NotFound,
  },
];

⛔️

routes: [
  {
    path: '/',
    render: () => import('./Home').then(mdl => mdl.default),
  },
  {
    path: '*',
    render: () => NotFound,
  },
];
  1. ??

  2. Profit!

usage

Setting up buttermilk involves placing a <Router> component on your page and feeding it an array of route definitions. If you learn better by reverse engineering, check out the holistic example.

basic example

import { Router } from 'buttermilk';
import React from 'react';

// whatever your folder structure looks like, etc.
import FooPage from '../foo';
import NotFoundPage from '../404';

class App extends React.Component {
  render() {
    return (
      <Router
        routes={[
          {
            path: '/foo',
            render: () => FooPage,
          },
          {
            path: '*',
            render: () => NotFoundPage,
          },
        ]}
      />
    );
  }
}

With the above setup, a URL like "https://yoursite.com/foo" would trigger the FooPage component to be rendered. All other paths would trigger the NotFoundPage component.

writing route configurations

Buttermilk has a highly flexible matching system, offering the following flavors of routing:

| flavor | syntax | | ------------------ | ----------------------------- | | static | "/foo" | | dynamic fragments | "/foo/:id" | | optional fragments | "/foo(/bar)" | | wildcard | "/foo*" | | splat | "/foo/**/bar.html" | | query string | "?foo=bar" | | fallback | "*" | | function callback | yourValidationFunction(url) | | regex | /^(?=bar)\/foo/ |

The only hard rule is there must be a fallback route at the end of the routing chain: path: "*". Otherwise, you are free to compose routes as it makes sense for your app.

A route configuration can take two forms:

  • A route that renders something:

    {
      path: String | RegExp | Function,
      render: Function,
    }
    
    // example
    
    {
      path: "/",
      render: () => "Hello world!",
    }

    Return whatever you'd like from the render function. A few ideas:

    • A React component class

      render: () => HelloWorldPage,
    • Some JSX

      render: () => <div>Hi!</div>,
    • A string

      render: () => 'Howdy!',
    • A React.lazy-wrapped dynamically-imported component

      render: () => React.lazy(() => import('./HelloWorld')),

    If it's a component class, Buttermilk will inject the routing context as props.

  • A route that redirects to another path:

    {
      path: String | RegExp | Function,
      redirect: String,
    }
    
    // example
    
    {
      path: "/bar",
      redirect: "/",
    }

You may also pass any other properties you'd like inside the route configuration object and they will be available to the RoutingState higher-order component, routing callbacks, etc.

components

<Router>

The gist of Buttermilk's router is that it acts like a controlled component when used server-side (driven by props.url) and an uncontrolled one client-side (driven by the value of window.location.href and intercepted navigation events.)

In the browser, use either a <Link> component or the route() utility method to change routes. The router will also automatically pick up popstate events caused by user-driven browser navigation (forward, back buttons, etc.)

Available props:

/**
 * Provide a spinner or something to look at while the promise
 * is in flight if using async routes.
 */
loadingComponent: PropTypes.oneOfType([
  PropTypes.func,
  PropTypes.string,
]),

/**
 * An optional app runtime component. Think of it like
 * the "shell" of your app, so perhaps the outer container,
 * nav bar, etc. You'll probably want to put any "Provider"
 * type components here that are intended to wrap your
 * whole application.
 */
outerComponent: PropTypes.oneOfType([
  PropTypes.func,
  PropTypes.string,
]),

routes: PropTypes.arrayOf(
  PropTypes.shape({

    /**
     * A RegExp, string, or function accepting the URL as
     * an argument and returning a boolean if valid.
     */
    path: PropTypes.oneOfType([
      PropTypes.instanceOf(RegExp),
      PropTypes.string,
      PropTypes.func,
    ]).isRequired,

    /**
     * A string URL path to a different route. If this is given,
     * then "render" is not required.
     */
    redirect: PropTypes.string,

    /**
     * A function that returns one of the following:
     *
     * 1. JSX.
     * 2. A React component class.
     * 3. A `React.lazy`-wrapped dynamic component import.
     */
    render: PropTypes.func,
  }),
).isRequired,

/**
 * A hook for reacting to an impending route transition.
 * Accepts a promise and will pause the route transition
 * until the promise is resolved. Return false or reject
 * a given promise to abort the routing update.
 *
 * Provides currentRouting and nextRouting as arguments.
 */
routeWillChange: PropTypes.func,

/**
 * A hook for reacting to a completed route transition. It
 * might be used for synchronizing some global state if
 * desired.
 *
 * Provides currentRouting and previousRouting as arguments.
 */
routeDidChange: PropTypes.func,

/**
 * A hook for synchronizing initial routing state.
 *
 * Providers initialRouting as an argument.
 */
routerDidInitialize: PropTypes.func,

/**
 * The initial URL to be used for processing, falls back to
 * window.location.href for non-SSR. Required for
 * environments without browser navigation eventing, like Node.
 */
url: PropTypes.string,

<RoutingState>

A render prop higher-order component (HOC) for arbitrarily consuming routing state.

<RoutingState>
  {routingProps => {
    // routingProps.location
    // (the parsed current URL in window.location.* form)

    // routingProps.params
    // (any extracted dynamic params from the URL)

    // routingProps.route
    // (the current route)

    return /* some JSX */;
  }}
</RoutingState>

<Link>

A polymorphic anchor link component. On click/tap/enter if the destination matches a valid route, the routing context will be modified and the URL updated without reloading the page. Otherwise, it will act like a normal anchor link.

A polymorphic component is one that can change shape as part of its public API. In the case of <Link>, props.as allows the developer to pass in their own base link component if desired.

This might make sense if you use a library like styled-components and want to make a shared, styled anchor link component.

If something other than an anchor tag is specified via props.as, a [role="link"] attribute will be added for basic assistive technology support.

Adds [data-active] if the given href matches the active route.

<Link as="button" href="/somewhere" target="_blank">
  Somewhere over the rainbow…
</Link>

Available props:

/**
 * An HTML tag name or valid ReactComponent class to
 * be rendered. Must be compatible with React.createElement.
 *
 * Defaults to an anchor "a" tag.
 */
as: PropTypes.oneOfType([
  PropTypes.func,
  PropTypes.string,
]),

/**
 * A valid relative or absolute URL string.
 */
href: PropTypes.string.isRequired,

/**
 * Any valid value of the anchor tag "target" attribute.
 *
 * See: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attr-target
 *
 * Defaults to "_self".
 */
target: PropTypes.string,

An example using a styled-components element base:

import { Link } from 'buttermilk';
import styled from 'styled-components';

const Anchor = styled.a`
  color: red;
`;

export default function StyledButtermilkLink(props) {
  return <Link {...props} as={Anchor} />;
}

utilities

match(routes, url)

This is an advanced API meant primarily for highly-custom server side rendering use cases. Provide your array of route defintions and the fully-resolved URL to receive the matched route, route context, and any suggested redirect.

import { match } from 'buttermilk';

const url = 'https://fizz.com/buzz';

const routes = [
  {
    path: '/foo',
    render: () => FooPage,
  },
  {
    path: '/bar',
    render: () => BarPage,
  },
  {
    path: '*',
    render: () => NotFoundPage,
  },
];

const { location, params, redirect, route } = match(routes, url);

When using this API, you'll probably want to have a more streamlined <Router> setup for the server since we're doing all the work upfront to find the correct route:

import { match, Router } from 'buttermilk';
import React from 'react';
import ReactDOMServer from 'react-dom/server';

import routes from '../routes';

/**
 * An example express middleware.
 */
export default function renderingMiddleware(req, res, next) {
  const url = req.protocol + '//' + req.get('host') + req.originalUrl;

  const { location, params, redirect, route } = match(routes, url);

  if (redirect) return res.redirect(redirect);

  const html = ReactDOMServer.renderToString(
    <Router
      url={url}
      routes={[
        {
          ...route,
          path: '*',
        },
      ]}
    />
  );

  /**
   * route.title below is an example arbitrary prop
   * you could add to the route configuration if desired
   */
  res.send(`
    <!doctype html>
    <html>
      <head><title>${route.title}</title></head>
      <body>${html}</body>
    </html>
  `);
}

route()

Use this API to programmatically change the route browser-side. It uses pushState or replaceState under the hood, depending on if you pass the second argument. Defaults to creating a new browser history entry.

// signature: route(url: String, addNewHistoryEntry: Boolean = true)

route('/some/other/url');

misc

RoutingContext

Used with the useContext react hook to get access to routingState in your functional components. Just an alternative to the RoutingState render prop component.

import { RoutingContext } from 'buttermilk';
import React, { useContext } from 'react';

function MyComponent(props) {
  const routing = useContext(RoutingContext);

  return <div {...props}>The current path is: {routing.location.pathname}</div>;
}

holistic example

See it live: https://codesandbox.io/s/2xrr26y2lp

/* Home.js */
export default () => 'Home';

/* index.js */
import React from 'react';
import ReactDOM from 'react-dom';

import { Router, RoutingState, Link } from 'buttermilk';

const App = props => (
  <div>
    <header>
      <h1>My sweet website</h1>
    </header>

    <nav>
      <Link href="/">Home</Link>
      <Link href="/blep/kitter">Kitter Blep!</Link>
      <Link href="/blep/corg">Corg Blep!</Link>
    </nav>

    <main>{props.children}</main>
  </div>
);

const NotFound = () => (
  <div>
    <h2>Oh noes, a 404 page!</h2>
    <RoutingState>
      {routing => (
        <p>
          No page was found with the path:
          <code>{routing.location.pathname}</code>
        </p>
      )}
    </RoutingState>

    <p>
      <Link href="/">Let's go back home.</Link>
    </p>
  </div>
);

const routes = [
  {
    path: '/',
    render: () => React.lazy(() => import('./Home')),
  },
  {
    path: '/blep/:animal',
    render: routing => (
      <img
        alt="Bleppin'"
        src={
          routing.params.animal === 'corg'
            ? 'http://static.damnlol.com/media/bc42fc943ada24176298871de477e0c6.jpg'
            : 'https://i.imgur.com/OvbGwwI.jpg'
        }
      />
    ),
  },
  {
    path: '*',
    render: () => NotFound,
  },
];

const root = document.body.appendChild(document.createElement('div'));

ReactDOM.render(<Router routes={routes} outerComponent={App} />, root);

without a bundler

You can also use consume Buttermilk from a CDN like unpkg:

https://unpkg.com/[email protected]/dist/standalone.js
https://unpkg.com/[email protected]/dist/standalone.min.js

The exports will be accessible at window.Buttermilk. Note that this requires react >= 16.8 (window.React),react-is >= 16.8 (window.ReactIs), and prop-types (window.PropTypes) to also be accessible in the window scope.

Both the minified and development versions ship with source maps for ease of debugging.

more examples

goals

  • centrally-managed routing
  • fast
  • first-class async support
  • HMR-friendly
  • obvious API
  • small
  • SSR

browser compatibility

internet explorer

Internet Explorer requires a polyfill to support the Event constructor.

Note that Babel does not transpile/polyfill this for you, so bootstrapped setups such as those based on Create React App will still need to manually include a polyfill.

Suggested: events-polyfill