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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@timberstack/hwr

v0.0.3

Published

A vanilla PHP-like experience for javascript

Downloads

6

Readme

Hono Web Router (HWR)

[!NOTE] This package is meant to be used with Deno. If you see the source code, you'll see a lot of apis imported from node. This is because I might give support to node eventually.

Plain, simple server side rendering using JSX as templating language. It uses Hono under the hood, so anything compatible with Hono should be compatible with HWR. It has an optional integration with Vite for handling JS and CSS files.

I developed this primarily for testing and better playing with the rest of the timberstack packages. I saw, however, a great potential to this, so I've decided to open source it. As you may imagine, this comes with no frontend framework integration. It's just like a vanilla PHP project but on steroids.

It assumes 0 config from your side. It does, however, create a hwr.config.ts file in the root of your project for exposing the most useful config options, just in case you need to tweak anything.

Given the following structure:

├── deno.json
├── public
├── resources
│   ├── entrypoints
│   │   └── example.js
│   └── js
└── views
    └── index.tsx

And your deno.json looking like this:

{
  "tasks": {
    "dev": "hwr dev",
    "start": "hwr start",
    "build": "hwr build",
    "init": "hwr init"
  },
  "compilerOptions": {
    "jsx": "precompile",
    "jsxImportSource": "hono/jsx"
  },
  "nodeModulesDir": "auto",
  "imports": {
    "@timberstack/hwr": "npm:@timberstack/[email protected]",
    "hono": "npm:hono@^4.7.8",
    "vite": "npm:vite@^6.3.4"
  }
}

You can start enjoying the simplicity of rendering your JSX on the server "without" build step. Well, unless you use vite, in which case you should just run the build command for generating the assets and the manifest.json file.

Commands

hwr init # generates the files from where you are exporting types and helpers
hwr dev # initializes the hono server and the vite dev server
hwr build # builds the hardcoded router, vite assets and generates a json file used to load the vite assets based on the manifest.json
hwr start # runs your application in production mode

Routing

This is how the routing works. Say you have this structure in your /views directory:

views
    ├── [all].tsx # /* -> catch all routes in the web router
    ├── about.tsx # /about
    ├── index.tsx # /
    └── nested
        ├── [all].tsx # /nested/*
        ├── $greeting.tsx # /nested/:greeting
        ├── $username
        │   └── user.tsx # /nested/:username/user
        ├── contact.tsx # /nested/contact
        └── index.tsx # /nested

File router folder

Under the hood, we are creating a folder in the root of your project called /file-router. You shall not touch anything inside it, since everything inside is auto-generated. The most relevant files are:

file-router
   ├── bootstrap.ts # Entry point of your application. After registering your routes, we also register an endpoint pointing at the public folder
   ├── names.json # Map for populating the named routes and actions when precompile options is set to true
   ├── route-getters.ts # We export two functions from here: route and action. More on those below
   ├── router.js # Hard coded routes for your views directory. Used when precompile is set to true. Useful for mitigating cold starts
   ├── routes.d.ts # Type definitions for your named routes and actions
   └── vite.scripts.json # Generated during vite build. Used by the vite head component to determine the scripts and css to be loaded based on the manifest.json

Anatomy of a file

import type { Context } from "hono";

// it includes the exported ViteHead component. No magic here, just create and use it.
import { Layout } from "./_components/layout.tsx";

// this name will be available for you to access the route without having to worry about the folder structure
export const name = "index";

// actions are functions called only via POST and GET methods. You can use the action helper to get intellisense.
// It would be equivalent to making a request to the current route with a ?action=actionName param.
export const actions = {
  createUser: (ctx: Context) => {
    return ctx.html(<Index user="Working" />);
  },
};

// Your JSX must be the default export
export default function Index({ user = "" }) {
  return <h1>Hello from Home page</h1>;
}

Utils

route and action

Remember the route-getters file is auto-generated. you should run hwr init for it to not error. alternatively, when running the dev server it will also generate it. The reason fot that is so we can get a good intellisense based on the dynamically generated types.

import { action, route } from "../file-router/route-getters.ts";

export default function () {
  return (
    <h1>
      <a href={route("about")}>About page at route /about</a>{" "}
      <a href={route("dynamic", { username: "jane" })}>
        Dynamic route at route /nested/:username/user
      </a>{" "}
      <form method="POST" action={action("createUser")}>
        {/* post request to /user?action=createUser */}
      </form>
    </h1>
  );
}

Components

ViteHead

import { ViteHead } from "@timberstack/hwr";

<head>
  {/* the script name must be relative to your specified resources dir path. By default, "/resources/entrypoints/" */}
  <ViteHead script="script.js" />
</head>;

Config

When first running the project, you'll have a file called hwr.config.ts in your project's root. It ships with the following defaults (code copied from source code):

const defaultConfig: Config = {
  // Folder to be used as root for the router
  viewsDir: "/views",
  // Folder from where to serve public assets. It will also be the outDir for vite
  publicDir: "/public",
  // Serve the routes directly from your root folder by default. Change it to true if you want to use the generated router file instead.
  precompile: false,
  // Folder where the generated files will live
  routerPath: "/file-router",
  // You can opt out from vite by setting this to false
  useVite: true,
  // Entry points for vite build
  resourcesPath: "/resources/entrypoints",
  // Set it to false for production. Don't forge to run "hwr build" first!
  viteDevMode: true,
  // It accepts a vite UserConfig like object. Under the hood, we are creating our own for the build, but just in case you need to add plugins and stuff.
  vite: {},
};