Last active
August 18, 2023 17:19
-
-
Save bryanberger/4c2f34c693fc295b8a4f1e1223de03c0 to your computer and use it in GitHub Desktop.
Aggressively Optimize Lottie JSON files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { readdir, readFileSync, writeFileSync, stat } from 'fs'; | |
import { join, basename } from 'path'; | |
function processJSONFiles(inputFolderPath, outputFolderPath) { | |
readdir(inputFolderPath, (err, files) => { | |
if (err) { | |
console.error('Error reading folder:', err); | |
return; | |
} | |
files.forEach(file => { | |
if (file.endsWith('.json')) { | |
const inputFilePath = join(inputFolderPath, file); | |
const outputFilePath = join(outputFolderPath, file); | |
optimizeJSONFile(inputFilePath, outputFilePath); | |
} | |
}); | |
}); | |
} | |
function optimizeJSONFile(inputFilePath, outputFilePath) { | |
try { | |
// Read the JSON file | |
const content = readFileSync(inputFilePath, 'utf-8'); | |
const jsonData = JSON.parse(content); | |
// Counters & References | |
let idCounter = 1; | |
const idReferences = {}; | |
// Generates a trimmed ID based on an incremental counter | |
function generateTrimmedId() { | |
const letter = String.fromCharCode(97 + (idCounter - 1) % 26); // 'a' to 'z' | |
const trimmedId = `${letter}${Math.floor((idCounter - 1) / 26)}`; | |
idCounter++; | |
return trimmedId; | |
} | |
// Processes numerical values, rounding them to a lesser precision | |
function processNumbers(value) { | |
if (typeof value === 'number' && !isNaN(value)) { | |
return parseFloat(value.toFixed(1)); | |
} | |
return value; | |
} | |
// Processes a sub-item within an array, handling both numerical values and nested objects | |
function processArraySubItem(subItem) { | |
if (typeof subItem === 'number' && !isNaN(subItem)) { | |
return processNumbers(subItem); | |
} else if (typeof subItem === 'object' && subItem !== null) { | |
return optimizeValues(subItem); | |
} else { | |
return subItem; | |
} | |
} | |
// Processes an array, iterating through its items and handling nested arrays and objects | |
function processArray(item) { | |
if (Array.isArray(item)) { | |
return item.map(processArraySubItem); | |
} else if (typeof item === 'object' && item !== null) { | |
return optimizeValues(item); | |
} else { | |
return processNumbers(item); | |
} | |
} | |
// Process string values | |
function processValue(value) { | |
if (typeof value === 'string' && idReferences[value]) { | |
return idReferences[value]; | |
} else if (typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value)) { | |
if (value.length === 7) { | |
const shorthand = `#${value[1]}${value[2]}${value[3]}`; | |
return shorthand.toLowerCase(); | |
} | |
} | |
return value; | |
} | |
// Function to simplify and shorten variable and property names | |
function optimizeValues(obj) { | |
if (Array.isArray(obj)) { | |
return obj.map(processArray); | |
} else if (typeof obj === 'object' && obj !== null) { | |
const optimizedObj = {}; | |
if (obj.meta) { | |
delete obj.meta; | |
} | |
for (const key in obj) { | |
if (typeof obj[key] === 'object' && obj[key] !== null) { | |
optimizedObj[key] = optimizeValues(obj[key]); | |
continue; | |
} | |
if (key === "ddd" || key === "ix" || key === "p") { | |
continue; | |
} | |
if (key === "v") { | |
optimizedObj[key] = obj[key]; | |
continue; | |
} | |
if (key === "id") { | |
const generatedId = generateTrimmedId(); | |
optimizedObj[key] = generatedId; | |
idReferences[obj[key]] = generatedId; | |
continue; | |
} | |
if (key === "nm" || key === "mn") { | |
optimizedObj[key] = generateTrimmedId(); | |
continue; | |
} | |
if (typeof obj[key] === 'number' && !isNaN(obj[key])) { | |
optimizedObj[key] = processNumbers(obj[key]) | |
continue; | |
} | |
if (typeof obj[key] === 'boolean') { | |
optimizedObj[key] = obj[key] ? 1 : 0; | |
continue; | |
} | |
optimizedObj[key] = processValue(obj[key]); | |
} | |
return optimizedObj; | |
} else { | |
return obj; | |
} | |
} | |
// Apply optimizations | |
const optimizedData = optimizeValues(jsonData); | |
// Convert optimized data to a minified JSON string | |
const optimizedJSON = JSON.stringify(optimizedData, null, 0); | |
// Write the optimized JSON back to the file, overwriting the original content | |
writeFileSync(outputFilePath, optimizedJSON, 'utf-8'); | |
// Get file stats for the input and output files | |
stat(inputFilePath, (err, inputStats) => { | |
if (err) { | |
console.error('Error:', err); | |
return; | |
} | |
stat(outputFilePath, (err, outputStats) => { | |
if (err) { | |
console.error('Error:', err); | |
return; | |
} | |
// Get the filename from the inputFilePath | |
const filename = basename(outputFilePath); | |
// Calculate file size differences | |
const inputSizeKB = inputStats.size / 1024; | |
const outputSizeKB = outputStats.size / 1024; | |
const differenceKB = inputSizeKB - outputSizeKB; | |
const percentageDifference = (differenceKB / inputSizeKB) * 100; | |
console.log(`Optimization successful: ${filename}`); | |
console.log(`Input file size: ${inputSizeKB.toFixed(2)} KB`); | |
console.log(`Output file size: ${outputSizeKB.toFixed(2)} KB`); | |
console.log(`Difference: ${differenceKB.toFixed(2)} KB (${percentageDifference.toFixed(2)}%)`); | |
}); | |
}); | |
} catch (error) { | |
console.error('Error optimizing JSON:', error); | |
} | |
} | |
// Call the function with the path to your input and output JSON folders | |
const inputFolderPath = 'input'; // Path to the input JSON folder | |
const outputFolderPath = 'output'; // Path to the output JSON folder | |
processJSONFiles(inputFolderPath, outputFolderPath); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment