1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 r"""
19 =====================
20 Javascript Minifier
21 =====================
22
23 rJSmin is a javascript minifier written in python.
24
25 The minifier is based on the semantics of `jsmin.c by Douglas Crockford`_\.
26
27 The module is a re-implementation aiming for speed, so it can be used at
28 runtime (rather than during a preprocessing step). Usually it produces the
29 same results as the original ``jsmin.c``. It differs in the following ways:
30
31 - there is no error detection: unterminated string, regex and comment
32 literals are treated as regular javascript code and minified as such.
33 - Control characters inside string and regex literals are left untouched; they
34 are not converted to spaces (nor to \n)
35 - Newline characters are not allowed inside string and regex literals, except
36 for line continuations in string literals (ECMA-5).
37 - "return /regex/" is recognized correctly.
38 - "+ +" and "- -" sequences are not collapsed to '++' or '--'
39 - Newlines before ! operators are removed more sensibly
40 - rJSmin does not handle streams, but only complete strings. (However, the
41 module provides a "streamy" interface).
42
43 Since most parts of the logic are handled by the regex engine it's way
44 faster than the original python port of ``jsmin.c`` by Baruch Even. The speed
45 factor varies between about 6 and 55 depending on input and python version
46 (it gets faster the more compressed the input already is). Compared to the
47 speed-refactored python port by Dave St.Germain the performance gain is less
48 dramatic but still between 1.2 and 7. See the docs/BENCHMARKS file for
49 details.
50
51 rjsmin.c is a reimplementation of rjsmin.py in C and speeds it up even more.
52
53 Both python 2 and python 3 are supported.
54
55 .. _jsmin.c by Douglas Crockford:
56 http://www.crockford.com/javascript/jsmin.c
57 """
58 __author__ = "Andr\xe9 Malo"
59 __author__ = getattr(__author__, 'decode', lambda x: __author__)('latin-1')
60 __docformat__ = "restructuredtext en"
61 __license__ = "Apache License, Version 2.0"
62 __version__ = '1.0.5'
63 __all__ = ['jsmin']
64
65 import re as _re
66
67
69 """
70 Generate JS minifier based on `jsmin.c by Douglas Crockford`_
71
72 .. _jsmin.c by Douglas Crockford:
73 http://www.crockford.com/javascript/jsmin.c
74
75 :Parameters:
76 `python_only` : ``bool``
77 Use only the python variant. If true, the c extension is not even
78 tried to be loaded. (tdi.c._tdi_rjsmin)
79
80 :Return: Minifier
81 :Rtype: ``callable``
82 """
83
84 if not python_only:
85 from tdi import c
86 rjsmin = c.load('rjsmin')
87 if rjsmin is not None:
88 return rjsmin.jsmin
89 try:
90 xrange
91 except NameError:
92 xrange = range
93
94 space_chars = r'[\000-\011\013\014\016-\040]'
95
96 line_comment = r'(?://[^\r\n]*)'
97 space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
98 string1 = \
99 r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
100 string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
101 strings = r'(?:%s|%s)' % (string1, string2)
102
103 charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
104 nospecial = r'[^/\\\[\r\n]'
105 regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
106 nospecial, charclass, nospecial
107 )
108 space = r'(?:%s|%s)' % (space_chars, space_comment)
109 newline = r'(?:%s?[\r\n])' % line_comment
110
111 def fix_charclass(result):
112 """ Fixup string of chars to fit into a regex char class """
113 pos = result.find('-')
114 if pos >= 0:
115 result = r'%s%s-' % (result[:pos], result[pos + 1:])
116
117 def sequentize(string):
118 """
119 Notate consecutive characters as sequence
120
121 (1-4 instead of 1234)
122 """
123 first, last, result = None, None, []
124 for char in map(ord, string):
125 if last is None:
126 first = last = char
127 elif last + 1 == char:
128 last = char
129 else:
130 result.append((first, last))
131 first = last = char
132 if last is not None:
133 result.append((first, last))
134 return ''.join(['%s%s%s' % (
135 chr(first),
136 last > first + 1 and '-' or '',
137 last != first and chr(last) or ''
138 ) for first, last in result])
139
140 return _re.sub(r'([\000-\040\047])',
141 lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
142 .replace('\\', '\\\\')
143 .replace('[', '\\[')
144 .replace(']', '\\]')
145 )
146 )
147
148 def id_literal_(what):
149 """ Make id_literal like char class """
150 match = _re.compile(what).match
151 result = ''.join([
152 chr(c) for c in xrange(127) if not match(chr(c))
153 ])
154 return '[^%s]' % fix_charclass(result)
155
156 def not_id_literal_(keep):
157 """ Make negated id_literal like char class """
158 match = _re.compile(id_literal_(keep)).match
159 result = ''.join([
160 chr(c) for c in xrange(127) if not match(chr(c))
161 ])
162 return r'[%s]' % fix_charclass(result)
163
164 not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
165 preregex1 = r'[(,=:\[!&|?{};\r\n]'
166 preregex2 = r'%(not_id_literal)sreturn' % locals()
167
168 id_literal = id_literal_(r'[a-zA-Z0-9_$]')
169 id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]')
170 id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
171
172 space_sub = _re.compile((
173 r'([^\047"/\000-\040]+)'
174 r'|(%(strings)s[^\047"/\000-\040]*)'
175 r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
176 r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
177 r'|(?<=%(id_literal_close)s)'
178 r'%(space)s*(?:(%(newline)s)%(space)s*)+'
179 r'(?=%(id_literal_open)s)'
180 r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
181 r'|(?<=\+)(%(space)s)+(?=\+)'
182 r'|(?<=-)(%(space)s)+(?=-)'
183 r'|%(space)s+'
184 r'|(?:%(newline)s%(space)s*)+'
185 ) % locals()).sub
186
187
188 def space_subber(match):
189 """ Substitution callback """
190
191 groups = match.groups()
192 if groups[0]: return groups[0]
193 elif groups[1]: return groups[1]
194 elif groups[2]: return groups[2]
195 elif groups[3]: return groups[3]
196 elif groups[4]: return '\n'
197 elif groups[5] or groups[6] or groups[7]: return ' '
198 else: return ''
199
200 def jsmin(script):
201 r"""
202 Minify javascript based on `jsmin.c by Douglas Crockford`_\.
203
204 Instead of parsing the stream char by char, it uses a regular
205 expression approach which minifies the whole script with one big
206 substitution regex.
207
208 .. _jsmin.c by Douglas Crockford:
209 http://www.crockford.com/javascript/jsmin.c
210
211 :Parameters:
212 `script` : ``str``
213 Script to minify
214
215 :Return: Minified script
216 :Rtype: ``str``
217 """
218 return space_sub(space_subber, '\n%s\n' % script).strip()
219
220 return jsmin
221
222 jsmin = _make_jsmin()
223
224
226 r"""
227 Minify javascript based on `jsmin.c by Douglas Crockford`_\.
228
229 Instead of parsing the stream char by char, it uses a regular
230 expression approach which minifies the whole script with one big
231 substitution regex.
232
233 .. _jsmin.c by Douglas Crockford:
234 http://www.crockford.com/javascript/jsmin.c
235
236 :Warning: This function is the digest of a _make_jsmin() call. It just
237 utilizes the resulting regex. It's just for fun here and may
238 vanish any time. Use the `jsmin` function instead.
239
240 :Parameters:
241 `script` : ``str``
242 Script to minify
243
244 :Return: Minified script
245 :Rtype: ``str``
246 """
247 def subber(match):
248 """ Substitution callback """
249 groups = match.groups()
250 return (
251 groups[0] or
252 groups[1] or
253 groups[2] or
254 groups[3] or
255 (groups[4] and '\n') or
256 (groups[5] and ' ') or
257 (groups[6] and ' ') or
258 (groups[7] and ' ') or
259 ''
260 )
261
262 return _re.sub(
263 r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
264 r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
265 r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
266 r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
267 r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
268 r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
269 r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
270 r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
271 r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
272 r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
273 r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
274 r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
275 r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#'
276 r'%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-'
277 r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^'
278 r'\000-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|'
279 r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\0'
280 r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\0'
281 r'00-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:'
282 r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
283 r']*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
284 ).strip()
285
286
287 if __name__ == '__main__':
288 import sys as _sys
289 _sys.stdout.write(jsmin(_sys.stdin.read()))
290