使用Grunt或类似工具自动化GAE的构建过程,包括uglify,npm,LESS等

时间:2013-10-31 16:54:05

标签: google-app-engine npm gruntjs

我是一个用Python编写的Google Appengine应用程序。我对这个平台以及我正在使用的IDE(JetBrain的PyCharm)感到相对满意。

由于我已准备好将我的项目提升到新的水平,我正在寻找精简“构建过程”的技巧和最佳实践建议,并牢记以下几点:

  1. 包管理器:我的html文件正在使用jQuery和其他一些前端库。现在我正在用我的项目检查这个库。我想使用某种包管理器(例如NPM),能够升级和其他好东西。
  2. 处理不力:我想预处理我的CSS文件。
  3. Uglify:我想缩小/ uglify我的静态javascript文件。或者,如果可能的话,我也想对我的动态(jinja2)文件进行uglify。
  4. 开发/制作:在本地,我想使用各种库的开放版本(以及我自己的文件)。在制作时,我想使用缩小版本,并修复所有引用
  5. 部署到GAE:appcfg也应该是自动化的一部分。但是,将来我可能想在其他地方部署。
  6. 某些文件只能在开发期间使用。一些文件应该在一些预处理之后部署到GAE。

    我对Grunt有点熟悉,我认为这是完成此类任务的正确任务管理器,但我希望听到基于其他人的解决方案(Maven,Ant,Microsoft Build,make等)。请分享你的智慧。

2 个答案:

答案 0 :(得分:3)

有一件重要的事情需要考虑,这将使这一切变得更加复杂:缓存破坏。

假设您在custom.js中有自己的脚本。您希望缓存custom.js,以便您的Web应用程序快速运行。但是,当您更改代码时,您希望下载并使用新版本而不是旧的缓存版本。

缓存清除的常见解决方案是从custom.js创建哈希值并将其添加到您的文件名中。因此,您提供custom.sdf879skdfhsdf9087we.js,当您更新代码时,您将提供custom.custom.gjf87dskhfhsdfv787we.js。同样的事情也适用于你的CSS。

显然你想自动化这个。其次,当您提供HTML时,它需要知道要插入HTML文件的正确脚本/ css文件名。这是困难的部分。

一些python框架为您处理此问题。例如Django 1.4,但我在Django 1.3上启动了我的应用程序,我不得不自己动手。所以我想要的完整过程与你的过程类似:

  • 将多个js文件合并为一个文件
  • minify js
  • 为CSS创建哈希并更新输出文件名。
  • 生成已更新输出文件名的python文件。

我的框架然后使用生成的python文件中的文件名。我的框架中也有一个全局设置,我可以打开一个“调试”模式,它将使用原始的js文件进行调试。 CSS也有类似的情况。

  1. 我将库保留在我的实际GAE项目文件夹之外,并使用软链接。 appcfg.py将关注该链接并上传链接指向的任何内容。

  2. (和3)我使用Apache ant在实际构建之前进行依赖性检查。因此,不是每次都构建所有内容,而是只构建改变了的内容。我不知道Grunt是否进行了依赖性检查,我认为它只是重建了一切。

  3. 最好设置框架以提供缩小/合并文件或原始文件以进行调试。这将涉及一些自定义代码。

  4. 我刚写了一个小python脚本来调用ant(执行前4个步骤),然后调用appcfg.py

  5. 作为额外提示,您可以检测您是在GAE生产还是在开发服务器上运行:

    import os
    
    from google.appengine.api import apiproxy_stub_map
    
    have_appserver = bool(apiproxy_stub_map.apiproxy.GetStub('datastore_v3'))
    on_production_server = have_appserver and \
        not os.environ.get('SERVER_SOFTWARE', '').lower().startswith('devel')
    

    我还在两个不同的应用程序ID下运行我的应用程序,一个用于实际生产,另一个用作GAE上的测试环境。我可以使用以下两者来区分:

    from google.appengine.api.app_identity import get_application_id
    

    如果你想使用ant,这里是我的ant构建文件的精简版本。我仍在使用YUI压缩器而不是uglify。它会生成一个名为script_hashes.py的文件,其中包含我稍后在模板中使用的脚本文件的所有名称。

    <project name="eat" default="complete" basedir=".">
    <taskdef resource="net/sf/antcontrib/antlib.xml">
        <classpath>
            <pathelement location="ant-contrib-1.0b3.jar"/>
        </classpath>
    </taskdef>
    <target name="concatenate" depends="clean" description="Concatenate all files for stove POS">
        <concat destfile="tmp/bootstrap-dropdown-transition-modal-tmp.js">
            <filelist dir="bootstrap/docs/assets/js" files="bootstrap-dropdown.js, bootstrap-transition.js, bootstrap-modal.js"/>
        </concat>
        <concat destfile="tmp/stovepos-tmp.js">
            <filelist dir="stove/scripts" files="pos-combo1.js, pos-combo2.js, pos-combo3.js"/>
            <filelist dir="eat/scripts" files="eat.js, eatorder.js, eatchannel.js, fastbtn.js"/>
            <filelist dir="stove/scripts" files="soundmanager2-nodebug-jsmin.js, stovepos.js"/>
        </concat>
        <concat destfile="tmp/stovehome-tmp.js">
            <fileset file="bootstrap/docs/assets/js/bootstrap-dropdown.js"/>
            <fileset file="eat/scripts/eat.js"/>
            <fileset file="stove/scripts/stovehome.js"/>
        </concat>
    </target>
    <target name="compress_js" depends="concatenate" description="Compress Javascript">
        <mkdir dir="scripts"/>
        <for param="file">
          <path>
              <fileset dir="tmp" includes="**/*.js"/>
          </path>
          <sequential>
              <apply executable="java" parallel="true">
                  <fileset file="@{file}" />
                  <arg line="-jar" />
                  <arg path="../Downloads/yuicompressor-2.4.7/build/yuicompressor-2.4.7.jar" />
                  <srcfile />
                  <arg line="-o" />
                  <mapper type="glob" from="*-tmp.js" to="scripts/*-min.js" />
                  <targetfile />
              </apply>
          </sequential>
        </for>
    </target>
    <target name="compress_css" depends="concatenate" description="Compress CSS">
        <mkdir dir="scripts"/>
        <for param="file">
          <path>
              <fileset dir="tmp" includes="**/*.css"/>
          </path>
          <sequential>
              <apply executable="java" parallel="true">
                  <fileset file="@{file}" />
                  <arg line="-jar" />
                  <arg path="../Downloads/yuicompressor-2.4.7/build/yuicompressor-2.4.7.jar" />
                  <srcfile />
                  <arg line="-o" />
                  <mapper type="glob" from="*-tmp.css" to="scripts/*-min.css" />
                  <targetfile />
              </apply>
          </sequential>
        </for>
    </target>
    <target name="clean_tmp" depends="compress_js, compress_css" description="remove all -tmp files">
        <delete dir="tmp"/>
    </target>
    <target name="complete_js" depends="clean_tmp" description="add checksum to filename">
        <for param="file">
          <path>
              <fileset dir="scripts" includes="**/*.js"/>
          </path>
          <sequential>
            <var name="md5" unset="true"/>
            <checksum file="@{file}" property="md5"/>
            <move todir="scripts">
                <fileset file="@{file}"/>
                <globmapper from="*.js" to="*.${md5}.js"/>
            </move>
            <var name="filename" unset="true"/>
            <propertyregex  input="@{file}" regexp="([^/]*).js$$" select="\1" property="filename"/>
            <var name="filename_" unset="true"/>
            <propertyregex  input="${filename}" regexp="-" replace="_" property="filename_"/>
            <propertyfile file="script_hashes.py">
                <entry key="${filename_}" value="'${md5}'"/>
            </propertyfile>
          </sequential>
        </for>
    </target>
    <target name="complete_css" depends="clean_tmp" description="add checksum to filename">
        <for param="file">
          <path>
              <fileset dir="scripts" includes="**/*.css"/>
          </path>
          <sequential>
            <var name="md5" unset="true"/>
            <checksum file="@{file}" property="md5"/>
            <move todir="scripts">
                <fileset file="@{file}"/>
                <globmapper from="*.css" to="*.${md5}.css"/>
            </move>
            <var name="filename" unset="true"/>
            <propertyregex  input="@{file}" regexp="([^/]*).css$$" select="\1" property="filename"/>
            <var name="filename_" unset="true"/>
            <propertyregex  input="${filename}" regexp="-" replace="_" property="filename_"/>
            <propertyfile file="script_hashes.py">
                <entry key="${filename_}_css" value="'${md5}'"/>
            </propertyfile>
          </sequential>
        </for>
    </target>
    <target name="complete" depends="complete_js, complete_css">
    </target>
    <target name="clean" description="remove all -tmp and -min files">
        <delete dir="tmp"/>
        <delete dir="scripts"/>
        <delete file="script_hashes.py"/>
    </target>
    </project>
    

答案 1 :(得分:1)

我没有使用谷歌应用程序引擎的任何经验,但Grunt真的是我的面包和黄油。有一大堆插件可以自动化你在那里列出的所有东西以及许多其他插件。它的设置快速简便,如果有一个用户已经没有解决的用例,那么它就是引擎盖下的所有JavaScript,因此您可以编写自己的自定义函数。回答你的观点:

  1. 使用Bower。许多常见的前端库(如jQuery和Bootstrap)都有可用的组件,版本管理也很简单。如果不需要,您不必检查组件到版本控制;它们很简单bower install

  2. https://github.com/gruntjs/grunt-contrib-less

  3. https://github.com/gruntjs/grunt-contrib-uglify

  4. 通过使用Bower进行实际的组件管理以及类似https://github.com/yeoman/grunt-usemin之类的操作来解决这个问题,以便稍后在构建步骤中将文件中的引用替换为缩小的引用。我在usemin上写了另一个答案,所以不需要在这里重复:grunt-usemin: Defining custom flow

  5. 再次,没有使用gae或此插件的经验,所以我无法保证它有多好(或不是),但无论如何它仍然存在:https://github.com/maciejzasada/grunt-gae

  6. 希望这有帮助。