This is a card in Dave's Virtual Box of Cards.

Line-breaking minified JS source

Created: 2023-12-03

Back to javascript

Minified source is great for being compact. But it’s pretty awful to work with, being in super long lines. Text editors hate it.

You can’t just run minified JS source through fmt. You can’t just break on any space anywhere. There’s quoted strings and wacky JS stuff like not allowing a line break after a throw statement. Also, breaking after a return does a secret insertion of a semicolon, so your actual return value on the next line will be ignored!

I’m sure somebody else has done this, but it was faster to write my own in Ruby than find one on the MoDeRN wEB.

I’m sure this has at least one bug, but it works with the minified source I threw at it:

# USAGE: jscols.rb 80 foo.js

cols = Integer(ARGV[0])
js = File.read(ARGV[1])

breakable = [' ',':',';',',','!','?',']',')''{','}']
last_break = 0
in_quote_char = nil
in_esc = false

for i in 0...js.length
  c = js[i]
  if in_esc ; in_esc = false ; next ; end
  if c == '\\' ; in_esc = true ; end

  if c == '"' || c == "'"
    if !in_quote_char
      in_quote_char = c
    elsif c == in_quote_char
      in_quote_char = nil
    end
  end
  if in_quote_char ; next ; end

  breaktime = (i - last_break) >= cols
  if breakable.include?(c) && breaktime
    # can't break after a 'throw' or 'return'!
    if c == ' ' && (js[i-5...i] == 'throw' || js[i-6...i] == 'return')
      next
    end
    puts js[last_break...i]
    #puts "print #{last_break} to #{i}"
    last_break = i
  end
end

if last_break < js.length
  puts js[last_break..-1]
end

Please let me know if you spot a bug or have any improvements.

Oh, and I don’t scan ahead, so your input column number is the minimum size, not the maximum, which is probably not what you’re expecting. However, I just kept giving it different column sizes until I got the nicest output at about the rough line width average I was looking for. I don’t expect to use this tool a lot, so this is a perfectly fine compromise for me.