main
  .container
    h1 Tiny JavaScript Minifier
    p.lead Using Google's Closure Compiler service
    
    hr.my-4
    
    .row.inputs
      .col-sm-6
        h4 Plop your JavaScript Here !
        .input-group
          textarea#input.form-control
            | // ADD YOUR CODE HERE
            | function hello(name) {
            |   alert('Hello, ' + name);
            | }
            | hello('New user');
      .col-sm-6
        h4 Get your compressed code here !
        .input-group
          textarea#output.form-control
        small#stats Click the compile button to see stats !
    
    #options
      h4 You might want to specify some options... 
        a(href="https://developers.google.com/closure/compiler/docs/api-ref" target="_blank") (documentation)
        
    button#go.btn.btn-outline-primary.btn-lg.d-block Compile !
    
    hr.my-4
    
    p
      small
        | Note: not all options are included here, this is supposed to be a small web app. 
        a(href="https://closure-compiler.appspot.com/home" target="_blank") Here
        |  is a more complex one.
    
    #log
View Compiled
html,
body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}

main {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  
  width: 100%;
  min-height: 100vh;
  
  background: #eee;
  
  .container {
    margin: auto;
    padding: 1em .8em;
    box-sizing: border-box;
    
    [class^='col-']{
      margin-bottom: 1em;
    }
    
    #input,
    #output {
      min-height: 50px;
    }
  }
  
}
/**
 * TODO
 * add options to UI
 * link the api reference
 * auto hide advanced options (show/hide)
 * add possibility of external file
 * add compression factor
 * add log (switch to json)
 */


/**
 * XMLHTTP requests to the API
 */

function getURL(domain, params) {
  if (domain.indexOf('?') === -1) domain += '?'
  
  let keys = Object.keys(params)
  for (key of keys) {
    if (Array.isArray(params[key])) {
      for (let i=0; i<params[key].length; i++) {
        addParameter(params[key][i])
      }
    } else {
      addParameter(params[key])
    }
  }
  domain = domain.slice(0, -1)
  
  return domain
      
  function addParameter(param) {
    domain += `${key}=${encodeURIComponent(param)}&`
  }
}


function getData(url, callback) {
  let xhttp = new XMLHttpRequest()
  
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      callback.call(window, xhttp.responseText)
    }
  }

  xhttp.open('POST', url)
  xhttp.withCredentials = false
  xhttp.send()
}


/**
 * UI Stuffs
 */

let DOM = {
  go: document.getElementById('go'),
  input: document.getElementById('input'),
  output: document.getElementById('output'),
  stats: document.getElementById('stats'),
  log: document.getElementById('log'),
  optionsWrapper: document.getElementById('options'),
  options: {} // Different inputs generated in addOptionsToWrapper
}

/**
 * Options initializer
 */

// Used for initalizing the options
const options = {
  // js_code: 'function hello(name){alert("Hello, "+name)}hello("New user");',
  // code_url: TEXTBOX
  compilation_level: {
    names: ['Whitespace', 'Simple', 'Advanced'],
    values: ['WHITESPACE_ONLY', 'SIMPLE_OPTIMIZATIONS', 'ADVANCED_OPTIMIZATIONS'],
    default: 'SIMPLE_OPTIMIZATIONS',
    label: 'Compilation'
  },
  // output_format: {
  //   names: ['XML', 'JSON', 'Text'],
  //   values: ['xml', 'json', 'text'],
  //   default: 'text'
  // },
  // output_info: {
  //   names: ['Compiled code', 'Warnings', 'Errors', 'Statistics'],
  //   values: ['compiled_code', 'warnings', 'errors', 'statistics'],
  //   default: 'compiled_code',
  //   label: 'Output info'
  // },
  // js_externs: TEXTBOX
  // externs_url: TEXTBOX
  // exclude_default_externs: {
  //   names: ['Yes', 'No'],
  //   values: ['true', 'false'],
  //   default: 'false',
  //   label: 'Excluse default externs'
  // },
  // output_file_name: TEXTBOX
  formatting: {
    names: ['Pretty print', 'Print input delimiter'],
    values: ['pretty_print', 'print_input_delimiter'],
    default: 'print_input_delimiter',
    label: 'Formatting'
  },
  // use_closure_library: {
  //   names: ['Yes', 'No'],
  //   values: ['true', 'false'],
  //   default: 'false',
  //   label: 'Use closure library'
  // },
  // warning_level: {
  //   names: ['Quiet', 'Default', 'Verbose'],
  //   values: ['QUIET', 'DEFAULT', 'VERBOSE'],
  //   default: 'DEFAULT',
  //   label: 'Warning level'
  // },
  language: {
    names: ['ECMAScript 3', 'ECMAScript 5', 'ECMAScript 5 strict', 'ECMAScript 6', 'ECMAScript 6 strict'],
    values: ['ECMASCRIPT3', 'ECMASCRIPT5', 'ECMASCRIPT5_STRICT', 'ECMASCRIPT6', 'ECMASCRIPT6_STRICT'],
    default: 'ECMASCRIPT6',
    label: 'Language in'
  },
  language_out: {
    names: ['ECMAScript 3', 'ECMAScript 5', 'ECMAScript 5 strict', 'ECMAScript 6', 'ECMAScript 6 strict'],
    values: ['ECMASCRIPT3', 'ECMASCRIPT5', 'ECMASCRIPT5_STRICT', 'ECMASCRIPT6', 'ECMASCRIPT6_STRICT'],
    default: 'ECMASCRIPT5',
    label: 'Language out'
  }
}


function addOptionsToWrapper(options, wrapper, domOptions) {
  let keys = Object.keys(options)
  
  let row
  for (let i=0; i<keys.length; i++) {
    if (i%2 === 0) {
      row = document.createElement('div')
      row.classList.add('row')
      wrapper.appendChild(row)
    }

    let col = document.createElement('div')
    col.classList.add('col-sm-6')

    let inputgroup = document.createElement('div')
    inputgroup.classList.add('input-group')

    let inputgroupprepend = document.createElement('div')
    inputgroupprepend.classList.add('input-group-prepend')
    let title = document.createElement('span')
    title.classList.add('input-group-text')
    title.innerHTML = options[keys[i]].label

    let select = document.createElement('select')
    select.classList.add('form-control')
    domOptions[keys[i]] = select

    let selectoptions = options[keys[i]]
    for (let j=0; j<selectoptions.names.length; j++) {
      let option = document.createElement('option')
      option.value = selectoptions.values[j]
      option.innerHTML = selectoptions.names[j]
      select.appendChild(option)
    }
    select.value = selectoptions.default

    inputgroupprepend.appendChild(title)
    inputgroup.appendChild(inputgroupprepend)
    inputgroup.appendChild(select)
    col.appendChild(inputgroup)
    row.appendChild(col)
  }
}

// I used to have more options, but removed some for simplicity sakes
// This is why I build this function
addOptionsToWrapper(options, DOM.optionsWrapper, DOM.options)


/**
 * Compiler
 */

DOM.go.onclick = function() {
  this.disabled = true
  this.innerHTML = 'Compiling...'
  
  getData(getURL('https://closure-compiler.appspot.com/compile', {
    js_code: DOM.input.value,
    compilation_level: DOM.options.compilation_level.value,
    output_format: 'json',
    output_info: ['compiled_code', 'warnings', 'errors', 'statistics'],
    // output_info: DOM.options.output_info.value,
    // exclude_default_externs: DOM.options.exclude_default_externs.value,
    formatting: DOM.options.formatting.value,
    // use_closure_library: DOM.options.use_closure_library.value,
    // warning_level: DOM.options.warning_level.value,
    language: DOM.options.language.value,
    language_out: DOM.options.language_out.value,
  }), function(data) {
    let parsed = JSON.parse(data)
    let keys = Object.keys(parsed)
    
    for (let i=0; i<keys.length; i++) {
      switch(keys[i]) {
        case 'compiledCode':
          DOM.output.innerHTML = parsed.compiledCode
          break
        case 'statistics':
          let s = parsed.statistics
          DOM.stats.innerHTML = `Compressed ${s.originalSize} bytes down to ${s.compressedSize} bytes (${Math.round((s.originalSize-s.compressedSize)/s.originalSize*100)}%) in ${s.compileTime} ms`
          break
        default:
          console.log(parsed[keys[i]])
          break
      }
    }
    
    DOM.go.disabled = false
    DOM.go.innerHTML = 'Compile !'
  })
}
View Compiled

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0/css/bootstrap.css

External JavaScript

This Pen doesn't use any external JavaScript resources.