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

@studiokloek/pixi-texturepacker

v2.0.14

Published

Pack sprites into texture atlases for PixiJS, with a built-in packer or the TexturePacker CLI.

Readme

pixi-texturepacker

Pack sprites into texture atlases for PixiJS and generate TypeScript constants for every sprite sheet.

The packer is built in: MaxRects packing with rotation, trimming, extruding, multipack and resolution variants, producing the same pixijs4 json TexturePacker does. It was measured against the TexturePacker CLI it replaces: same number of sheets, slightly less sheet area, identical frame metadata and pixels within one level of the scaled sprites. The CLI is still supported as an alternative engine.

Installation

npm i @studiokloek/pixi-texturepacker --save-dev

No other software is needed. Only with "engine": "texturepacker" an installation of the TexturePacker CLI with a valid licence is required; the TexturePacker executable must then be in your PATH.

Usage

Create an assets-sprites.json in your project. The defaults follow the Studio Kloek project layout, so usually the list of directories is all you need:

{
  "directories": [
    "world/background",
    "world/player{jpg}",
    "hud{name=1}{size}"
  ]
}

Then run:

pixi-texturepacker assets-sprites.json

For every directory the packer writes <name>-<hash>/<name>-<hash>-<n><variant>.{png|jpg,json} to targetDirectory and a sprites-<name>.ts is generated in scriptDirectory, exporting the sprite ids (or their metadata) and a ...LoaderInfo object.

Command line

pixi-texturepacker [file...]              pack every settings file in turn (default assets-sprites.json)
pixi-texturepacker <file> --force         ignore the cache and pack everything
pixi-texturepacker <file> --no-cache      do not use the cache at all
pixi-texturepacker <file> --watch         repack when a source image changes
pixi-texturepacker <file> --watch --watch-delay 2000  debounce in ms (default 500)
pixi-texturepacker unused [file...]       list the sprites no code refers to (see Finding unused sprites)
pixi-texturepacker unused <file> --search ./src   search another directory than scriptDirectory (repeatable)
pixi-texturepacker migrate <file...>      show the migrated settings (see Migrating)
pixi-texturepacker migrate <file...> --write   write them back

Several settings files can be given at once; they are packed one after the other and the exit code is 1 when any of them fails. The exit code is also 1 when a single directory fails, so a broken pack never passes as a successful build. A source directory that is not there, or that holds no images, is the exception: it is reported as missing or empty and the exit code stays 0, so one stale entry does not stop a chain of packers.

Output

Every line starts with the label sprites, so the output stays readable when another packer (for example the sounds packer, labelled sounds) runs in the same terminal. A run looks like this:

sprites  ▶ assets-sprites.json  3 directories  2x → 1x,2x · png · max 2048
sprites  –   game/fx         31 sprites    52ms
sprites  ✔   game/weide      17 sprites    1.4s  →  1 sheet  [jpg · size only]  +1 added, ~2 changed
sprites  ✔   profile/base    11 sprites    1.1s  →  1 sheet  [1x → 1x,2x · name 1 part]
sprites  ✔ assets-sprites.json  done  3 directories · 59 sprites · 2 sheets  2.7s
  • opens a settings file with its number of directories and the global settings that shape the atlases: source resolution and output resolutions, format and maximum size, plus extruded, no hash, code only, another metadata level or another number of name parts when set, and force or no cache when those flags are given. at the end sums the run up. The directory lines in between are indented, so one settings file reads as one block.
  • Per directory the symbol says what happened: packed, unchanged (the whole line is dimmed), failed and missing or empty.
  • Then, in columns that line up across the file: the number of source images and the duration. What was done trails the line, where it cannot push those columns apart: the number of atlas sheets per resolution, then between brackets only what differs from the global settings ([jpg · size only], [max 4096 · no metadata], [code only]), and in yellow +added, ~changed and -removed compared with the previous run, when the cache knows one.
  • In watch mode a line such as game/fx changed hero.png · added coin.png precedes every repack. Spinners are only shown for a one-off run in a terminal; in watch mode and outside a TTY (CI) every step is a plain line, so the output of two tools does not get mixed up.
  • Several directories are packed at the same time, but they are always reported in the order of the settings file, so two runs of the same file read the same. A single spinner counts the run down while it goes; the messages of a directory (Removed 3 generated files of ...) stay with the line that reports it. See Packing several directories at once.

Packing several directories at once

Directories of one settings file are packed at the same time, as many at once as there are worker threads (the number of cores minus one, at most eight). This adds no threads: the workers that compose the sheets are shared and queued either way. The point is to keep them busy, because hashing the sources, writing the json, generating the code and cleaning up earlier output all happen off that pool, and used to leave it idle. On a project of 28 directories and 392 sprites, packing everything took 18s one at a time and 9s this way, using about the same amount of memory: how many sheets are composed at once is decided by the pool, not by this.

PIXI_TEXTUREPACKER_DIRECTORIES sets the number by hand; PIXI_TEXTUREPACKER_DIRECTORIES=1 packs one directory at a time, the way older versions did. With "engine": "texturepacker" it is always one: the TexturePacker licence does not allow several instances at the same time, whatever this is set to.

Settings

Settings live at the top level of the file. Every setting marked in path can also be set per directory with a {token} appended to its path, which overrides the global value for that directory.

| Setting | Default | In path | Description | | --- | --- | --- | --- | | sourceDirectory | ./assets/sprites/ | | Root of the source images. Every entry in directories is relative to this. | | targetDirectory | ./source/static/sprites/ | | Where the sheets and json are written. | | scriptDirectory | ./source/script/ | | Where the generated sprites-<name>.ts files are written. | | directories | [] | | Directories to pack, see below. | | format | png | {jpg} {png} | Texture format. | | maxSize | 2048 | {max=1024} | Maximum texture size in pixels. | | extrude | false | {extrude} {noextrude} | Extrude sprites by one pixel (disables shape padding). | | metadata | full | {size} {nodata} {meta=…} | How much per-sprite information the generated code holds, see below. | | codeOnly | false | {codeonly} {nocodeonly} | The textures are final: only generate the code. | | hash | true | {hash} {nohash} | Put a content hash in the output directory and file names. | | nameParts | 2 | {name=3} | How many trailing parts of the directory path are used for the exported variable name: world/duin with 2 gives WorldDuinSprites. | | sourceResolution | 2 | | Resolution the source images were made for. Variants are scaled relative to it, PNGExpress positions are divided by it. | | resolutions | [1, 2] | | Resolutions to generate; 1 gets no suffix, 2, 3 and 4 get @2x, @3x, @4x. | | engine | internal | | internal packs with the built-in packer, texturepacker runs the TexturePacker CLI. Switching changes the layout, so every atlas is packed again and gets a new hash. |

Watching and caching are not settings but command line flags: --watch, --watch-delay, --no-cache and --force.

Directories

Every entry is a path, optionally followed by {token} options in any order:

"world/background"
"world/background{jpg}"
"map/tiles{jpg}{max=1024}{extrude}"

For the rare option without a token (scriptDirectory, sourceResolution) an object can be used:

{ "path": "profile/kids/hindu{name=3}", "scriptDirectory": "./other/" }

When an object sets an option that its path also carries as a token, the object wins.

A settings file must list at least one directory; an empty list is an error rather than an empty run. The same directory may appear more than once with different options, and each variant keeps its own cache entry.

Metadata

metadata decides what the generated code holds per sprite:

| Value | Token | Result | | --- | --- | --- | | full | {meta=full} | id, width, height plus x, y, zIndex, visible and opacity from the nearest pngexpress-metadata.json | | size | {size} | id, width and height | | none | {nodata} | only the sprite id, as a plain string |

Generated code

For world/duin containing eiland/boot-1.png the generated sprites-duin.ts looks like:

export const WorldDuinSprites = {
  eiland: {
    BOOT_1: {
      id: "world/duin/eiland/boot-1",
      width: 100,
      height: 50
    }
  }
};
export const WorldDuinSpritesLoaderInfo = {
  assets: WorldDuinSprites,
  fileName : 'world/duin-a1b2c3d4e5',
  numberOfParts : 1,
  type: 'sprites'
};

File and folder names are made safe for use as identifiers; the last part is upper-cased. Two or more consecutive non-word characters create a nested object.

The script is written to <scriptDirectory>/<parent path>/assets/sprites-<name>.ts; a directory with fewer than two parent parts keeps its own name in the path (hudhud/assets/sprites-hud.ts).

Hashed output

With hash (on by default) the output directory and the file prefix get a content hash, so browsers never serve a stale atlas after a deploy. For world/fx with hash a1b2c3d4e5:

<targetDirectory>/world/fx-a1b2c3d4e5/fx-a1b2c3d4e5-1.json
<targetDirectory>/world/fx-a1b2c3d4e5/fx-a1b2c3d4e5-1.png
<targetDirectory>/world/fx-a1b2c3d4e5/[email protected]
<targetDirectory>/world/fx-a1b2c3d4e5/[email protected]

The generated code gets fileName : 'world/fx-a1b2c3d4e5'; the sprite ids, numberOfParts and the path of the .ts file do not change. meta.image in the json points to the hashed png, as TexturePacker writes it.

The hash covers the source images, the directory path, the packing options and resolutions and the engine. It does not cover the source/target/script directories, the package version or code-only options such as nameParts, so it is identical on every machine and only changes when the atlas itself changes.

After a successful pack, directories of earlier hashes and the files of the unhashed layout are removed. Other files in the unhashed directory (such as nested atlases) are left alone. Using {nohash} removes the hashed directories again.

Directories with codeOnly get no hash, because their textures are not produced here. Adding {codeonly} to a directory that was packed before removes the atlas of that earlier run: its json and image files, and the directories of its hashes. The directories that output lived in are removed as well, as far up as they are left empty, so nothing but the target directory itself remains. One that still holds something (a nested atlas, a .gitignore, the output of another tool) stops that. Only the generated code stays. Taking {codeonly} off packs the directory again on the next run, so the textures come back.

Finding unused sprites

pixi-texturepacker unused packs nothing: it lists the source images of every directory, gives them the names the generated code gives them, and searches the code for references to those names. What is found nowhere is listed with its source file, so it can be deleted by hand. Nothing is removed by the tool.

sprites  ▶ assets-sprites.json  12 directories  searching ./source/script/
sprites  ⚠ feest           2 of 41 unused  FeestSprites
sprites      labels/item-7.png  labels.ITEM_7
sprites      photos/item-2.png  photos.ITEM_2
sprites  – map/background  3 sprites not checked · tiles used as a group in source/script/map/tiles.ts:41
sprites  ✖ profile/base    never referenced · 8 sprites
sprites  ⚠ assets-sprites.json  done  12 directories · 61 sprites · 9 all used · 1 never referenced · 10 unused · 3 not checked  0.2s

Only directories that need attention get a line; the ones whose sprites are all used are counted in the closing line. By default the scriptDirectory of the settings file (and any per-directory ones) is searched; --search replaces that. All .ts, .tsx, .js, .jsx, .mjs, .cjs, .vue and .svelte files are read, except the generated assets/sprites-*.ts files and node_modules. Comments and import statements are ignored, so a commented-out reference does not count as a use. The exit code is 1 only when a settings file could not be loaded or a search directory does not exist; unused sprites do not fail the run.

The search is textual and looks at what follows every occurrence of a generated constant:

  • FeestSprites.labels.ITEM_1, FeestSprites.labels['ITEM_1'], FeestSprites?.labels?.ITEM_1 and lodash get(FeestSprites, 'labels.ITEM_1') or get(FeestSprites, ['labels', 'ITEM_1']) count as a use of that one sprite. So do the keys of const { ITEM_1, ITEM_2 } = FeestSprites.labels.
  • A template key such as FeestSprites.labels[ITEM_${n}] or get(BaseKidsSprites, ${religion}.KIND_DEFAULT) becomes a pattern: ${…} matches anything, the fixed text has to match. Every sprite the pattern matches counts as used and the pattern is listed with how many it matched.
  • A simple alias in the same file, const Sprites = FeestSprites; or const Labels = FeestSprites.labels;, is followed: what is done with Sprites and Labels counts for the constant.
  • Anything the tool cannot resolve marks everything under that path as not checked instead of unused: the constant passed to a function or spread, Object.values(FeestSprites.labels), FeestSprites[index] and get(FeestSprites, this.assetId). Each of those is reported with its file and line, so you can decide yourself. Better to miss a sprite than to list one as unused while it is not.

Directories that are codeOnly are skipped: their sources are not what ends up in the build, so there is nothing to remove there. A directory whose constant occurs nowhere (its ...LoaderInfo does not count) is reported as never referenced in one line rather than sprite by sprite. Reading a metadata field, FeestSprites.BG.y, counts as a use of BG like any other member access.

Caching

A hash of the source images, the nearest pngexpress-metadata.json, the packing options and the relevant settings is stored in node_modules/.cache/pixi-texturepacker/. When it matches and all outputs still exist, the directory is not packed at all. Use --force or --no-cache to pack anyway.

The package version is part of the key, so the first run after an update repacks everything; the output names do not change, because the version is deliberately not part of the content hash. The entry also keeps the digests of the source images, which is what the +added, ~changed and -removed counts in the output compare against.

Under sources/ a small manifest per settings file records which scripts it generated. After a run, a sprites-<name>.ts that an earlier run generated and that no directory generates any more is removed, along with an assets directory left empty by that, so removing a directory from the settings removes its code too. A deleted settings file takes its scripts with it. Only paths recorded in a manifest are candidates; the script directory itself is never scanned, and a script another settings file still generates is always kept. These manifests are kept even with --no-cache. Removing node_modules also removes them; run every settings file once before relying on this again.

A settings file counts as deleted when it is gone but its directory still exists. So a branch that does not have one of your settings files yet makes a run on that branch remove the scripts of that file; they come back when it is packed again. A settings file whose directory is gone too (the project moved, or is seen through another path) is left alone, manifest and all.

The atlas output in the target directory is left alone by that step, because the target can hold files of other tools. It is cleaned up per directory instead, while it is packed.

Output of earlier runs that no longer belongs to the current one (extra multipack parts, resolutions that were removed from resolutions, directories of earlier hashes) is cleaned up after packing, in watch mode too. Nothing of an earlier run is removed until the new code has been generated, so a failed run never leaves a directory without usable output.

A directory that is still listed but missing on disk is reported as missing with , instead of letting the packer report an error of its own or, for a codeonly directory, silently counting as unchanged. The run itself still succeeds, because a settings file may list a directory that only exists on another branch. Its script and atlas files stay until it is removed from the settings.

A directory that is there but holds no images is reported as empty, also with and also without failing the run. Its script is kept, as there is no atlas to generate a new one from, but the atlas of an earlier run is removed: no source images means no sheets, and a pack is the only moment the target directory is cleaned up. Only files named after the directory itself go, so a nested atlas of another one stays where it is.

--watch reacts to every image type TexturePacker reads, not only png and jpg.

Migrating from 1.x

The settings file was simplified: most defaults now match how the tool is actually used, watching and caching moved to the command line, and per-directory options are written as {token}s in the path.

pixi-texturepacker migrate assets-sprites.json          # show what changes
pixi-texturepacker migrate assets-sprites.json --write  # apply it

What changed:

| 1.x | Now | | --- | --- | | { "sprites": { … } } wrapper | settings at the top level (the wrapper still works, and migrate keeps it when the file also holds other blocks) | | textureFormat | format | | onlyGenerateCode | codeOnly | | numTitleParts | nameParts | | originalResolution | sourceResolution | | includeSizeInfo + includePNGExpressMetadata | metadata (full, size or none) | | watch, watchDelay | --watch, --watch-delay (default 500 ms) | | cache: false | --no-cache | | ["path", { … }] | "path{token}" | | includeSizeInfo / includePNGExpressMetadata default off | metadata: "full" by default | | hash default off | on by default |

Because hashing is now on by default, the first run after migrating writes every atlas to a new hashed directory. Use {nohash} (or "hash": false) to keep the old layout.

Settings with a wrong type, globally or on a directory entry, are now an error instead of being silently ignored. This catches "sourceResolution": false, which (as originalResolution) used to disable the resolution variants without warning. Old setting names give a warning that points at migrate.

migrate warns for every directory that gains PNGExpress metadata in its generated code, which is only the case when a pngexpress-metadata.json is found for it.

Development

dist/ is not committed; run yarn build after cloning before bin/index.js (and therefore yarn test) will work.

yarn build       # tsc -> dist/, needed once before the commands below work
yarn typecheck   # tsc --noEmit
yarn lint        # eslint
yarn test:unit   # node --test
yarn test        # packs ./test/assets.json
yarn test:unused # lists the sprites of ./test/assets.json no code in ./test/script refers to

Two environment variables exist to take the packer apart when something looks wrong. Neither changes what is written, so a run with them set produces the same atlases as one without:

PIXI_TEXTUREPACKER_DIRECTORIES=1   # pack one directory at a time instead of filling the worker pool
PIXI_TEXTUREPACKER_WORKERS=0       # do everything on this thread, no worker threads at all