images2atlas 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +179 -0
- package/package.json +48 -0
- package/spritesheet-templates.d.ts +10 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Porky Ke
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# images2atlas
|
|
2
|
+
|
|
3
|
+
Generate a spritesheet atlas and matching style/template output from a directory
|
|
4
|
+
of PNG images. This module scans folders recursively, copies non-PNG files as-is,
|
|
5
|
+
and produces atlas files per directory with configurable formats and suffixes.
|
|
6
|
+
|
|
7
|
+
## Core Features
|
|
8
|
+
Use these features to understand what the tool produces and how it behaves.
|
|
9
|
+
- Directory-based spritesheet generation (one atlas per folder).
|
|
10
|
+
- Recursive traversal with include/exclude filters.
|
|
11
|
+
- Copies non-PNG files into the destination tree.
|
|
12
|
+
- Multiple template formats via [`spritesheet-templates`](https://github.com/twolfson/spritesheet-templates).
|
|
13
|
+
- Optional watch mode with debounced re-pack on changes.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
```
|
|
17
|
+
npm i images2atlas --save-dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
```ts
|
|
22
|
+
import { images2atlas } from 'images2atlas';
|
|
23
|
+
|
|
24
|
+
await images2atlas({
|
|
25
|
+
src: '/path/to/icons',
|
|
26
|
+
dest: '/path/to/output/icons',
|
|
27
|
+
});
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Options
|
|
31
|
+
Use these options to control inputs, outputs, and packing behavior.
|
|
32
|
+
| Option | Type | Default | Description |
|
|
33
|
+
| --- | --- | --- | --- |
|
|
34
|
+
| `src` | `string` | required | Source directory containing images. Must be a directory. |
|
|
35
|
+
| `dest` | `string` | required | Destination directory (no file extension). |
|
|
36
|
+
| `exclude` | `(info, src) => boolean` | `() => false` | Return `true` to skip a file or folder. |
|
|
37
|
+
| `include` | `(info, src) => boolean` | `() => true` | Return `true` to include a PNG in the atlas. |
|
|
38
|
+
| `suffix` | `string` | `-atlas` | Output suffix for atlas files. |
|
|
39
|
+
| `delay` | `number` | `500` | Debounce delay (ms) for watch mode. |
|
|
40
|
+
| `silent` | `boolean` | `true` | Suppress logging when `true`. |
|
|
41
|
+
| `watch` | `boolean` | `false` | Watch `src` and re-pack on changes. |
|
|
42
|
+
| `spritesmithOptions` | `object` | `{ padding: 2, exportOpts: { format: 'png', quality: 100 } }` | Options passed to [`spritesmith`](https://github.com/twolfson/spritesmith). |
|
|
43
|
+
| `templatesOptions` | `object` | `{ format: 'css' }` | Options passed to [`spritesheet-templates`](https://github.com/twolfson/spritesheet-templates?tab=readme-ov-file). |
|
|
44
|
+
|
|
45
|
+
## Output Behavior
|
|
46
|
+
Each directory with PNG files generates a pair of outputs in its corresponding
|
|
47
|
+
`dest` folder:
|
|
48
|
+
|
|
49
|
+
- `${src}${suffix}.png` (the atlas image)
|
|
50
|
+
- `${src}${suffix}.<ext>` (template output)
|
|
51
|
+
|
|
52
|
+
The template file extension is derived from `templatesOptions.format`, supporting:
|
|
53
|
+
`css`, `json`, `less`, `sass`, `scss`, `styl` and [`custom format`](https://github.com/twolfson/spritesheet-templates?tab=readme-ov-file#custom).
|
|
54
|
+
The format check is a prefix match, so `styl` also matches formats like
|
|
55
|
+
`stylus`.
|
|
56
|
+
|
|
57
|
+
The spritesheet image reference in templates uses the pattern:
|
|
58
|
+
```
|
|
59
|
+
<dest-dir-name><suffix>.png
|
|
60
|
+
```
|
|
61
|
+
If a directory contains no PNG files, no atlas or template files are generated
|
|
62
|
+
for that directory.
|
|
63
|
+
|
|
64
|
+
## Example Structure
|
|
65
|
+
Input:
|
|
66
|
+
```
|
|
67
|
+
src/
|
|
68
|
+
└── icons/
|
|
69
|
+
├── a.png
|
|
70
|
+
├── b.png
|
|
71
|
+
└── bg.jpg
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Output (with `suffix: '-atlas'` and `format: 'css'`):
|
|
75
|
+
```
|
|
76
|
+
dest/
|
|
77
|
+
├── icons-atlas.png
|
|
78
|
+
├── icons-atlas.css
|
|
79
|
+
└── icons/
|
|
80
|
+
└── bg.jpg
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Notes
|
|
84
|
+
Use these notes to understand how filters and paths are interpreted.
|
|
85
|
+
- Only `.png` files are packed into the atlas. Non-PNG files are copied through.
|
|
86
|
+
- `include` only affects whether a PNG participates in the atlas. If `include`
|
|
87
|
+
returns `false`, the PNG is copied instead.
|
|
88
|
+
- `exclude` skips both atlas participation and file copying for the matched path.
|
|
89
|
+
- `src` must exist and be a directory path with no file extension.
|
|
90
|
+
- `dest` must be a directory path with no file extension.
|
|
91
|
+
- Filenames are filtered via `isSafeFilename` from [`web-build-utils`](https://github.com/porky-prince/web-build-tools/packages/web-build-utils).
|
|
92
|
+
|
|
93
|
+
## Changelog
|
|
94
|
+
|
|
95
|
+
See [`CHANGELOG.md`](CHANGELOG.md) for release history.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import templater from 'spritesheet-templates';
|
|
3
|
+
import Spritesmith from 'spritesmith';
|
|
4
|
+
/**
|
|
5
|
+
* Options for Images2atlas
|
|
6
|
+
*
|
|
7
|
+
* @property {string} src - The source directory containing images to pack. Must be a directory.
|
|
8
|
+
* @property {string} dest - The destination directory for output files. Must be a directory.
|
|
9
|
+
* @property {function} [exclude] - Function to exclude files/directories from packing. Receives parsed path info and full path. Return true to exclude.
|
|
10
|
+
* @property {function} [include] - Function to include files/directories for packing. Receives parsed path info and full path. Return true to include.
|
|
11
|
+
* @property {string} [suffix] - Suffix for output files (e.g., '-atlas'). Default is '-atlas'.
|
|
12
|
+
* @property {number} [delay] - Debounce delay (ms) for packing after changes. Default is 500ms.
|
|
13
|
+
* @property {boolean} [silent] - If true, suppresses plugin logging. Default is true.
|
|
14
|
+
* @property {boolean} [watch] - If true, watch src and re-pack on changes. Default is false.
|
|
15
|
+
* @property {object} [spritesmithOptions] - Options for Spritesmith (e.g., padding, export format, quality).
|
|
16
|
+
* @property {object} [templatesOptions] - Options for spritesheet-templates (e.g., output format, custom templates).
|
|
17
|
+
*/
|
|
18
|
+
export interface Images2atlasOptions {
|
|
19
|
+
src: string;
|
|
20
|
+
dest: string;
|
|
21
|
+
exclude?: (srcInfo: path.ParsedPath, src: string) => boolean;
|
|
22
|
+
include?: (srcInfo: path.ParsedPath, src: string) => boolean;
|
|
23
|
+
suffix?: string;
|
|
24
|
+
delay?: number;
|
|
25
|
+
silent?: boolean;
|
|
26
|
+
watch?: boolean;
|
|
27
|
+
spritesmithOptions?: Spritesmith.SpritesmithParams & Spritesmith.SpritesmithProcessImagesOptions;
|
|
28
|
+
templatesOptions?: Parameters<typeof templater>[1];
|
|
29
|
+
}
|
|
30
|
+
export declare function images2atlas(options: Images2atlasOptions): Promise<void>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.images2atlas = images2atlas;
|
|
16
|
+
const chokidar_1 = require("chokidar");
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
19
|
+
const spritesheet_templates_1 = __importDefault(require("spritesheet-templates"));
|
|
20
|
+
const spritesmith_1 = __importDefault(require("spritesmith"));
|
|
21
|
+
const debounce_1 = __importDefault(require("debounce"));
|
|
22
|
+
const web_build_utils_1 = require("web-build-utils");
|
|
23
|
+
// Supported output template formats for spritesheet-templates.
|
|
24
|
+
const formatTypes = ['css', 'json', 'less', 'sass', 'scss', 'styl'];
|
|
25
|
+
/**
|
|
26
|
+
* Images2atlas
|
|
27
|
+
*
|
|
28
|
+
* Plugin to generate a spritesheet atlas and style/template files from a directory of PNG images.
|
|
29
|
+
* Automatically watches for changes and repacks as needed.
|
|
30
|
+
*/
|
|
31
|
+
class Images2atlas {
|
|
32
|
+
/**
|
|
33
|
+
* Constructor
|
|
34
|
+
* Validates and initializes plugin options.
|
|
35
|
+
* Throws error if src or dest are not directories.
|
|
36
|
+
*/
|
|
37
|
+
constructor(options) {
|
|
38
|
+
// File watcher instance
|
|
39
|
+
this._watcher = null;
|
|
40
|
+
if (!fs_extra_1.default.existsSync(options.src) ||
|
|
41
|
+
!fs_extra_1.default.statSync(options.src).isDirectory()) {
|
|
42
|
+
throw new Error('The options.src must be a directory.');
|
|
43
|
+
}
|
|
44
|
+
if (path_1.default.extname(options.dest)) {
|
|
45
|
+
throw new Error('The options.dest must be a directory.');
|
|
46
|
+
}
|
|
47
|
+
this._options = Object.assign({ exclude: () => false, include: () => true, suffix: '-atlas', delay: 500, silent: true, watch: false, spritesmithOptions: {
|
|
48
|
+
padding: 2,
|
|
49
|
+
exportOpts: {
|
|
50
|
+
format: 'png',
|
|
51
|
+
quality: 100,
|
|
52
|
+
},
|
|
53
|
+
}, templatesOptions: {} }, options);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Logs messages using console if not silent.
|
|
57
|
+
*/
|
|
58
|
+
log(...args) {
|
|
59
|
+
if (this._options.silent) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
console.log(...args);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Initializes and returns a file watcher for the source directory.
|
|
66
|
+
* Calls the provided callback on file events.
|
|
67
|
+
*/
|
|
68
|
+
getWatcher(cb) {
|
|
69
|
+
if (!this._watcher) {
|
|
70
|
+
fs_extra_1.default.ensureDirSync(this._options.src);
|
|
71
|
+
this._watcher = (0, chokidar_1.watch)('.', {
|
|
72
|
+
cwd: this._options.src,
|
|
73
|
+
ignoreInitial: true,
|
|
74
|
+
});
|
|
75
|
+
this._watcher.on('all', cb);
|
|
76
|
+
}
|
|
77
|
+
return this._watcher;
|
|
78
|
+
}
|
|
79
|
+
runPack() {
|
|
80
|
+
const { src, dest, delay, watch } = this._options;
|
|
81
|
+
const pack = () => this.pack(src, dest);
|
|
82
|
+
if (watch) {
|
|
83
|
+
// Debounce to avoid re-packing on bursty file events.
|
|
84
|
+
const delayPack = (0, debounce_1.default)(pack, delay);
|
|
85
|
+
this.getWatcher((e, p) => {
|
|
86
|
+
if (!(0, web_build_utils_1.isSafeFilename)(p, true)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.log(e, p);
|
|
90
|
+
delayPack();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return pack();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Packs images from the source directory into the destination directory.
|
|
97
|
+
* Recursively processes files and directories, collects PNGs for spritesheet, copies other files.
|
|
98
|
+
* @param src Source directory
|
|
99
|
+
* @param dest Destination directory
|
|
100
|
+
*/
|
|
101
|
+
pack(src, dest) {
|
|
102
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
103
|
+
this.log('pack', src);
|
|
104
|
+
const { exclude, include } = this._options;
|
|
105
|
+
const fullNames = yield fs_extra_1.default.readdir(src);
|
|
106
|
+
const pngPaths = [];
|
|
107
|
+
yield Promise.all(fullNames.map((fullName) => __awaiter(this, void 0, void 0, function* () {
|
|
108
|
+
const srcPath = path_1.default.join(src, fullName);
|
|
109
|
+
const info = path_1.default.parse(srcPath);
|
|
110
|
+
if (!(0, web_build_utils_1.isSafeFilename)(srcPath, true) || exclude(info, srcPath)) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const destPath = path_1.default.join(dest, info.base);
|
|
114
|
+
if (info.ext) {
|
|
115
|
+
// File
|
|
116
|
+
if (info.ext === '.png' && include(info, srcPath)) {
|
|
117
|
+
// Collect PNGs for atlas generation later.
|
|
118
|
+
pngPaths.push(srcPath);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
// Copy single file
|
|
122
|
+
yield fs_extra_1.default.outputFile(destPath, yield fs_extra_1.default.readFile(srcPath));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
// Dir
|
|
127
|
+
yield this.pack(srcPath, destPath);
|
|
128
|
+
}
|
|
129
|
+
})));
|
|
130
|
+
// Generate atlas and template outputs for collected PNGs.
|
|
131
|
+
yield this.packSpriteSheet(pngPaths, dest);
|
|
132
|
+
this.log('packed', dest);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Generates the spritesheet and style/template files from collected PNG paths.
|
|
137
|
+
* Uses Spritesmith to create the atlas and spritesheet-templates for output files.
|
|
138
|
+
* @param pngPaths Array of PNG image paths
|
|
139
|
+
* @param dest Destination directory
|
|
140
|
+
*/
|
|
141
|
+
packSpriteSheet(pngPaths, dest) {
|
|
142
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
143
|
+
if (pngPaths.length === 0) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
const { suffix, spritesmithOptions, templatesOptions } = this._options;
|
|
147
|
+
const format = templatesOptions.format || 'css';
|
|
148
|
+
yield new Promise((resolve, reject) => {
|
|
149
|
+
spritesmith_1.default.run(Object.assign(Object.assign({}, spritesmithOptions), { src: pngPaths }), (err, result) => __awaiter(this, void 0, void 0, function* () {
|
|
150
|
+
if (err) {
|
|
151
|
+
return reject(err);
|
|
152
|
+
}
|
|
153
|
+
// Map image coordinates to sprite objects
|
|
154
|
+
const sprites = Object.keys(result.coordinates).map((imgPath) => {
|
|
155
|
+
const info = path_1.default.parse(imgPath);
|
|
156
|
+
return Object.assign(Object.assign({}, result.coordinates[imgPath]), { name: info.name });
|
|
157
|
+
});
|
|
158
|
+
// Spritesheet properties and output image path
|
|
159
|
+
const spritesheet = Object.assign(Object.assign({}, result.properties), { image: `${path_1.default.basename(dest) + suffix}.png` });
|
|
160
|
+
// Generate template/style file
|
|
161
|
+
const temp = (0, spritesheet_templates_1.default)({
|
|
162
|
+
sprites,
|
|
163
|
+
spritesheet,
|
|
164
|
+
}, templatesOptions);
|
|
165
|
+
// Determine output file extension
|
|
166
|
+
const ext = formatTypes.find((type) => format.startsWith(type)) || format;
|
|
167
|
+
yield Promise.all([
|
|
168
|
+
fs_extra_1.default.outputFile(dest + suffix + '.' + ext, temp),
|
|
169
|
+
fs_extra_1.default.outputFile(dest + suffix + '.png', result.image),
|
|
170
|
+
]);
|
|
171
|
+
resolve();
|
|
172
|
+
}));
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function images2atlas(options) {
|
|
178
|
+
return new Images2atlas(options).runPack();
|
|
179
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "images2atlas",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Plugin that converts set of images into a spritesheet by its directory and SASS/LESS/Stylus mixins",
|
|
5
|
+
"author": "Porky Ke",
|
|
6
|
+
"homepage": "https://github.com/porky-prince/web-build-tools/packages/images2atlas#readme",
|
|
7
|
+
"bugs": "https://github.com/porky-prince/web-build-tools/issues",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"types": "dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"spritesheet-templates.d.ts"
|
|
14
|
+
],
|
|
15
|
+
"publishConfig": {
|
|
16
|
+
"access": "public"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git@github.com:porky-prince/web-build-tools.git",
|
|
21
|
+
"directory": "packages/images2atlas"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@types/spritesmith": "^3.4.5",
|
|
25
|
+
"chokidar": "^4.0.3",
|
|
26
|
+
"debounce": "^2.2.0",
|
|
27
|
+
"fs-extra": "^11.3.2",
|
|
28
|
+
"spritesheet-templates": "^10.5.2",
|
|
29
|
+
"spritesmith": "^3.5.1",
|
|
30
|
+
"web-build-utils": "^0.0.4"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/fs-extra": "^11.0.4",
|
|
34
|
+
"@types/node": "^24.6.2"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"icons",
|
|
38
|
+
"images",
|
|
39
|
+
"sprites",
|
|
40
|
+
"spritesmith"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"start": "tsc -w",
|
|
44
|
+
"tsc": "tsc --declaration",
|
|
45
|
+
"build": "del dist && npm run tsc",
|
|
46
|
+
"test": "jest"
|
|
47
|
+
}
|
|
48
|
+
}
|