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 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.
@@ -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
+ }
@@ -0,0 +1,10 @@
1
+ declare module 'spritesheet-templates' {
2
+ interface Options {
3
+ // spritesheetName?: string;
4
+ format?: string;
5
+ formatOpts?: Record<string, any>;
6
+ }
7
+
8
+ function templater(data: any, options: Options): string;
9
+ export = templater;
10
+ }