UNPKG

gzipper

Version:

CLI for compressing files.

186 lines 15.5 kB
import { lstat, readdir } from 'node:fs/promises'; import path from 'node:path'; import { Worker } from 'node:worker_threads'; import { createFolders, readableHrtime, getCPUs, chunkArray, filterObject, } from './helpers.js'; import { Logger } from './logger/Logger.js'; import { NO_FILES_MESSAGE, NO_PATH_MESSAGE, DEFAULT_OUTPUT_FORMAT_MESSAGE, INCREMENTAL_ENABLE_MESSAGE, WORKER_STARTED, } from './constants.js'; import { Incremental } from './Incremental.js'; import { Config } from './Config.js'; import { LogLevel } from './logger/LogLevel.enum.js'; import { CompressService } from './Compress.service.js'; /** * Compressing files. */ export class Compress { incremental; config; outputPath; target; service; logger; options; compressionInstances; /** * Creates an instance of Compress. */ constructor(target, outputPath, options = {}) { this.logger = new Logger(); this.logger.initialize({ verbose: options.verbose, color: options.color, }); this.config = new Config(); if (!target) { const message = NO_PATH_MESSAGE; this.logger.log(message, LogLevel.ERROR); throw new Error(message); } if (outputPath) { this.outputPath = path.resolve(process.cwd(), outputPath); } if (options.incremental) { this.incremental = new Incremental(this.config); } this.target = path.resolve(process.cwd(), target); this.options = options; this.service = new CompressService(this.options); this.compressionInstances = this.service.getCompressionInstances(); } /** * Start compressing files. */ async run() { let files; let hrtime; try { if (this.outputPath) { await createFolders(this.outputPath); } if (this.options.incremental) { await this.config.readConfig(); this.logger.log(INCREMENTAL_ENABLE_MESSAGE, LogLevel.INFO); await this.incremental.initCacheFolder(); await this.incremental.readConfig(); } this.compressionLog(); const hrtimeStart = process.hrtime(); const workersResponse = await this.createWorkers(); files = workersResponse.files; hrtime = process.hrtime(hrtimeStart); if (this.options.incremental) { this.incremental.filePaths = workersResponse.filePaths; await this.incremental.updateConfig(); await this.config.writeConfig(); } } catch (error) { this.logger.log(error, LogLevel.ERROR); throw new Error(error.message); } const filesCount = files.length; if (filesCount) { this.logger.log(`${filesCount} ${filesCount > 1 ? 'files have' : 'file has'} been compressed. (${readableHrtime(hrtime)})`, LogLevel.SUCCESS); } else { this.logger.log(NO_FILES_MESSAGE, LogLevel.WARNING); } return files; } /** * Returns available files to compress. */ async getFilesToCompress(target = this.target) { const compressedFiles = []; const isFileTarget = (await lstat(target)).isFile(); let filesList; if (isFileTarget) { const targetParsed = path.parse(target); target = targetParsed.dir; filesList = [targetParsed.base]; } else { filesList = await readdir(target); } for (const file of filesList) { const filePath = path.resolve(target, file); const fileStat = await lstat(filePath); if (fileStat.isDirectory()) { compressedFiles.push(...(await this.getFilesToCompress(filePath))); } else if (fileStat.isFile() && this.service.isValidFileExtensions(path.extname(filePath).slice(1))) { if (fileStat.size < (this.options.threshold ?? 0)) { continue; } compressedFiles.push(filePath); } } return compressedFiles; } /** * Create workers for parallel compression. */ async createWorkers() { const files = await this.getFilesToCompress(); const cpus = process.env.NODE_ENV !== 'test' ? this.options.workers || getCPUs() - 1 : 1; const size = Math.ceil(files.length / cpus); const chunks = chunkArray(files, size); const workers = chunks.map((chunk) => this.runCompressWorker(chunk)); const results = await Promise.all(workers); return results.reduce((accumulator, value) => { return { files: [...accumulator.files, ...value.files], filePaths: { ...accumulator.filePaths, ...value.filePaths }, }; }, { files: [], filePaths: {}, }); } /** * Run compress worker */ async runCompressWorker(chunk) { return new Promise((resolve, reject) => { const worker = new Worker(path.resolve(import.meta.dirname, process.env.NODE_ENV !== 'test' ? './Compress.worker.js' : '../test/__mocks__/Compress.worker.import.js'), { workerData: { cwd: process.cwd(), chunk, target: this.target, outputPath: this.outputPath, options: this.options, incrementalFilePaths: this.options.incremental && filterObject(this.incremental.filePaths, (key) => chunk.includes(key)), }, execArgv: [...process.execArgv, '--unhandled-rejections=strict'], }); worker.on('online', () => { this.logger.log(`[${worker.threadId}] ${WORKER_STARTED}`, LogLevel.INFO); }); worker.once('message', (result) => { worker.terminate(); resolve(result); }); worker.on('error', (error) => { worker.terminate(); reject(error); }); }); } /** * Show message with compression params. */ compressionLog() { for (const instance of this.compressionInstances) { this.logger.log(`Compression ${instance.readableOptions()}`, LogLevel.INFO); } if (!this.options.outputFileFormat) { this.logger.log(DEFAULT_OUTPUT_FORMAT_MESSAGE, LogLevel.INFO); } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQ29tcHJlc3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvQ29tcHJlc3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUNsRCxPQUFPLElBQUksTUFBTSxXQUFXLENBQUM7QUFDN0IsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBRTdDLE9BQU8sRUFDTCxhQUFhLEVBQ2IsY0FBYyxFQUNkLE9BQU8sRUFDUCxVQUFVLEVBQ1YsWUFBWSxHQUNiLE1BQU0sY0FBYyxDQUFDO0FBQ3RCLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUM1QyxPQUFPLEVBQ0wsZ0JBQWdCLEVBQ2hCLGVBQWUsRUFDZiw2QkFBNkIsRUFDN0IsMEJBQTBCLEVBQzFCLGNBQWMsR0FDZixNQUFNLGdCQUFnQixDQUFDO0FBTXhCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUMvQyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3JDLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUNyRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFHeEQ7O0dBRUc7QUFDSCxNQUFNLE9BQU8sUUFBUTtJQUNGLFdBQVcsQ0FBZTtJQUMxQixNQUFNLENBQVM7SUFDZixVQUFVLENBQXFCO0lBQy9CLE1BQU0sQ0FBUztJQUNmLE9BQU8sQ0FBa0I7SUFDakMsTUFBTSxDQUFTO0lBQ2YsT0FBTyxDQUFrQjtJQUN6QixvQkFBb0IsQ0FBb0I7SUFFakQ7O09BRUc7SUFDSCxZQUNFLE1BQWMsRUFDZCxVQUEwQixFQUMxQixVQUEyQixFQUFFO1FBRTdCLElBQUksQ0FBQyxNQUFNLEdBQUcsSUFBSSxNQUFNLEVBQUUsQ0FBQztRQUMzQixJQUFJLENBQUMs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