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

@dark-engine/web-router

v1.4.2

Published

The isomorphic Dark router designed for rendering universal web applications that work both on the client and on the server

Downloads

5,365

Readme

@dark-engine/web-router 🌖

The isomorphic Dark router designed for rendering universal web applications that work both on the client and on the server.

More about Dark

Features

  • 🌳 Nested routes
  • 🍩 Lazy loading
  • ↪️ Redirects
  • 🌠 Wildcards a.k.a Fallbacks
  • 🔄 Combination wildcards and redirects
  • 🔢 Parameters
  • 📈 Hooks
  • 💽 SSR
  • 🔀 Concurrent transitions
  • ✂️ No deps
  • 📦 Small size (4 kB gzipped)

Installation

npm:

npm install @dark-engine/web-router

yarn:

yarn add @dark-engine/web-router

CDN:

<script src="https://unpkg.com/@dark-engine/web-router/dist/umd/dark-web-router.production.min.js"></script>

API

import {
  type Routes,
  type RouterRef,
  Router,
  Link,
  NavLink,
  useLocation,
  useHistory,
  useParams,
  useMatch,
  usePending,
  VERSION,
} from '@dark-engine/web-router';

Defining a basic route

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component', component: SecondComponent },
];

const App = component(() => {
  return (
    <Router routes={routes}>
      {slot => {
        return (
          <>
            <header>
              <NavLink to='/first-component'>first-component</NavLink>
              <NavLink to='/second-component'>second-component</NavLink>
            </header>
            <main>{slot}</main> {/*<-- a route content will be placed here*/}
          </>
        );
      }}
    </Router>
  );
});

<base href>

You must add the element to the application's index.html for pushState routing to work.

<base href="/">

Also you must pass the baseUrl to Router if it is different from /.

<Router routes={routes} baseUrl={YOUR_BASE_URL}>{slot => slot}</Router>

Route order

The order of routes is important because the Router uses a first-match wins strategy when matching routes, so more specific routes should be placed above less specific routes. List routes with a static path first, followed by an empty path route, which matches the default route. The wildcard route comes last because it matches every URL and the Router selects it only if no other routes match first.

Getting route information

const FirstComponent = component(() => {
  const location = useLocation(); // url, protocol, host, pathname, search, key
  const match = useMatch(); // url prefix for links

  return <div>FirstComponent</div>;
});

Setting up wildcard routes

{ path: '**', component: ComponentName }

Displaying a 404 page

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component', component: SecondComponent },
  { path: '**', component: PageNotFoundComponent },  // Wildcard route for a 404 page
];

Setting up redirects

const routes: Routes = [
  { path: 'first-component', component: FirstComponent },
  { path: 'second-component', component: SecondComponent },
  { path: '',   redirectTo: '/first-component', pathMatch: 'full' }, // redirect to `first-component`
  { path: '**', component: PageNotFoundComponent },  // Wildcard route for a 404 page
];

Nesting routes

const routes: Routes = [
  {
    path: 'first-component',
    component: FirstComponent, // The component receives children routes as slot
    children: [
      {
        path: 'child-a', 
        component: ChildAComponent,
      },
      {
        path: 'child-b',
        component: ChildBComponent,
      },
    ],
  },
];

Flat Nesting routes

const routes: Routes = [
  {
    path: 'first-component/child-a',
    component: ChildAComponent,
  },
   {
    path: 'first-component/child-b',
    component: ChildBComponent,
  },
  {
    path: 'first-component',
    component: FirstComponent, // In this case slot will be null
  },
];

Nested wildcards

const routes: Routes = [
 {
    path: 'first-component',
    component: FirstComponent,
    children: [
      {
        path: 'child-a', 
        component: ChildAComponent,
      },
      {
        path: 'child-b',
        component: ChildBComponent,
      },
      {
        path: '**',
        redirectTo: 'child-a',
      },
    ],
  },
];

or

const routes: Routes = [
 {
    path: 'first-component',
    component: FirstComponent,
    children: [
      {
        path: 'child-a', 
        component: ChildAComponent,
      },
      {
        path: 'child-b',
        component: ChildBComponent,
      },
      {
        path: '**',
        component: PageNotFoundComponent,
      },
    ],
  },
];

Navigation

via Link or NavLink

<Link to='/user/50'>Go to profile</Link>
<NavLink to='/home'>Home</NavLink>

NavLink internally uses Link, but at the same time provides a CSS class .active-link if the current URL is equal to or contains the to parameter of NavLink. NavLink can be used for headers and menus, which will continue to be on the page when it is clicked and the content is changed. Link means that it will disappear from the screen after you click it and go to another page. Of course you can create your own logic based on Link, using it as a base component.

via history

const SomeComponent = component(() => {
  const history = useHistory();

  useEffect(() => {
    history.push('/home'); // or history.replace('/home');
  }, []);

  return <div>SomeComponent</div>;
});

Parameters

Sometimes, a feature of your application requires accessing a part of a route, such as a parameter like id of something. You can define parameterized route like below.

const routes: Routes = [
  {
    path: 'first-component/:id',
    component: FirstComponent,
  },
  {
    path: 'second-component',
    component: SecondComponent,
  },
];

Then get access for parameter through hook

const FirstComponent = component(() => {
  const params = useParams();
  const id = Number(params.get('id'));

  return <div>FirstComponent: {id}</div>;
});

Lazy loading

You can configure your routes to lazy load modules, which means that Dark only loads modules as needed, rather than loading all modules when the application launches.

import { lazy } from '@dark-engine/core';

const Home = lazy(() => import('../components/home'));
const About = lazy(() => import('../components/about'));
const Contacts = lazy(() => import('../components/contacts'));

const routes: Routes = [
  {
    path: 'home',
    component: Home,
  },
  {
    path: 'about',
    component: About,
  },
  {
    path: 'contacts',
    component: Contacts,
  },
];

Imperative access to router

const App = component<AppProps>(({ url, routes }) => {
  const ref = useRef<RouterRef>(null);

  useEffect(() => {
    ref.current.navigateTo('/about');
  }, []);

  return (
    <Router ref={ref} routes={routes}>
      {slot => slot}
    </Router>
  );
});

Server-Side Rendering (SSR)

If you are rendering the application on the server, then you must pass the request URL to the router to emulate routing when rendering to a string.

server.get('*', async (req, res) => {
  const { url } = req;
  const app = await renderToString(Page({ title: 'My App', slot: App({ url }) }));
  const page = `<!DOCTYPE html>${app}`;

  res.statusCode = 200;
  res.send(page);
});
const App = component(({ url }) => {
  <Router routes={routes} url={url}>{slot => slot}</Router>
})

Full example SSR routing you can see in /examples.

Concurrent mode

In this mode, the router will automatically render through non-blocking transitions, using startTransition under the hood. To track the isPending flag and show the waiting UI you can use the special usePending hook.

<Router routes={routes} mode='concurrent'>
  {slot => slot}
</Router>
const Pending = component(() => {
  const isPending = usePending();

  // Makes the content on the screen a little transparent while the transition is in progress.
  return <Overlay isPending={isPending} />;
});

LICENSE

MIT © Alex Plex