Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(9)

Side by Side Diff: Source/devtools/scripts/rjsmin.py

Issue 252633006: Compact JS resource better by using rjsmin. (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: Created 6 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # -*- coding: ascii -*-
3 #
4 # Copyright 2011 - 2013
5 # Andr\xe9 Malo or his licensors, as applicable
6 #
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
10 #
11 # http://www.apache.org/licenses/LICENSE-2.0
12 #
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
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.7'
63 __all__ = ['jsmin']
64
65 import re as _re
66
67
68 def _make_jsmin(python_only=False):
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 # pylint: disable = R0912, R0914, W0612
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 # pylint: disable = W0622
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])', # for better portability
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 dull = r'[^\047"/\000-\040]'
175
176 space_sub = _re.compile((
177 r'(%(dull)s+)'
178 r'|(%(strings)s%(dull)s*)'
179 r'|(?<=%(preregex1)s)'
180 r'%(space)s*(?:%(newline)s%(space)s*)*'
181 r'(%(regex)s%(dull)s*)'
182 r'|(?<=%(preregex2)s)'
183 r'%(space)s*(?:%(newline)s%(space)s)*'
184 r'(%(regex)s%(dull)s*)'
185 r'|(?<=%(id_literal_close)s)'
186 r'%(space)s*(?:(%(newline)s)%(space)s*)+'
187 r'(?=%(id_literal_open)s)'
188 r'|(?<=%(id_literal)s)(%(space)s)+(?=%(id_literal)s)'
189 r'|(?<=\+)(%(space)s)+(?=\+)'
190 r'|(?<=-)(%(space)s)+(?=-)'
191 r'|%(space)s+'
192 r'|(?:%(newline)s%(space)s*)+'
193 ) % locals()).sub
194 #print space_sub.__self__.pattern
195
196 def space_subber(match):
197 """ Substitution callback """
198 # pylint: disable = C0321, R0911
199 groups = match.groups()
200 if groups[0]: return groups[0]
201 elif groups[1]: return groups[1]
202 elif groups[2]: return groups[2]
203 elif groups[3]: return groups[3]
204 elif groups[4]: return '\n'
205 elif groups[5] or groups[6] or groups[7]: return ' '
206 else: return ''
207
208 def jsmin(script): # pylint: disable = W0621
209 r"""
210 Minify javascript based on `jsmin.c by Douglas Crockford`_\.
211
212 Instead of parsing the stream char by char, it uses a regular
213 expression approach which minifies the whole script with one big
214 substitution regex.
215
216 .. _jsmin.c by Douglas Crockford:
217 http://www.crockford.com/javascript/jsmin.c
218
219 :Parameters:
220 `script` : ``str``
221 Script to minify
222
223 :Return: Minified script
224 :Rtype: ``str``
225 """
226 return space_sub(space_subber, '\n%s\n' % script).strip()
227
228 return jsmin
229
230 jsmin = _make_jsmin()
231
232
233 def jsmin_for_posers(script):
234 r"""
235 Minify javascript based on `jsmin.c by Douglas Crockford`_\.
236
237 Instead of parsing the stream char by char, it uses a regular
238 expression approach which minifies the whole script with one big
239 substitution regex.
240
241 .. _jsmin.c by Douglas Crockford:
242 http://www.crockford.com/javascript/jsmin.c
243
244 :Warning: This function is the digest of a _make_jsmin() call. It just
245 utilizes the resulting regex. It's just for fun here and may
246 vanish any time. Use the `jsmin` function instead.
247
248 :Parameters:
249 `script` : ``str``
250 Script to minify
251
252 :Return: Minified script
253 :Rtype: ``str``
254 """
255 def subber(match):
256 """ Substitution callback """
257 groups = match.groups()
258 return (
259 groups[0] or
260 groups[1] or
261 groups[2] or
262 groups[3] or
263 (groups[4] and '\n') or
264 (groups[5] and ' ') or
265 (groups[6] and ' ') or
266 (groups[7] and ' ') or
267 ''
268 )
269
270 return _re.sub(
271 r'([^\047"/\000-\040]+)|((?:(?:\047[^\047\\\r\n]*(?:\\(?:[^\r\n]|\r?'
272 r'\n|\r)[^\047\\\r\n]*)*\047)|(?:"[^"\\\r\n]*(?:\\(?:[^\r\n]|\r?\n|'
273 r'\r)[^"\\\r\n]*)*"))[^\047"/\000-\040]*)|(?<=[(,=:\[!&|?{};\r\n])(?'
274 r':[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*'
275 r'(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*'
276 r'[^*]*\*+(?:[^/*][^*]*\*+)*/))*)*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?:('
277 r'?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/\\\['
278 r'\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[\000-#%-,./:-@\[-^`{-~-]return'
279 r')(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/'
280 r'))*(?:(?:(?://[^\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:'
281 r'/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))*((?:/(?![\r\n/*])[^/\\\[\r\n]*(?'
282 r':(?:\\[^\r\n]|(?:\[[^\\\]\r\n]*(?:\\[^\r\n][^\\\]\r\n]*)*\]))[^/'
283 r'\\\[\r\n]*)*/)[^\047"/\000-\040]*)|(?<=[^\000-!#%&(*,./:-@\[\\^`{|'
284 r'~])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)'
285 r'*/))*(?:((?:(?://[^\r\n]*)?[\r\n]))(?:[\000-\011\013\014\016-\040]'
286 r'|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))*)+(?=[^\000-\040"#%-\047)*,./'
287 r':-@\\-^`|-~])|(?<=[^\000-#%-,./:-@\[-^`{-~-])((?:[\000-\011\013\01'
288 r'4\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=[^\000-#%-,./:'
289 r'-@\[-^`{-~-])|(?<=\+)((?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*'
290 r'\*+(?:[^/*][^*]*\*+)*/)))+(?=\+)|(?<=-)((?:[\000-\011\013\014\016-'
291 r'\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/)))+(?=-)|(?:[\000-\011\013'
292 r'\014\016-\040]|(?:/\*[^*]*\*+(?:[^/*][^*]*\*+)*/))+|(?:(?:(?://[^'
293 r'\r\n]*)?[\r\n])(?:[\000-\011\013\014\016-\040]|(?:/\*[^*]*\*+(?:[^'
294 r'/*][^*]*\*+)*/))*)+', subber, '\n%s\n' % script
295 ).strip()
296
297
298 if __name__ == '__main__':
299 import sys as _sys
300 _sys.stdout.write(jsmin(_sys.stdin.read()))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698