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

@opentf/esdev-plugin-afw

v0.9.0

Published

Compile AFW components and serve file-based routes in esdev.

Readme

@opentf/esdev-plugin-afw

Compile AFW components and serve file-based routes in esdev

Docs | Changelog | GitHub

An Open Tech Foundation project

One line in esdev.json gives esdev the AFW compiler: .jsx, .tsx, .mdx and .md compile on import in esdev start, esdev build and esdev test, and app/ becomes a route map.

{
  "plugins": ["@opentf/esdev-plugin-afw"],
  "build": {
    "targets": {
      "web": { "entry": "index.html", "outdir": "dist" }
    }
  }
}
// entry.js, loaded by index.html next to an #app element
import { mountApp } from "@opentf/afw";
import { pages, guard, loaderRoutes } from "@afw/routes";

mountApp({ pages, guard, loaders: loaderRoutes, target: document.getElementById("app") });

@afw/routes is generated from app/**/{page,layout,404}.{jsx,tsx,mdx,md}, so adding a file adds a route. CSS, Tailwind and assets go through esdev's own pipeline.

[!NOTE] Alpha. Options can change between minor versions; see the changelog.

Install

pnpm add @opentf/afw
pnpm add -D @opentf/esdev-plugin-afw

Requires esdev 0.16 or newer (0.17+ for development refresh). The afwc compiler comes from @opentf/afw-compiler, installed as a dependency. esdev create my-app --template=spa generates a project already configured this way.

Rendering modes

Pass options to pick how pages are rendered:

{
  "plugins": [{
    "module": "@opentf/esdev-plugin-afw",
    "options": { "mode": "ssg", "appDir": "app" }
  }]
}

The plugin chooses a compiler backend per module from the build's context:

| Context | mode: "spa" (default) | mode: "ssg" / "ssr" | | --- | --- | --- | | Server build, esdev start, or a script | ssg | ssg | | Browser release build | csr | hydrate | | Browser development or DOM test | csr | csr |

Server builds import framework helpers from @opentf/afw/server, which renders HTML without browser Custom Elements, so a server target can point straight at server.js.

Options

| Option | Default | Purpose | | --- | --- | --- | | mode | "spa" | spa, ssg or ssr; selects backends as in the table above | | target | — | Force one backend: csr, ssg or hydrate | | appDir | "app" | Route folder. A bare name is found next to the importing entry; a path such as "website/app" resolves from the working directory | | exclude | [] | Folder names to leave out of the route map | | routes | true | false turns off @afw/routes, for libraries | | failOnError | true | false replaces a broken module with a diagnostic stub instead of failing the build |

Server

The plugin generates @afw/server for a server target: one Fetch handler that runs the app's middleware around API routes, server functions (*.server.js) and loader data, then your files and renderPage steps.

// server.js — a server target, run by `esdev start` with "dev": { "run": "server" }
import { serve } from "runtime:http";
import { createHandler, createPageRenderer } from "@afw/server";

const handle = createHandler({ files: serveStatic, renderPage: createPageRenderer({ shell }), fallback });
serve({ port: 3000 }, handle);
  • createPageRenderer({ shell }) exists with mode: "ssr": pages render per request.
  • Browser builds get fetch stubs for *.server.js modules; server builds keep them.
  • See Server Setup for a complete entry with static files.

Development refresh

In esdev start, an edited component or page refreshes in place: the host, props and named $state values survive, while effects and lifecycle hooks are disposed and re-run. Components rendered by the edited page, layout or component keep their $state too, matched by tag and position. A page's metadata and headings (toc) refresh with it, and an edited $expose method is re-installed on the same host. A compile error keeps the previous view on screen until the fix is saved.

Changes the refresh cannot apply safely fall back to a reload: renamed components, changed props or $state declarations, a renamed or removed exposed method, and an edit to a module's other exports (export const SIZES = …), whose importers hold the old value. Editing only the components in such a module still refreshes in place. Release, server and test output contains no refresh code.

Source maps

Compiled modules return a version-3 source map that esdev composes into the bundle. Statements, expressions and event callbacks keep their original JSX/TSX positions. Markdown maps to its generated JSX for now.

API

| Export | Purpose | | --- | --- | | default / createAfwAppPlugin(options) | Compiler plus @afw/routes and @afw/server: the factory esdev.json uses | | createAfwServer({ appDir, exclude, getCompiler, pages }) | @afw/server and the .server stubs, for custom pipelines | | createAfwPlugin({ target, mode, failOnError }) | Compiler only, for custom pipelines and test configs | | createAfwRoutes({ appDir, exclude }) | Route map only | | afwPlugin(afwc, { target, failOnError }) | Compiler plugin for a runtime:build script | | resolveCompiler() | Find afwc: AFWC_BIN, then the packaged binary, then a local Cargo checkout | | startCompilerServer(afwc, { sourceMap }) | A persistent afwc serve child: { compile, close } | | closeCompilers() | Stop every compiler child started in this process | | compileError(payload) | Turn a compiler error reply into an Error with .diag |

import { build } from "runtime:build";
import { afwPlugin, closeCompilers, resolveCompiler } from "@opentf/esdev-plugin-afw";

const { afwc } = await resolveCompiler();
const bundle = await build({ input: "app/page.jsx", plugins: [afwPlugin(afwc, { target: "csr" })] });
const { output } = await bundle.generate({ format: "esm" });
await bundle.close();
await closeCompilers();

All modules compile through one long-lived afwc serve process per plugin instance, so the binary starts once per build.

Documentation

| Doc | Purpose | | --- | --- | | Installation | Creating and configuring a project | | Static generation | mode: "ssg" with a prerender step | | Server rendering | mode: "ssr" with a server target | | esdev plugins | How esdev loads and orders plugins |

License

MIT — see LICENSE.