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

@damienmortini/server

v1.1.0

Published

Simple live reloading HTTP2 server for development

Downloads

375

Readme

@damienmortini/server

Simple live reloading HTTP2 server for development

npx server [--path <path>] [--root <path>] [--base <base>] [--port <port>]
           [--watch <path>] [--watch-ignore <a,b>] [--proxy <path> <target>]
           [--auth <user:pass>] [--external-certificate] [--resolve-modules]
           [--directory-listing] [--verbose]

--watch and --proxy may be repeated. --directory-listing serves a browsable listing for a directory that has no index.html, which otherwise 404s; it is off by default because a listing exposes every file name under the served root. Live reload is always on. The --auth credential can also be passed as SERVER_AUTH in the environment, which keeps it out of argv — and so out of ps and /proc/<pid>/cmdline; the flag wins when both are set.

TypeScript served from source (no build step)

A request for <package>/dist/<path>.js is served from the sibling <package>/src/<path>.ts whenever that source exists — the source always wins, so a stale dist/ on disk is never served. Types are stripped with node:module's stripTypeScriptTypes; the module body is otherwise unchanged, apart from the bare-specifier rewrite described below.

HTML can therefore keep pointing <script src=".../dist/element/index.js"> at the published path: edit the .ts source and reload, with no build to run and no dist/ to refresh. Keep pointing at that dist/ URL rather than at src/ — the mapping is keyed on /dist/, so a direct .ts request is served raw and the browser rejects it on MIME grounds.

Bare specifiers in the browser (--resolve-modules)

With --resolve-modules, every served HTML page gets a generated <script type="importmap"> injected at the top of its <head>. Browser code can import installed packages by bare name, exactly like Node — there is no need to fetch a package.json and compute a path by hand:

import { Signal } from '@damienmortini/signal';

const module = await import(`@app/${name}-element/demo`); // computed names and subpaths too

Only bare specifiers go through the map. Relative imports must carry real extensions (./x.js) — they are standard browser ESM and pass through as-is.

Resolution anchors on the served root (--root, defaulting to the working directory), so the tree the server serves is the tree it resolves against wherever the process itself was started from.

What gets an entry

The server crawls the page's module graph — the <script type="module"> tags and their imports, transitively — and adds:

  • One entry per bare specifier the crawl reached, pointing at the canonical served URL of that module. Each specifier is resolved from the importing module's own location (Node semantics, so pnpm-style nested node_modules work), and symlinks are collapsed so a package reached through different links still maps to a single URL — browsers deduplicate modules by URL. A link pointing outside the served root (a submodule linked to a sibling checkout) is kept at the path it sits at, since there is no in-root path to collapse it onto; that path then mounts the checkout, so links deeper into the same one — pnpm gives every workspace dependency a link inside its consumer's own node_modules — collapse back onto it rather than staying distinct URLs. When the same specifier resolves differently depending on the importer, the odd ones out get a scopes entry keyed on the importer's directory.
  • One entry per installed package name, for every package found along the node_modules chains of the crawled modules. Import maps have no fallback for unmapped bare specifiers, so a name the crawl never reached would throw before any network request. Those names map to the reserved /@resolve/<name> route instead of a file, which keeps them lazy.
  • One trailing-slash prefix entry per installed package (<name>/ → /@resolve/<name>/), so subpath imports (@app/number-input-element/demo) resolve as well.

/@resolve/<specifier> resolves the specifier server-side at import time — relative to the importing page, taken from the Referer — and answers with a small re-export shim pointing at the canonical served URL (re-exporting the default too when the target declares one). That is what makes a computed import('@app/' + name) work: the browser only needs the name to be a map key, and the actual resolution happens on the request.

Static bare imports inside served module bodies are rewritten to resolved URLs server-side rather than left to the map, so module Workers — which never receive the page's import map — resolve their dependencies too.

Interaction with the dist/ → src/ rewrite

Map entries point at each package's published entry point (typically dist/….js), which is also what the browser requests, so the URLs in the map stay stable while the code behind them is always the live source. A package whose dist/ has never been built still gets a map entry: when Node resolution fails because the target file is absent, the specifier is resolved through the package's package.json without an existence check, and the request is then answered from src/.

Limits

  • A module reached only through /@resolve/ was never crawled, so package names visible solely from that module's own non-hoisted node_modules are not enumerated.
  • A specifier that cannot be resolved is left out of the map on purpose, so the browser's error names the real specifier. The server logs it too.
  • The generated map is injected first, and a document's import maps merge in order with the earlier map winning on a conflicting key, so a page's own hand-authored <script type="importmap"> cannot override a generated entry. Prefer deleting it and relying on the generated one.

Opting out of automatic reloads

Before reloading on a file change or reconnect, the injected live-reload client dispatches a cancelable server:livereload event (CustomEvent<{ reason: 'change' | 'reconnect' }>) on window. Call event.preventDefault() to take over the update yourself — for example to show a manual refresh control instead of reloading the page:

window.addEventListener('server:livereload', (event) => {
  event.preventDefault();
  showRefreshButton();
});