picslim 0.0.7 → 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/README.md +44 -8
- package/config.json +8 -0
- package/package.json +23 -2
- package/picslim.js +152 -133
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# PicSlim
|
|
2
2
|
|
|
3
|
-
**Picslim** is a Node.js package that allows you to optimize images
|
|
3
|
+
**Picslim** is a Node.js package that allows you to efficiently optimize images within a specified directory. It supports **JPEG** and **PNG** images, image formats, offering fine-grained control over image quality and resizing options during the optimization process. With **Picslim**, you can effortlessly reduce file sizes and enhance the loading performance of your images.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -10,6 +10,12 @@ You can install picslim globally using npm:
|
|
|
10
10
|
npm install -g picslim
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
or
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx picslim
|
|
17
|
+
```
|
|
18
|
+
|
|
13
19
|
# Usage
|
|
14
20
|
|
|
15
21
|
Once installed, you can use the optimizimage command in your terminal. Here's how you can use it:
|
|
@@ -18,17 +24,43 @@ Once installed, you can use the optimizimage command in your terminal. Here's ho
|
|
|
18
24
|
picslim [options]
|
|
19
25
|
```
|
|
20
26
|
|
|
21
|
-
Options
|
|
27
|
+
### Options
|
|
28
|
+
|
|
29
|
+
- `-c, --config <path>`: Path to the configuration file. (default: 'config.json')
|
|
30
|
+
- `-q, --quality <number>`: Image quality (0-100).
|
|
31
|
+
- `-l, --compressionLevel <number>`: PNG compression level (0-9).
|
|
32
|
+
- `-w, --maxWidth <number>`: Maximum width allowed for images.
|
|
33
|
+
- `-h, --maxHeight <number>`: Maximum height allowed for images.
|
|
34
|
+
- `-i, --input <path>`: Path to the input directory.
|
|
35
|
+
- `-o, --output <path>`: Path to the output directory.
|
|
36
|
+
|
|
37
|
+
### Configuration File
|
|
38
|
+
|
|
39
|
+
You can create a `config.json` file in your project directory to specify default settings. Here's an example configuration:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"inputDir": "./in",
|
|
44
|
+
"outputDir": "./min",
|
|
45
|
+
"quality": 80,
|
|
46
|
+
"maxWidth": null,
|
|
47
|
+
"maxHeight": null,
|
|
48
|
+
"compressionLevel": 9
|
|
49
|
+
}
|
|
50
|
+
```
|
|
22
51
|
|
|
23
|
-
|
|
24
|
-
**-mw, --maxWidth [value]:** Set the maximum width allowed (default: null).<br>
|
|
25
|
-
**-mh, --maxHeight [value]:** Set the maximum height allowed (default: null).<br>
|
|
26
|
-
**-c, --compressionLevel [value]:** Set the compression level (0 to 9, default: 9).
|
|
52
|
+
### Example:
|
|
27
53
|
|
|
28
|
-
|
|
54
|
+
Optimize images using default settings from the configuration file:
|
|
29
55
|
|
|
30
56
|
```bash
|
|
31
|
-
picslim
|
|
57
|
+
picslim
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Optimize images with custom settings:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
picslim -q 90 -w 800 -h 600 -l 4 -i input_images -o output_images
|
|
32
64
|
```
|
|
33
65
|
|
|
34
66
|
This will optimize all JPEG and PNG images in the current directory, and the optimized images will be saved in a 'min' directory.
|
|
@@ -65,3 +97,7 @@ This project is licensed under the MIT License. See the LICENSE file for details
|
|
|
65
97
|
### Author
|
|
66
98
|
|
|
67
99
|
Ivan Mercedes
|
|
100
|
+
|
|
101
|
+
### Contributors
|
|
102
|
+
|
|
103
|
+
- [Elminson De Oleo Baez](https://github.com/elminson)
|
package/config.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "picslim",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Picslim is a Node.js package that allows you to optimize images in a specified directory.",
|
|
5
5
|
"main": "picslim.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
},
|
|
9
9
|
"source": "picslim.js",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"start": "node picslim.js -q 80"
|
|
11
|
+
"start": "node picslim.js -q 80",
|
|
12
|
+
"format": "standard --fix",
|
|
13
|
+
"prepare": "husky install"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
14
16
|
"optimization",
|
|
@@ -26,11 +28,30 @@
|
|
|
26
28
|
"license": "MIT",
|
|
27
29
|
"files": [
|
|
28
30
|
"picslim.js",
|
|
31
|
+
"config.json",
|
|
29
32
|
"package.json",
|
|
30
33
|
"README.md"
|
|
31
34
|
],
|
|
35
|
+
"eslintConfig": {
|
|
36
|
+
"extends": "./node_modules/standard/eslintrc.json"
|
|
37
|
+
},
|
|
38
|
+
"husky": {
|
|
39
|
+
"hooks": {
|
|
40
|
+
"pre-commit": "lint-staged"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"lint-staged": {
|
|
44
|
+
"*.{js}": [
|
|
45
|
+
"standard --fix"
|
|
46
|
+
]
|
|
47
|
+
},
|
|
32
48
|
"dependencies": {
|
|
33
49
|
"sharp": "^0.32.6",
|
|
34
50
|
"yargs": "^17.7.2"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"husky": "^8.0.0",
|
|
54
|
+
"lint-staged": "^15.0.2",
|
|
55
|
+
"standard": "^17.1.0"
|
|
35
56
|
}
|
|
36
57
|
}
|
package/picslim.js
CHANGED
|
@@ -1,36 +1,76 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const fs = require(
|
|
3
|
-
const sharp = require(
|
|
4
|
-
const yargs = require(
|
|
2
|
+
const fs = require('fs').promises
|
|
3
|
+
const sharp = require('sharp')
|
|
4
|
+
const yargs = require('yargs')
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Parses command line arguments using yargs library.
|
|
8
8
|
*/
|
|
9
|
-
function parseArguments()
|
|
10
|
-
{
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
9
|
+
function parseArguments () {
|
|
10
|
+
return yargs.options({
|
|
11
|
+
c: {
|
|
12
|
+
alias: 'config',
|
|
13
|
+
describe: 'Path to the configuration file',
|
|
14
|
+
demandOption: false,
|
|
15
|
+
type: 'string',
|
|
16
|
+
default: 'config.json'
|
|
17
|
+
},
|
|
18
|
+
q: {
|
|
19
|
+
alias: 'quality',
|
|
20
|
+
describe: 'Image quality',
|
|
21
|
+
demandOption: false,
|
|
22
|
+
type: 'number',
|
|
23
|
+
default: null
|
|
24
|
+
},
|
|
25
|
+
l: {
|
|
26
|
+
alias: 'compressionLevel',
|
|
27
|
+
describe: 'PNG compression level',
|
|
28
|
+
demandOption: false,
|
|
29
|
+
type: 'number',
|
|
30
|
+
default: null
|
|
31
|
+
},
|
|
32
|
+
w: {
|
|
33
|
+
alias: 'maxWidth',
|
|
34
|
+
describe: 'Maximum width allowed',
|
|
35
|
+
demandOption: false,
|
|
36
|
+
type: 'number',
|
|
37
|
+
default: null
|
|
38
|
+
},
|
|
39
|
+
h: {
|
|
40
|
+
alias: 'maxHeight',
|
|
41
|
+
describe: 'Maximum height allowed',
|
|
42
|
+
demandOption: false,
|
|
43
|
+
type: 'number',
|
|
44
|
+
default: null
|
|
45
|
+
},
|
|
46
|
+
i: {
|
|
47
|
+
alias: 'input',
|
|
48
|
+
describe: 'Path to the input directory',
|
|
49
|
+
demandOption: false,
|
|
50
|
+
type: 'string',
|
|
51
|
+
default: null
|
|
52
|
+
},
|
|
53
|
+
o: {
|
|
54
|
+
alias: 'output',
|
|
55
|
+
describe: 'Path to the output directory',
|
|
56
|
+
demandOption: false,
|
|
57
|
+
type: 'string',
|
|
58
|
+
default: null
|
|
59
|
+
}
|
|
60
|
+
}).argv
|
|
20
61
|
}
|
|
21
62
|
|
|
22
63
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* @
|
|
64
|
+
* Creates an output directory if it doesn't exist.
|
|
65
|
+
* @param {string} dir - The directory path to create.
|
|
66
|
+
* @returns {Promise<void>}
|
|
26
67
|
*/
|
|
27
|
-
async function createOutputDirectory(dir)
|
|
28
|
-
{
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
68
|
+
async function createOutputDirectory (dir) {
|
|
69
|
+
try {
|
|
70
|
+
await fs.access(dir)
|
|
71
|
+
} catch (error) {
|
|
72
|
+
await fs.mkdir(dir)
|
|
73
|
+
}
|
|
34
74
|
}
|
|
35
75
|
|
|
36
76
|
/**
|
|
@@ -44,90 +84,56 @@ async function createOutputDirectory(dir)
|
|
|
44
84
|
* @param {number} quality - Image quality.
|
|
45
85
|
* @param {number} compressionLevel - PNG compression level.
|
|
46
86
|
*/
|
|
47
|
-
async function processImage(inputPath, outputPath, file, maxWidth, maxHeight, quality, compressionLevel)
|
|
48
|
-
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
87
|
+
async function processImage (inputPath, outputPath, file, maxWidth, maxHeight, quality, compressionLevel) {
|
|
88
|
+
try {
|
|
89
|
+
const image = sharp(inputPath)
|
|
90
|
+
const metadata = await image.metadata()
|
|
91
|
+
const originalWidth = metadata.width
|
|
92
|
+
const originalHeight = metadata.height
|
|
93
|
+
const imageProcessor = image.resize(
|
|
94
|
+
originalWidth > maxWidth ? maxWidth : null,
|
|
95
|
+
originalHeight > maxHeight ? maxHeight : null
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
if (file.match(/\.(jpg|jpeg)$/i)) {
|
|
99
|
+
await imageProcessor.jpeg({ quality }).toFile(outputPath)
|
|
100
|
+
console.log(`Optimized JPEG image: ${file}`)
|
|
101
|
+
} else if (file.match(/\.(png)$/i)) {
|
|
102
|
+
await imageProcessor.png({ quality, compressionLevel }).toFile(outputPath)
|
|
103
|
+
console.log(`Optimized PNG image: ${file}`)
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// console.error(`Optimization error ${file}: `, error)
|
|
107
|
+
}
|
|
69
108
|
}
|
|
70
109
|
|
|
71
|
-
|
|
72
110
|
/**
|
|
73
|
-
* Load settings from a configuration file.
|
|
111
|
+
* Load settings from a configuration file. If the specified configuration file doesn't exist,
|
|
112
|
+
* it will use the default 'config.json' from the package.
|
|
74
113
|
*
|
|
75
114
|
* @param {string} configFile - Path to the configuration file.
|
|
76
115
|
*/
|
|
77
|
-
async function loadConfig(configFile)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
console.error("Configuration file is not a valid JSON object.");
|
|
99
|
-
process.exit(1);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if ( !config.inputDir || !config.outputDir ) {
|
|
104
|
-
console.error("Configuration error: 'inputDir' and 'outputDir' are required fields.");
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return config;
|
|
109
|
-
} catch ( error ) {
|
|
110
|
-
console.error("Error loading or parsing the configuration file: ", error);
|
|
111
|
-
process.exit(1);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function validateJson(json)
|
|
116
|
-
{
|
|
117
|
-
const isJson = (str) =>
|
|
118
|
-
{
|
|
119
|
-
try {
|
|
120
|
-
JSON.parse(str);
|
|
121
|
-
} catch ( e ) {
|
|
122
|
-
//Error
|
|
123
|
-
//JSON is not okay
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return isJson(json);
|
|
116
|
+
async function loadConfig (configFile) {
|
|
117
|
+
let config
|
|
118
|
+
try {
|
|
119
|
+
const configData = await fs.readFile(configFile, 'utf8')
|
|
120
|
+
config = JSON.parse(configData)
|
|
121
|
+
} catch (error) {
|
|
122
|
+
console.error('Using the default configuration from the package. ✅')
|
|
123
|
+
config = require('./config.json') // Load default configuration from the package
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!validateConfigObject(config)) {
|
|
127
|
+
console.error('Invalid configuration object.')
|
|
128
|
+
process.exit(1)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!config.inputDir || !config.outputDir) {
|
|
132
|
+
console.error("Configuration error: 'inputDir' and 'outputDir' are required fields.")
|
|
133
|
+
process.exit(1)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return config
|
|
131
137
|
}
|
|
132
138
|
|
|
133
139
|
/**
|
|
@@ -135,42 +141,55 @@ function validateJson(json)
|
|
|
135
141
|
*
|
|
136
142
|
* @param {object} config - The loaded configuration object.
|
|
137
143
|
*/
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
function validateConfigObject (config) {
|
|
145
|
+
return (
|
|
146
|
+
typeof config === 'object' &&
|
|
147
|
+
!Array.isArray(config) &&
|
|
148
|
+
config.inputDir &&
|
|
149
|
+
config.outputDir
|
|
150
|
+
)
|
|
145
151
|
}
|
|
146
152
|
|
|
147
153
|
/**
|
|
148
|
-
*
|
|
154
|
+
* Returns the input directory path based on the provided arguments and configuration.
|
|
155
|
+
* If the provided arguments or configuration are '.', returns the current working directory.
|
|
156
|
+
* @param {string} argvInput - The input directory path provided as an argument.
|
|
157
|
+
* @param {string} configInputDir - The input directory path provided in the configuration.
|
|
158
|
+
* @returns {string} - The input directory path.
|
|
149
159
|
*/
|
|
150
|
-
|
|
151
|
-
{
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
function getInputDirectory (argvInput, configInputDir) {
|
|
161
|
+
if (argvInput === '.' || configInputDir === '.') {
|
|
162
|
+
return process.cwd()
|
|
163
|
+
}
|
|
154
164
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const quality = config.quality;
|
|
158
|
-
const maxWidth = config.maxWidth;
|
|
159
|
-
const maxHeight = config.maxHeight;
|
|
160
|
-
const compressionLevel = config.compressionLevel;
|
|
161
|
-
|
|
162
|
-
await createOutputDirectory(outputDir);
|
|
165
|
+
return argvInput || configInputDir
|
|
166
|
+
}
|
|
163
167
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
/**
|
|
169
|
+
* The main function that processes all image files in the input directory.
|
|
170
|
+
*/
|
|
171
|
+
async function main () {
|
|
172
|
+
const argv = parseArguments()
|
|
173
|
+
const config = await loadConfig(argv.config)
|
|
174
|
+
const inputDir = getInputDirectory(argv.input, config.inputDir)
|
|
175
|
+
const outputDir = getInputDirectory(argv.output, config.outputDir)
|
|
176
|
+
const quality = argv.quality ? argv.quality : config.quality
|
|
177
|
+
const maxWidth = argv.maxWidth ? argv.maxWidth : config.maxWidth
|
|
178
|
+
const maxHeight = argv.maxHeight ? argv.maxHeight : config.maxHeight
|
|
179
|
+
const compressionLevel = argv.compressionLevel ? argv.compressionLevel : config.compressionLevel
|
|
180
|
+
|
|
181
|
+
await createOutputDirectory(outputDir)
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const files = await fs.readdir(inputDir)
|
|
185
|
+
for (const file of files) {
|
|
186
|
+
const inputPath = `${inputDir}/${file}`
|
|
187
|
+
const outputPath = `${outputDir}/${file}`
|
|
188
|
+
await processImage(inputPath, outputPath, file, maxWidth, maxHeight, quality, compressionLevel)
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error('Error reading input directory: ', error)
|
|
192
|
+
}
|
|
174
193
|
}
|
|
175
194
|
|
|
176
|
-
main()
|
|
195
|
+
main()
|