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

@memoized-dom/vite

v0.0.9

Published

Vite 8 adapter for connected memoized-dom module graphs

Readme

@memoized-dom/vite

Vite 8 integration for compiler-linked Memoized DOM applications. One plugin owns the client compiler graph and, when configured, the server boundary, server-function facade, SSR document, and development request dispatch.

import { defineConfig } from 'vite';
import memoizedDom from '@memoized-dom/vite';

export default defineConfig({
  plugins: [
    memoizedDom({
      clientEntry: 'src/entry.client.ts',
      serverEntry: 'src/entry.server.ts',
      server: 'src/server',
    }),
  ],
});

There is no separate fullstack plugin. serverEntry tells Vite where the server graph begins, and server identifies the application-owned server root used for conventions and boundary checks. A client-only application only needs clientEntry.

memoizedDom({ clientEntry: 'src/main.ts' });

The client entry contains the application mount() call. The server entry default-exports the application returned by serve():

import { serve } from '@memoized-dom/server';
import { App } from './App';

const app = serve({
  createLocals: () => ({ requestId: crypto.randomUUID() }),
});

app.get('/api/health', () => ({ ok: true }));
app.ssr(App);

export default app;

Server config contract

When present, <server>/config/index.ts exports the application-wide server types:

export interface ServerTypes {
  locals: {
    requestId: string;
    user?: User;
  };
  platform?: PlatformBindings;
  services: ApplicationServices;
}

The plugin generates .memoized/server-config.d.ts. That declaration merges the application contract into the server type registry, so normal imports from @memoized-dom/server automatically carry these types:

import {
  getServerContext,
  type ServerMiddleware,
} from '@memoized-dom/server';

const session: ServerMiddleware = async (context, next) => {
  context.locals.user = await readSession(context.request);
  return next();
};

export function getCurrentUser() {
  const context = getServerContext();
  return context.services.users.find(context.locals.user);
}

No call-site generic or relative import from config is needed. TypeScript projects must include .memoized/**/*.d.ts; generated project templates do this automatically. If an editor shows Record<string, never> for locals, the generated declaration is not loaded by that TypeScript project.

Plain TypeScript resolution also needs the server alias mapping (Vite handles the runtime/build side):

{
  "compilerOptions": {
    "paths": {
      "#server/*": ["./src/server/*"],
      "#server-functions": ["./.memoized/server-functions.d.ts"]
    }
  },
  "include": ["src", ".memoized/**/*.d.ts"]
}

Only ServerTypes has framework meaning. Other files beneath server/config are ordinary server-only application modules and are a good home for environment parsing, authentication policy, error types, constants, host-binding adapters, and typed service factories. Import them with the server-root alias:

import { env } from '#server/config/env';
import { createServices } from '#server/config/services';

Routes, middleware ordering, handlers, and SSR registration do not belong in the config folder; compose them through the serve() application.

An application-scoped service factory can perform asynchronous startup once:

// src/server/config/services.ts
export async function createServices() {
  const database = await connectDatabase(env.DATABASE_URL);
  return { database, stories: createStoryRepository(database) };
}

// src/entry.server.ts
import { serve } from '@memoized-dom/server';
import { createServices } from '#server/config/services';

const app = serve({ createServices });

The result is available as typed context.services in middleware, routes, SSR, and server functions. Per-request state stays in context.locals.

Server boundaries

  • @memoized-dom/server is the framework API carrying registered app types.
  • #server/* resolves application-owned modules under the configured server root and is rejected from the client graph.
  • #server-functions is the generated colorless facade. It preserves each function's parameters and resolved return type without exposing its server implementation to the browser.

Erased import type declarations may refer to server modules. Runtime value imports from #server/* are intentionally rejected in the client graph. Shared DTOs and schemas should live in a neutral application directory; a browser-visible operation must be an explicit server function imported from #server-functions.

$routed compilation and transport

$routed is the router's authored route-preparation intrinsic. The same Vite plugin produces its client and server forms; there is no route-loader plugin or endpoint configuration to add.

import { $routed, redirectRoute } from '@memoized-dom/router';
import { reports } from '#server/repositories/reports';

export function ReportPage() {
  const page = $routed(({ params, locals, signal }) => {
    if (!locals.user) return redirectRoute('/login');
    return reports.loadPage(params.reportId, locals.user.id, { signal });
  });

  return <h1>{page.report.title}</h1>;
}

For a server-backed preparation, the plugin:

  1. keeps the callback and its #server/* dependencies in the server graph;
  2. erases that callback body and those server imports from client output;
  3. registers the extracted preparation with the generated server application;
  4. installs the internal /_memoized/routed transport beside generated server-function routes; and
  5. forwards only the preparation ID, destination URL/params, and JSON-safe application-owned state values from the browser.

The server attaches the real request, typed locals, platform, and services at dispatch time. Those capabilities never enter the client bundle or transport payload. Universal preparations that use only fields such as params, query, signal, and state retain their callback in both environments and do not generate a server round trip.

The callback must remain inline, synchronous, and assigned to a component-local const. Return a service or server-function result directly; the router settles it before navigation commits. See packages/router/README.md for the complete authored and navigation contract.

Development replacement

The adapter compiles connected module graphs and caches output per Vite environment. During an edit it recompiles before notifying the browser, invalidates every generated module whose output changed, and keeps generated modules as self-accepting HMR boundaries. A failed transform leaves the last successful graph active; the next successful edit sends a recovery update so Vite can clear its overlay and replace the mounted definition.

Server requests load the current server entry through Vite's SSR environment. Re-evaluation creates a fresh serve() application, preventing routes and middleware from accumulating across edits. Generated declarations and server-function manifests are replaced only after successful generation.

Restart the Vite process after changing or rebuilding this plugin itself; Vite does not hot-replace its own plugin hooks.

Commands

bun run --cwd packages/vite test
bun run --cwd packages/vite bench