6

Since webassets won't work on GAE to compress a js/css on the fly, it seems the best approach is to do it upon deployment.

After a lot of googling I came up with the script below to achieve this.

At first I thought the best would be to leave the javascript path in base.html as it is and simply compress the css/js.

cssmin compresses the css and overwrites the original. However closure doesn't allow overwriting the original and this concept fails already.

The second problem is, even if I got closure overwriting the original file, caching will be a problem. For this reason each deployment of minified css/js should come with a random number in file name, so that the new versions are actually picked up after a new deployment. With the concept I came up with, this won't be possible though.

Hence the only way to achieve this would be modifying the base.html with sed or something.

Before I reinvent the wheel, is there a better approach to do this? Many thanks

import sys, os
import cssmin

def main():
    if len(sys.argv) == 1:
        return

    appId = sys.argv[1]
    print "appId", appId

    cmd = r'java -jar compiler.jar --js=/src/application/static/f11/f11.js --js_output_file=/src/application/static/f11/f11.min.js'
    os.system(cmd)

    output = cssmin.cssmin(open('/src/application/static/f11/f11.css').read())
    f = open('/src/application/static/f11/f11.css','w')
    f.write(output)    

    # Perform appcfg.py to update GAE server
    cmd = r'"\google_appengine\appcfg.py"'
    os.system(cmd + " update . " + " -A %s"%appId)


if __name__ == "__main__":
    main()
Community
  • 1
  • 1
Houman
  • 64,245
  • 87
  • 278
  • 460
  • Is there a specific reason for not compressing css/js with command-line tools and then having a little bash script to take care of the deployment procedure? My deployment script consists of both minifying the css/js I use as well as running the `appcfg` command. – dlebech Jun 20 '13 at 18:00
  • In development you need the uncompressed assets and for production you need them compressed. Do you change the path to your assets from .js to .min.js manually by hand? And how do you deal with versioning? – Houman Jun 20 '13 at 18:14
  • I have [shared my deploy/development scripts here](https://gist.github.com/dlebech/5846132). In my html, I always reference the .min.js files and then I have a script for development (run_debug.sh) and one for deployment (deploy.sh). They both use the compile.sh script which is situated in my static folder. It either just copies development css/js to the .min file or it does actual minification. I don't know if this is helpful but that is at least how I make it work :-) – dlebech Jun 23 '13 at 19:15
  • I can suggest one alternative solution here: utilizing memcached and a version number/cache buster on a single `/style.css?cb=k2h4k23h4`. The style.css points to a class which checks for the cached key of same name and if not found, uses a list of css files, compresses them and merges in order, then stores in memcached. Subsequent requests of the same file come from the cache until next version change when uploading new assests. For my use case, where assets don't change often, this single step of changing version number is OK. This URL could also be used as an origin for an external CDN. – ljs.dev Sep 03 '13 at 17:50
  • and just for other people landing on this somewhat old question, there is the built in support for Pagespeed within App Engine: https://developers.google.com/appengine/docs/python/config/appconfig?hl=en#Python_app_yaml_Custom_PageSpeed_configuration – ljs.dev Sep 03 '13 at 17:58

1 Answers1

1

You should do a one-time detection on instance startup that sets some global vars depending on if your app is running on the dev server. You generate URLs you need to your assets based on that, and have a list of versions for each asset.

python example (full context):

JQUERY_VERSION = '1.7.2'
JQUERY_UI_VERSION = '1.8.20'
ANGULAR_VERSION = '1.0.2'

if os.environ['SERVER_SOFTWARE'].startswith('Google'):
    JQUERY_URL = "//ajax.googleapis.com/ajax/libs/jquery/%(version)s/jquery.min.js" %{ 'version': JQUERY_VERSION }
    JQUERY_UI_URL = "//ajax.googleapis.com/ajax/libs/jqueryui/%(version)s/jquery-ui.min.js" %{ 'version': JQUERY_UI_VERSION }
    JQUERY_UI_CSS_URL = "//ajax.googleapis.com/ajax/libs/jqueryui/%(version)s/themes/base/jquery.ui.all.css" %{ 'version': JQUERY_UI_VERSION }
    ANGULAR_URL = "//ajax.googleapis.com/ajax/libs/angularjs/%(version)s/angular.min.js" %{ 'version': ANGULAR_VERSION }
else:
    JQUERY_URL = "/static/js/jquery-%(version)s.min.js" %{ 'version': JQUERY_VERSION }
    JQUERY_UI_URL = "/static/js/jquery-ui-%(version)s.min.js" %{ 'version': JQUERY_UI_VERSION }
    JQUERY_UI_CSS_URL = "/static/css/jquery-ui/jquery-ui-%(version)s.css" %{ 'version': JQUERY_UI_VERSION }
    ANGULAR_URL = "/static/js/angular-%(version)s.min.js" %{ 'version': ANGULAR_VERSION }

go example (full context):

func init() {
    angular_ver := "1.0.5"
    bootstrap_ver := "2.3.1"
    jquery_ver := "1.9.1"

    if appengine.IsDevAppServer() {
        Angular = fmt.Sprintf("/static/js/angular-%v.js", angular_ver)
        BootstrapCss = fmt.Sprintf("/static/css/bootstrap-%v.css", bootstrap_ver)
        BootstrapJs = fmt.Sprintf("/static/js/bootstrap-%v.js", bootstrap_ver)
        Jquery = fmt.Sprintf("/static/js/jquery-%v.js", jquery_ver)
    } else {
        Angular = fmt.Sprintf("//ajax.googleapis.com/ajax/libs/angularjs/%v/angular.min.js", angular_ver)
        BootstrapCss = fmt.Sprintf("//netdna.bootstrapcdn.com/twitter-bootstrap/%v/css/bootstrap-combined.min.css", bootstrap_ver)
        BootstrapJs = fmt.Sprintf("//netdna.bootstrapcdn.com/twitter-bootstrap/%v/js/bootstrap.min.js", bootstrap_ver)
        Jquery = fmt.Sprintf("//ajax.googleapis.com/ajax/libs/jquery/%v/jquery.min.js", jquery_ver)
    }
}

If you have a deploy stript that minifies your local (i.e., non CDN) content, run that here, and use the method above, but with a local url with a .min extension.

mjibson
  • 16,852
  • 8
  • 31
  • 42