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

@r2u/glb-pipeline

v0.2.1

Published

Manifest-driven GLB texture optimization, KTX2 compression, and LOD generation pipeline.

Downloads

796

Readme

@r2u/glb-pipeline

Manifest-driven GLB pipeline for texture optimization, KTX2 compression, material overrides, and geometry-only LOD generation.

Requirements

  • Node.js 22 or newer.
  • KTX-Software ktx CLI for KTX2 steps.
    • Linux x64/arm64: downloaded on demand and verified by SHA-256 when it is not on PATH.
    • macOS/Windows: install KTX-Software and add ktx to PATH.
  • Linux automatic provisioning also requires tar with bzip2 support.

No binary is downloaded during package installation.

Install

npm install --save-dev @r2u/glb-pipeline
# or
yarn add --dev @r2u/glb-pipeline

Global installation is also supported:

npm install --global @r2u/glb-pipeline

CLI

r2u-glb build --manifest ./models/manifest.yml
r2u-glb build --manifest ./models/manifest.yml --dry-run
r2u-glb optimize --input ./models --output ./models/optimized --keep-nodes
r2u-glb lod ./models/model.glb --ratios 1,0.5,0.2 --keep-nodes

Without a global install, use npx @r2u/glb-pipeline ... or yarn r2u-glb ....

Run r2u-glb <command> --help for every option.

Manifest

Manifest paths are relative to the manifest directory. Outputs must remain in a subdirectory of that root. Unknown fields are rejected to catch typos.

version: 1
output: optimized

presets:
  full:
    steps:
      - type: ktx2
        slots:
          baseColor: { mode: uastc, size: 2048 }
          normal: { mode: uastc, size: 1024 }
          metallicRoughness: { mode: etc1s, size: 512 }
      - type: lod
        ratios: [1.0, 0.5, 0.2]

  lite:
    output: optimized-light
    steps:
      - type: ktx2
        slots:
          baseColor: { mode: etc1s, size: 1024 }
          normal: { mode: etc1s, size: 512 }
      - type: lod
        ratios: [0.75, 0.5, 0.2]

assets:
  - input: 'models/*.glb'
    preset: full
  - input: 'models/*.glb'
    preset: lite

The unversioned manifest format used by r2u-maquete-imersiva is accepted as version 1. The complete example is in examples/manifest.yml, and the JSON Schema is exported as @r2u/glb-pipeline/manifest-schema.json.

Steps

  • texture: resize and encode selected slots as WebP, JPEG, or PNG.
  • ktx2: encode globally or per slot with UASTC/ETC1S.
  • lod: generate geometry-only siblings named <model>_LOD_<index>.glb.
  • draco: compress geometry in the main GLB and every LOD generated by earlier steps. Must appear only once and be the final step.

Steps run in declaration order. A source can target multiple outputs, which is how normal and lite variants are produced.

keepUniqueNames defaults to true for texture and ktx2, including preset steps and the exported optimization defaults. It maps directly to dedup({ keepUniqueNames }): equivalent materials, meshes, textures and skins with different names remain distinct. Accessor deduplication is unchanged. Set keepUniqueNames: false on a step or use standalone optimize --no-keep-unique-names to restore name-insensitive deduplication; --keep-unique-names explicitly enables preservation. Equivalent resources with the same name can still merge, and unused resources can still be pruned. This control does not replace keepNodes: with keepNodes: true, meshes are excluded from deduplication even when keepUniqueNames: false; with keepNodes: false, distinct mesh names are protected by keepUniqueNames: true, but empty/detached nodes can still be pruned. LOD continues to omit all materials/textures and deduplicate only accessors; Draco does not perform deduplication. Neither step accepts keepUniqueNames. A later optimization step with explicit false can merge resources preserved earlier.

steps:
  - type: texture # keepUniqueNames: true is implicit
  - type: ktx2
    keepUniqueNames: false # allow merging equivalent resources with different names

keepNodes defaults to true for texture, ktx2, and lod; it can be omitted from the manifest. This preserves all nodes, including empty and detached nodes. Set keepNodes: false on an individual step to allow pruning; standalone optimize and lod accept --no-keep-nodes for the same opt-out. --keep-nodes remains supported. The exported optimization and LOD defaults also use true.

For texture and ktx2, preservation excludes nodes and meshes from pruning and excludes meshes from deduplication, retaining node/mesh associations and mesh names. Other unused resources can still be cleaned up. LOD generation may still remove empty geometry/meshes during simplification and always omits materials/textures. A later step with explicit keepNodes: false can remove nodes preserved by earlier steps.

steps:
  - type: texture # keepNodes: true is implicit
  - type: lod
    keepNodes: false # explicitly allow node pruning in this step

Design choice: In glTF-Transform 4.5.0, prune() removes empty scene leaves recursively and can remove unreferenced nodes. keepLeaves: true only protects leaves in scenes; it does not guarantee preservation of detached nodes. Disabling prune() entirely would also retain unused meshes, textures and other resources. In texture optimization, the default preservation excludes nodes and meshes from prune's propertyTypes instead. Although dedup() does not deduplicate nodes, its default mesh deduplication can replace distinct mesh names referenced by nodes; with keepNodes, mesh deduplication is disabled while other deduplication respects keepUniqueNames. Standalone LOD generation restricts its deduplication to accessors. Preservation is enabled on every manifest step by default; explicit false restores the previous pruning behavior. See the real-GLB regression analysis for a reproducible comparison.

TypeScript API

import { buildAssets, type PipelineLogger } from '@r2u/glb-pipeline'

const logger: PipelineLogger = {
  debug: console.debug,
  info: console.info,
  warn: console.warn,
  error: console.error,
}

const result = await buildAssets({
  manifestPath: './models/manifest.yml',
  dryRun: false,
  logger,
})

if (result.errorCount > 0) {
  throw new Error(`${result.errorCount} asset(s) failed`)
}

Granular APIs such as loadManifest, createBuildPlan, optimizeGlb, and generateLods are also exported. Core APIs return typed results and never terminate the host process.

Draco geometry compression

Draco is opt-in and compatible with KTX2 and WebP/JPEG/PNG texture compression. Add it after texture processing and optional LOD generation:

steps:
  - type: ktx2
    slots:
      baseColor: { mode: etc1s, size: 2048 }
      normal: { mode: uastc, size: 1024 }
  - type: lod
    ratios: [1, 0.5, 0.2]
  - type: draco
    method: sequential
    quantizePosition: 14

See examples/draco.manifest.yml for architecture, assets and materials presets. Manifest inputs must follow that example's folder structure; the flat files in temp/ are used by the separate local benchmark.

Draco compresses the staged main file and all LODs created in that asset execution, not existing LODs found on disk. All must succeed before promotion. keepNodes is accepted for consistency, but the Draco stage always preserves node/mesh identity: it does not prune, deduplicate meshes or weld vertices. Non-indexed triangle primitives are indexed in their original order. Other primitive modes may remain uncompressed.

Default method is sequential, deliberately different from glTF-Transform's edgebreaker default. The supplied assets showed a change in triangle index counts with edgebreaker; sequential retained counts. Edgebreaker remains available explicitly and may remove degenerate faces. Neither method guarantees identical vertex ordering or lossless attributes.

Options: method (sequential/edgebreaker), encodeSpeed/decodeSpeed (integers 0–10, default 5), quantizePosition (default 14), quantizeNormal (10), quantizeColor (8), quantizeTexcoord (12), quantizeGeneric (12), and quantizationVolume (mesh default, or scene). Quantization bits must be integers 1–30. More bits generally preserve more precision at a size cost.

Standalone commands support --draco and corresponding kebab-case encoder flags, e.g.:

r2u-glb optimize --input ./models --output ./optimized --keep-nodes --draco
r2u-glb lod ./models/model.glb --keep-nodes --draco --draco-quantize-position 16

Direct OptimizeConfig and LodConfig accept optional draco: DracoConfig; omit it to preserve existing behavior. The dedicated compressDraco(inputPath, { outputPath?, config?, dryRun? }) API defaults to replacing the input, uses a temporary file before replacement, and rejects on error. Its dry-run only validates configuration and reads file size; it does not encode. Always provide outputPath to keep an original untouched.

The consumer must support KHR_draco_mesh_compression (e.g. configure DRACOLoader with Three.js GLTFLoader). Draco does not reduce triangle counts by design, does not compress textures and does not guarantee smaller output on already-compressed or tiny geometry. Existing Draco inputs are decoded and recompressed when explicitly requested, potentially adding quantization loss.

Local benchmark: yarn tsx test/benchmark-draco.ts, with three input GLBs in temp/. Outputs and JSON report go to temp/draco-results/; summary and limitations: test/draco-benchmark.md. KTX2 combination validation requires the native ktx binary.

Output safety

Each asset is processed in a staging directory. Existing output files are replaced only after all declared steps for that asset succeed. If processing fails before promotion, existing outputs remain untouched.

Manifest inputs and outputs cannot use absolute paths or .. to escape the manifest directory. Direct low-level APIs still operate on paths explicitly provided by the caller.

KTX cache

The default cache is platform-specific:

  • Linux: $XDG_CACHE_HOME/r2u-glb-pipeline or ~/.cache/r2u-glb-pipeline;
  • macOS: ~/Library/Caches/r2u-glb-pipeline;
  • Windows: %LOCALAPPDATA%/r2u-glb-pipeline.

Set R2U_GLB_CACHE_DIR to override it. Use --no-ktx-download to require a preinstalled ktx binary.

Development

yarn install
yarn format:check
yarn lint
yarn typecheck
yarn test
yarn build
npm pack --dry-run

Set R2U_TEST_KTX2=1 when running tests to enable the KTX2 integration test.

Publishing

This is a public NPM package with a proprietary license. Public availability does not grant permission to copy, modify, redistribute, or create derivative works; see LICENSE.

The package must exist in the NPM registry before a trusted publisher can be configured. An authorized @r2u maintainer must bootstrap the first beta with 2FA:

npm publish --access public --tag beta

After the initial publication:

  1. Create the protected npm environment in r2u-io/glb-pipeline on GitHub.
  2. In the package settings on npmjs.com, configure a GitHub Actions trusted publisher for organization r2u-io, repository glb-pipeline, workflow publish.yml, environment npm, and npm publish permission.
  3. Publish subsequent versions by creating a GitHub release. .github/workflows/publish.yml validates and publishes through OIDC without a long-lived NPM token.

The current publishConfig.tag is beta; change it when promoting a stable release.