@pz4l/tinyimg-unplugin 0.0.0 → 0.1.0-beta.1
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/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -7
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Buffer } from "node:buffer";
|
|
2
2
|
import process from "node:process";
|
|
3
|
-
import { compressImage, formatBytes, loadKeys } from "tinyimg-core";
|
|
3
|
+
import { compressImage, formatBytes, loadKeys } from "@pz4l/tinyimg-core";
|
|
4
4
|
import { createUnplugin } from "unplugin";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import micromatch from "micromatch";
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/filter.ts","../src/stats.ts","../src/logger.ts","../src/options.ts","../src/index.ts"],"sourcesContent":["import path from 'node:path'\n// @ts-expect-error - micromatch doesn't have types\nimport micromatch from 'micromatch'\n\nexport interface FilterOptions {\n include?: string | string[]\n exclude?: string | string[]\n}\n\nexport const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg'])\n\nexport function shouldProcessImage(id: string, options: FilterOptions = {}): boolean {\n // 1. Check extension first (fast path)\n const ext = path.extname(id).toLowerCase()\n if (!IMAGE_EXTENSIONS.has(ext)) {\n return false\n }\n\n // 2. Check include pattern if provided\n if (options.include) {\n const includePatterns = Array.isArray(options.include) ? options.include : [options.include]\n const isInclude = micromatch.isMatch(id, includePatterns)\n if (!isInclude)\n return false\n }\n\n // 3. Check exclude pattern if provided\n if (options.exclude) {\n const excludePatterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude]\n const isExclude = micromatch.isMatch(id, excludePatterns)\n if (isExclude)\n return false\n }\n\n return true\n}\n","import { formatBytes } from 'tinyimg-core'\n\nexport interface FileResult {\n path: string\n originalSize: number\n compressedSize?: number\n cached: boolean\n error?: string\n}\n\nexport class CompressionStats {\n private compressedCount = 0\n private cachedCount = 0\n private originalSize = 0\n private compressedSize = 0\n private fileResults: FileResult[] = []\n\n recordCompressed(path: string, originalSize: number, compressedSize: number): void {\n this.compressedCount++\n this.originalSize += originalSize\n this.compressedSize += compressedSize\n this.fileResults.push({ path, originalSize, compressedSize, cached: false })\n }\n\n recordCached(path: string, size: number): void {\n this.cachedCount++\n this.originalSize += size\n this.compressedSize += size\n this.fileResults.push({ path, originalSize: size, compressedSize: size, cached: true })\n }\n\n recordError(path: string, error: string): void {\n this.fileResults.push({ path, originalSize: 0, cached: false, error })\n }\n\n getSummary() {\n return {\n compressedCount: this.compressedCount,\n cachedCount: this.cachedCount,\n originalSize: this.originalSize,\n compressedSize: this.compressedSize,\n bytesSaved: this.originalSize - this.compressedSize,\n fileCount: this.fileResults.length,\n }\n }\n\n formatSummary(): string[] {\n const summary = this.getSummary()\n const lines: string[] = []\n\n if (summary.compressedCount === 0 && summary.cachedCount > 0) {\n // All cached (D-11)\n lines.push(`[tinyimg] All images cached (0 compressed, ${summary.cachedCount} cached)`)\n }\n else {\n // Normal summary (D-09)\n lines.push(`✓ [tinyimg] Compressed ${summary.fileCount} images (${summary.cachedCount} cached, ${summary.compressedCount} compressed)`)\n lines.push(`✓ [tinyimg] Saved ${formatBytes(summary.bytesSaved)} (original: ${formatBytes(summary.originalSize)} → compressed: ${formatBytes(summary.compressedSize)})`)\n }\n\n return lines\n }\n\n getFileResults(): readonly FileResult[] {\n return this.fileResults\n }\n}\n","import { formatBytes } from 'tinyimg-core'\nimport { CompressionStats } from './stats'\n\nexport interface LoggerOptions {\n verbose?: boolean\n strict?: boolean\n}\n\nexport class TinyimgLogger {\n private stats: CompressionStats\n private verbose: boolean\n private strict: boolean\n\n constructor(options: LoggerOptions = {}) {\n this.verbose = options.verbose ?? false\n this.strict = options.strict ?? false\n this.stats = new CompressionStats()\n }\n\n logCompressing(path: string): void {\n if (this.verbose) {\n console.log(`[tinyimg] Compressing ${path}...`)\n }\n }\n\n logCompressed(path: string, originalSize: number, compressedSize: number): void {\n this.stats.recordCompressed(path, originalSize, compressedSize)\n\n if (this.verbose) {\n const saved = ((1 - compressedSize / originalSize) * 100).toFixed(1)\n console.log(`[tinyimg] ✓ Compressed: ${formatBytes(originalSize)} → ${formatBytes(compressedSize)} (${saved}% saved)`)\n }\n }\n\n logCacheHit(path: string, size: number): void {\n this.stats.recordCached(path, size)\n\n if (this.verbose) {\n console.log(`[tinyimg] Cache hit: ${path}`)\n }\n }\n\n logError(path: string, error: string): void {\n this.stats.recordError(path, error)\n\n if (this.strict) {\n // Strict mode (D-13)\n console.error(`[tinyimg] ✖ Failed to compress ${path}: ${error}. Build failed.`)\n }\n else {\n // Non-strict mode (D-12)\n console.warn(`[tinyimg] ⚠ Failed to compress ${path}: ${error}. Using original file.`)\n }\n }\n\n logSummary(): void {\n const lines = this.stats.formatSummary()\n lines.forEach(line => console.log(line))\n }\n\n getStats(): CompressionStats {\n return this.stats\n }\n\n shouldThrowOnError(): boolean {\n return this.strict\n }\n}\n\nexport function createLogger(options: LoggerOptions = {}): TinyimgLogger {\n return new TinyimgLogger(options)\n}\n","import type { FilterOptions } from './filter'\n\nexport interface TinyimgUnpluginOptions extends FilterOptions {\n mode?: 'random' | 'round-robin' | 'priority'\n cache?: boolean\n parallel?: number\n strict?: boolean\n verbose?: boolean\n}\n\nexport interface NormalizedOptions {\n mode: 'random' | 'round-robin' | 'priority'\n cache: boolean\n parallel: number\n strict: boolean\n verbose: boolean\n include?: string[]\n exclude?: string[]\n}\n\nconst VALID_MODES = new Set(['random', 'round-robin', 'priority'])\n\nexport function normalizeOptions(options: TinyimgUnpluginOptions = {}): NormalizedOptions {\n // Validate mode\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(`Invalid mode: \"${options.mode}\". Must be one of: random, round-robin, priority`)\n }\n\n // Validate parallel\n if (options.parallel !== undefined && options.parallel <= 0) {\n throw new RangeError(`Invalid parallel: ${options.parallel}. Must be a positive number`)\n }\n\n return {\n mode: options.mode ?? 'random',\n cache: options.cache ?? true,\n parallel: options.parallel ?? 8,\n strict: options.strict ?? false,\n verbose: options.verbose ?? false,\n include: options.include ? (Array.isArray(options.include) ? options.include : [options.include]) : undefined,\n exclude: options.exclude ? (Array.isArray(options.exclude) ? options.exclude : [options.exclude]) : undefined,\n }\n}\n","import type { TinyimgUnpluginOptions } from './options'\nimport { Buffer } from 'node:buffer'\nimport process from 'node:process'\nimport { compressImage, loadKeys } from 'tinyimg-core'\nimport { createUnplugin } from 'unplugin'\nimport { shouldProcessImage } from './filter'\nimport { createLogger } from './logger'\nimport { normalizeOptions } from './options'\n\n// Regex for matching image file extensions\nconst IMAGE_REGEX = /\\.(png|jpg|jpeg|gif|webp|svg)$/i\n\nexport default createUnplugin((options: TinyimgUnpluginOptions = {}): any => {\n // Normalize options\n const normalized = normalizeOptions(options)\n\n // Validate TINYPNG_KEYS (D-15, D-16)\n const keys = loadKeys()\n if (keys.length === 0) {\n throw new Error('TINYPNG_KEYS environment variable is required')\n }\n\n // Create logger\n const logger = createLogger({\n verbose: normalized.verbose,\n strict: normalized.strict,\n })\n\n return {\n name: 'tinyimg-unplugin',\n enforce: 'post', // Run after other transformations (D-02)\n\n async transform(code: any, id: any) {\n // Filter non-image files\n const shouldProcess = shouldProcessImage(id, normalized)\n if (!shouldProcess) {\n return null\n }\n\n // Check production build (D-01)\n const isProd = isProductionBuild(this)\n if (!isProd) {\n return null\n }\n\n // Convert to Buffer\n const buffer = Buffer.from(code)\n\n // Get relative path for logging\n const relativePath = getRelativePath(id)\n\n // Log compression start\n logger.logCompressing(relativePath)\n\n try {\n // Compress image\n const compressed = await compressImage(buffer, {\n projectCacheOnly: true, // Only project cache (D-17)\n cache: normalized.cache,\n mode: normalized.mode as any,\n })\n\n // Log success\n logger.logCompressed(relativePath, buffer.length, compressed.length)\n\n return { code: compressed, map: null }\n }\n catch (error: any) {\n // Log error\n logger.logError(relativePath, error.message)\n\n // Check strict mode\n if (logger.shouldThrowOnError()) {\n throw error\n }\n\n // Non-strict: return null to use original file\n return null\n }\n },\n\n buildEnd() {\n logger.logSummary()\n },\n }\n})\n\n// Helper functions\nfunction isProductionBuild(context: any): boolean {\n // Vite: check config.isBuild (D-01)\n if (context?.config?.isBuild !== undefined) {\n return context.config.isBuild\n }\n\n // Webpack: check mode (D-01)\n if (context?.mode !== undefined) {\n return context.mode === 'production'\n }\n\n // Fallback: check NODE_ENV\n return process.env.NODE_ENV === 'production'\n}\n\nfunction getRelativePath(id: string): string {\n // Convert absolute path to relative for logging\n const root = process.cwd()\n return id.replace(root, '').replace(IMAGE_REGEX, '')\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/filter.ts","../src/stats.ts","../src/logger.ts","../src/options.ts","../src/index.ts"],"sourcesContent":["import path from 'node:path'\n// @ts-expect-error - micromatch doesn't have types\nimport micromatch from 'micromatch'\n\nexport interface FilterOptions {\n include?: string | string[]\n exclude?: string | string[]\n}\n\nexport const IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg'])\n\nexport function shouldProcessImage(id: string, options: FilterOptions = {}): boolean {\n // 1. Check extension first (fast path)\n const ext = path.extname(id).toLowerCase()\n if (!IMAGE_EXTENSIONS.has(ext)) {\n return false\n }\n\n // 2. Check include pattern if provided\n if (options.include) {\n const includePatterns = Array.isArray(options.include) ? options.include : [options.include]\n const isInclude = micromatch.isMatch(id, includePatterns)\n if (!isInclude)\n return false\n }\n\n // 3. Check exclude pattern if provided\n if (options.exclude) {\n const excludePatterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude]\n const isExclude = micromatch.isMatch(id, excludePatterns)\n if (isExclude)\n return false\n }\n\n return true\n}\n","import { formatBytes } from '@pz4l/tinyimg-core'\n\nexport interface FileResult {\n path: string\n originalSize: number\n compressedSize?: number\n cached: boolean\n error?: string\n}\n\nexport class CompressionStats {\n private compressedCount = 0\n private cachedCount = 0\n private originalSize = 0\n private compressedSize = 0\n private fileResults: FileResult[] = []\n\n recordCompressed(path: string, originalSize: number, compressedSize: number): void {\n this.compressedCount++\n this.originalSize += originalSize\n this.compressedSize += compressedSize\n this.fileResults.push({ path, originalSize, compressedSize, cached: false })\n }\n\n recordCached(path: string, size: number): void {\n this.cachedCount++\n this.originalSize += size\n this.compressedSize += size\n this.fileResults.push({ path, originalSize: size, compressedSize: size, cached: true })\n }\n\n recordError(path: string, error: string): void {\n this.fileResults.push({ path, originalSize: 0, cached: false, error })\n }\n\n getSummary() {\n return {\n compressedCount: this.compressedCount,\n cachedCount: this.cachedCount,\n originalSize: this.originalSize,\n compressedSize: this.compressedSize,\n bytesSaved: this.originalSize - this.compressedSize,\n fileCount: this.fileResults.length,\n }\n }\n\n formatSummary(): string[] {\n const summary = this.getSummary()\n const lines: string[] = []\n\n if (summary.compressedCount === 0 && summary.cachedCount > 0) {\n // All cached (D-11)\n lines.push(`[tinyimg] All images cached (0 compressed, ${summary.cachedCount} cached)`)\n }\n else {\n // Normal summary (D-09)\n lines.push(`✓ [tinyimg] Compressed ${summary.fileCount} images (${summary.cachedCount} cached, ${summary.compressedCount} compressed)`)\n lines.push(`✓ [tinyimg] Saved ${formatBytes(summary.bytesSaved)} (original: ${formatBytes(summary.originalSize)} → compressed: ${formatBytes(summary.compressedSize)})`)\n }\n\n return lines\n }\n\n getFileResults(): readonly FileResult[] {\n return this.fileResults\n }\n}\n","import { formatBytes } from '@pz4l/tinyimg-core'\nimport { CompressionStats } from './stats'\n\nexport interface LoggerOptions {\n verbose?: boolean\n strict?: boolean\n}\n\nexport class TinyimgLogger {\n private stats: CompressionStats\n private verbose: boolean\n private strict: boolean\n\n constructor(options: LoggerOptions = {}) {\n this.verbose = options.verbose ?? false\n this.strict = options.strict ?? false\n this.stats = new CompressionStats()\n }\n\n logCompressing(path: string): void {\n if (this.verbose) {\n console.log(`[tinyimg] Compressing ${path}...`)\n }\n }\n\n logCompressed(path: string, originalSize: number, compressedSize: number): void {\n this.stats.recordCompressed(path, originalSize, compressedSize)\n\n if (this.verbose) {\n const saved = ((1 - compressedSize / originalSize) * 100).toFixed(1)\n console.log(`[tinyimg] ✓ Compressed: ${formatBytes(originalSize)} → ${formatBytes(compressedSize)} (${saved}% saved)`)\n }\n }\n\n logCacheHit(path: string, size: number): void {\n this.stats.recordCached(path, size)\n\n if (this.verbose) {\n console.log(`[tinyimg] Cache hit: ${path}`)\n }\n }\n\n logError(path: string, error: string): void {\n this.stats.recordError(path, error)\n\n if (this.strict) {\n // Strict mode (D-13)\n console.error(`[tinyimg] ✖ Failed to compress ${path}: ${error}. Build failed.`)\n }\n else {\n // Non-strict mode (D-12)\n console.warn(`[tinyimg] ⚠ Failed to compress ${path}: ${error}. Using original file.`)\n }\n }\n\n logSummary(): void {\n const lines = this.stats.formatSummary()\n lines.forEach(line => console.log(line))\n }\n\n getStats(): CompressionStats {\n return this.stats\n }\n\n shouldThrowOnError(): boolean {\n return this.strict\n }\n}\n\nexport function createLogger(options: LoggerOptions = {}): TinyimgLogger {\n return new TinyimgLogger(options)\n}\n","import type { FilterOptions } from './filter'\n\nexport interface TinyimgUnpluginOptions extends FilterOptions {\n mode?: 'random' | 'round-robin' | 'priority'\n cache?: boolean\n parallel?: number\n strict?: boolean\n verbose?: boolean\n}\n\nexport interface NormalizedOptions {\n mode: 'random' | 'round-robin' | 'priority'\n cache: boolean\n parallel: number\n strict: boolean\n verbose: boolean\n include?: string[]\n exclude?: string[]\n}\n\nconst VALID_MODES = new Set(['random', 'round-robin', 'priority'])\n\nexport function normalizeOptions(options: TinyimgUnpluginOptions = {}): NormalizedOptions {\n // Validate mode\n if (options.mode !== undefined && !VALID_MODES.has(options.mode)) {\n throw new TypeError(`Invalid mode: \"${options.mode}\". Must be one of: random, round-robin, priority`)\n }\n\n // Validate parallel\n if (options.parallel !== undefined && options.parallel <= 0) {\n throw new RangeError(`Invalid parallel: ${options.parallel}. Must be a positive number`)\n }\n\n return {\n mode: options.mode ?? 'random',\n cache: options.cache ?? true,\n parallel: options.parallel ?? 8,\n strict: options.strict ?? false,\n verbose: options.verbose ?? false,\n include: options.include ? (Array.isArray(options.include) ? options.include : [options.include]) : undefined,\n exclude: options.exclude ? (Array.isArray(options.exclude) ? options.exclude : [options.exclude]) : undefined,\n }\n}\n","import type { TinyimgUnpluginOptions } from './options'\nimport { Buffer } from 'node:buffer'\nimport process from 'node:process'\nimport { compressImage, loadKeys } from '@pz4l/tinyimg-core'\nimport { createUnplugin } from 'unplugin'\nimport { shouldProcessImage } from './filter'\nimport { createLogger } from './logger'\nimport { normalizeOptions } from './options'\n\n// Regex for matching image file extensions\nconst IMAGE_REGEX = /\\.(png|jpg|jpeg|gif|webp|svg)$/i\n\nexport default createUnplugin((options: TinyimgUnpluginOptions = {}): any => {\n // Normalize options\n const normalized = normalizeOptions(options)\n\n // Validate TINYPNG_KEYS (D-15, D-16)\n const keys = loadKeys()\n if (keys.length === 0) {\n throw new Error('TINYPNG_KEYS environment variable is required')\n }\n\n // Create logger\n const logger = createLogger({\n verbose: normalized.verbose,\n strict: normalized.strict,\n })\n\n return {\n name: 'tinyimg-unplugin',\n enforce: 'post', // Run after other transformations (D-02)\n\n async transform(code: any, id: any) {\n // Filter non-image files\n const shouldProcess = shouldProcessImage(id, normalized)\n if (!shouldProcess) {\n return null\n }\n\n // Check production build (D-01)\n const isProd = isProductionBuild(this)\n if (!isProd) {\n return null\n }\n\n // Convert to Buffer\n const buffer = Buffer.from(code)\n\n // Get relative path for logging\n const relativePath = getRelativePath(id)\n\n // Log compression start\n logger.logCompressing(relativePath)\n\n try {\n // Compress image\n const compressed = await compressImage(buffer, {\n projectCacheOnly: true, // Only project cache (D-17)\n cache: normalized.cache,\n mode: normalized.mode as any,\n })\n\n // Log success\n logger.logCompressed(relativePath, buffer.length, compressed.length)\n\n return { code: compressed, map: null }\n }\n catch (error: any) {\n // Log error\n logger.logError(relativePath, error.message)\n\n // Check strict mode\n if (logger.shouldThrowOnError()) {\n throw error\n }\n\n // Non-strict: return null to use original file\n return null\n }\n },\n\n buildEnd() {\n logger.logSummary()\n },\n }\n})\n\n// Helper functions\nfunction isProductionBuild(context: any): boolean {\n // Vite: check config.isBuild (D-01)\n if (context?.config?.isBuild !== undefined) {\n return context.config.isBuild\n }\n\n // Webpack: check mode (D-01)\n if (context?.mode !== undefined) {\n return context.mode === 'production'\n }\n\n // Fallback: check NODE_ENV\n return process.env.NODE_ENV === 'production'\n}\n\nfunction getRelativePath(id: string): string {\n // Convert absolute path to relative for logging\n const root = process.cwd()\n return id.replace(root, '').replace(IMAGE_REGEX, '')\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pz4l/tinyimg-unplugin",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.1.0-beta.1",
|
|
5
5
|
"description": "unplugin for automatic image compression during build (Vite, Webpack, Rolldown)",
|
|
6
6
|
"author": "pzehrel",
|
|
7
7
|
"license": "MIT",
|
|
@@ -44,9 +44,8 @@
|
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=18"
|
|
46
46
|
},
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"typecheck": "tsc --noEmit"
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
50
49
|
},
|
|
51
50
|
"peerDependencies": {
|
|
52
51
|
"vite": ">=2.0.0",
|
|
@@ -62,12 +61,16 @@
|
|
|
62
61
|
},
|
|
63
62
|
"dependencies": {
|
|
64
63
|
"micromatch": "^4.0.8",
|
|
65
|
-
"
|
|
66
|
-
"
|
|
64
|
+
"unplugin": "^1.0.0",
|
|
65
|
+
"@pz4l/tinyimg-core": "0.1.0-beta.1"
|
|
67
66
|
},
|
|
68
67
|
"devDependencies": {
|
|
69
68
|
"fast-glob": "^3.3.3",
|
|
70
69
|
"webpack": "^5.105.4",
|
|
71
70
|
"webpack-cli": "^7.0.2"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsdown",
|
|
74
|
+
"typecheck": "tsc --noEmit"
|
|
72
75
|
}
|
|
73
|
-
}
|
|
76
|
+
}
|