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

wirejs-scripts

v3.0.195

Published

Basic build and start commands for wirejs apps

Readme

wirejs-scripts

Experimental: WireJS is an experimental hobby project and is not production software. For a professionally supported project in a similar spirit, see AWS Blocks, which benefits from lessons and ideas explored here.

Experimental build and development commands for WireJS apps.

This package owns the CLI behavior behind the generated app scripts, including TypeScript bundling, local development serving, SSR/SSG processing, hydration bundles, static asset handling, and workspace helper commands.

Commands

Generated apps typically expose these through package.json scripts:

npm run start        # local development server + watch builds
npm run start:public # local development with public/NAT-PMP behavior when available
npm run build        # production build

The underlying CLI is wirejs-scripts.

Expected app layout

api/          API/resource workspace
src/ssr/      server-rendered request-time routes
src/ssg/      statically generated pages/artifacts
src/          shared source/components/layouts
static/       public static assets, served as /static/*
dist/         generated output
pre-dist/     intermediate build output

SSR handling

Files under src/ssr/ are compiled to dist/ssr/. At request time, WireJS hosting matches the request path to the best compiled SSR module and invokes its exported generate(context) function.

A minimal request-time route can return a plain body:

// src/ssr/debug.txt.ts -> /debug.txt
import type { Context } from 'wirejs-resources';

export async function generate(context: Context) {
	context.responseHeaders['cache-control'] = 'no-store';
	return `Path: ${context.location.pathname}\n`;
}

For non-HTML SSR responses, the hosting layer infers a content-type from the requested extension when possible. You can also set one explicitly through context.responseHeaders, which is recommended when you care about charset or a more specific MIME type:

// src/ssr/feed.xml.ts -> /feed.xml
import type { Context } from 'wirejs-resources';

export async function generate(context: Context) {
	context.responseHeaders['content-type'] = 'application/rss+xml; charset=utf-8';
	context.responseHeaders['cache-control'] = 'max-age=300';

	return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>Example feed</title></channel></rss>`;
}
// src/ssr/manifest.json.ts -> /manifest.json
import type { Context } from 'wirejs-resources';

export async function generate(context: Context) {
	context.responseHeaders['content-type'] = 'application/manifest+json; charset=utf-8';
	return JSON.stringify({
		name: 'Example app',
		start_url: '/',
		display: 'standalone',
	}, null, 2);
}

The examples in generated apps use wirejs-dom/v2 for HTML. That is not required by wirejs-scripts, but it demonstrates the intended SSR + client-bundle flow:

// src/ssr/counter.ts -> /counter
import { html, id, text, hydrate as wireHydrate } from 'wirejs-dom/v2';
import type { Context } from 'wirejs-resources';

function Counter(init: { count?: number } = {}) {
	let count = init.count ?? 0;
	const self = html`<button id="counter" ${id('button')}>
		Clicked ${text('count', String(count))} times
	</button>`;

	self.data.button.onclick = () => {
		count += 1;
		self.data.count = String(count);
	};

	return self;
}

export async function generate(context: Context) {
	return html`<html>
		<head><title>Counter</title></head>
		<body>${Counter({ count: Number(context.location.searchParams.get('count') ?? 0) })}</body>
	</html>`;
}

export function onload() {
	wireHydrate('counter', () => Counter());
}

generate() is used for the server-side render. The exported onload() function is used as the browser entrypoint. This lets a module share selected components/functions between server generation and the client bundle while keeping server-only code inside generate() or behind server-only imports.

If generate() returns a DOM/document-like value, hosting serializes it as HTML. If it returns a string or other non-DOM body, hosting sends that body with a content type inferred from the requested extension when possible, unless context.responseHeaders['content-type'] overrides it.

Routing notes:

  • Request paths are matched against compiled .js files under dist/ssr/.
  • % in an SSR filename acts as a wildcard pattern.
  • Wildcard SSR routes use a canonical encoded client script path such as /wild/%25.client.js, so all instances of the route share one cacheable browser bundle.
  • Direct /ssr and /ssr/* static-file access is blocked; SSR modules are executable server code, not public assets.
  • If SSR code changes context.location, hosting can emit a redirect-style response when no explicit responseCode is set.
  • Pre-extension filenames work for SSR non-HTML responses: src/ssr/feed.xml.ts compiles to a handler for /feed.xml; src/ssr/data.json.ts handles /data.json.

Client hydration bundles

SSR/SSG pages may register browser-side hydration through the DOM package used by the app. wirejs-scripts builds associated browser bundles for those pages so event handlers and client-only behavior can attach after generated HTML loads.

For SSR, the compiled server module lives under dist/ssr/*.js and the browser bundle is served through the corresponding *.client.js path when hydration is needed.

For SSG HTML outputs, wirejs-scripts emits a browser IIFE bundle for the page module. The generated HTML references that bundle only when the server/build module exports onload(). Non-HTML SSG outputs do not receive sibling browser JS bundles.

A useful pattern is to split code by what can safely run in both places:

// shared: safe for server generation and browser hydration
function TodoList() { /* returns DOM and attaches browser-safe handlers */ }

// server-only: reads request context, private resources, secrets, etc.
export async function generate(context) {
	const todos = await loadTodosFor(context);
	return Page({ todos });
}

// browser-only entrypoint: attaches behavior to generated markup
export function onload() {
	wireHydrate('todos', () => TodoList());
}

Because the browser bundle starts from the exported onload() entrypoint, you can avoid pulling server-only dependencies into the client by keeping them out of the functions imported/called by onload().

SSG handling

Files under src/ssg/ are built and executed at build time. Each module's generate() output is written to dist/ as a static artifact. Build fails if any SSG page cannot be generated, so deploys do not continue with missing static pages.

// src/ssg/about.ts -> dist/about.html
import { html } from 'wirejs-dom/v2';

export async function generate() {
	return html`<html><body><h1>About</h1></body></html>`;
}

SSG uses the same shared-component pattern as SSR. For static HTML with browser behavior, export an onload() function from the page module and call your DOM library's hydration registration there. The server/build side calls generate() once, and the emitted client bundle imports the page module and calls exported onload() in the browser.

Non-HTML SSG outputs

SSG supports "pre-extension" filenames for generated assets that are not HTML:

// src/ssg/feed.xml.ts -> dist/feed.xml
export async function generate() {
	return `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>Example</title></channel></rss>`;
}
// src/ssg/manifest.json.ts -> dist/manifest.json
export async function generate() {
	return {
		name: 'Example app',
		start_url: '/',
		display: 'standalone',
	};
}

Convention:

  • src/ssg/page.ts emits dist/page.html.
  • src/ssg/name.ext.ts emits dist/name.ext.
  • .json outputs are JSON-stringified when generate() returns an object.
  • Other non-HTML outputs are stringified with String(value) unless generate() returns a string or buffer.
  • Non-HTML outputs do not get hydration/client JS bundles.

Use SSG for pages or files that can be produced ahead of time. Use SSR for request-dependent content, per-user data, redirects, request-specific headers/cookies, or data that must be fresh on every request.

In local watch/start mode, wirejs-scripts watches source files and debounces SSG rebuilds. Rebuilds are queued so overlapping changes do not run multiple SSG builds concurrently.

Static files

Put public static assets in the app's top-level static/ directory and reference them with root-relative /static/... URLs, for example:

<link rel="stylesheet" href="/static/default.css">
<img src="/static/images/logo.svg">

Current behavior:

  • Local development serves /static/* directly from the source static/ directory.
  • Production/build output expects static assets to be public under the /static/ URL space.
  • Static path resolution is normalized so requests cannot escape the intended static/dist root.
  • Do not put private files in static/; it is public web content.

API and request handling

WireJS APIs are imported like ordinary TypeScript modules. Client code, SSR, and SSG should import from the app's API package and call functions directly; wirejs-scripts handles the client/server wiring, request protocol, serialization, and server execution.

In short: treat the API as a module that just happens to run on the server.

// api/index.ts
export const todos = {
	async list(userId: string) {
		return [{ id: 't1', title: `Todo for ${userId}` }];
	},
};
// src/ssg or src/ssr or browser/client code
import { todos } from 'internal-api';

const items = await todos.list('user-1');

The generated client module preserves end-to-end TypeScript types without requiring app code to know the wire protocol. Do not manually call WireJS's internal API transport endpoints from app code. If you need a real URL/path for callers that are not using the WireJS module protocol, define a custom Endpoint resource in the API package.

Custom Endpoint is appropriate for:

  • webhooks and third-party callbacks;
  • OIDC/provider callback paths;
  • custom admin/settings portals;
  • compatibility routes for callers that cannot import the WireJS API package;
  • serving non-API content from an API module, such as generated images, files, feeds, or health-check text.

The local server creates a wirejs-resources Context for each request, then routes through static handling, SSR, API, endpoint, and not-found behavior. API modules and endpoint handlers receive the same context model used by SSR.

Deployment config

Generated apps may include a top-level deployment-config.ts exporting DeploymentConfig from wirejs-resources. wirejs-scripts reads this file for build-time behavior, and deployment providers read the same file for hosting/infrastructure behavior.

deployment-config.ts is the provider-neutral place for build/deployment intent that should be supported consistently by hosting providers over time, including:

  • runtime memory/timeout/Node version preferences;
  • server bundle options such as bundled node modules, output format, and minification;
  • extra SSG externals for modules that should remain external during static generation;
  • branch/domain mapping;
  • host redirects;
  • DNS records.

Minimal build/runtime example:

import { DeploymentConfig } from 'wirejs-resources';

export default {
	runtimeDesiredMemoryMB: 1024,
	runtimeNodeVersion: 22,
	bundleNodeModules: ['jsdom'],
	ssgExternalModules: ['sharp'],
} satisfies DeploymentConfig;

Domain-oriented example:

import { DeploymentConfig } from 'wirejs-resources';

export default {
	domainsByBranch: {
		main: 'staging.example.com',
		prod: 'www.example.com',
		'*': '{branch}.example.com',
	},
	redirects: [
		{ from: 'example.com', to: 'www.example.com', mode: 'permanent' },
		{ from: '{branch}.old.example.com', to: '{branch}.new.example.com' },
	],
	dnsRecordsByBranch: {
		main: [
			{ name: '@', zoneDomain: 'example.com', type: 'MX', values: ['1 aspmx.l.google.com.'] },
		],
		'*': [
			{ name: '_verify.{branch}', zoneDomain: 'example.com', type: 'TXT', values: ['"ok-{branch}"'] },
		],
	},
} satisfies DeploymentConfig;

Provider-specific packages decide which fields they currently support and how those fields map to infrastructure. The intent is that new deployment providers should use the same DeploymentConfig surface whenever the concept applies.

Implemented deployments

Currently documented deployment provider:

  • wirejs-deploy-cdk — AWS CDK deployment with Lambda, CloudFront, S3, DynamoDB, Cognito, custom domains, redirects, and Route53 DNS records.

When working in an installed app, prefer version-locked docs from node_modules:

node -e "console.log(require.resolve('wirejs-deploy-cdk/package.json').replace(/package\.json$/, 'README.md'))"

If browsing online, use npm as a secondary reference: https://www.npmjs.com/package/wirejs-deploy-cdk

Workspace helpers

Generated apps use wirejs-scripts ws-run-parallel ... to run matching scripts across workspaces during development.

WireJS is experimental. Expect breaking changes and prefer the README installed with your exact package version.