Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'release/0.3'

  • Loading branch information...
commit eb31d0cf1af8d41902c9ee75a8cfe89adad3b3a3 2 parents bdc006b + 2a1b0cf
@rspivak authored
View
10 CHANGES
@@ -1,12 +1,16 @@
Change History
-**************
+--------------
+
+0.3 (2011-05-09)
+****************
+- Added minifier
0.2 (2011-05-07)
-================
+****************
- Added a JavaScript parser
- Added pretty printer
- Added node visitor
0.1 (2011-05-02)
-================
+****************
- Initial public version. It contains only a JavaScript lexer
View
45 README.rst
@@ -5,8 +5,44 @@ Welcome to SlimIt
It compiles JavaScript into more compact code so that it downloads
and runs faster.
-At version `0.2` it provides a library that includes a JavaScript
-parser, lexer, pretty printer and a tree visitor.
+`SlimIt` also provides a library that includes a JavaScript parser,
+lexer, pretty printer and a tree visitor.
+
+Let's minify some code
+----------------------
+
+From the command line:
+
+$ slimit -h
+Usage: slimit [input file]
+
+If no input file is provided STDIN is used by default.
+Minified JavaScript code is printed to STDOUT.
+
+$ cat test.js
+var a = function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+};
+$
+$ slimit < test.js
+var a=function(obj){for(var name in obj){return false;}return true;};
+
+Or using library API:
+
+>>> from slimit import minify
+>>> text = """
+... var a = function( obj ) {
+... for ( var name in obj ) {
+... return false;
+... }
+... return true;
+... };
+... """
+>>> print minify(text)
+var a=function(obj){for(var name in obj){return false;}return true;};
Iterate over, modify a JavaScript AST and pretty print it
---------------------------------------------------------
@@ -79,3 +115,8 @@ Using ``pip``::
Using ``easy_install``::
$ sudo easy_install slimit
+
+Roadmap
+-------
+- More minifications
+
View
2  docs-source/_templates/sidebarintro.html
@@ -1,6 +1,6 @@
<h3>About SlimIt</h3>
<p>
- SlimIt is a JavaScript minifier and a library that parses JavaScript and returns AST
+ SlimIt is a JavaScript minifier
</p>
<h3>Useful Links</h3>
<ul>
View
51 docs-source/index.rst
@@ -10,8 +10,48 @@ Welcome to SlimIt
It compiles JavaScript into more compact code so that it downloads
and runs faster.
-At version `0.2` it provides a library that includes a JavaScript
-parser, lexer, pretty printer and a tree visitor.
+`SlimIt` also provides a library that includes a JavaScript parser,
+lexer, pretty printer and a tree visitor.
+
+Let's minify some code
+----------------------
+
+From the command line:
+
+.. code-block:: bash
+
+ $ slimit -h
+ Usage: slimit [input file]
+
+ If no input file is provided STDIN is used by default.
+ Minified JavaScript code is printed to STDOUT.
+
+ $ cat test.js
+ var a = function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ };
+ $
+ $ slimit < test.js
+ var a=function(obj){for(var name in obj){return false;}return true;};
+
+Or using library API:
+
+.. code-block:: python
+
+ >>> from slimit import minify
+ >>> text = """
+ ... var a = function( obj ) {
+ ... for ( var name in obj ) {
+ ... return false;
+ ... }
+ ... return true;
+ ... };
+ ... """
+ >>> print minify(text)
+ var a=function(obj){for(var name in obj){return false;}return true;};
Iterate over, modify a JavaScript AST and pretty print it
---------------------------------------------------------
@@ -86,8 +126,9 @@ Using ``easy_install``::
$ sudo easy_install slimit
+Roadmap
+-------
+- More minifications
+
.. toctree::
:maxdepth: 2
-
-
-
View
3  setup.py
@@ -17,7 +17,7 @@ def read(*rel_names):
setup(
name='slimit',
- version='0.2',
+ version='0.3',
url='http://slimit.org',
license='MIT',
description='SlimIt - JavaScript minifier',
@@ -29,6 +29,7 @@ def read(*rel_names):
zip_safe=False,
entry_points="""\
[console_scripts]
+ slimit = slimit.minifier:main
""",
classifiers=filter(None, classifiers.split('\n')),
long_description=read('README.rst') + '\n\n' + read('CHANGES'),
View
27 src/slimit/__init__.py
@@ -0,0 +1,27 @@
+###############################################################################
+#
+# Copyright (c) 2011 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+from minifier import minify
View
57 src/slimit/minifier.py
@@ -0,0 +1,57 @@
+###############################################################################
+#
+# Copyright (c) 2011 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+import sys
+import optparse
+import textwrap
+
+from slimit.parser import Parser
+from slimit.visitors.minvisitor import ECMAMinifier
+
+
+def minify(text):
+ parser = Parser()
+ tree = parser.parse(text)
+ minified = ECMAMinifier().visit(tree)
+ return minified
+
+def main():
+ usage = textwrap.dedent("""\
+ %prog [input file]
+
+ If no input file is provided STDIN is used by default.
+ Minified JavaScript code is printed to STDOUT.
+ """)
+ parser = optparse.OptionParser(usage=usage)
+ options, args = parser.parse_args()
+
+ if len(args) == 1:
+ text = open(args[1]).read()
+ else:
+ text = sys.stdin.read()
+
+ minified = minify(text)
+ sys.stdout.write(minified)
View
98 src/slimit/tests/test_minifier.py
@@ -0,0 +1,98 @@
+###############################################################################
+#
+# Copyright (c) 2011 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+import unittest
+
+from slimit import minify
+
+
+class MinifierTestCase(unittest.TestCase):
+
+ def assertMinified(self, source, expected):
+ minified = minify(source)
+ self.assertSequenceEqual(minified, expected)
+
+ TEST_CASES = [
+ ("""
+ jQuery.fn = jQuery.prototype = {
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ }
+ };
+ """,
+ 'jQuery.fn=jQuery.prototype={_data:function(elem,name,data){return jQuery.data(elem,name,data,true);}};'),
+
+ ('context = context instanceof jQuery ? context[0] : context;',
+ 'context=context instanceof jQuery?context[0]:context;'
+ ),
+
+ ("""
+ /*
+ * A number of helper functions used for managing events.
+ * Many of the ideas behind this code originated from
+ * Dean Edwards' addEvent library.
+ */
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+ """,
+
+ 'if(elem&&elem.parentNode){if(elem.id!==match[2]){return rootjQuery.find(selector);}this.length=1;this[0]=elem;}'
+ ),
+
+ ("""
+ var a = function( obj ) {
+ for ( var name in obj ) {
+ return false;
+ }
+ return true;
+ };
+ """,
+ 'var a=function(obj){for(var name in obj){return false;}return true;};'
+ ),
+
+ ]
+
+
+def make_test_function(input, expected):
+
+ def test_func(self):
+ self.assertMinified(input, expected)
+
+ return test_func
+
+for index, (input, expected) in enumerate(MinifierTestCase.TEST_CASES):
+ func = make_test_function(input, expected)
+ setattr(MinifierTestCase, 'test_case_%d' % index, func)
View
13 src/slimit/tests/test_parser.py
@@ -38,6 +38,19 @@ def test_line_terminator_at_the_end_of_file(self):
parser = Parser()
parser.parse('var $_ = function(x){}(window);\n')
+ # XXX: function expression ?
+ def _test_function_expression(self):
+ text = """
+ if (true) {
+ function() {
+ foo;
+ location = 'http://anywhere.com';
+ }
+ }
+ """
+ parser = Parser()
+ parser.parse(text)
+
def test_modify_tree(self):
text = """
for (var i = 0; i < 10; i++) {
View
5 src/slimit/visitors/ecmavisitor.py
@@ -55,8 +55,7 @@ def visit_Block(self, node):
return s
def visit_VarStatement(self, node):
- s = 'var %s;' % ', '.join(
- self.visit(child) for child in node)
+ s = 'var %s;' % ', '.join(self.visit(child) for child in node)
return s
def visit_VarDecl(self, node):
@@ -329,7 +328,7 @@ def visit_BracketAccessor(self, node):
def visit_FunctionCall(self, node):
s = '%s(%s)' % (self.visit(node.identifier),
- ', '.join(self.visit(arg) for arg in node.args))
+ ', '.join(self.visit(arg) for arg in node.args))
return s
def visit_Object(self, node):
View
330 src/slimit/visitors/minvisitor.py
@@ -0,0 +1,330 @@
+###############################################################################
+#
+# Copyright (c) 2011 Ruslan Spivak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+###############################################################################
+
+__author__ = 'Ruslan Spivak <ruslan.spivak@gmail.com>'
+
+from slimit import ast
+
+
+class ECMAMinifier(object):
+
+ def visit(self, node):
+ method = 'visit_%s' % node.__class__.__name__
+ return getattr(self, method, self.generic_visit)(node)
+
+ def generic_visit(self, node):
+ return 'GEN: %r' % node
+
+ def visit_Program(self, node):
+ return ''.join(self.visit(child) for child in node)
+
+ def visit_Block(self, node):
+ s = '{%s}' % ''.join(self.visit(child) for child in node)
+ return s
+
+ def visit_VarStatement(self, node):
+ s = 'var %s;' % ','.join(self.visit(child) for child in node)
+ return s
+
+ def visit_VarDecl(self, node):
+ output = []
+ output.append(self.visit(node.identifier))
+ if node.initializer is not None:
+ output.append('=%s' % self.visit(node.initializer))
+ return ''.join(output)
+
+ def visit_Identifier(self, node):
+ return node.value
+
+ def visit_Assign(self, node):
+ template = '%s%s%s'
+ if getattr(node, '_parens', False):
+ template = '(%s)' % template
+ return template % (
+ self.visit(node.left), node.op, self.visit(node.right))
+
+ def visit_Number(self, node):
+ return node.value
+
+ def visit_Comma(self, node):
+ return '%s,%s' % (self.visit(node.left), self.visit(node.right))
+
+ def visit_EmptyStatement(self, node):
+ return node.value
+
+ def visit_If(self, node):
+ s = 'if('
+ if node.predicate is not None:
+ s += self.visit(node.predicate)
+ s += ')'
+ s += self.visit(node.consequent)
+ if node.alternative is not None:
+ s += 'else '
+ s += self.visit(node.alternative)
+ return s
+
+ def visit_Boolean(self, node):
+ return node.value
+
+ def visit_For(self, node):
+ s = 'for('
+ if node.init is not None:
+ s += self.visit(node.init)
+ if node.init is None:
+ s += ';'
+ elif isinstance(node.init, (ast.Assign, ast.Comma)):
+ s += ';'
+ else:
+ s += ''
+ if node.cond is not None:
+ s += self.visit(node.cond)
+ s += ';'
+ if node.count is not None:
+ s += self.visit(node.count)
+ s += ')' + self.visit(node.statement)
+ return s
+
+ def visit_ForIn(self, node):
+ if isinstance(node.item, ast.VarDecl):
+ template = 'for(var %s in %s)'
+ else:
+ template = 'for(%s in %s)'
+ s = template % (self.visit(node.item), self.visit(node.iterable))
+ s += self.visit(node.statement)
+ return s
+
+ def visit_BinOp(self, node):
+ if node.op in ('instanceof', 'in'):
+ template = '%s %s %s'
+ else:
+ template = '%s%s%s'
+ if getattr(node, '_parens', False):
+ template = '(%s)' % template
+ return template % (
+ self.visit(node.left), node.op, self.visit(node.right))
+
+ def visit_UnaryOp(self, node):
+ s = self.visit(node.value)
+ if node.postfix:
+ s += node.op
+ elif node.op in ('delete', 'void', 'typeof'):
+ s = '%s %s' % (node.op, s)
+ else:
+ s = '%s%s' % (node.op, s)
+ if getattr(node, '_parens', False):
+ s = '(%s)' % s
+ return s
+
+ def visit_ExprStatement(self, node):
+ return '%s;' % self.visit(node.expr)
+
+ def visit_DoWhile(self, node):
+ s = 'do'
+ s += self.visit(node.statement)
+ s += 'while(%s);' % self.visit(node.predicate)
+ return s
+
+ def visit_While(self, node):
+ s = 'while(%s)' % self.visit(node.predicate)
+ s += self.visit(node.statement)
+ return s
+
+ def visit_Null(self, node):
+ return 'null'
+
+ def visit_String(self, node):
+ return node.value
+
+ def visit_Continue(self, node):
+ if node.identifier is not None:
+ s = 'continue %s;' % self.visit_Identifier(node.identifier)
+ else:
+ s = 'continue;'
+ return s
+
+ def visit_Break(self, node):
+ if node.identifier is not None:
+ s = 'break %s;' % self.visit_Identifier(node.identifier)
+ else:
+ s = 'break;'
+ return s
+
+ def visit_Return(self, node):
+ if node.expr is None:
+ return 'return;'
+
+ expr_text = self.visit(node.expr)
+ if expr_text.startswith(('(', '{')):
+ return 'return%s;' % expr_text
+ else:
+ return 'return %s;' % expr_text
+
+ def visit_With(self, node):
+ s = 'with(%s)' % self.visit(node.expr)
+ s += self.visit(node.statement)
+ return s
+
+ def visit_Label(self, node):
+ s = '%s:%s' % (
+ self.visit(node.identifier), self.visit(node.statement))
+ return s
+
+ def visit_Switch(self, node):
+ s = 'switch(%s){' % self.visit(node.expr)
+ for case in node.cases:
+ s += self.visit_Case(case)
+ if node.default is not None:
+ s += self.visit_Default(node.default)
+ s += '}'
+ return s
+
+ def visit_Case(self, node):
+ s = 'case %s:' % self.visit(node.expr)
+ elements = ''.join(self.visit(element) for element in node.elements)
+ if elements:
+ s += elements
+ return s
+
+ def visit_Default(self, node):
+ s = 'default:'
+ s += ''.join(self.visit(element) for element in node.elements)
+ if node.elements is not None:
+ s += ''
+ return s
+
+ def visit_Throw(self, node):
+ s = 'throw %s;' % self.visit(node.expr)
+ return s
+
+ def visit_Debugger(self, node):
+ return '%s;' % node.value
+
+ def visit_Try(self, node):
+ s = 'try '
+ s += self.visit(node.statements)
+ if node.catch is not None:
+ s += ' ' + self.visit(node.catch)
+ if node.fin is not None:
+ s += ' ' + self.visit(node.fin)
+ return s
+
+ def visit_Catch(self, node):
+ s = 'catch(%s)%s' % (
+ self.visit(node.identifier), self.visit(node.elements))
+ return s
+
+ def visit_Finally(self, node):
+ s = 'finally %s' % self.visit(node.elements)
+ return s
+
+ def visit_FuncDecl(self, node):
+ elements = ''.join(self.visit(element) for element in node.elements)
+ s = 'function %s(%s){%s' % (
+ self.visit(node.identifier),
+ ','.join(self.visit(param) for param in node.parameters),
+ elements,
+ )
+ s += '}'
+ return s
+
+ def visit_FuncExpr(self, node):
+ elements = ''.join(self.visit(element) for element in node.elements)
+
+ ident = node.identifier
+ ident = '' if ident is None else ' %s' % self.visit(ident)
+
+ header = 'function%s(%s)'
+ if getattr(node, '_parens', False):
+ header = '(' + header
+ s = (header + '{%s') % (
+ ident,
+ ','.join(self.visit(param) for param in node.parameters),
+ elements,
+ )
+ s += '}'
+ if getattr(node, '_parens', False):
+ s += ')'
+ return s
+
+ def visit_Conditional(self, node):
+ if getattr(node, '_parens', False):
+ template = '(%s?%s:%s)'
+ else:
+ template = '%s?%s:%s'
+
+ s = template % (
+ self.visit(node.predicate),
+ self.visit(node.consequent), self.visit(node.alternative))
+ return s
+
+ def visit_Regex(self, node):
+ if getattr(node, '_parens', False):
+ return '(%s)' % node.value
+ else:
+ return node.value
+
+ def visit_NewExpr(self, node):
+ s = 'new %s(%s)' % (
+ self.visit(node.identifier),
+ ','.join(self.visit(arg) for arg in node.args)
+ )
+ return s
+
+ def visit_DotAccessor(self, node):
+ if getattr(node, '_parens', False):
+ template = '(%s.%s)'
+ else:
+ template = '%s.%s'
+ s = template % (self.visit(node.node), self.visit(node.identifier))
+ return s
+
+ def visit_BracketAccessor(self, node):
+ s = '%s[%s]' % (self.visit(node.node), self.visit(node.expr))
+ return s
+
+ def visit_FunctionCall(self, node):
+ s = '%s(%s)' % (self.visit(node.identifier),
+ ','.join(self.visit(arg) for arg in node.args))
+ return s
+
+ def visit_Object(self, node):
+ s = '{%s}' % ','.join(self.visit(prop) for prop in node.properties)
+ return s
+
+ def visit_Array(self, node):
+ s = '['
+ length = len(node.items) - 1
+ for index, item in enumerate(node.items):
+ if isinstance(item, ast.Elision):
+ s += ','
+ elif index != length:
+ s += self.visit(item) + ','
+ else:
+ s += self.visit(item)
+ s += ']'
+ return s
+
+ def visit_This(self, node):
+ return 'this'
+
Please sign in to comment.
Something went wrong with that request. Please try again.