0

我有一个用 Python 编写的 Google Appengine 应用程序。我对这个平台以及我正在使用的 IDE(JetBrain 的 PyCharm)都比较满意。

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

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

某些文件应仅在开发期间可用。一些文件应该部署到 GAE,可能需要经过一些预处理。

我对 Grunt 有点熟悉,我相信它是此类任务的正确任务管理器,但我想听听基于其他解决方案(Maven、Ant、Microsoft Build、make 等)的解决方案。请分享你的智慧。

4

2 回答 2

3

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

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

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

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

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

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

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

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

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

  3. 最好将您的框架设置为提供缩小/组合文件或用于调试的原始文件。这将涉及一些自定义代码。

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

作为一个额外的提示,您可以检测您是在 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>
于 2013-11-01T17:13:35.497 回答
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

希望这可以帮助。

于 2013-10-31T19:22:09.713 回答