- minify javascripts with jsmin at compile time
authorBarış Metin <Talip-Baris.Metin@sophia.inria.fr>
Fri, 12 Jun 2009 14:40:14 +0000 (14:40 +0000)
committerBarış Metin <Talip-Baris.Metin@sophia.inria.fr>
Fri, 12 Jun 2009 14:40:14 +0000 (14:40 +0000)
- enable browser cache for all files

Makefile
httpd/plewww.conf
jsmin.py [new file with mode: 0644]
plewww.spec

index 12b1117..af4b4da 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -37,6 +37,13 @@ else
        +$(RSYNC) planetlab plekit modules $(SSHURL)/var/www/html/
 endif
 
+compress:
+       $(foreach file,\
+               $(shell find . -type f -iname "*.js"),\
+               $(shell python jsmin.py < $(file) > $(file).new && mv $(file).new $(file)))
+       @echo "Compressed .js files with jsmin.py"
+
+.PHONY: compress
 
 #################### convenience, for debugging only
 # make +foo : prints the value of $(foo)
index 2240a7b..f4555c8 100644 (file)
@@ -48,7 +48,7 @@ DirectoryIndex index.php
 # note: drupal's botstrap.inc does: header("Expires: Sun, 19 Nov 1978 05:00:00 GMT");
 # so forget about optimizing php content
 ExpiresActive On
-<FilesMatch "\.(css|js|png)$">
+<FilesMatch "\.(css|js|png|gif|jpg)$">
 ExpiresDefault "access plus 12 hours"
 </FilesMatch>
 
diff --git a/jsmin.py b/jsmin.py
new file mode 100644 (file)
index 0000000..ae75814
--- /dev/null
+++ b/jsmin.py
@@ -0,0 +1,218 @@
+#!/usr/bin/python\r
+\r
+# This code is original from jsmin by Douglas Crockford, it was translated to\r
+# Python by Baruch Even. The original code had the following copyright and\r
+# license.\r
+#\r
+# /* jsmin.c\r
+#    2007-05-22\r
+#\r
+# Copyright (c) 2002 Douglas Crockford  (www.crockford.com)\r
+#\r
+# Permission is hereby granted, free of charge, to any person obtaining a copy of\r
+# this software and associated documentation files (the "Software"), to deal in\r
+# the Software without restriction, including without limitation the rights to\r
+# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\r
+# of the Software, and to permit persons to whom the Software is furnished to do\r
+# so, subject to the following conditions:\r
+#\r
+# The above copyright notice and this permission notice shall be included in all\r
+# copies or substantial portions of the Software.\r
+#\r
+# The Software shall be used for Good, not Evil.\r
+#\r
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r
+# SOFTWARE.\r
+# */\r
+\r
+from StringIO import StringIO\r
+\r
+def jsmin(js):\r
+    ins = StringIO(js)\r
+    outs = StringIO()\r
+    JavascriptMinify().minify(ins, outs)\r
+    str = outs.getvalue()\r
+    if len(str) > 0 and str[0] == '\n':\r
+        str = str[1:]\r
+    return str\r
+\r
+def isAlphanum(c):\r
+    """return true if the character is a letter, digit, underscore,\r
+           dollar sign, or non-ASCII character.\r
+    """\r
+    return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or\r
+            (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126));\r
+\r
+class UnterminatedComment(Exception):\r
+    pass\r
+\r
+class UnterminatedStringLiteral(Exception):\r
+    pass\r
+\r
+class UnterminatedRegularExpression(Exception):\r
+    pass\r
+\r
+class JavascriptMinify(object):\r
+\r
+    def _outA(self):\r
+        self.outstream.write(self.theA)\r
+    def _outB(self):\r
+        self.outstream.write(self.theB)\r
+\r
+    def _get(self):\r
+        """return the next character from stdin. Watch out for lookahead. If\r
+           the character is a control character, translate it to a space or\r
+           linefeed.\r
+        """\r
+        c = self.theLookahead\r
+        self.theLookahead = None\r
+        if c == None:\r
+            c = self.instream.read(1)\r
+        if c >= ' ' or c == '\n':\r
+            return c\r
+        if c == '': # EOF\r
+            return '\000'\r
+        if c == '\r':\r
+            return '\n'\r
+        return ' '\r
+\r
+    def _peek(self):\r
+        self.theLookahead = self._get()\r
+        return self.theLookahead\r
+\r
+    def _next(self):\r
+        """get the next character, excluding comments. peek() is used to see\r
+           if an unescaped '/' is followed by a '/' or '*'.\r
+        """\r
+        c = self._get()\r
+        if c == '/' and self.theA != '\\':\r
+            p = self._peek()\r
+            if p == '/':\r
+                c = self._get()\r
+                while c > '\n':\r
+                    c = self._get()\r
+                return c\r
+            if p == '*':\r
+                c = self._get()\r
+                while 1:\r
+                    c = self._get()\r
+                    if c == '*':\r
+                        if self._peek() == '/':\r
+                            self._get()\r
+                            return ' '\r
+                    if c == '\000':\r
+                        raise UnterminatedComment()\r
+\r
+        return c\r
+\r
+    def _action(self, action):\r
+        """do something! What you do is determined by the argument:\r
+           1   Output A. Copy B to A. Get the next B.\r
+           2   Copy B to A. Get the next B. (Delete A).\r
+           3   Get the next B. (Delete B).\r
+           action treats a string as a single character. Wow!\r
+           action recognizes a regular expression if it is preceded by ( or , or =.\r
+        """\r
+        if action <= 1:\r
+            self._outA()\r
+\r
+        if action <= 2:\r
+            self.theA = self.theB\r
+            if self.theA == "'" or self.theA == '"':\r
+                while 1:\r
+                    self._outA()\r
+                    self.theA = self._get()\r
+                    if self.theA == self.theB:\r
+                        break\r
+                    if self.theA <= '\n':\r
+                        raise UnterminatedStringLiteral()\r
+                    if self.theA == '\\':\r
+                        self._outA()\r
+                        self.theA = self._get()\r
+\r
+\r
+        if action <= 3:\r
+            self.theB = self._next()\r
+            if self.theB == '/' and (self.theA == '(' or self.theA == ',' or\r
+                                     self.theA == '=' or self.theA == ':' or\r
+                                     self.theA == '[' or self.theA == '?' or\r
+                                     self.theA == '!' or self.theA == '&' or\r
+                                     self.theA == '|' or self.theA == ';' or\r
+                                     self.theA == '{' or self.theA == '}' or\r
+                                     self.theA == '\n'):\r
+                self._outA()\r
+                self._outB()\r
+                while 1:\r
+                    self.theA = self._get()\r
+                    if self.theA == '/':\r
+                        break\r
+                    elif self.theA == '\\':\r
+                        self._outA()\r
+                        self.theA = self._get()\r
+                    elif self.theA <= '\n':\r
+                        raise UnterminatedRegularExpression()\r
+                    self._outA()\r
+                self.theB = self._next()\r
+\r
+\r
+    def _jsmin(self):\r
+        """Copy the input to the output, deleting the characters which are\r
+           insignificant to JavaScript. Comments will be removed. Tabs will be\r
+           replaced with spaces. Carriage returns will be replaced with linefeeds.\r
+           Most spaces and linefeeds will be removed.\r
+        """\r
+        self.theA = '\n'\r
+        self._action(3)\r
+\r
+        while self.theA != '\000':\r
+            if self.theA == ' ':\r
+                if isAlphanum(self.theB):\r
+                    self._action(1)\r
+                else:\r
+                    self._action(2)\r
+            elif self.theA == '\n':\r
+                if self.theB in ['{', '[', '(', '+', '-']:\r
+                    self._action(1)\r
+                elif self.theB == ' ':\r
+                    self._action(3)\r
+                else:\r
+                    if isAlphanum(self.theB):\r
+                        self._action(1)\r
+                    else:\r
+                        self._action(2)\r
+            else:\r
+                if self.theB == ' ':\r
+                    if isAlphanum(self.theA):\r
+                        self._action(1)\r
+                    else:\r
+                        self._action(3)\r
+                elif self.theB == '\n':\r
+                    if self.theA in ['}', ']', ')', '+', '-', '"', '\'']:\r
+                        self._action(1)\r
+                    else:\r
+                        if isAlphanum(self.theA):\r
+                            self._action(1)\r
+                        else:\r
+                            self._action(3)\r
+                else:\r
+                    self._action(1)\r
+\r
+    def minify(self, instream, outstream):\r
+        self.instream = instream\r
+        self.outstream = outstream\r
+        self.theA = '\n'\r
+        self.theB = None\r
+        self.theLookahead = None\r
+\r
+        self._jsmin()\r
+        self.instream.close()\r
+\r
+if __name__ == '__main__':\r
+    import sys\r
+    jsm = JavascriptMinify()\r
+    jsm.minify(sys.stdin, sys.stdout)\r
index d538c4c..dc0dd4c 100644 (file)
@@ -20,6 +20,7 @@ Group: Applications/Systems
 Source0: %{name}-%{version}.tar.gz
 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
 BuildArch: noarch
+BuildRequires: python
 
 Vendor: OneLab
 Packager: OneLab <support@one-lab.org>
@@ -57,8 +58,8 @@ This subset of the plewww package has general purpose features for the benefit o
 %setup -q
 
 %build
-echo "There is no build stage for this component"
-echo "All files just need to be installed as is from the codebase"
+echo "Compressing javascript files"
+make compress
 
 %install
 rm -rf $RPM_BUILD_ROOT
@@ -71,7 +72,7 @@ rm -rf $RPM_BUILD_ROOT
 echo "* PLEWWW: Installing web pages"
 mkdir -p $RPM_BUILD_ROOT/var/www/html
 # exclude codebase just in case
-rsync -a --exclude Makefile --exclude httpd --exclude \*.spec --exclude .svn ./ $RPM_BUILD_ROOT/var/www/html/
+rsync -a --exclude jsmin.py --exclude Makefile --exclude httpd --exclude \*.spec --exclude .svn ./ $RPM_BUILD_ROOT/var/www/html/
 
 echo "* PLEWWW: Installing conf files for httpd"
 mkdir -p $RPM_BUILD_ROOT/etc/httpd/conf.d