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

@jts-studios/web-build

v0.1.0

Published

The build for a multi-frontend PHP + Vite project: one command, generated server config, a staged deploy directory.

Readme

@jts-studios/web-build

The build for a multi-frontend PHP + Vite project: one command, generated server config, and a staged deploy directory that is complete or refuses to exist.

Why this is a tool and not a Vite plugin

Mostly a tool, with one plugin inside it.

runBuild drives Vite once per site, then stages a deploy directory, installs a production backend and renders the server config for all sites together. A Vite plugin runs inside a single build and cannot orchestrate other builds or see other sites, so that part could never be one.

htaccessPlugin is a plugin, and has to be: it hooks writeBundle so each site emits its own .htaccess, including when you build that one site on its own rather than through the top-level command.

createFrontendViteConfig is neither — it is a config factory that returns a Vite config with the plugin already applied.

Install

npm install -D @jts-studios/web-build
{
  "scripts": {
    "build": "jts-build",
    "build:list": "jts-build --list"
  }
}

Configure

One build.config.mjs at the project root. Everything in it is a fact about that project or its server; everything else lives in the package.

import { SITE_LIST } from "./shared/frontend/tooling/sites.mjs";

export default {
    sites: SITE_LIST,
    server: {
        // apache or nginx. A machine runs one, and the build emits only that
        // one's config — the other would be a file read by nothing.
        type: "nginx",
        root: "/srv/example/current",
        phpSocket: "/run/php/php8.3-fpm.sock",
        certificate: "/etc/ssl/cloudflare/example.com.pem",
        certificateKey: "/etc/ssl/cloudflare/example.com.key",
        uploadLimit: "24m",
    },
    nginx: { name: "example" },
    probe: {
        classes: ["App\\Backend\\ServiceFactory"],
        methods: ["users"],
    },
};

| | | |---|---| | sites | every site: dir, docroot, host, and optionally csp, noindex, cache, api, redirects, notes | | server.type | "apache" or "nginx". Decides which config is generated; anything else is an error | | server | paths on the deploy target. Only the deploy knows these, which is why they are not in an .env | | nginx.name | names the output: <name>.conf and <name>-http.conf | | probe | classes that must load from the staged autoloader, and accessors classes[0] must carry | | backendInclude | optional; defaults to bootstrap, cleanup, migrations, src, composer files, schema.sql |

sites stays in your project. It is the part that genuinely differs, and splitting it out is what lets the renderers be shared at all.

Each site's vite.config.js

import { defineConfig } from "vite";
import { PHP } from "@jts-studios/vite-plugin-php";
import { createFrontendViteConfig } from "@jts-studios/web-build/vite";
import { SITES, SERVER_TYPE } from "../shared/frontend/tooling/sites.mjs";

export default defineConfig(
    createFrontendViteConfig({ phpPlugin: PHP, site: SITES.www, server: SERVER_TYPE })
);

What it produces

deploy/
  backend/          production vendor, installed not copied
  <docroot>/        one per site
  nginx/            only when server.type is "nginx"

Under "apache" each docroot gets its own generated .htaccess and there is no nginx/. Under "nginx" there are no .htaccess files at all and the server blocks are staged instead. Never both: a machine runs one web server, and shipping the other's rules produces a file that looks like configuration, is read by nothing, and will eventually be edited by someone who assumes it matters.

The same choice reaches each site's vite.config.js, which is why it is declared once in your sites.mjs and imported by both.

Two renderers, one set of site definitions. A CSP change or a new host is written once and comes out correct in whichever config you generate — and identical in the other if you ever switch. Four hand-maintained .htaccess files once drifted apart and took a host down; that is what this exists to prevent, and keeping a copy of the renderers per project would recreate it one level up.

Exports

import {
    runBuild,                  // the orchestrator
    createFrontendViteConfig,  // config factory
    renderHtaccess,            // Apache, one site
    htaccessPlugin,            // the Vite plugin that writes it
    renderNginxSites,          // nginx server blocks, all sites
    renderNginxHttp,           // the http-context half: log_format, map, gzip
    buildCsp,                  // the shared policy builder
} from "@jts-studios/web-build";

Checks it will not skip

The build fails rather than produce a deploy directory you should not upload:

  • a site with no CSS or JS emitted
  • a missing or empty index.php or robots.txt, and .htaccess under Apache
  • an empty file anywhere in public/, which would ship as-is
  • a staged autoloader that cannot resolve the classes in probe
  • a working checkout left with an authoritative classmap