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.6'
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.
79
80 :Return: Minifier
81 :Rtype: ``callable``
82 """
83
84 if not python_only:
85 try:
86 import _rjsmin
87 except ImportError:
88 pass
89 else:
90 return _rjsmin.jsmin
91 try:
92 xrange
93 except NameError:
94 xrange = range
95
96 space_chars = r'[\000-\011\013\014\016-\040]'
97
98 line_comment = r'(?://[^\r\n]*)'
99 space_comment = r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)'
100 string1 = \
101 r'(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^\047\\\r\n]*)*\047)'
102 string2 = r'(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|\r)[^"\\\r\n]*)*")'
103 strings = r'(?:%s|%s)' % (string1, string2)
104
105 charclass = r'(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\])'
106 nospecial = r'[^/\\\[\r\n]'
107 regex = r'(?:/(?![\r\n/*])%s*(?:(?:\\[^\r\n]|%s)%s*)*/)' % (
108 nospecial, charclass, nospecial
109 )
110 space = r'(?:%s|%s)' % (space_chars, space_comment)
111 newline = r'(?:%s?[\r\n])' % line_comment
112
113 def fix_charclass(result):
114 """ Fixup string of chars to fit into a regex char class """
115 pos = result.find('-')
116 if pos >= 0:
117 result = r'%s%s-' % (result[:pos], result[pos + 1:])
118
119 def sequentize(string):
120 """
121 Notate consecutive characters as sequence
122
123 (1-4 instead of 1234)
124 """
125 first, last, result = None, None, []
126 for char in map(ord, string):
127 if last is None:
128 first = last = char
129 elif last + 1 == char:
130 last = char
131 else:
132 result.append((first, last))
133 first = last = char
134 if last is not None:
135 result.append((first, last))
136 return ''.join(['%s%s%s' % (
137 chr(first),
138 last > first + 1 and '-' or '',
139 last != first and chr(last) or ''
140 ) for first, last in result])
141
142 return _re.sub(r'([\000-\040\047])',
143 lambda m: '\\%03o' % ord(m.group(1)), (sequentize(result)
144 .replace('\\', '\\\\')
145 .replace('[', '\\[')
146 .replace(']', '\\]')
147 )
148 )
149
150 def id_literal_(what):
151 """ Make id_literal like char class """
152 match = _re.compile(what).match
153 result = ''.join([
154 chr(c) for c in xrange(127) if not match(chr(c))
155 ])
156 return '[^%s]' % fix_charclass(result)
157
158 def not_id_literal_(keep):
159 """ Make negated id_literal like char class """
160 match = _re.compile(id_literal_(keep)).match
161 result = ''.join([
162 chr(c) for c in xrange(127) if not match(chr(c))
163 ])
164 return r'[%s]' % fix_charclass(result)
165
166 not_id_literal = not_id_literal_(r'[a-zA-Z0-9_$]')
167 preregex1 = r'[(,=:\[!&|?{};\r\n]'
168 preregex2 = r'%(not_id_literal)sreturn' % locals()
169
170 id_literal = id_literal_(r'[a-zA-Z0-9_$]')
171 id_literal_open = id_literal_(r'[a-zA-Z0-9_${\[(!+-]')
172 id_literal_close = id_literal_(r'[a-zA-Z0-9_$}\])"\047+-]')
173
174 space_sub = _re.compile((
175 r'([^\047"/\000-\040]+)'
176 r'|(%(strings)s[^\047"/\000-\040]*)'
177 r'|(?:(?<=%(preregex1)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
178 r'|(?:(?<=%(preregex2)s)%(space)s*(%(regex)s[^\047"/\000-\040]*))'
179 r'|(?<=%(id_literal_close)s)'
180 r'%(space)s*(?:(%(newline)s)%(space)s*)+'
181 r'(?=%(id_literal_open)s)'
182 r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
183 r'|(?<=\+)(%(space)s)+(?=\+)'
184 r'|(?<=-)(%(space)s)+(?=-)'
185 r'|%(space)s+'
186 r'|(?:%(newline)s%(space)s*)+'
187 ) % locals()).sub
188
189
190 def space_subber(match):
191 """ Substitution callback """
192
193 groups = match.groups()
194 if groups[0]: return groups[0]
195 elif groups[1]: return groups[1]
196 elif groups[2]: return groups[2]
197 elif groups[3]: return groups[3]
198 elif groups[4]: return '\n'
199 elif groups[5] or groups[6] or groups[7]: return ' '
200 else: return ''
201
202 def jsmin(script):
203 r"""
204 Minify javascript based on `jsmin.c by Douglas Crockford`_\.
205
206 Instead of parsing the stream char by char, it uses a regular
207 expression approach which minifies the whole script with one big
208 substitution regex.
209
210 .. _jsmin.c by Douglas Crockford:
211 http://www.crockford.com/javascript/jsmin.c
212
213 :Parameters:
214 `script` : ``str``
215 Script to minify
216
217 :Return: Minified script
218 :Rtype: ``str``
219 """
220 return space_sub(space_subber, '\n%s\n' % script).strip()
221
222 return jsmin
223
224 jsmin = _make_jsmin()
225
226
228 r"""
229 Minify javascript based on `jsmin.c by Douglas Crockford`_\.
230
231 Instead of parsing the stream char by char, it uses a regular
232 expression approach which minifies the whole script with one big
233 substitution regex.
234
235 .. _jsmin.c by Douglas Crockford:
236 http://www.crockford.com/javascript/jsmin.c
237
238 :Warning: This function is the digest of a _make_jsmin() call. It just
239 utilizes the resulting regex. It's just for fun here and may
240 vanish any time. Use the `jsmin` function instead.
241
242 :Parameters:
243 `script` : ``str``
244 Script to minify
245
246 :Return: Minified script
247 :Rtype: ``str``
248 """
249 def subber(match):
250 """ Substitution callback """
251 groups = match.groups()
252 return (
253 groups[0] or
254 groups[1] or
255 groups[2] or
256 groups[3] or
257 (groups[4] and '\n') or
258 (groups[5] and ' ') or
259 (groups[6] and ' ') or
260 (groups[7] and ' ') or
261 ''
262 )
263
264 return _re.sub(
265 r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
266 r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
267 r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?:(?<=[(,=:\[!&|?{};\r\n]'
268 r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
269 r'))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*'
270 r'(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*'
271 r'))|(?:(?<=[\000-#%-,./:-@\[-^`{-~-]return)(?:[\000-\011\013\014\01'
272 r'6-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*((?:/(?![\r\n/*])[^/'
273 r'\\\[\r\n]*(?:(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]'
274 r'*)*\]))[^/\\\[\r\n]*)*/)[^\047"/\000-\040]*))|(?<=[^\000-!#%&(*,./'
275 r':-@\[\\^`{|~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/'
276 r'*][^*]*\*+)*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\01'
277 r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#'
278 r'%-\047)*,./:-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-'
279 r'\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^'
280 r'\000-#%-,./:-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|'
281 r'(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\0'
282 r'13\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\0'
283 r'00-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:'
284 r'(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*'
285 r']*\*+(?:[^/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
286 ).strip()
287
288
289 if __name__ == '__main__':
290 import sys as _sys
291 _sys.stdout.write(jsmin(_sys.stdin.read()))
292