185 lines
6.6 KiB
Python
185 lines
6.6 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
# Created by: juancarlospaco
|
|
# GitHub Repo: https://github.com/juancarlospaco/css-html-js-minify
|
|
# Modified by: xdavidhu
|
|
|
|
"""JavaScript Minifier functions for CSS-HTML-JS-Minify."""
|
|
|
|
|
|
from io import StringIO # pure-Python StringIO supports unicode.
|
|
|
|
import logging as log
|
|
import re
|
|
|
|
from .css_minifier import condense_semicolons
|
|
|
|
|
|
__all__ = ['js_minify']
|
|
|
|
|
|
def remove_commented_lines(js):
|
|
"""Force remove commented out lines from Javascript."""
|
|
log.debug("Force remove commented out lines from Javascript.")
|
|
result = ""
|
|
for line in js.splitlines():
|
|
line = re.sub(r"/\*.*\*/" ,"" ,line) # (/*COMMENT */)
|
|
line = re.sub(r"//.*","" ,line) # (//COMMENT)
|
|
result += '\n'+line
|
|
return result
|
|
|
|
|
|
def simple_replacer_js(js):
|
|
"""Force strip simple replacements from Javascript."""
|
|
log.debug("Force strip simple replacements from Javascript.")
|
|
return condense_semicolons(js.replace("debugger;", ";").replace(
|
|
";}", "}").replace("; ", ";").replace(" ;", ";").rstrip("\n;"))
|
|
|
|
|
|
def js_minify_keep_comments(js):
|
|
"""Return a minified version of the Javascript string."""
|
|
log.info("Compressing Javascript...")
|
|
ins, outs = StringIO(js), StringIO()
|
|
JavascriptMinify(ins, outs).minify()
|
|
return force_single_line_js(outs.getvalue())
|
|
|
|
|
|
def force_single_line_js(js):
|
|
"""Force Javascript to a single line, even if need to add semicolon."""
|
|
log.debug("Forcing JS from ~{0} to 1 Line.".format(len(js.splitlines())))
|
|
return ";".join(js.splitlines()) if len(js.splitlines()) > 1 else js
|
|
|
|
|
|
class JavascriptMinify(object):
|
|
|
|
"""Minify an input stream of Javascript, writing to an output stream."""
|
|
|
|
def __init__(self, instream=None, outstream=None):
|
|
"""Init class."""
|
|
self.ins, self.outs = instream, outstream
|
|
|
|
def minify(self, instream=None, outstream=None):
|
|
"""Minify Javascript using StringIO."""
|
|
if instream and outstream:
|
|
self.ins, self.outs = instream, outstream
|
|
write, read = self.outs.write, self.ins.read
|
|
space_strings = ("abcdefghijklmnopqrstuvwxyz"
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$\\")
|
|
starters, enders = '{[(+-', '}])+-"\''
|
|
newlinestart_strings = starters + space_strings
|
|
newlineend_strings = enders + space_strings
|
|
do_newline, do_space = False, False
|
|
doing_single_comment, doing_multi_comment = False, False
|
|
previous_before_comment, in_quote = '', ''
|
|
in_re, quote_buf = False, []
|
|
previous = read(1)
|
|
next1 = read(1)
|
|
if previous == '/':
|
|
if next1 == '/':
|
|
doing_single_comment = True
|
|
elif next1 == '*':
|
|
doing_multi_comment = True
|
|
else:
|
|
write(previous)
|
|
elif not previous:
|
|
return
|
|
elif previous >= '!':
|
|
if previous in "'\"":
|
|
in_quote = previous
|
|
write(previous)
|
|
previous_non_space = previous
|
|
else:
|
|
previous_non_space = ' '
|
|
if not next1:
|
|
return
|
|
while True:
|
|
next2 = read(1)
|
|
if not next2:
|
|
last = next1.strip()
|
|
conditional_1 = (doing_single_comment or doing_multi_comment)
|
|
if not conditional_1 and last not in ('', '/'):
|
|
write(last)
|
|
break
|
|
if doing_multi_comment:
|
|
if next1 == '*' and next2 == '/':
|
|
doing_multi_comment = False
|
|
next2 = read(1)
|
|
elif doing_single_comment:
|
|
if next1 in '\r\n':
|
|
doing_single_comment = False
|
|
while next2 in '\r\n':
|
|
next2 = read(1)
|
|
if not next2:
|
|
break
|
|
if previous_before_comment in ')}]':
|
|
do_newline = True
|
|
elif previous_before_comment in space_strings:
|
|
write('\n')
|
|
elif in_quote:
|
|
quote_buf.append(next1)
|
|
|
|
if next1 == in_quote:
|
|
numslashes = 0
|
|
for c in reversed(quote_buf[:-1]):
|
|
if c != '\\':
|
|
break
|
|
else:
|
|
numslashes += 1
|
|
if numslashes % 2 == 0:
|
|
in_quote = ''
|
|
write(''.join(quote_buf))
|
|
elif next1 in '\r\n':
|
|
conditional_2 = previous_non_space in newlineend_strings
|
|
if conditional_2 or previous_non_space > '~':
|
|
while 1:
|
|
if next2 < '!':
|
|
next2 = read(1)
|
|
if not next2:
|
|
break
|
|
else:
|
|
conditional_3 = next2 in newlinestart_strings
|
|
if conditional_3 or next2 > '~' or next2 == '/':
|
|
do_newline = True
|
|
break
|
|
elif next1 < '!' and not in_re:
|
|
conditional_4 = next2 in space_strings or next2 > '~'
|
|
conditional_5 = previous_non_space in space_strings
|
|
conditional_6 = previous_non_space > '~'
|
|
if (conditional_5 or conditional_6) and (conditional_4):
|
|
do_space = True
|
|
elif next1 == '/':
|
|
if in_re:
|
|
if previous != '\\':
|
|
in_re = False
|
|
write('/')
|
|
elif next2 == '/':
|
|
doing_single_comment = True
|
|
previous_before_comment = previous_non_space
|
|
elif next2 == '*':
|
|
doing_multi_comment = True
|
|
else:
|
|
in_re = previous_non_space in '(,=:[?!&|'
|
|
write('/')
|
|
else:
|
|
if do_space:
|
|
do_space = False
|
|
write(' ')
|
|
if do_newline:
|
|
write('\n')
|
|
do_newline = False
|
|
write(next1)
|
|
if not in_re and next1 in "'\"":
|
|
in_quote = next1
|
|
quote_buf = []
|
|
previous = next1
|
|
next1 = next2
|
|
if previous >= '!':
|
|
previous_non_space = previous
|
|
|
|
|
|
def js_minify(js):
|
|
"""Minify a JavaScript string."""
|
|
js = remove_commented_lines(js)
|
|
js = js_minify_keep_comments(js)
|
|
return js.strip()
|