ant 是 java 方面有用的构建工具,贯穿开发的编译,打包,测试,部署等各个阶段,其实对于前端开发,如果合理利用同样能够大幅减少繁琐的构建过程,摆脱各个平台晦涩异化的批处理脚本。
1.文件与目录准备操作
主要包括前期的一些准备工作:例如最常用的将源码复制到构建目录,准备进一步的操作,主要利用 copy 任务
<copy todir="目的地目录"
encoding="读取编码"
outputencoding="写入编码">
<fileset dir="文件来源目录"
includes="**/*(文件名称正则)"/>
</copy>
其中 fileset 表示无序文件集合,若要求有序,可替换为 filelist 有序列表以及广义的 path。 注意点为 fileset 的 includes 与 excludes 属性,其中 ** 匹配0个或多个目录,*匹配0个或多个文件。在copy过程中也可进行一些过滤处理,设置过滤器集合对源码进行一些标记替换,如加入构建时间:
<!--设置日期格式-->
<tstamp>
<format property="timestamp.isoformat"
pattern="yyyy-MM-dd' 'HH:mm:ss" locale="en"/>
</tstamp>
<copy ...">
<fileset ... />
<filterset>
<filter token="TIMESTAMP" value="${timestamp.isoformat}"/>
</filterset>
</copy>
即将源码中的预设标记 @[email protected],替换为当前时间。其它还有 mkdir , move 等类似对对应批处理命令的加强。
2.压缩
对应于编译型语言构建的编译阶段,压缩优化 源码 ,推荐 css采用 yuicompressor ,JavaScript采用 google closure-compiler 。ant 可以通过 java 任务来方便地调用外部 java 程序,并且在同一 jvm 内运行也避免了通过 exec 执行外部程序的效率问题。不过由于压缩器每次只能针对单个源文件进行操作,这时就需要使用 ant 的 批处理任务(bulk task):apply
<apply executable="java"
dest="目的地"
failοnerrοr="true"
parallel="并行执行"
>
<fileset dir="css源地址"
includes="**/*.css"/>
<arg line="-jar"/>
<arg path="yuicompressor.jar"/>
<arg line="--charset ${charset}"/>
<srcfile/>
<arg line="-o"/>
<targetfile/>
<mapper type="regexp" from="^(.*)\.(css|js)$" to="\1-min.\2"/>
</apply>
两个比较关键的元素为 srcfile 与 targetfile ,srcfile 注意用fileset的每个文件替换掉 srcfile,并且经过 mapper 转换后,用转换后包含完整路径的文件名替换掉 targetfile,达到了对文件集合中的每个文件逐一执行 yuicompressor 的目的。
3.打包
对于前端代码,打包的意义在于减少http链接数 ,主要用到的 ant 任务:concat ,将多个文件合并为一个文件
<concat destfile="目的文件"
encoding="读取编码"
outputencoding="写入编码">
<filelist .../>
</concat>
同 copy 类似,若要求合并后的文件内容顺序,则使用filelist,否则使用fileset即可。
注意:
windows 下保存 utf-8 编码的文件时常带 bom 标记 ,而多个 bom 文件如果使用 concat 原封不动合并起来,那么实际上该合并后的脚本是语法错误的,这时合并时需要使用 filterchain 来消除文件中的 bom 标记:
<concat destfile="z-pkg.js" encoding="utf-8" outputencoding="utf-8">
<path location="x"/>
<path location="y"/>
<filterchain>
<deletecharacters chars="" />
</filterchain>
</concat>
而另一方面 ie 载入 utf-8 格式的文件如果不带 bom ,则会有奇异问题(缓存情况下的动态加载报错),这是最好合并后再使用 header 把 bom 加上:
<concat destfile="z-pkg.js" encoding="utf-8" outputencoding="utf-8">
<header filtering="no" trimleading="yes"></header>
<path location="x"/>
<path location="y"/>
<filterchain>
<deletecharacters chars="" />
</filterchain>
</concat>
4.部署编码注意
2010-09-25 update:
打包与压缩只是单单对文本进行变换处理,并没有改变文件的编码,而最终部署到应用环境时,常常要求对编码的普适性,在中文环境中则不可避免会遇到各种各样的乱码,还好 javascript 支持 unicode 转义字符 ,
In string literals, regular expression literals, and identifiers, any character (code unit) may also be expressed as a Unicode escape sequence consisting of six characters, namely \u plus four hexadecimal digits.
通过 native2ascii 任务(yui compressor 可忽略这步),可以将代码中的中文字符转换为 "\uxxxx" 对应的 unicode 表示。
<native2ascii
encoding="${charset}"
src="${build.dir}"
dest="${build.dir}"
includes="**/*-pkg-min.js"
>
<mapper type="regexp" from="^(.*)\.js$" to="\1-ascii.js"/>
</native2ascii>
属性同 copy 部分,mapper 同 apply 部分。
5.分支与循环
如果想要对构建过程进行细粒度的控制,例如根据依赖条件进行特定的构建,则可使用 ant contrib 第三方任务包的 if 任务 。if的判断条件可以直接使用ant的condition 任务的条件。
xmlns:ac="antlib:net.sf.antcontrib"
<ac:if>
<!--能否上网?-->
<http url="http://www.github.com"/>
<ac:then>
<!--能-->
</ac:then>
<ac:else>.
<!--不能-->
</ac:else>
</ac:if>
另外如果代码依赖于目录组织结构,相互间具备平行关系,则可使用循环 for 任务,依次对所有目录进行操作,使得构建具备良好的扩展性。
<ac:for param="biz">
<path>
<dirset dir="循环目录集合父目录" includes="*"/>
</path>
<sequential>
<!--通过 @{biz} 得到单个枚举-->
</sequential>
</ac:for>
6.多模块组织
当项目中多个模块间构建文件具备明显的重复目标(target)时,可通过 import 任务,利用 template 模式抽像公共目标为模板,各个子模块引入公共模板后进行特化的目标重写或者更简单的属性重定义。
公共模板:
<project>
<target name="common">
取 属性 ${p1} 进行操作
</target>
</project>
子模块
<project>
定义 p1 属性
导入公共目标模板
<import ...>
</project>
子模块可继承公共模板的所有目标,只需定义相应属性即可。
7.多模块构建
目前为止,一个项目的所有子模块都有了自己独立的构建文件,若我们想一次将整个项目构建,则需要对所有的子模块运行各自的构建,这时就需要 subant 任务,运行构建文件的集合。
<subant target="所有的构建目标" inheritall="子构建同父构建属性独立">
<fileset dir="所有构建文件来源" includes="**/build.xml"/>
</subant>
其中fileset 即为构建文件的集合。
8.扩展 ant 命令
待续
总结
ant 初始是作为声明式语言而设计,而由于其接口开放性,自从出现了 ant contrib 等第三方任务包,使其具备了过程化,越来越接近于传统的批处理脚本,并且由于 java 天生的跨平台性以及 xml 完善的描述性语法,相信在 java 构建之外也会有广阔的天地。