Developer
JS Minifier
Minify JavaScript by stripping comments and collapsing whitespace, or beautify with clean indentation. See exactly how much size you save.
Ctrl+Enter to minify
---
Original
---
Output
---
Saved
---
Lines In
Output
Developer Reference
Core Algorithm & Standalone Script
Standalone, zero-dependency JavaScript implementation powering this tool. Free to inspect, copy, and build upon.
function formatBytes(n) {
if (n < 1024) return n + ' B';
return (n / 1024).toFixed(1) + ' KB';
}
function onInput() {
const input = document.getElementById('jsInput').value;
if (input.trim()) {
const ob = new TextEncoder().encode(input).length;
document.getElementById('statOrig').textContent = formatBytes(ob);
document.getElementById('statLines').textContent = input.split('\n').length.toLocaleString();
document.getElementById('statOut').textContent = '---';
document.getElementById('statSaved').textContent = '---';
document.getElementById('statSaved').classList.remove('success');
}
}
function updateStats(original, output) {
const ob = new TextEncoder().encode(original).length;
const outb = new TextEncoder().encode(output).length;
document.getElementById('statOrig').textContent = formatBytes(ob);
document.getElementById('statOut').textContent = formatBytes(outb);
const pct = ob > 0 ? Math.round((1 - outb / ob) * 100) : 0;
const savedEl = document.getElementById('statSaved');
if (pct > 0) {
savedEl.textContent = '-' + pct + '%';
savedEl.classList.add('success');
} else {
savedEl.textContent = (pct === 0 ? '0' : '+' + Math.abs(pct)) + '%';
savedEl.classList.remove('success');
}
document.getElementById('statLines').textContent = original.split('\n').length.toLocaleString();
}
function removeComments(js) {
let result = '';
let i = 0;
const len = js.length;
let inStr = false, strChar = '';
while (i < len) {
if (!inStr && (js[i] === "'" || js[i] === '"' || js[i] === '`')) {
inStr = true; strChar = js[i]; result += js[i++]; continue;
}
if (inStr) {
if (js[i] === '\\') { result += js[i] + (js[i+1]||''); i += 2; continue; }
if (js[i] === strChar) { inStr = false; strChar = ''; }
result += js[i++]; continue;
}
// Single-line comment
if (js[i] === '/' && js[i+1] === '/') {
while (i < len && js[i] !== '\n') i++;
continue;
}
// Multi-line comment
if (js[i] === '/' && js[i+1] === '*') {
i += 2;
while (i < len && !(js[i] === '*' && js[i+1] === '/')) i++;
i += 2;
result += ' '; continue;
}
result += js[i++];
}
return result;
}
function collapseWhitespace(js) {
let result = '';
let i = 0;
const len = js.length;
let inStr = false, strChar = '';
while (i < len) {
if (!inStr && (js[i] === "'" || js[i] === '"' || js[i] === '`')) {
inStr = true; strChar = js[i]; result += js[i++]; continue;
}
if (inStr) {
if (js[i] === '\\') { result += js[i] + (js[i+1]||''); i += 2; continue; }
if (js[i] === strChar) { inStr = false; strChar = ''; }
result += js[i++]; continue;
}
if (/\s/.test(js[i])) {
let j = i;
while (j < len && /\s/.test(js[j])) j++;
if (j < len) {
const prev = result.slice(-1);
const next = js[j];
// Keep space only between identifiers/keywords
if (/[\w$]/.test(prev) && /[\w$]/.test(next)) result += ' ';
}
i = j; continue;
}
result += js[i++];
}
return result.trim();
}
function minifyJS() {
const input = document.getElementById('jsInput').value;
if (!input.trim()) return;
const noComments = removeComments(input);
const minified = collapseWhitespace(noComments);
document.getElementById('jsOutput').textContent = minified;
updateStats(input, minified);
}
function beautifyJS() {
const input = document.getElementById('jsInput').value;
if (!input.trim()) return;
const noComments = removeComments(input).replace(/\s+/g, ' ').trim();
let result = '', indent = 0, inStr = false, strChar = '';
for (let i = 0; i < noComments.length; i++) {
const c = noComments[i];
if (!inStr && (c === "'" || c === '"' || c === '`')) {
inStr = true; strChar = c; result += c; continue;
}
if (inStr) {
if (c === '\\') { result += c + (noComments[i+1]||''); i++; continue; }
if (c === strChar) { inStr = false; strChar = ''; }
result += c; continue;
}
if (c === '{') {
result = result.trimEnd() + ' {\n' + ' '.repeat(++indent);
} else if (c === '}') {
indent = Math.max(0, indent - 1);
result = result.trimEnd() + '\n' + ' '.repeat(indent) + '}';
const next = noComments[i+1];
if (next && next !== ';' && next !== ',' && next !== ')' && next !== ' ') {
result += '\n' + ' '.repeat(indent);
}
} else if (c === ';') {
result += ';\n' + ' '.repeat(indent);
} else if (c === ' ' && (result.endsWith('\n') || result.endsWith(' '))) {
// skip
} else {
result += c;
}
}
result = result.replace(/\n{3,}/g, '\n\n').trim();
document.getElementById('jsOutput').textContent = result;
updateStats(input, result);
}
function copyOutput(btn) {
const text = document.getElementById('jsOutput').textContent;
if (!text) return;
navigator.clipboard.writeText(text).catch(() => {});
btn.textContent = 'copied!'; btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 2000);
}
function clearAll() {
document.getElementById('jsInput').value = '';
document.getElementById('jsOutput').textContent = '';
['statOrig','statOut','statSaved','statLines'].forEach(id => {
document.getElementById(id).textContent = '---';
});
document.getElementById('statSaved').classList.remove('success');
}
document.getElementById('jsInput').addEventListener('keydown', function(e) {
if ((e.ctrlKey||e.metaKey)&&e.key==='Enter') { e.preventDefault(); minifyJS(); }
if (e.key==='Tab') {
e.preventDefault();
const s=this.selectionStart,end=this.selectionEnd;
this.value=this.value.substring(0,s)+' '+this.value.substring(end);
this.selectionStart=this.selectionEnd=s+2;
}
});
// Sample JS
document.getElementById('jsInput').value = `// Utility functions for toolpad.cc
// Handles formatting and display logic
function formatBytes(bytes, decimals = 2) {
/* Convert bytes to human-readable format */
if (bytes === 0) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
}
const copyToClipboard = async (text) => {
try {
await navigator.clipboard.writeText(text);
return true;
} catch (err) {
// Fallback for older browsers
console.error('Copy failed:', err);
return false;
}
};
class ToolManager {
constructor(toolName) {
this.toolName = toolName;
this.history = [];
}
addToHistory(input, output) {
this.history.push({ input, output, timestamp: Date.now() });
if (this.history.length > 50) this.history.shift();
}
}`;
onInput();