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();