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

create-jamstack

v0.1.0

Published

Scaffold a WebGL/WebGPU game jam starter (stack + shaders + physics + deploy).

Readme

create-jamstack

Scaffolds a project from the flags produced by the jamstack site's picker (components/main/GeneratedCommand.tsx). Flag values are the same slugs the site's slugify() produces from its option labels.

npx create-jamstack@latest my-jam-game --stack=r3f-vite --shaders=glsl --physics=rapier --deploy=vercel
npx create-jamstack@latest my-jam-game --stack=threejs-vite --shaders=tsl --physics=cannonjs --deploy=vercel
npx create-jamstack@latest my-jam-game --stack=pixi-vite --shaders=pixi-filters --physics=matterjs --deploy=itchio

Status

Every stack and every --shaders/--physics option in the site's picker is now scaffoldable via the CLI — full parity with SelectionTabs.tsx, nothing left in "known to the validator but not implemented" state.

| Stack | Engine | --shaders | --physics | | --- | --- | --- | --- | | r3f-vite | three | tsl, glsl, wgsl | rapier, cannonjs, ammojs, jolt | | threejs-vite | three | tsl, glsl, wgsl | rapier, cannonjs, ammojs, jolt | | tresjs-vue | three | tsl, glsl, wgsl | rapier, cannonjs, ammojs, jolt | | tresjs-nuxt | three | tsl, glsl, wgsl | rapier, cannonjs, ammojs, jolt | | pixi-vite | pixi | pixi-filters | rapier, matterjs, planckjs | | pixi-next | pixi | pixi-filters | rapier, matterjs, planckjs | | babylon-vite | babylon | glsl, wgsl, nme | rapier, cannonjs, ammojs, jolt | | babylon-next | babylon | glsl, wgsl, nme | rapier, cannonjs, ammojs, jolt | | phaser-vite | phaser | phaser-pipelines | matterjs, planckjs, arcade-physics |

--deploy is vercel, itchio, or wavedash for any of them.

Verification gap: see "Verification performed" below — the tsl/glsl rows above (all stacks) and babylon-vite's glsl/wgsl/nme + rapier/cannonjs/ammojs/jolt combos were scaffolded and built for real. tresjs-vue/tresjs-nuxt's shader+physics modules, all of phaser-vite, babylon-next with wgsl/nme, and the 3D asset optimization loaders described below were written to the same standard (real APIs read from shipped .d.ts/source before use — see the per-module code comments for what was cross-checked and how) but a sandbox outage cut the verification pass short before npm install && npm run build could be run against them. Treat those as unverified until someone runs that command for real.

3D asset optimization (always on, not a picker option)

Every three-engine stack (r3f-vite, threejs-vite, tresjs-vue, tresjs-nuxt) and every babylon-engine stack (babylon-vite, babylon-next) always gets a loaders/modelLoader.ts (path varies per stack — see src/stacks/model-loader-{three,babylon}.js) wiring up Draco geometry compression and KTX2/Basis texture compression, regardless of --shaders/--physics. Unlike those two flags this isn't a stylistic either/or choice, so it isn't a --flag or a SelectionTabs.tsx category — every one of this CLI's 3D stacks just gets it, since real GLTF/GLB assets in a shipped web game are the actual reason those compression formats exist. pixi-vite/pixi-next/phaser-vite are 2D, so they're unaffected.

  • three-engine stacks: createModelLoader(renderer) returns a GLTFLoader with a DRACOLoader and KTX2Loader attached, decoding via Google's/jsDelivr's CDN-hosted decoder binaries by default (the same defaults three.js's own examples use) — see the code comment in model-loader-three.js for how to self-host those instead, and for the WebGPU-renderer caveat (detectSupportAsync instead of detectSupport). Since the demo scene is procedural and doesn't load any models, nothing calls this by default.
  • babylon-engine stacks: adds @babylonjs/loaders as a dependency (the base templates only had @babylonjs/core, which has no .gltf/.glb parser at all) and a loadModel(scene, url) helper. Babylon's Draco/KTX2 decompression is built into @babylonjs/core itself with CDN-hosted decoder defaults already configured — registering the loaders package is the only step needed to turn it on.

Two independent axes: flavor and engine

Module content depends on two things that turned out not to be the same axis, even though the first two stacks made them look that way:

  • flavor — the code shape: react (hooks/JSX/components — r3f-vite), vanilla-oop (plain classes, no framework — threejs-vite, pixi-vite, babylon-vite/babylon-next, phaser-vite), or vue (<script setup> SFCs — tresjs-vue/tresjs-nuxt).
  • engine — which library the module is written against: three vs. pixi vs. babylon vs. phaser.

threejs-vite and pixi-vite are both vanilla-oop but need completely different shader/physics code, which is why modules are keyed [flavor][engine] (src/modules/shaders/index.js / src/modules/physics/index.js) rather than just [flavor]:

src/modules/shaders/
  react/            # three engine, react flavor (r3f-vite)
  vanilla/three/    # three engine, vanilla-oop flavor (threejs-vite)
  vanilla/pixi/     # pixi engine, vanilla-oop flavor (pixi-vite)
  vanilla/babylon/  # babylon engine, vanilla-oop flavor (babylon-vite/-next)
  vanilla/phaser/   # phaser engine, vanilla-oop flavor (phaser-vite)

src/modules/physics/ mirrors that layout, plus a vue/three/ directory that shaders/index.js doesn't need — the vue flavor's shader modules just reuse vanilla/three's directly (see the comment in src/modules/shaders/index.js), since those modules only export a plain THREE.Material subclass with no React/Vue coupling, so the exact same file content works from a Vue <script setup> too. Physics modules can't be shared that way — sceneFileContent generates a whole framework-shaped file (a class for vanilla-oop, an SFC for vue) — so vue/three/*.js duplicates the physics logic from vanilla/three restructured into TresJS's useLoop()/shallowRef shape.

src/catalog.js records each stack's engine and flavor; src/validate.js and each stack's generate() both look modules up via MODULES_BY_FLAVOR_ENGINE[stack.flavor][stack.engine].

The vanilla-oop shape, per engine

Both vanilla-oop stacks split "fixed environment" from "generated jam content" via a World class holding a generated GameScene (object/view + init() + update(delta)), but otherwise lean on whatever their engine already provides rather than reinventing it:

  • threejs-vite — three.js has no built-in app loop, resize handling, or camera wrapper, so this template builds a small EventEmitter base class plus Sizes/Time (emit resize/tick) and dedicated Camera/Renderer classes that subscribe to Sizes independently rather than App pushing updates into them. App just wires these together and relays Time's tickCamera.update()World.update()Renderer.update(). Materials are THREE.ShaderMaterial/MeshBasicNodeMaterial subclasses with their own update(delta).
  • pixi-vite — PixiJS's Application already provides an equivalent of Sizes/Time/Renderer (resizeTo, ticker, the renderer itself), so reimplementing those would just be duplicate abstraction; App here is a thin wrapper that initializes Application and relays ticker.add() to World.update(). There's no Camera class — Pixi's 2D stage has no camera concept and this template's scene doesn't pan or zoom, so it would have nothing to do. The shader-equivalent option (pixi-filters) is a PIXI.Filter subclass with its own update(delta), applied to the demo object via .filters = [...] rather than substituted in as a material.
  • babylon-vite/babylon-next — Babylon meshes/lights auto-register against the Scene they're constructed with, so unlike three.js/pixi's World there's no .object/.view to parent — GameScene just takes the Scene directly. App normally builds the Engine/Scene/camera synchronously in its constructor; when wgsl is selected that all moves into init() instead, since a WebGPUEngine needs an awaited initAsync() before anything else can touch it.
  • phaser-vite — Phaser's Scene class already is the fixed environment/app-loop role World plays elsewhere, so World here is a thin Phaser.Scene subclass that just constructs a GameScene in create() and relays update(). The shader-equivalent option (phaser-pipelines) is a PostFXPipeline subclass applied via gameObject.setPostPipeline(...); unlike every other shader module it isn't driven by an explicit update(delta) call — pipelines get their own onPreRender() hook, called automatically once per frame.

Each stack's actual file layout (what's static template vs. generated) lives in src/stacks/<stack>.js.

How it's put together

  • bin/create-jamstack.js — entry point: parses argv, validates the selection, calls the scaffolder.
  • src/catalog.js — mirrors the stack/shader/physics compatibility rules from components/main/SelectionTabs.tsx on the site, so the CLI rejects combinations the site itself wouldn't let you pick.
  • src/scaffold.js — thin dispatcher: picks src/stacks/<stack>.js's generate() function based on --stack.
  • src/stacks/*.js — one file per implemented stack. Copies templates/<stack>, then generates the files whose content depends on which shader/physics modules were selected (these can't be static files, since any shader can be paired with any physics engine).
  • src/modules/{shaders,physics}/<flavor>/<engine>/*.js — one file per option per flavor+engine, each exporting the code it injects into the scaffolded project.
  • src/modules/deploy/*.js — shared across every stack/flavor/engine; they only touch package.json scripts and drop config files, nothing stack-specific.
  • src/shared.js — file-writing helpers and deploy-module wiring shared across all stacks.

Notes on the riskier modules

  • tsl and wgsl (three engine) render through three's WebGPURenderer — they need a browser with WebGPU support to actually run. glsl runs on the default WebGL renderer.
  • ammojs and jolt (three engine) have no maintained wrapper for either flavor, so they step the physics world by hand each frame and copy the resulting transform onto the mesh — more code than rapier/cannonjs, which lean on maintained bindings (@react-three/rapier / @dimforge/rapier3d-compat, @react-three/cannon / cannon-es).
  • rapier and planckjs on pixi-vite run physics in meters and convert to pixels only when positioning graphics (PIXELS_PER_METER in each module) — both are Box2D-lineage engines tuned for roughly meter-scale bodies, and feeding them raw pixel-magnitude numbers (hundreds of units) risks an unstable simulation. matterjs skips this conversion deliberately — Matter.js's own defaults are tuned for pixel-scale usage.
  • pixi-filters forces the whole pixi-vite app to the WebGL backend (preference: "webgl" in core/App.ts) because it only ships a glProgram, not the gpuProgram Pixi's WebGPU backend needs — without forcing WebGL, the filter would silently no-op on a WebGPU-preferring browser (per Filter's own doc comment in pixi.js).
  • wavedash's deploy module doesn't shell out to a Wavedash-specific CLI command — that flow wasn't something this template could verify — so it just documents the build output (dist/) for you to upload by hand.
  • nme (babylon) hand-builds a NodeMaterial block graph via code (InputBlock/TransformBlock/TrigonometryBlock/LerpBlock/etc.) rather than loading one from the visual Node Material Editor or its snippet server, neither of which is available offline. NodeMaterial compiles to whichever backend (Engine or WebGPUEngine) is active, so unlike wgsl it doesn't force a WebGPU engine.
  • wgsl (babylon) uses Babylon's own WGSL preprocessor dialect (ShaderStore.ShadersStoreWGSL, attribute/varying/uniform declarations, vertexOutputs/fragmentOutputs structs) rather than raw WGSL — mirrored from the shaders shipped under @babylonjs/core/ShadersWGSL/*.vertex.js/*.fragment.js, since that convention isn't obvious from the public API surface alone.
  • matterjs/arcade-physics on phaser-vite use Phaser's own built-in Matter/Arcade physics plugins (configured via the Phaser.Game config's physics block) rather than driving a physics world by hand — Phaser auto-syncs each Game Object's transform with its body every frame, unlike every other engine's physics modules in this CLI. planckjs has no built-in Phaser integration, so it's manually driven like the pixi-vite version.
  • tresjs-vue/tresjs-nuxt's tsl/wgsl shaders need a WebGPU renderer; TresCanvas's renderer prop takes a synchronous factory, but TresJS internally detects a WebGPU-style Renderer ("isRenderer" in value) and awaits its .init() before starting the render loop — confirmed by reading @tresjs/core's compiled source, since this isn't documented as prominently as React Three Fiber's equivalent (gl={async (props) => ...}) that it mirrors.

Verification performed

Every --shaders / --physics / --deploy value on r3f-vite, threejs-vite, and pixi-vite, plus babylon-vite/babylon-next with glsl across all four physics options, was scaffolded and run through npm install && npm run build against the real currently-published versions of every dependency. This caught and fixed real bugs each time it was done:

  • r3f-vite: a missing vite-env.d.ts, two TSL node type-cast errors.
  • threejs-vite: a wrong relative import path (GameScene.ts lives one directory deeper than Scene.tsx did), an unused-private-field error in the ammojs module.
  • pixi-vite: none on the first real build — the Pixi v8 filter API (Filter/GlProgram/defaultFilterVert) and physics package APIs (@dimforge/rapier2d-compat, matter-js, planck) were read directly out of their shipped .d.ts/source before being used, rather than written from memory first, specifically to avoid the trial-and-error cycle the other two stacks needed. matter-js's export = default export did require adding esModuleInterop: true to the template's tsconfig.json up front.
  • babylon-vite/babylon-next (glsl + rapier/cannonjs/ammojs/ jolt): none — built clean on the first try.

The Jolt integration's WASM API calls were additionally cross-checked by hand against the .d.ts shipped in jolt-physics.

babylon-vite with wgsl and with nme (each paired with a physics option) were also scaffolded and built for real, with no errors — this confirms the WGSL preprocessor dialect and the hand-built NodeMaterial graph are both syntactically and type valid.

What wasn't verified, because a sandbox outage (Bash became unavailable mid-session, coinciding with an npm install filesystem error) cut this verification pass short:

  • babylon-next with wgsl/nme (the WebGPUEngine-specific App.ts variant, buildBabylonWebGPUAppTs({ variant: "next" }), was never actually built).
  • All of tresjs-vue/tresjs-nuxt's new shader and physics modules (src/modules/physics/vue/three/*.js, src/stacks/tresjs-shared.js).
  • All of phaser-vite's new shader and physics modules (src/modules/{shaders,physics}/vanilla/phaser/*.js).

Those were written to the same standard as everything else here — real APIs read from shipped .d.ts/source before use (see the code comments in each module for specifics) — but that's not a substitute for an actual npm install && npm run build, let alone a browser. Run that command against them before relying on this table.

Separately, and true of every stack including the previously-verified ones: nothing here was ever run in an actual browser, so in-scene correctness (physics behavior, WebGPU rendering, shader/filter/pipeline visuals) is unconfirmed beyond "it type-checks and bundles" even where the build itself was verified.