diff --git a/ .npmignore b/ .npmignore
new file mode 100644
index 0000000..6db059f
--- /dev/null
+++ b/ .npmignore
@@ -0,0 +1,3 @@
+test
+demo
+doc
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 91dfed8..85cc041 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,6 @@
+.cache
+.debug.js
.DS_Store
-node_modules
\ No newline at end of file
+*.sublime-workspace
+node_modules
+demo
diff --git a/README.md b/README.md
index 83ea958..ce84088 100644
--- a/README.md
+++ b/README.md
@@ -1,214 +1,203 @@
-# TmodJS-前端模板编译工具
+# TmodJS
-###### 基于文件系统组织维护前端模板
+> 项目已经停止维护,请使用更好的代替方案:[art-template-loader](https://github.com/aui/art-template-loader)
-## 关于 TmodJS
+TmodJS(原名 atc)是一个简单易用的前端模板预编译工具。它通过预编译技术让前端模板突破浏览器限制,实现后端模板一样的同步“文件”加载能力。它采用目录来组织维护前端模板,从而让前端模板实现工程化管理,最终保证前端模板在复杂单页 web 应用下的可维护性。同时预编译输出的代码经过多层优化,能够在最大程度节省客户端资源消耗。
-TmodJS(原名 atc)是一款前端模板编译工具,它可以让前端模板外置、实现类似后端模板一样按文件与目录组织前端模板,并且模板之间可以使用``include``语句相互包含。
+一、**按文件与目录组织模板**
-### 像后端一样书写前端模板
-
-相对与前端模板,后端模板有两个优秀的特征:
-
-1. 模板按文件与目录组织
-2. 模板之间可以相互引用
-
-通过 TmodJS 预编译技术让前端模板突破浏览器的文本文件加载限制,支持模板按文件存放,并且支持多层目录组织模板,并且模板之间可通过``include``语句进行复用。
-
-### 模板编译输出为 js 文件
-
-它会将模板编译成 js 文件,编译后的代码体积非常小,不包含模板引擎也无需依赖脚本加载器。因为是预编译,省去前端模板客户端动态编译过程,也能够在移动设备中节省一定的系统资源。
-
-编译成 js 后,原来字符串模板遇到的跨域加载、异步按需加载已经不是问题,甚至可以接入 GruntJS 等自动化工具进行部署。
-
-### 支持接入 AMD 或者 CMD 部署工具
-
-默认情况下,模板目录将会被打包成 js, 可以直接在页面中使用传统 Script 标签加载,简单且有效。除此之外还可以设置将单个模板文件输出为单个的 AMD 、CMD 异步模块,以便接入它们自动化部署工具进行深度定制化的优化,这样可以实现按需加载、合并等高级的优化手段。
-
-### 横跨前后端运行
-
-支持输出基于 NodeJS 的同步规范模块,前后端可轻松的共用同一套模板。
-
-### 动态调试支持
+```
+template('tpl/home/main', data)
+```
-支持输出调试版本,模板运行中错误可精确到模板源文件所在行。
+二、**模板支持引入子模板**
-### 即时编译
+```
+{{include '../public/header'}}
+```
-支持设置检测模板的修改进行即时编译,这样几乎可以忽略编译过程的存在,模板编写只需要这两个步骤:1、修改模板并保存 2、刷新浏览器预览效果
+TmodJS 一经启动,就无需人工干预,每次模板创建与更新都会自动编译,引入一个 js 即可使用``template(path)``接口调用本地模板文件,直到正式上线都无需对代码进行任何修改,整个过程简单自然。
+
+## 所有特性
+
+0. 编译模板为不依赖引擎的 js 文件
+1. 前端模板按文件与目录组织
+3. 模板之间支持引入外部模板
+4. 使用同步模板加载接口
+5. 可选多种规范的模块输出:AMD、CMD、CommonJS
+6. 支持作为 GruntJS 的插件构建项目
+7. 模板目录可被前后端共享
+8. 支持检测修改即时编译
+9. 支持模板运行时调试
+10. 配置文件支持多人共享
+
+若想深入了解,请阅读:《[进击!前端模板工程化](https://github.com/aui/tmodjs/blob/master/doc/why-tmodjs.md)》
+
+## 文档目录
+
+* [安装](#安装)
+* [编写模板](#编写模板)
+* [编译模板](#编译模板)
+* [使用模板](#使用模板)
+* [演示](#演示)
+* [配置](#配置)
+* [grunt](#grunt)
+* [gulp](#gulp)
+* [常见问题](#常见问题)
+* [更新日志](#更新日志)
+* [加入我们](#加入我们)
+* [授权协议](#授权协议)
## 安装
-先安装 [NodeJS](http://nodejs.org) 与 npm (最新版 NodeJS 已经附带 npm),执行:
+使用 [NodeJS](http://nodejs.org) 附带的``npm``命令,执行:
```
-$ npm install tmodjs -g
+npm install -g tmodjs
```
-> Mac OSX 可能需要管理员权限运行: ``$ sudo npm install tmodjs -g``
+> Mac OSX 可能需要管理员权限运行: ``sudo npm install -g tmodjs``
+
+## 编写模板
+TmodJS 的前端模板不再耦合在业务页面中,而是和后端模板一样有专门的目录管理。目录名称只支持英文、数字、下划线的组合,一个模板对应一个``.html``文件。
-## 快速入门
+支持基本的模板语法,如输出变量、条件判断、循环、包含子模板。[模板语法参考](https://github.com/aui/tmodjs/wiki/模板语法)
-学习 TmodJS 只需要理解这四个关键点就好,7分钟可入门:
+> 完全支持 [artTemplate](https://github.com/aui/artTemplate) 的语法
-1. 建立模板目录:TmodJS 是基于目录编译,你至少要给项目建立一个专用的前端模板目录
-2. 编写模板:你需要了解 TmodJS 的模板语句,如输出变量、条件判断、循环等
-3. 编译模板:编译模板你需要知道如何使用命令参数
-4. 调用模板:如何在项目中加载模板
+## 编译模板
-### 一、建立模板目录
+只需要运行``tmod``这个命令即可,默认配置参数可以满足绝大多数项目。
-TmodJS 的前端模板不再耦合在业务页面中,而是和后端模板一样有专门的目录管理。目录名称支持英文、数字、下划线。
+```
+tmod [模板目录] [配置参数]
+```
-### 二、编写模板
+模板目录必须是模板的根目录,若无参数则为默认使用当前工作目录,tmodjs 会监控模板目录修改,每次模板修改都会增量编译。
-一个模板对应一个文件,模板后缀可以是``.html``、``.htm``、``.tpl``。
+### 配置参数
+
+* ``--debug`` 输出调试版本
+* ``--charset value`` 定义模板编码,默认``utf-8``
+* ``--output value`` 定义输出目录,默认``./build``
+* ``--type value`` 定义输出模块格式,默认``default``,可选``cmd``、``amd``、``commonjs``
+* ``--no-watch`` 关闭模板目录监控
+* ``--version`` 显示版本号
+* ``--help`` 显示帮助信息
-模板支持输出变量、条件判断、循环、包含子模板,请查看:[模板语法参考](https://github.com/aui/tmodjs/wiki/模板语法)
+配置参数将会保存在模板目录[配置文件](#配置)中,下次运行无需输入配置参数(``--no-watch`` 与 ``--debug`` 除外)。
-### 三、编译模板
+#### 示例
```
-$ tmod [path] [options]
+tmod ./tpl --output ./build
```
-其中 path 是模板目录。TmodJS 基于目录进行处理。
+## 使用模板
-#### options
-
-* ``-w``或``--watch``设置监控模板修改触发编译
-* ``-d``或``--debug``输出调试版本
-* ``--charset value``定义模板编码,默认``utf-8``
-* ``--output value``定义输出目录,默认``./build``
-* ``--type value``定义输出模块格式,默认``templatejs``,可选``cmd``、``amd``、``commonjs``
-* ``--version``显示 TmodJS 版本号
-* ``--help``显示帮助信息
+根据编译的``type``的配置不同,会有两种不同使用方式:
-运行完成后,程序会将模板编译成 js 文件。如果你经常需要修改模板,可以开启``-w``参数,让它检测修改自动编译。
+### 使用默认的格式
-> 首次运行后,会对模板根目录进行初始化,生成 package.json,也可以编辑它进行更多配置,包括语法、公用辅助方法、压缩选项等,参考[配置](#配置)。
-> 当模板目录初始化后,下次编译模板可以无需输入配置参数,将沿用上一次的参数进行编译(-w 与 -d 除外)。
+TmodJS 默认将整个目录的模板压缩打包到一个名为 template.js 的脚本中,可直接在页面中使用它:
-### 四、调用模板
+
+
-模板编译后,模板目录会生成 build 子目录,里面包含了所有的模板编译版本。其中 build/template.js 是压缩后的模板包,通常情况下你只需要在页面中引入它就好。例如:
+> template.js 还支持 RequireJS、SeaJS、NodeJS 加载。[示例](http://aui.github.io/tmodjs/test/index.html)
-```
-
-```
+### 指定格式(amd / cmd / commonjs)
-这是默认的加载方式,除此之外还支持 RequireJS、SeaJS、NodeJS 加载。[示例](http://aui.github.io/tmodjs/test/index.html)
-
-#### 模板接口
+此时每个模板就是一个单独的模块,无需引用 template.js:
```
-template(path, data)
-```
-
-path 参数是**模板目录相对路径**,并且**不带后缀名**,例如 :
-
-```
-var html = template('news/list', {hot: [...]});
-document.getElementById('list').innerHTML = html;
+var render = require('./tpl/build/news/list');
+var html = render(_list);
```
-## 编译演示项目
+> 注意:模板路径不能包含模板后缀名
-源码包中 ./test 是一个演示项目,./test/tpl 是项目的模板目录,包含了若干模板。你可以通过这个演示项目快速了解 TmodJS 用法以及模板语法、模板加载方式。
+## 演示
-首先,使用 cd 命令切换到 TmodJS 目录后,你可以编译这个目录模板:
+[TmodJS 源码包](https://github.com/aui/tmodjs/archive/master.zip)中``test/tpl``是一个演示项目的前端模板目录,基于默认配置。切换到源码目录后,编译:
```
-$ tmod ./test/tpl
+tmod test/tpl
```
-编译完毕后你可以在浏览器中打开 [test/index.html](http://aui.github.io/tmodjs/test/index.html) 查看如何加载模板。
+编译完毕后你可以在浏览器中打开 [test/index.html](http://aui.github.io/tmodjs/test/index.html) 查看如何使用编译后的模板。
-## 对外接口
+## 配置
-若想集成 TmodJS 到其它自动化工具中(如 GruntJS),可以使用 TmodJS 提供的 API:
+TmodJS 的项目配置文件保存在模板目录的 package.json 文件中:
```
-var TmodJS = require('tmodjs');
-
-// 模板目录
-var path = './demo/templates';
-
-// 配置
-var options = {
- output: './build',
- charset: 'utf-8',
- debug: false // 此字段不会保存在配置中
-};
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "default",
+ "runtime": "template.js",
+ "combo": true,
+ "minify": true,
+ "cache": false
+ }
+}
+```
-// 初始化 TmodJS
-// path {String} 模板目录
-// options {Object} 选项
-TmodJS.init(path, options);
+字段 | 类型 | 默认值| 说明
+------------ | ------------- | ------------ | ------------
+output | String | ``"./build"`` | 编译输出目录设置。如果设置为 false 则不输出
+charset | String | ``"utf-8"`` | 模板使用的编码(暂时只支持 utf-8)
+syntax | String | ``"simple"`` | 定义模板采用哪种语法。可选:``simple``、``native``
+helpers | String | ``null`` | 自定义辅助方法路径
+escape | Boolean | ``true`` | 是否过滤 XSS。如果后台给出的数据已经进行了 XSS 过滤,就可以关闭模板的过滤以提升模板渲染效率
+compress | Boolean | ``true`` | 是否压缩 HTML 多余空白字符
+type | String | ``"default"`` | 输出的模块类型,可选:``default``、``cmd``、``amd``、``commonjs``
+runtime | String | ``"template.js"`` | 设置输出的运行时名称
+alias | String | ``null`` | 设置模块依赖的运行时路径(仅针对于非``default``的类型模块配置字段。如果不指定模块内部会自动使用相对 runtime 的路径)
+combo | Boolean | ``true`` | 是否合并模板(仅针对于 default 类型的模块)
+minify | Boolean | ``true`` | 是否输出为压缩的格式
+cache | Boolean | ``true`` | 是否开启编译缓存
+verbose | Boolean | ``true`` | 是否打印日志
+
+## grunt
-// 编译模板
-// file {String} 参数可选,无则编译整个模板目录,否则编译指定的模板文件
-// recursion {Boolean} 若为 false 则不编译依赖的模板
-TmodJS.compile(file, recursion);
+让 TmodJS 作为 Grunt 的一个插件使用:
-// 监控模板修改
-TmodJS.watch();
+```
+npm install grunt-tmod --save-dev
+```
-// 保存用户设置到模板目录 package.json 文件中
-TmodJS.saveUserConfig();
+由[@Jsonzhang](https://github.com/Jsonzhang)开发,项目主页:
-// 监听编译过程的事件
-// 支持:change、load、compileError、combo
-TmodJS.on('compile', function (data) {});
-```
+
-## 配置
+## gulp
-配置最终会保存在模板目录的 package.json 文件中,可以对它直接修改。
+让 TmodJS 作为 Gulp 的一个插件使用:
```
-{
- // 编译输出目录设置
- output: './build',
-
- // 模板使用的编码。(注意:非 utf-8 编码的模板缺乏测试)
- charset: 'utf-8',
-
- // 模板合并规则
- // 注意:type 参数的值为 templatejs 才会生效
- combo: ['*'],
-
- // 定义模板采用哪种语法,可选:
- // simple: 默认语法,易于读写。可参看语法文档
- // native: 功能丰富,灵活多变。语法类似微型模板引擎 tmpl
- syntax: 'simple',
-
- // 自定义辅助方法路径
- helpers: null,
-
- // 是否输出为压缩的格式
- minify: true,
-
- // 是否内嵌异步加载插件(beta)
- // 可以支持 template.async(path, function (render) {}) 方式异步载入模板
- // 注意:type 参数是 templatejs 的时候才生效
- async: false,
-
- // 是否嵌入模板引擎,否则编译为不依赖引擎的纯 js 代码
- // 通常来说,模板不多的情况下,编译为原生的 js 打包后体积更小,因为不必嵌入引擎
- // 当模板很多的时候,内置模板引擎,模板使用字符串存储的方案会更能节省空间
- engine: false,
-
- // 输出的模块类型(不区分大小写),可选:
- // templatejs: 模板目录将会打包后输出,可使用 script 标签直接引入,也支持 NodeJS/RequireJS/SeaJS。
- // cmd: 这是一种兼容 RequireJS/SeaJS 的模块(类似 atc v1版本编译结果)
- // amd: 支持 RequireJS 等流行加载器
- // commonjs: 编译为 NodeJS 模块
- type: 'templatejs'
-}
+npm install gulp-tmod --save-dev
```
-
+
+由[@lichunqiang](https://github.com/lichunqiang)开发,项目主页:
+
+
+
## 常见问题
**问**:TmodJS 需要部署到服务器中吗?
@@ -225,7 +214,7 @@ TmodJS.on('compile', function (data) {});
**问**:线上运行的模板报错了如何调试?
-> 开启 debug 模式编译,如``-d``,这样会输出调试版本,可以让你快速找到模板运行错误的语句以及数据。
+> 开启 debug 模式编译,如``--debug``,这样会输出调试版本,可以让你快速找到模板运行错误的语句以及数据。
**问**:如何不压缩输出 js?
@@ -241,7 +230,7 @@ TmodJS.on('compile', function (data) {});
**问**:可以使用使用类似 tmpl 那种的 js 原生语法作为模板语法吗?
-> 可以。编辑配置文件,设置``"syntax": "native"``即可,这也是模板引擎 artTemplate 的默认语法,目前 TmodJS 默认使用的是 simple 语法。
+> 可以。编辑配置文件,设置``"syntax": "native"``即可,目前 TmodJS 默认使用的是 simple 语法。
**问**:如何兼容旧版本 atc 的项目?
@@ -251,22 +240,71 @@ TmodJS.on('compile', function (data) {});
> 请参考:[页面中的模板迁移指南](https://github.com/aui/tmodjs/wiki/页面中的模板迁移指南)。
+**问**:我需要手动合并模板,如何让 tmodjs 不合并输出?
+
+> 编辑配置文件,设置``combo:false``。
## 更新日志
-### TmodJS v0.0.2
+### v1.0.4
+
+* 设置``"output":false``则不进行输出,方便Gulp插件利用
+* 新增``"verbose": true``选项,选择是否显示日志
+
+### v1.0.1
+
+* 解决新版本设置``"minify":true``输出后,输出的脚本中文没有被编码的问题
+* 给引入后缀名的模板给予报错提示
+
+### v1.0.0
+
+* 使用 artTemplate3.0 作为模板引擎,NodJS 可直接共享前端的模板目录的模板,无需预编译
+* 提供 GruntJS 插件
+* 取消``engine``配置
+* 增加``runtime``配置
+* 接口重构,支持多实例
+
+### v0.1.1
+
+* 给压缩打包合并后的每条模板增加版本标记,例如``/*v:13*/``,以便对比线上版本
+
+### v0.1.0
+
+* 增加自动递增的模板版本号,控制台可显示模板被修改的次数
+* 优化默认设置下的文件输出,仅保留``template.js``,临时文件使用隐藏的``.cache``目录存放
+* 自动清理``.debug.js``文件
+* 对非规范的``include``语句模板在编译过程给予提示
+* 修复``compileError``事件触发异常的 bug
+* 减少对磁盘的读写,优化性能
+
+### v0.0.4
+
+* 增加``escape``配置,如果后台给出的数据已经进行了 XSS 过滤,就可以关闭模板的默认过滤以提升模板渲染效率
+* 简化``combo``功能,default只提供全部合并与不合并两个选项,值为布尔类型(兼容旧的版本的配置,会自动转换成布尔类型)
+* 取消鸡肋的异步载入插件,同时``async``配置失效
+* 为了便于理解,命令行输入的``--output``参数不再相对于模板目录,而是工作目录(配置文件的``output``参数仍保持不变)
+* 优化控制台日志显示
+
+### v0.0.3
+
+* 修复``combo``配置不能为空数组的 BUG
+* 支持页面内嵌动态编译与预编译两种方案共存(请设置``engine:true``,并在页面中中引入 TmodJS 输出的 template.js。如果想让 template.js 不内置合并的模板,可以设置``combo:[]``)
+* 运行时性能优化
+* 增加``alias``配置字段,在 AMD 与 CMD 模式下可以指定运行时依赖 ID
+
+### v0.0.2
修复极其特殊情况下 TmodJS 无法为 AMD/CMD 模块正确声明依赖的问题[#14](https://github.com/aui/tmodjs/issues/14)
-### TmodJS v0.0.1
+### v0.0.1
-这是一个革命性的版本!同时项目更名为 **TmodJS**,内部版本号收归到 0.0.1,这是一个新的开始,未来将稳步更新。
+这是一个革命性的版本,内部大量优化!同时项目更名为 **TmodJS**,内部版本号收归到 0.0.1
使用 TemplageJS 格式的模块作为默认输出的类型:它包含模板目录中所有模板,除了支持页面 Script 直接引入之外还支持 RequireJS、SeaJS、NodeJS 加载,并且接口统一,跨架构与前后端运行!
详细更新列表:
-* 吸收了来自业务的一些建议,编译方案的大调整,内部进行无数次优化,编译后的代码更小。
+* 吸收了来自业务的一些建议,编译方案的大调整,内部进行无数次优化,编译后的代码更小
* 编译后的脚本使用统一的接口:``template(path, data)`` 其中 path 相对于 template.js 所在目录
* 自动打包目录与子目录的模板
* 可选支持异步载入模板功能
@@ -314,18 +352,34 @@ NodeJS 版本:
## 加入我们
-如果你也认同 TmodJS 的理念、它能让你在开发中体会到书写模板的快乐,那么我希望你也能参与到 TmodJS 这个开源项目中来,无论是贡献代码或者撰写博文推广它等。
+TmodJS 是一个开源项目,如果你喜欢,非常期待你通过微博或者博客等来宣传 TmodJS。
### 使用 TmodJS 的项目
+* QQ空间
+* 腾讯视频
+* 爱奇艺
+* 爱拍原创
* Spa(迅雷)
* MicroTrend(腾讯)
* Tracker(腾讯)
-* Qzone(腾讯)
+* UR(腾讯)
+* ……
-[提交项目](https://github.com/aui/tmodjs/issues/1)
+如果你使用了 TmodJS 敬请留下项目名,我们将在 TmodJS 主页展示你的项目。[提交](https://github.com/aui/tmodjs/issues/1)
-### 贡献者
+### 代码贡献名单
+* [@aui](https://github.com/aui)
+* [@Jsonzhang](https://github.com/Jsonzhang)(grunt 插件作者)
+* [@lichunqiang](https://github.com/lichunqiang)(gulp 插件作者)
* [@TooBug](https://github.com/TooBug)
+* [@bammoo](https://github.com/bammoo)
+
+### 特别感谢
+
* [@warmhug](https://github.com/warmhug)(在工具雏形阶段的热心的测试与反馈)
+
+## 授权协议
+
+BSD.
diff --git a/bin/tmod b/bin/tmod
index 4996c37..decf324 100644
--- a/bin/tmod
+++ b/bin/tmod
@@ -2,8 +2,13 @@
'use strict';
-var TmodJS = require('../tmod.js');
+var TmodJS = require('../src/tmod.js');
+var version = require('../package.json').version;
+
var fs = require('fs');
+var path = require('path');
+var exec = require('child_process').exec;
+var os = require('os');
var options = {};
@@ -12,33 +17,22 @@ var help = function () {
var message = [
'\x1B[7mTmodJS - Template Compiler\x1B[27m',
'',
- 'Usage:',
+ 'Usage',
' tmod [options]',
- 'Options:',
+ '',
+ 'Options',
[
- ' -w, --watch',
- '\x1B[90m use tmod in watch mode (auto compile when file changed)\x1B[39m',
-
- ' -d, --debug',
- '\x1B[90m debugging Template\x1B[39m',
-
- ' --type',
- '\x1B[90m optional: templatejs (default) | cmd (RequireJS/SeaJS) | amd (RequireJS) | commonjs (NodeJS)\x1B[39m',
-
- ' --output value',
- '\x1B[90m defining an output directory\x1B[39m',
-
- ' --charset value',
- '\x1B[90m charset, utf-8 by default\x1B[39m',
-
- ' --version',
- '\x1B[90m display the version of TmodJS\x1B[39m',
-
- ' --help',
- '\x1B[90m show this help infomation\x1B[39m'
+ ' -d, --debug Debugging Template',
+ ' --type Optional: default | cmd (RequireJS/SeaJS) | amd (RequireJS) |',
+ ' commonjs (NodeJS)',
+ ' --output value Defining an output directory',
+ '--charset value Charset, "utf-8" by default',
+ ' --no-watch Does not monitor template directory',
+ ' --version Print the tmodjs version',
+ ' --help Display this help text'
].join('\n'),
'',
- '\x1B[90m' + 'Documentation can be found at http://aui.github.io/tmodjs/' + '\x1B[39m'
+ '\x1B[90m' + 'Documentation can be found at https://github.com/aui/tmodjs' + '\x1B[39m'
];
message = message.join('\n');
@@ -47,14 +41,16 @@ var help = function () {
-var dir;
+var base;
var value;
-var isWatch = false;
+var userConfig;
+var isWatch = true;
+var isEditConfig = false;
var args = process.argv.slice(2);
if (args[0] && /^[^-]|\//.test(args[0])) {
- dir = args.shift();
+ base = args.shift();
}
@@ -68,22 +64,60 @@ while (args.length > 0) {
isWatch = true;
break;
+ case '--no-watch':
+ isWatch = false;
+ break;
+
// 调试模式
case '-d':
case '--debug':
options.debug = true;
break;
+ case '--no-debug':
+ options.debug = false;
+ break;
+
+ // 对输出值编码
+ case '--escape':
+ options.escape = true;
+ break;
+
+ case '--no-escape':
+ options.escape = false;
+ break;
+
+ // 打包合并
+ case '--combo':
+ options.combo = true;
+ break;
+
+ case '--no-combo':
+ options.combo = false;
+ break;
+
+ // 压缩代码
+ case '--minify':
+ options.minify = true;
+ break;
+
+ case '--no-minify':
+ options.minify = false;
+ break;
+
+ // 使用缓存
+ case '--cache':
+ options.cache = true;
+ break;
+ case '--no-cache':
+ options.cache = false;
+ break;
+
// 输出目录
case '--output':
options.output = args.shift();
break;
- // 嵌入引擎
- case '--engine':
- options.engine = true;
- break;
-
// 模板输出类型
case '--type':
options.type = args.shift();
@@ -104,6 +138,10 @@ while (args.length > 0) {
options.helpers = args.shift();
break;
+ case '--config':
+ isEditConfig = true;
+ break;
+
// 显示帮助
case '-h':
case '--help':
@@ -114,46 +152,59 @@ while (args.length > 0) {
// 版本号
case '-v':
case '--version':
- var version = require('../package.json').version;
process.stdout.write(version + '\n');
process.exit();
break;
default:
- if (!dir) {
- dir = value;
+ if (!base) {
+ base = value;
}
}
}
-if (!dir) {
- dir = './';
+if (!base) {
+ base = './';
}
-if (!fs.existsSync(dir)) {
+if (!fs.existsSync(base)) {
process.stdout.write('Error: directory does not exist\n');
- help();
process.exit(1);
-};
+}
+
+var tmodjs = new TmodJS(base, options);
-TmodJS.init(dir, options);
-TmodJS.on('error', function (data) {
+tmodjs.on('compileError', function (data) {
if (!isWatch) {
process.exit(1);
}
});
-TmodJS.saveUserConfig();
-TmodJS.compile();
+userConfig = tmodjs.saveConfig();
-if (isWatch) {
- TmodJS.watch();
-}
+if (isEditConfig) {
+
+ process.stdout.write('Open: ' + userConfig + '\n');
+
+ exec(
+ (/windows/i.test(os.type()) ? 'start' : 'open')
+ + ' ' + userConfig, {timeout: 0}, function () {}
+ );
+
+} else {
+
+ tmodjs.compile();
+
+ if (isWatch) {
+ tmodjs.watch();
+ }
+
+}
diff --git a/doc/helper.md b/doc/helper.md
index 8af993e..c0c4170 100644
--- a/doc/helper.md
+++ b/doc/helper.md
@@ -6,40 +6,55 @@
## 一、新建一个辅助方法文件配置
-在模板目录新建一个 ./config/template-helper.js 文件,然后编辑 ./package.json 设置``"helpers": "./config/template-helper.js"``。
+在模板目录新建一个 config/template-helper.js 文件,然后编辑 package.json 设置``"helpers": "./config/template-helper.js"``。
## 二、编写辅助方法
-在 ./config/template-helper.js 中声明辅助方法。
+在 config/template-helper.js 中声明辅助方法。
### 示例
-1\. 让模板可访问全局的``Math``对象:
+以日期格式化为例:
```
-template.helper('Math', Math);
-```
-
-2\. 扩展一个 UBB 替换方法:
-
-```
-template.helper('$ubb2html', function (content) {
- return content
- .replace(/\[b\]([^\[]*?)\[\/b\]/igm, '$1 ')
- .replace(/\[i\]([^\[]*?)\[\/i\]/igm, '$1 ')
- .replace(/\[u\]([^\[]*?)\[\/u\]/igm, '$1 ')
- .replace(/\[url=([^\]]*)\]([^\[]*?)\[\/url\]/igm, '$2 ')
- .replace(/\[img\]([^\[]*?)\[\/img\]/igm, ' ');
+template.helper('dateFormat', function (date, format) {
+
+ date = new Date(date);
+
+ var map = {
+ "M": date.getMonth() + 1, //月份
+ "d": date.getDate(), //日
+ "h": date.getHours(), //小时
+ "m": date.getMinutes(), //分
+ "s": date.getSeconds(), //秒
+ "q": Math.floor((date.getMonth() + 3) / 3), //季度
+ "S": date.getMilliseconds() //毫秒
+ };
+ format = format.replace(/([yMdhmsqS])+/g, function(all, t){
+ var v = map[t];
+ if(v !== undefined){
+ if(all.length > 1){
+ v = '0' + v;
+ v = v.substr(v.length-2);
+ }
+ return v;
+ }
+ else if(t === 'y'){
+ return (date.getFullYear() + '').substr(4 - all.length);
+ }
+ return all;
+ });
+ return format;
});
```
+
## 三、在模板中使用辅助方法
-
-在模板中的使用方式:
```
-{{Math.min(1000, a, b)}}
-{{$ubb2html content}}
+{{time | dateFormat:'yyyy-MM-dd hh:mm:ss'}}
```
-> 注意:引擎不会对辅助方法输出的 HTML 字符进行转义。
\ No newline at end of file
+----------------------------------------------
+
+本文档针对 TmodJS v1.0.0+ 编写
\ No newline at end of file
diff --git a/doc/logo.png b/doc/logo.png
new file mode 100644
index 0000000..e35e654
Binary files /dev/null and b/doc/logo.png differ
diff --git a/doc/syntax-simple.md b/doc/syntax-simple.md
index 0fe9724..5fe89b5 100644
--- a/doc/syntax-simple.md
+++ b/doc/syntax-simple.md
@@ -4,72 +4,56 @@
TmodJS 默认采用 simple 语法,它非常易于读写。
-## 表达式
-
-``{{``与``}}``符号包裹起来的语句则为模板的逻辑表达式。
-
-### 输出表达式
-
-输出模板变量:
+## 表达式
-```
-{{content}}
-```
-
-
-默认会对变量中 HTML 字符编码输出,避免 XSS 漏洞。
-
-输出原始模板变量 - 不编码:
-
-```
-{{echo content}}
-```
-
-### 条件表达式
-
-```
-{{if admin}}
- {{content}}
-{{/if}}
-{{if user === 'admin'}}
- {{content}}
-{{else if user === '007'}}
- hello world
-{{/if}}
-```
-
-### 遍历表达式
-
-无论数组或者对象都可以用``each``进行遍历。
+``{{`` 与 ``}}`` 符号包裹起来的语句则为模板的逻辑表达式。
-```
-{{each list}}
- {{$index}}. {{$value.user}}
-{{/each}}
-```
+### 输出表达式
-其中 list 为要遍历的数据名称, ``$value`` 与 ``$index`` 是系统变量, ``$value`` 表示数据单条内容, ``$index`` 表示索引值,这两个变量也可以自定义:
+对内容编码输出:
-```
-{{each list as value index}}
- {{index}}. {{value.user}}
-{{/each}}
-```
-
-### 模板包含表达式
-
-例如嵌入一个 inc 目录下名为 demo 的模板:
+ {{content}}
-```
-{{include './inc/demo'}}
-```
-
-还可以传入指定的数据到子模板:
+不编码输出:
-```
-{{include './inc/demo' data}}
-```
-
+ {{#content}}
+
+编码可以防止数据中含有 HTML 字符串,避免引起 XSS 攻击。
+
+### 条件表达式
+
+ {{if admin}}
+ admin
+ {{else if code > 0}}
+ master
+ {{else}}
+ error!
+ {{/if}}
+
+### 遍历表达式
+
+无论数组或者对象都可以用 each 进行遍历。
+
+ {{each list as value index}}
+ {{index}} - {{value.user}}
+ {{/each}}
+
+亦可以被简写:
+
+ {{each list}}
+ {{$index}} - {{$value.user}}
+ {{/each}}
+
+### 模板包含表达式
+
+用于嵌入子模板。
+
+ {{include 'template_name'}}
+
+子模板默认共享当前数据,亦可以指定数据:
+
+ {{include 'template_name' news_list}}
+
#### include 路径规范约定
1. 路径不能带后缀名
@@ -79,4 +63,14 @@ TmodJS 默认采用 simple 语法,它非常易于读写。
## 辅助方法
-这属于插件的范畴,请参考:
+ {{time | dateFormat:'yyyy-MM-dd hh:mm:ss'}}
+
+支持传入参数与嵌套使用:
+
+ {{time | say:'cd' | ubb | link}}
+
+定义辅助方法请参考:
+
+----------------------------------------------
+
+本文档针对 TmodJS v1.0.0+ 编写
\ No newline at end of file
diff --git a/doc/upgrade.md b/doc/upgrade.md
index d80485b..3fd0150 100644
--- a/doc/upgrade.md
+++ b/doc/upgrade.md
@@ -1,13 +1,13 @@
# 页面中的模板迁移指南
-如果你之前项目中采用的是 artTemplate,则很容易迁移到 TModJS 中来,因为 TModJS 也是基于 artTemplate 的子项目,不同的是模板是预编译的。
+如果你之前项目中采用的是 artTemplate 则很容易迁移到 TmodJS 中来,因为 TmodJS 也是基于 artTemplate 的子项目,不同的是模板是预编译的。
迁移过程比较简单,大约如下四个步骤:
## 一、迁移模板
迁移模板之前,首先需要在你的项目中新建一个前端模板目录,然后寻找页面中类似这样的模板内容:
-
+
```
```
-
-你可以将``
+
+它使用一个特殊的````标签来存放模板(由于浏览器不支持这种类型的声明,它存放的代码不会当作 js 运行,代码也不会被显示出来)。使用模板引擎渲染模板的示例:
+
+ var html = tmpl('user_tmpl', data);
+ document.getElementById('content').innerHTML = html;
+
+[在线示例](http://aui.github.io/artTemplate/demo/basic.html)
+
+通过前端模板引擎将 UI 分离后,模板的书写与修改就变得简单多了,也提升了可维护性。但是,随着这种方式规模化后其弊端也随之而来。
+
+## 模板内嵌的弊端
+
+### 开发调试
+
+每次修改前端模板需要改动页面的代码,如果不是纯静态页面,无法使用类似 fiddler 的工具将页面映射到本地进行开发,开发调试依赖只能服务端环境,效率低下。
+
+### 自动化构建
+
+在现代 web 前端工程体系中,几乎每一个环节都拥有相应的优化工具,这些几乎都被 grunt 这个自动构建工具连接起来。但是前端模板若内嵌到页面中,复杂度会比较高,现有工具因为受限难以进行自动优化。
+
+### 模块化
+
+前端模板集中在一个文件中这必然会引起多人协作的问题,随着项目复杂度增加,按文件模块化迫在眉睫。
+
+## 现有的模板外置解决方案
+
+目前越来越的项目已经将模板从页面中迁移出来,主要有两种方式:
+
+### ajax 拉取方案
+
+通过 ajax 加载远程模板,然后再使用模板引擎解析。这种方式的好处就是模板可以按文件存放,书写起来也是十分便利,但弊端相当明显:由于浏览器同源策略限制的,导致模板无法部署到 CDN 网络中。
+
+### 在 JS 文件中存放模板
+
+为避免上述加载模板方案无法跨域的致命缺陷,模板存放在 js 文件中又成了最佳实践方式,但是这种情况下需要对回车符进行转义,对书写不友好,严重影响开发效率。例如:
+
+ var user_tmpl =
+ '{{each users as value}}\
+ \
+ {{value.name}} \
+ \
+ {{/each}}';
+
+或者:
+
+ var user_tmpl =
+ '{{each users as value}}'
+ + ''
+ + '{{value.name}} '
+ + ' '
+ +'{{/each}}';
+
+## 现有模板组织方案优劣总结
+
+组织方式 | 开发效率 | 优化空间 | 本地调试 | 代码复用 | 团队协作
+------ | ------ | ------ | ------ | ------ | ------
+内嵌业务页中 | ✓ | ✗ | ✗ | ✗ | ✗
+Ajax 远程加载 | ✓ | ✗| ✓ | ✓ | ✓
+嵌入 js 文件 | ✗ | ✓ | ✓ | ✓ | ✓
+
+总结:方便优化的模式不利于开发;利于开发的模式不利于优化。
+
+## 理想模式
+
+看下服务端模板是如何做的:
+
+一、**模板按文件与目录组织模板**
+
+```
+template('tpl/home/main', data)
+```
+
+二、**模板使用 include 语句完成复用**
+
+```
+{{include '../public/header'}}
+```
+
+这一切看起来很美,前端是否也可以采用这样的模式?但是现实告诉我们,这是一个艰巨的任务。
+
+## 现实难题
+
+* 浏览器对文本加载会有跨域限制
+* 浏览器同步加载会引起界面卡顿
+* 加载大量的模板文件会带来 http 资源消耗问题
+
+## 解决方案
+
+为了实现上述“理想模式”,我们推出了 TmodJS——模板预编译器,以下是它的简介:
+
+> TmodJS(原名 atc)是一个简单易用的前端模板预编译工具。它通过预编译技术让前端模板突破浏览器限制,实现后端模板一样的同步“文件”加载能力。它采用目录来组织维护前端模板,从而让前端模板实现工程化管理,最终保证前端模板在复杂单页 web 应用下的可维护性。同时预编译输出的代码经过多层优化,能够在最大程度节省客户端资源消耗。
+
+它采用三种方案来解决难题:
+
+### 1.本地构建
+
+模板编写完成后,通过一个本地工具将模板编译成浏览器可执行的代码——js,这样就可以用脚本的方式来加载模板,不必受浏览器的同源策略限制,模板可以部署到任意 CDN,而无需处理跨域问题。
+
+工具内部采用模板引擎——[artTemplate](https://github.com/aui/artTemplate) 完成模板编译,输出 js 文件。artTemplate 也是来自腾讯的开源项目,它支持预编译,编译后的代码可以无需引擎运行。
+
+### 2.种子文件
+
+为了实现``template(path, data)``这种同步接口,TmodJS 会不断的更新一个名为 template.js 的种子文件,这个文件合并了公用方法与编译后的模板,项目只需要引用这个文件就可以按路径同步的方式调用模板。例如:
+
+ var tpl = template('home/index');
+ var html = tpl(data);
+ document.getElementById('content').innerHTML = html;
+
+### 3.模板目录
+
+为了让团队成员、自动化工具能更好的管理模板,前端模板不再内嵌到页面中,而是使用专门的目录进行组织维护;使用路径作为模板的 ID,这样与源文件保持对应关系——这样好处就是极大的增加了可维护性。例如:页面头部底部的公用模板可以放在``tpl/public``目录下,首屏的模板可以放在``tpl/home``下面。
+
+## 模板与工程化
+
+TmodJS 采用了自动编译机制,一经启动后就无需人工干预,每次模板创建与更新都会自动编译,直到正式上线都无需对代码进行任何修改。实现文件系统的前端模板只是 TmodJS 最基本的任务,它在背后还做了这些优化:
+
+### 模板压缩与合并
+
+TmodJS 编译之前会压缩掉模板的空白字符,编译为 js 后又会进行一次压缩,此时输出的 js 甚至会比原始模板更小(最高可减少一半的体积)。
+
+在默认设置下,TmodJS 会将模板目录所有模板编译后再进行压缩与合并,输出后的 template.js 称之为模板包(内部名称叫 TemplateJS 格式)这种打包的方式非常适合移动端单页 WebApp,输出后的模板包可直接可作为开发阶段与线上运行的文件,适合中小型项目。
+
+[查看编译后的模板示例](http://microtrend.cdc.tencent.com/tpl/dist/template.js)
+
+### 依赖管理
+
+当然,将所有前端模板都打包在一个文件中不一定适合每一个项目,因为很多大型项目需要更细致的优化,将模板编译为 AMD、CMD、CommonJS 类型的的模块是一个不错的选择,此时模板内部的``include``语句会编译成``requier('xxx/xxx')``形式声明依赖,接入对应的 grunt 插件可自动完成依赖合并。
+
+### 本地调试
+
+因为模板已经被独立出来,所以使用 fiddler 将线上模板映射到本地进行开发调试将十分容易。如果线上模板报错,开启 TmodJS 的``debug``模式后可以直接找到出错的模板路径以及所在行号,例如:
+
+ Template Error
+
+
+ public/header
+
+
+ Render Error
+
+
+ Cannot read property '0' of undefined
+
+
+ 5
+
+
+ {{users[0].name}}
+
+### 沙箱与扩展
+
+很多开发者习惯在模板中访问页面中全局定义的函数,如果模板内嵌到页面中问题似乎不大,一旦模板外置后这种隐含的依赖关系将会导致严重的维护问题,TmodJS 采用沙箱机制来解决此问题:限制开发者访问外部对象,模板用到的所有变量在闭包中被强制指向模板数据。
+
+为了方便扩展模板的功能,可指定一个外部 js 作为公用方法(辅助方法),这个 js 会被合并到到 template.js 中。
+
+### 安全过滤
+
+模板的变量输出默认都会被过滤函数包裹,在运行时进行过滤,从而避免模板开发者因为疏忽导致站点 XSS 漏洞。例如:
+
+模板
+
+ {{title}}
+
+编译代码
+
+ "" + $escape(title) + " "
+
+### 与第三方自动化构建工具配合
+
+目前 TmodJS 已有 grunt 与 gulp 这两种流行的自动化构建工具的插件,未来将支持更多的自动化工具。
+
+### 前后端模板共享
+
+TmodJS 与 artTemplate 模板引擎使用同样的模板语法,而 artTemplate 提供了 NodeJS 版本,可以直接读取 TmodJS 的模板目录,这意味着可以轻松的做到前后端模板共享,技术方案自由切换。
+
+## 成果
+
+组织方式 | 开发效率 | 优化空间 | 本地调试 | 代码复用 | 团队协作
+------ | ------ | ------ | ------ | ------ | ------
+TmodJS | ✓ | ✓ | ✓ | ✓ | ✓
+
+
+## 关于 TmodJS
+
+起源于腾讯内部公用组件平台的开源项目(atc),历经无数次的改进,目前已经有多个项目在使用。
+
+
+
+### 案例
+
+* QQ空间
+* 腾讯视频
+* 爱奇艺
+* 爱拍原创
+* Spa(迅雷)
+* MicroTrend(腾讯)
+* Tracker(腾讯)
+* UR(腾讯)
+* ……
+
+### 愿景
+
+忘记前端模板。
+
+
diff --git a/lib/template-AOTcompile.js b/lib/template-AOTcompile.js
deleted file mode 100644
index 0c1998c..0000000
--- a/lib/template-AOTcompile.js
+++ /dev/null
@@ -1,253 +0,0 @@
-/*!
- * TmodJS - AOT Template Compiler
- * https://github.com/aui/tmodjs
- * Released under the MIT, BSD, and GPL Licenses
- */
-
-var template = require('./template.js');
-
-
-var SLASH_RE = /\\\\/g;
-var DOT_RE = /\/\.\//g;
-var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
-var DIRNAME_RE = /[^/]+$/;
-var ANONYMOUS_RE = /^function\s+anonymous/;
-var EXTNAME_RE = /\.(html|htm|tpl)$/i;
-var INCLUDE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*include|(?:^|[^$])\binclude\s*\(\s*(["'])(.+?)\1/g; //"
-
-
-
-// 编译模板
-var compile = function (id, source, debug) {
-
- template.isCompress = true;
- template.onerror = function (e) {
- throw e;
- };
-
- var render = template.compile(id, source, debug);
- delete template.cache[id];
-
- return render
- .toString()
- .replace(ANONYMOUS_RE, 'function');
-};
-
-
-// 检查模板是否有逻辑语法
-var testTemplateSyntax = function (source) {
- return source.indexOf(template.openTag) !== -1;
-};
-
-
-// 模板 ID 规范检查
-var testId = function (id, fromID) {
- if (!/^\./.test(id) || EXTNAME_RE.test(id)) {
- var error = {
- name: 'Syntax Error',
- message: id + '\n'
- + 'Template must be a relative path, and can not have a suffix.'
- };
-
- if (fromID) {
- error.id = fromID;
- }
-
- throw error;
- }
-};
-
-
-// 依赖分析
-var parseDependencies = function (code) {
- var list = [];
- var uniq = {};
-
- code
- .replace(SLASH_RE, '')
- .replace(INCLUDE_RE, function(m, m1, m2) {
- if (m2 && !uniq.hasOwnProperty(m2)) {
- list.push(m2);
- uniq[m2] = true;
- }
- });
-
- return list;
-};
-
-
-// 获取上一层 ID
-var dirname = function (id) {
- return id.replace(DIRNAME_RE, '');
-};
-
-
-// 分解为标准化 ID
-var resolve = function (id) {
- id = id.replace(DOT_RE, '/');
- while (id.match(DOUBLE_DOT_RE)) {
- id = id.replace(DOUBLE_DOT_RE, '/');
- }
- return id;
-};
-
-
-// 构造字符串表达式
-var stringify = function (code) {
- return "'" + code
- .replace(/('|\\)/g, '\\$1')
- .replace(/\r/g, '\\r')
- .replace(/\n/g, '\\n')
- + "'";
-};
-
-
-// 压缩 HTML 字符串
-var compressHTML = function (code) {
- code = code
- .replace(/[\n\r\t\s]+/g, ' ')
- .replace(//g, '');
-
- return code;
-};
-
-
-/**
- * 模板预编译器,根据设置生成不同格式的 javascript 模块
- * @param {String} 模板ID,例如 ./home/list
- * 要求:必须是 . 的相对路径开头,且末尾不能有后缀名
- * @param {String} 模板源代码
- * @param {Object} 可选项
- */
-template.AOTcompile = function (id, source, options) {
-
- testId(id);
-
- // 是否嵌入完整模板引擎,嵌入后将把模板保存为字符串
- var isEngine = options.engine;
-
- // 是否为编译为调试版本
- var isDebug = options.debug;
-
- // 编译的模块类型
- var type = options.type;
-
- // 运行时模块别名
- var runtime = options.runtime;
-
-
- var RUNTIME = 'template';
- var code = '';
- var render = compile(id, source, isDebug);
- var requires = parseDependencies(render);
- var isLogic = testTemplateSyntax(source);
- var dir = dirname(id);
-
- var isHTML = isEngine || !isLogic;
-
-
- if (isHTML) {
- code = isDebug ? source : compressHTML(source);
- code = stringify(code);
- } else {
- code = render.replace(ANONYMOUS_RE, 'function');
- }
-
-
- // 计算主入口相对于当前模板路径
- var getRuntime = function () {
- if (runtime) {
- return runtime;
- }
- var prefix = './';
- var length = dir.split('/').length - 2;
- if (length) {
- prefix = (new Array(length + 1)).join('../');
- }
- return prefix + RUNTIME;
- };
-
-
- // 生成 require 函数依赖声明代码
- var getRequireCode = function () {
- var requiresCode = [];
-
- requires.forEach(function (id) {
- requiresCode.push("require('" + id + "');");
- });
-
- return requiresCode.join('\n');
- };
-
-
- switch (type || '') {
-
- // TemplateJS 模块格式
- case '':
- case 'templatejs':
-
- code = "template('" + id + "'," + code + ");";
- break;
-
-
- // RequireJS / SeaJS 兼容模块格式
- case 'cmd':
-
- code
- = "define(function(require){"
- + getRequireCode()
- + "return require('" + getRuntime() + "')"
- + "('" + id + "', " + code + ");"
- + "});";
- break;
-
-
- // RequireJS 模块格式
- case 'amd':
-
- code
- = "define("
- + "['" + getRuntime() + "','" + requires.join("','") + "'],"
- + "function(template){"
- + "return template('" + id + "', " + code + ");"
- + "});";
- break;
-
-
- // NodeJS 模块格式
- case 'commonjs':
-
- code
- = "var template=require('" + getRuntime() + "');"
- + getRequireCode()
- + "module.exports=template('" + id + "'," + code + ");";
- break;
-
-
- default:
-
- throw {
- id: id,
- name: 'Type Error',
- message: 'Unsupported type: ' + type
- };
-
- }
-
-
- return {
-
- // 编译结果
- code: code,
-
- // 依赖的子模板
- requires: requires.map(function (subId) {
- testId(subId, id);
- return resolve(dir + subId);
- })
-
- };
-
-};
-
-module.exports = template;
\ No newline at end of file
diff --git a/lib/template-async.js b/lib/template-async.js
deleted file mode 100644
index 22c38d2..0000000
--- a/lib/template-async.js
+++ /dev/null
@@ -1,163 +0,0 @@
-/*!
- * artTemplate - Asynchronous load extension
- * https://github.com/aui/artTemplate
- * Released under the MIT, BSD, and GPL Licenses
- */
-template.async = (function () {
-
- var helpers = template.helpers;
- var cache = template.cache;
- var resolve = helpers.$resolve;
- var each = helpers.$each;
- var isBrowser = typeof document !== 'undefined';
- var onload = 'onload';
- var onreadystatechange = 'onreadystatechange';
- var getElementsByTagName = 'getElementsByTagName';
- var INCLUDE_RE = /\binclude\s*\(\s*(["'])(.+?)\1\s*(,\s*(.+?)\s*)?\)/g;
- var SLASH_RE = /\\\\/g;
- var loadList = {};
-
-
- if (isBrowser) {
- var doc = document;
- var head = doc[getElementsByTagName]('head')[0] || doc.documentElement;
- var baseElement = head[getElementsByTagName]('base')[0];
-
- var READY_STATE_RE = /^(?:loaded|complete|undefined)$/;
- var NAME_RE = /\btemplate\.js\b/;
-
- var DIRNAME_RE = /[^?#]*\//;
- var scripts = doc[getElementsByTagName]('script');
- var current = scripts[scripts.length - 1];
-
- each([].slice.call(scripts), function (script) {
- if (NAME_RE.test(script.src)) {
- current = script;
- }
- })
-
- template.base = (
- current.hasAttribute
- ? current.src
- : current.getAttribute('src', 4)
- ).match(DIRNAME_RE)[0];
-
- } else {
- template.base = __dirname + '/';
- }
-
- // 加载器
- var loader = function (id, callback, charset) {
-
- if (cache[id]) {
- callback(id);
- return;
- }
-
- var url = template.base + id + '.js';
-
- if (!isBrowser) {
- var fs = require('fs');
- var path = require('path');
- var vm = require("vm");
- var tpl = fs.readFileSync(url, charset || 'utf-8');
- vm.runInNewContext(tpl, {
- template: template
- });
- callback(id);
- return;
- }
-
-
- var node = doc.createElement('script');
-
- if (charset) {
- node.charset = charset;
- }
-
-
- node[onload] = node[onreadystatechange] = function () {
- if (READY_STATE_RE.test(node.readyState)) {
-
- node[onload] = node[onreadystatechange] = null;
-
- head.removeChild(node);
-
- node = null;
-
- callback(id);
- }
- };
-
- node.async = true;
- node.src = url;
-
-
- baseElement ?
- head.insertBefore(node, baseElement) :
- head.appendChild(node);
- };
-
-
- var getDependencies = function (id, code) {
-
- code = cache[id];
- var dependencies = code.deps;
-
- if (!dependencies) {
- dependencies = [];
- var uniq = {};
-
- (code + '')
- .replace(SLASH_RE, '')
- .replace(INCLUDE_RE, function($1, $2, $3) {
- $3 = $3 && resolve(id, $3).replace(/^\.\//, '');
- if ($3 && !uniq.hasOwnProperty($3)) {
- dependencies.push($3);
- uniq[$3] = true;
- }
- });
-
- code.deps = dependencies;
- }
-
- return dependencies;
- };
-
- var loadTemplates = function (list, callback, charset) {
-
- list = typeof list === 'string' ? [list] : list;
-
- var ids = list.join(',');
- var len = list.length;
-
- if (loadList[ids] || !len) {
- loadList[ids] = true;
- callback(cache[ids]);
- return;
- }
-
- var counter = function (id) {
- if (!--len) {
- loadList[ids] = true;
- callback(cache[id]);
- }
- };
-
- each(list, function (id, index) {
-
- var render = cache[id];
- var cb = function () {
- loadTemplates(getDependencies(id), function () {
- loader(id, counter, charset);
- }, charset);
- };
-
- render ? cb() : loader(id, cb, charset);
- });
- };
-
-
- return loadTemplates;
-
-})();
diff --git a/lib/template-full.js b/lib/template-full.js
deleted file mode 100644
index dc0fdca..0000000
--- a/lib/template-full.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/*! '> '<:debug:>'*/
-
-'<:engine:>'
-!function (global, template) {
-
- var get = template.get;
- var helpers = template.helpers;
-
- helpers.$resolve = function (from, to) {
- var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
- var dirname = from.replace(/[^/]+$/, '');
- var id = dirname + to;
-
- id = id.replace(/\/\.\//g, '/');
- while (id.match(DOUBLE_DOT_RE)) {
- id = id.replace(DOUBLE_DOT_RE, '/');
- }
- return id;
- };
-
- helpers.$include = function (path, data, from) {
- var id = helpers.$resolve(from, path);
- return template.render(id, data);
- };
-
- template.get = function (id) {
- return get(id.replace(/^([^.])/, './$1'));
- };
-
- '<:syntax:>'
- '<:helpers:>'
- '<:plugins:>'
- '<:templates:>'
-
-}(this, this.template);
-
diff --git a/lib/template-runtime.js b/lib/template-runtime.js
deleted file mode 100644
index 7edd584..0000000
--- a/lib/template-runtime.js
+++ /dev/null
@@ -1,157 +0,0 @@
-/*! '> '<:debug:>'*/
-
-!function (global) {
-
- var template = function (path, content) {
- return template[
- /string|function/.test(typeof content) ? 'compile' : 'render'
- ].apply(template, arguments);
- };
-
-
- var cache = template.cache = {};
- var helpers = template.helpers = {
-
- $string: function (value) {
-
- var type = typeof value;
-
- if (!/string|number/.test(type)) {
- value = type === 'function'
- ? helpers.$string(value()) : '';
- }
-
- return value + '';
- },
-
-
- $escape: function (content) {
- var m = {
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "&": "&"
- };
- return helpers.$string(content)
- .replace(/&(?![\w#]+;)|[<>"']/g, function (s) {
- return m[s];
- });
- },
-
-
- $each: function (data, callback) {
- var isArray = Array.isArray || function (obj) {
- return ({}).toString.call(obj) === '[object Array]';
- };
-
- if (isArray(data)) {
- for (var i = 0, len = data.length; i < len; i++) {
- callback.call(data, data[i], i, data);
- }
- } else {
- for (i in data) {
- callback.call(data, data[i], i);
- }
- }
- },
-
-
- $resolve: function (from, to) {
- var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
- var dirname = from.replace(/[^/]+$/, '');
- var id = dirname + to;
-
- id = id.replace(/\/\.\//g, '/');
- while (id.match(DOUBLE_DOT_RE)) {
- id = id.replace(DOUBLE_DOT_RE, '/');
- }
- return id;
- },
-
-
- $include: function (path, data, from) {
- var id = helpers.$resolve(from, path);
- return template.render(id, data);
- }
-
- };
-
-
- var debug = function (e) {
-
- var message = '';
- for (var name in e) {
- message += '<' + name + '>\n' + e[name] + '\n\n';
- }
-
- if (message && global.console) {
- console.error('Template Error\n\n' + message);
- }
-
- return function () {
- return '{Template Error}';
- };
- };
-
-
- template.render = function (path, data) {
- var fn = template.get(path) || debug({
- id: path,
- name: 'Render Error',
- message: 'No Template'
- });
-
- return data ? fn(data) : fn;
- };
-
-
- template.compile = function (path, fn) {
- var isFunction = typeof fn === 'function';
- var render = cache[path] = function (data) {
- try {
- return isFunction ? new fn(data, path) + '' : fn;
- } catch (e) {
- return debug(e)();
- }
- };
-
- render.prototype = fn.prototype = helpers;
- render.toString = function () {
- return fn + '';
- };
-
- return render;
- };
-
-
- template.get = function (id) {
- return cache[id.replace(/^([^.])/, './$1')];
- };
-
-
- template.helper = function (name, helper) {
- helpers[name] = helper;
- };
-
-
- '<:helpers:>'
- '<:plugins:>'
- '<:templates:>'
-
-
- // RequireJS && SeaJS
- if (typeof define === 'function') {
- define(function() {
- return template;
- });
-
- // NodeJS
- } else if (typeof exports !== 'undefined') {
- module.exports = template;
- } else {
- global.template = template;
- }
-
-}(this);
-
diff --git a/lib/template-syntax.js b/lib/template-syntax.js
deleted file mode 100644
index bb50c1d..0000000
--- a/lib/template-syntax.js
+++ /dev/null
@@ -1,92 +0,0 @@
-/*!
- * artTemplate - Syntax Extensions
- * https://github.com/aui/artTemplate
- * Released under the MIT, BSD, and GPL Licenses
- */
-
-(function (exports) {
-
- exports.openTag = '{{';
- exports.closeTag = '}}';
-
-
- exports.parser = function (code) {
- code = code.replace(/^\s/, '');
-
- var split = code.split(' ');
- var key = split.shift();
- var args = split.join(' ');
-
- switch (key) {
-
- case 'if':
-
- code = 'if(' + args + '){';
- break;
-
- case 'else':
-
- if (split.shift() === 'if') {
- split = ' if(' + split.join(' ') + ')';
- } else {
- split = '';
- }
-
- code = '}else' + split + '{';
- break;
-
- case '/if':
-
- code = '}';
- break;
-
- case 'each':
-
- var object = split[0] || '$data';
- var as = split[1] || 'as';
- var value = split[2] || '$value';
- var index = split[3] || '$index';
-
- var param = value + ',' + index;
-
- if (as !== 'as') {
- object = '[]';
- }
-
- code = '$each(' + object + ',function(' + param + '){';
- break;
-
- case '/each':
-
- code = '});';
- break;
-
- case 'echo':
-
- code = 'print(' + args + ');'
- break;
-
- case 'include':
-
- code = 'include(' + split.join(',') + ');';
- break;
-
- default:
-
- if (exports.helpers.hasOwnProperty(key)) {
-
- code = '==' + key + '(' + split.join(',') + ');';
-
- } else {
-
- code = code.replace(/[\s;]*$/, '');
- code = '=' + code;
- }
-
- break;
- }
-
-
- return code;
- };
-})(template);
diff --git a/lib/template.js b/lib/template.js
deleted file mode 100644
index 3c5ff4a..0000000
--- a/lib/template.js
+++ /dev/null
@@ -1,566 +0,0 @@
-/*!
- * artTemplate - Template Engine
- * https://github.com/aui/artTemplate
- * Released under the MIT, BSD, and GPL Licenses
- */
-
-(function (global) {
-
-'use strict';
-
-/**
- * 模板引擎
- * 若第二个参数类型为 String 则执行 compile 方法, 否则执行 render 方法
- * @name template
- * @param {String} 模板ID
- * @param {Object, String} 数据或者模板字符串
- * @return {String, Function} 渲染好的HTML字符串或者渲染方法
- */
-var template = function (id, content) {
- return template[
- typeof content === 'string' ? 'compile' : 'render'
- ].apply(template, arguments);
-};
-
-
-template.version = '2.0.2';
-template.openTag = '<%'; // 设置逻辑语法开始标签
-template.closeTag = '%>'; // 设置逻辑语法结束标签
-template.isEscape = true; // HTML字符编码输出开关
-template.isCompress = false; // 剔除渲染后HTML多余的空白开关
-template.parser = null; // 自定义语法插件接口
-
-
-
-/**
- * 渲染模板
- * @name template.render
- * @param {String} 模板ID
- * @param {Object} 数据
- * @return {String} 渲染好的HTML字符串
- */
-template.render = function (id, data) {
-
- var cache = template.get(id) || _debug({
- id: id,
- name: 'Render Error',
- message: 'No Template'
- });
-
- return cache(data);
-};
-
-
-
-/**
- * 编译模板
- * 2012-6-6 @TooBug: define 方法名改为 compile,与 Node Express 保持一致
- * @name template.compile
- * @param {String} 模板ID (可选,用作缓存索引)
- * @param {String} 模板字符串
- * @return {Function} 渲染方法
- */
-template.compile = function (id, source) {
-
- var params = arguments;
- var isDebug = params[2];
- var anonymous = 'anonymous';
-
- if (typeof source !== 'string') {
- isDebug = params[1];
- source = params[0];
- id = anonymous;
- }
-
-
- try {
-
- var Render = _compile(id, source, isDebug);
-
- } catch (e) {
-
- e.id = id || source;
- e.name = 'Syntax Error';
-
- return _debug(e);
-
- }
-
-
- function render (data) {
-
- try {
-
- return new Render(data, id) + '';
-
- } catch (e) {
-
- if (!isDebug) {
- return template.compile(id, source, true)(data);
- }
-
- return _debug(e)();
-
- }
-
- }
-
-
- render.prototype = Render.prototype;
- render.toString = function () {
- return Render.toString();
- };
-
-
- if (id !== anonymous) {
- _cache[id] = render;
- }
-
-
- return render;
-
-};
-
-
-
-var _cache = template.cache = {};
-
-
-
-
-// 辅助方法集合
-var _helpers = template.helpers = {
-
- $include: template.render,
-
- $string: function (value, type) {
-
- if (typeof value !== 'string') {
-
- type = typeof value;
- if (type === 'number') {
- value += '';
- } else if (type === 'function') {
- value = _helpers.$string(value());
- } else {
- value = '';
- }
- }
-
- return value;
-
- },
-
- $escape: function (content) {
- var m = {
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "&": "&"
- };
- return _helpers.$string(content)
- .replace(/&(?![\w#]+;)|[<>"']/g, function (s) {
- return m[s];
- });
- },
-
- $each: function (data, callback) {
- var isArray = Array.isArray || function (obj) {
- return ({}).toString.call(obj) === '[object Array]';
- };
-
- if (isArray(data)) {
- for (var i = 0, len = data.length; i < len; i++) {
- callback.call(data, data[i], i, data);
- }
- } else {
- for (i in data) {
- callback.call(data, data[i], i);
- }
- }
- }
-};
-
-
-
-
-/**
- * 添加模板辅助方法
- * @name template.helper
- * @param {String} 名称
- * @param {Function} 方法
- */
-template.helper = function (name, helper) {
- _helpers[name] = helper;
-};
-
-
-
-
-/**
- * 模板错误事件
- * @name template.onerror
- * @event
- */
-template.onerror = function (e) {
- var message = 'Template Error\n\n';
- for (var name in e) {
- message += '<' + name + '>\n' + e[name] + '\n\n';
- }
-
- if (global.console) {
- console.error(message);
- }
-};
-
-
-
-
-
-
-
-// 获取模板缓存
-template.get = function (id) {
-
- var cache;
-
- if (_cache.hasOwnProperty(id)) {
- cache = _cache[id];
- } else if ('document' in global) {
- var elem = document.getElementById(id);
-
- if (elem) {
- var source = elem.value || elem.innerHTML;
- cache = template.compile(id, source.replace(/^\s*|\s*$/g, ''));
- }
- }
-
- return cache;
-};
-
-
-
-// 模板调试器
-var _debug = function (e) {
-
- template.onerror(e);
-
- return function () {
- return '{Template Error}';
- };
-};
-
-
-
-// 模板编译器
-var _compile = (function () {
-
-
- // 数组迭代
- var forEach = _helpers.$each;
-
-
- // 静态分析模板变量
- var KEYWORDS =
- // 关键字
- 'break,case,catch,continue,debugger,default,delete,do,else,false'
- + ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
- + ',throw,true,try,typeof,var,void,while,with'
-
- // 保留字
- + ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
- + ',final,float,goto,implements,import,int,interface,long,native'
- + ',package,private,protected,public,short,static,super,synchronized'
- + ',throws,transient,volatile'
-
- // ECMA 5 - use strict
- + ',arguments,let,yield'
-
- + ',undefined';
-
- var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|[\s\t\n]*\.[\s\t\n]*[$\w\.]+/g;
- var SPLIT_RE = /[^\w$]+/g;
- var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
- var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
- var BOUNDARY_RE = /^,+|,+$/g;
-
- var getVariable = function (code) {
- return code
- .replace(REMOVE_RE, '')
- .replace(SPLIT_RE, ',')
- .replace(KEYWORDS_RE, '')
- .replace(NUMBER_RE, '')
- .replace(BOUNDARY_RE, '')
- .split(/^$|,+/);
- };
-
-
- return function (id, source, isDebug) {
-
- var openTag = template.openTag;
- var closeTag = template.closeTag;
- var parser = template.parser;
-
-
- var code = source;
- var tempCode = '';
- var line = 1;
- var uniq = {$data:1,$id:1,$helpers:1,$out:1,$line:1};
- var prototype = {};
-
-
- var variables = "var $helpers=this,"
- + (isDebug ? "$line=0," : "");
-
- var isNewEngine = ''.trim;// '__proto__' in {}
- var replaces = isNewEngine
- ? ["$out='';", "$out+=", ";", "$out"]
- : ["$out=[];", "$out.push(", ");", "$out.join('')"];
-
- var concat = isNewEngine
- ? "if(content!==undefined){$out+=content;return content;}"
- : "$out.push(content);";
-
- var print = "function(content){" + concat + "}";
-
- var include = "function(id,data){"
- + "data=data||$data;"
- + "var content=$helpers.$include(id,data,$id);"
- + concat
- + "}";
-
-
- // html与逻辑语法分离
- forEach(code.split(openTag), function (code, i) {
- code = code.split(closeTag);
-
- var $0 = code[0];
- var $1 = code[1];
-
- // code: [html]
- if (code.length === 1) {
-
- tempCode += html($0);
-
- // code: [logic, html]
- } else {
-
- tempCode += logic($0);
-
- if ($1) {
- tempCode += html($1);
- }
- }
-
-
- });
-
-
-
- code = tempCode;
-
-
- // 调试语句
- if (isDebug) {
- code = "try{" + code + "}catch(e){"
- + "throw {"
- + "id:$id,"
- + "name:'Render Error',"
- + "message:e.message,"
- + "line:$line,"
- + "source:" + stringify(source)
- + ".split(/\\n/)[$line-1].replace(/^[\\s\\t]+/,'')"
- + "};"
- + "}";
- }
-
-
- code = variables + replaces[0] + code
- + "return new String(" + replaces[3] + ");";
-
-
- try {
-
- var Render = new Function("$data", "$id", code);
- Render.prototype = prototype;
-
- return Render;
-
- } catch (e) {
- e.temp = "function anonymous($data,$id) {" + code + "}";
- throw e;
- }
-
-
-
-
- // 处理 HTML 语句
- function html (code) {
-
- // 记录行号
- line += code.split(/\n/).length - 1;
-
- // 压缩多余空白与注释
- if (template.isCompress) {
- code = code
- .replace(/[\n\r\t\s]+/g, ' ')
- .replace(//g, '');
- }
-
- if (code) {
- code = replaces[1] + stringify(code) + replaces[2] + "\n";
- }
-
- return code;
- }
-
-
- // 处理逻辑语句
- function logic (code) {
-
- var thisLine = line;
-
- if (parser) {
-
- // 语法转换插件钩子
- code = parser(code);
-
- } else if (isDebug) {
-
- // 记录行号
- code = code.replace(/\n/g, function () {
- line ++;
- return "$line=" + line + ";";
- });
-
- }
-
-
- // 输出语句. 转义: <%=value%> 不转义:<%==value%>
- if (code.indexOf('=') === 0) {
-
- var isEscape = code.indexOf('==') !== 0;
-
- code = code.replace(/^=*|[\s;]*$/g, '');
-
- if (isEscape && template.isEscape) {
-
- // 转义处理,但排除辅助方法
- var name = code.replace(/\s*\([^\)]+\)/, '');
- if (
- !_helpers.hasOwnProperty(name)
- && !/^(include|print)$/.test(name)
- ) {
- code = "$escape(" + code + ")";
- }
-
- } else {
- code = "$string(" + code + ")";
- }
-
-
- code = replaces[1] + code + replaces[2];
-
- }
-
- if (isDebug) {
- code = "$line=" + thisLine + ";" + code;
- }
-
- getKey(code);
-
- return code + "\n";
- }
-
-
- // 提取模板中的变量名
- function getKey (code) {
-
- code = getVariable(code);
-
- // 分词
- forEach(code, function (name) {
-
- // 除重
- if (!uniq.hasOwnProperty(name)) {
- setValue(name);
- uniq[name] = true;
- }
-
- });
-
- }
-
-
- // 声明模板变量
- // 赋值优先级:
- // 内置特权方法(include, print) > 私有模板辅助方法 > 数据 > 公用模板辅助方法
- function setValue (name) {
-
- var value;
-
- if (name === 'print') {
-
- value = print;
-
- } else if (name === 'include') {
-
- prototype["$include"] = _helpers['$include'];
- value = include;
-
- } else {
-
- value = "$data." + name;
-
- if (_helpers.hasOwnProperty(name)) {
-
- prototype[name] = _helpers[name];
-
- if (name.indexOf('$') === 0) {
- value = "$helpers." + name;
- } else {
- value = value
- + "===undefined?$helpers." + name + ":" + value;
- }
- }
-
-
- }
-
- variables += name + "=" + value + ",";
- };
-
-
- // 字符串转义
- function stringify (code) {
- return "'" + code
- // 单引号与反斜杠转义
- .replace(/('|\\)/g, '\\$1')
- // 换行符转义(windows + linux)
- .replace(/\r/g, '\\r')
- .replace(/\n/g, '\\n') + "'";
- };
-
-
- };
-})();
-
-
-// RequireJS && SeaJS
-if (typeof define === 'function') {
- define(function() {
- return template;
- });
-
-// NodeJS
-} else if (typeof exports !== 'undefined') {
- module.exports = template;
-}
-
-global.template = template;
-
-
-})(this);
-
-
diff --git a/lib/uglify.js b/lib/uglify.js
deleted file mode 100644
index 9f81947..0000000
--- a/lib/uglify.js
+++ /dev/null
@@ -1,384 +0,0 @@
-"use strict";
-
-
-var sys = require("util");
-var fs = require("fs");
-var path = require("path");
-var UglifyJS = require("uglify-js");
-var async = require("async");
-var acorn;
-
-
-exports.beautify = function (file) {
-
- var isJSON = /\.json$/i.test(file);
-
- var command = {
- _: [file],
- expr: false,
- beautify: "width=80",
- b: "width=80",
- self: false,
- v: false,
- verbose: false,
- stats: false,
- acorn: false,
- spidermonkey: false,
- lint: false,
- V: false,
- version: false,
- output: file,
- o: file,
- comments: '//',
- screw_ie8: false,
- export_all: false
- };
-
- if (isJSON) {
- command.expr = true;
- command.beautify = command.b = 'quote-keys=true,width=80';
- }
-
- init(command);
-};
-
-
-
-exports.minify = function (file) {
- var command = {
- _: [file],
- expr: false,
- self: false,
- v: false,
- verbose: false,
- stats: false,
- acorn: false,
- spidermonkey: false,
- lint: false,
- V: false,
- version: false,
- output: file,
- o: file,
- mangle: true,
- m: true,
- reserved: 'include,require',
- r: 'include,require',
- comments: '/|^$/',
- compress: 'warnings=fasle',
- c: 'warnings=false',
- beautify: 'beautify=false,ascii-only=true',
- b: 'beautify=false,ascii-only=true',
- screw_ie8: false,
- export_all: false
- };
-
- init(command);
-};
-
-
-
-
-function init (ARGS) {
-
-
-
-var COMPRESS = getOptions("c", true);
-var MANGLE = getOptions("m", true);
-var BEAUTIFY = getOptions("b", true);
-
-if (ARGS.d) {
- if (COMPRESS) COMPRESS.global_defs = getOptions("d");
-}
-
-if (ARGS.r) {
- if (MANGLE) MANGLE.except = ARGS.r.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
-}
-
-var OUTPUT_OPTIONS = {
- beautify: BEAUTIFY ? true : false
-};
-
-if (ARGS.screw_ie8) {
- if (COMPRESS) COMPRESS.screw_ie8 = true;
- if (MANGLE) MANGLE.screw_ie8 = true;
- OUTPUT_OPTIONS.screw_ie8 = true;
-}
-
-if (BEAUTIFY)
- UglifyJS.merge(OUTPUT_OPTIONS, BEAUTIFY);
-
-if (ARGS.comments) {
- if (/^\//.test(ARGS.comments)) {
- OUTPUT_OPTIONS.comments = new Function("return(" + ARGS.comments + ")")();
- } else if (ARGS.comments == "all") {
- OUTPUT_OPTIONS.comments = true;
- } else {
- OUTPUT_OPTIONS.comments = function(node, comment) {
- var text = comment.value;
- var type = comment.type;
- if (type == "comment2") {
- // multiline comment
- return /@preserve|@license|@cc_on/i.test(text);
- }
- }
- }
-}
-
-var files = ARGS._.slice();
-
-if (ARGS.self) {
- if (files.length > 0) {
- sys.error("WARN: Ignoring input files since --self was passed");
- }
- files = UglifyJS.FILES;
- if (!ARGS.wrap) ARGS.wrap = "UglifyJS";
- ARGS.export_all = true;
-}
-
-var ORIG_MAP = ARGS.in_source_map;
-
-if (ORIG_MAP) {
- ORIG_MAP = JSON.parse(fs.readFileSync(ORIG_MAP));
- if (files.length == 0) {
- sys.error("INFO: Using file from the input source map: " + ORIG_MAP.file);
- files = [ ORIG_MAP.file ];
- }
- if (ARGS.source_map_root == null) {
- ARGS.source_map_root = ORIG_MAP.sourceRoot;
- }
-}
-
-if (files.length == 0) {
- files = [ "-" ];
-}
-
-if (files.indexOf("-") >= 0 && ARGS.source_map) {
- sys.error("ERROR: Source map doesn't work with input from STDIN");
- process.exit(1);
-}
-
-if (files.filter(function(el){ return el == "-" }).length > 1) {
- sys.error("ERROR: Can read a single file from STDIN (two or more dashes specified)");
- process.exit(1);
-}
-
-var STATS = {};
-var OUTPUT_FILE = ARGS.o;
-var TOPLEVEL = null;
-var P_RELATIVE = ARGS.p && ARGS.p == "relative";
-
-var SOURCE_MAP = ARGS.source_map ? UglifyJS.SourceMap({
- file: P_RELATIVE ? path.relative(path.dirname(ARGS.source_map), OUTPUT_FILE) : OUTPUT_FILE,
- root: ARGS.source_map_root,
- orig: ORIG_MAP,
-}) : null;
-
-OUTPUT_OPTIONS.source_map = SOURCE_MAP;
-
-try {
- var output = UglifyJS.OutputStream(OUTPUT_OPTIONS);
- var compressor = COMPRESS && UglifyJS.Compressor(COMPRESS);
-} catch(ex) {
- if (ex instanceof UglifyJS.DefaultsError) {
- sys.error(ex.msg);
- sys.error("Supported options:");
- sys.error(sys.inspect(ex.defs));
- process.exit(1);
- }
-}
-
-async.eachLimit(files, 1, function (file, cb) {
- read_whole_file(file, function (err, code) {
- if (err) {
- sys.error("ERROR: can't read file: " + file);
- process.exit(1);
- }
- if (ARGS.p != null) {
- if (P_RELATIVE) {
- file = path.relative(path.dirname(ARGS.source_map), file);
- } else {
- var p = parseInt(ARGS.p, 10);
- if (!isNaN(p)) {
- file = file.replace(/^\/+/, "").split(/\/+/).slice(ARGS.p).join("/");
- }
- }
- }
- time_it("parse", function(){
- if (ARGS.spidermonkey) {
- var program = JSON.parse(code);
- if (!TOPLEVEL) TOPLEVEL = program;
- else TOPLEVEL.body = TOPLEVEL.body.concat(program.body);
- }
- else if (ARGS.acorn) {
- TOPLEVEL = acorn.parse(code, {
- locations : true,
- trackComments : true,
- sourceFile : file,
- program : TOPLEVEL
- });
- }
- else {
- TOPLEVEL = UglifyJS.parse(code, {
- filename : file,
- toplevel : TOPLEVEL,
- expression : ARGS.expr,
- });
- };
- });
- cb();
- });
-}, function () {
- if (ARGS.acorn || ARGS.spidermonkey) time_it("convert_ast", function(){
- TOPLEVEL = UglifyJS.AST_Node.from_mozilla_ast(TOPLEVEL);
- });
-
- if (ARGS.wrap) {
- TOPLEVEL = TOPLEVEL.wrap_commonjs(ARGS.wrap, ARGS.export_all);
- }
-
- if (ARGS.enclose) {
- var arg_parameter_list = ARGS.enclose;
- if (arg_parameter_list === true) {
- arg_parameter_list = [];
- }
- else if (!(arg_parameter_list instanceof Array)) {
- arg_parameter_list = [arg_parameter_list];
- }
- TOPLEVEL = TOPLEVEL.wrap_enclose(arg_parameter_list);
- }
-
- var SCOPE_IS_NEEDED = COMPRESS || MANGLE || ARGS.lint;
-
- if (SCOPE_IS_NEEDED) {
- time_it("scope", function(){
- TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
- if (ARGS.lint) {
- TOPLEVEL.scope_warnings();
- }
- });
- }
-
- if (COMPRESS) {
- time_it("squeeze", function(){
- TOPLEVEL = TOPLEVEL.transform(compressor);
- });
- }
-
- if (SCOPE_IS_NEEDED) {
- time_it("scope", function(){
- TOPLEVEL.figure_out_scope({ screw_ie8: ARGS.screw_ie8 });
- if (MANGLE) {
- TOPLEVEL.compute_char_frequency(MANGLE);
- }
- });
- }
-
- if (MANGLE) time_it("mangle", function(){
- TOPLEVEL.mangle_names(MANGLE);
- });
- time_it("generate", function(){
- TOPLEVEL.print(output);
- });
-
- output = output.get();
-
- if (SOURCE_MAP) {
- fs.writeFileSync(ARGS.source_map, SOURCE_MAP, "utf8");
- var source_map_url = ARGS.source_map_url || (
- P_RELATIVE
- ? path.relative(path.dirname(OUTPUT_FILE), ARGS.source_map)
- : ARGS.source_map
- );
- output += "\n//# sourceMappingURL=" + source_map_url;
- }
-
- if (OUTPUT_FILE) {
- fs.writeFileSync(OUTPUT_FILE, output, "utf8");
- } else {
- sys.print(output);
- sys.error("\n");
- }
-
- if (ARGS.stats) {
- sys.error(UglifyJS.string_template("Timing information (compressed {count} files):", {
- count: files.length
- }));
- for (var i in STATS) if (STATS.hasOwnProperty(i)) {
- sys.error(UglifyJS.string_template("- {name}: {time}s", {
- name: i,
- time: (STATS[i] / 1000).toFixed(3)
- }));
- }
- }
-});
-
-/* -----[ functions ]----- */
-
-
-function getOptions(x, constants) {
- x = ARGS[x];
- if (!x) return null;
- var ret = {};
- if (x !== true) {
- var ast;
- try {
- ast = UglifyJS.parse(x);
- } catch(ex) {
- if (ex instanceof UglifyJS.JS_Parse_Error) {
- sys.error("Error parsing arguments in: " + x);
- process.exit(1);
- }
- }
- ast.walk(new UglifyJS.TreeWalker(function(node){
- if (node instanceof UglifyJS.AST_Toplevel) return; // descend
- if (node instanceof UglifyJS.AST_SimpleStatement) return; // descend
- if (node instanceof UglifyJS.AST_Seq) return; // descend
- if (node instanceof UglifyJS.AST_Assign) {
- var name = node.left.print_to_string({ beautify: false }).replace(/-/g, "_");
- var value = node.right;
- if (constants)
- value = new Function("return (" + value.print_to_string() + ")")();
- ret[name] = value;
- return true; // no descend
- }
- sys.error(node.TYPE)
- sys.error("Error parsing arguments in: " + x);
- process.exit(1);
- }));
- }
- return ret;
-}
-
-function read_whole_file(filename, cb) {
- if (filename == "-") {
- var chunks = [];
- process.stdin.setEncoding('utf-8');
- process.stdin.on('data', function (chunk) {
- chunks.push(chunk);
- }).on('end', function () {
- cb(null, chunks.join(""));
- });
- process.openStdin();
- } else {
- fs.readFile(filename, "utf-8", cb);
- }
-}
-
-function time_it(name, cont) {
- var t1 = new Date().getTime();
- var ret = cont();
- if (ARGS.stats) {
- var spent = new Date().getTime() - t1;
- if (STATS[name]) STATS[name] += spent;
- else STATS[name] = spent;
- }
- return ret;
-}
-
-
-/*init end*/
-};
-
-
-
-
diff --git a/package.json b/package.json
index e35d84c..fdef2d3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "tmodjs",
- "version": "0.0.2",
+ "version": "1.0.4",
"readmeFilename": "README.md",
"description": "Template Compiler",
"homepage": "https://github.com/aui/tmodjs",
@@ -19,17 +19,19 @@
"type": "git",
"url": "https://github.com/aui/tmodjs.git"
},
- "main": "./tmod.js",
+ "main": "./src/tmod.js",
"bin": {
- "tmod": "./bin/tmod"
+ "tmod": "./bin/tmod",
+ "tmodjs": "./bin/tmod"
},
"scripts": {
"test": "tmod ./test/tpl",
- "test2": "tmod ./test/test/include"
+ "test2": "tmod ./test/test-all/*"
},
"dependencies": {
- "uglify-js": "~2.4.0",
- "async": "~0.2.6"
+ "art-template": "3.0.x",
+ "uglify-js": "2.4.0",
+ "semver": "2.3.0"
},
"license": "BSD"
-}
\ No newline at end of file
+}
diff --git a/src/AOTcompile.js b/src/AOTcompile.js
new file mode 100644
index 0000000..27a6877
--- /dev/null
+++ b/src/AOTcompile.js
@@ -0,0 +1,268 @@
+/*!
+ * TmodJS - AOT Template Compiler
+ * https://github.com/aui/tmodjs
+ * Released under the MIT, BSD, and GPL Licenses
+ */
+
+'use strict';
+
+module.exports = function (template) {
+ /**
+ * 模板预编译器,根据设置生成不同格式的 javascript 模块
+ * @param {String} 模板源代码
+ * @param {Object} 选项
+ */
+ template.AOTcompile = function (source, options) {
+
+ var uri = './' + options.filename;
+
+ // 是否为编译为调试版本
+ var debug = options.debug;
+
+ // 模板名(不能以 . 或者 / 开头)
+ var filename = options.filename;
+
+ // 编译的模块类型
+ var type = options.type;
+
+ // 运行时模块别名。设置此后 runtime 的路径将被写死
+ var alias = options.alias;
+
+ // 运行时名
+ var runtime = options.runtime;
+
+ // 是否压缩 HTML 多余空白字符
+ var compress = debug ? true : options.compress;
+
+ options.cache = false;
+
+ var render = compile(source, options);
+ var requires = parseDependencies(render);
+ var isLogic = testTemplateSyntax(source, options);
+ var dir = dirname(uri);
+ var code = '';
+
+
+ if (!isLogic) {
+ code = compress ? compressHTML(source) : source;
+ code = stringify(code);
+
+ } else {
+
+ code = render.replace(ANONYMOUS_RE, 'function');
+ }
+
+
+ // 计算主入口相对于当前模板路径
+ var getRuntime = function () {
+ if (alias) {
+ return alias;
+ }
+ var prefix = './';
+ var length = dir.split('/').length - 2;
+ if (length) {
+ prefix = (new Array(length + 1)).join('../');
+ }
+ return prefix + runtime.replace(/\.js$/, '');
+ };
+
+
+ // 生成 require 函数依赖声明代码
+ var getRequireCode = function () {
+ var requiresCode = [];
+
+ requires.forEach(function (uri) {
+ requiresCode.push("require('" + uri + "');");
+ });
+
+ return requiresCode.join('\n');
+ };
+
+
+ switch (type) {
+
+ // RequireJS / SeaJS 兼容模块格式
+ case 'cmd':
+
+ code
+ = "define(function(require){"
+ + getRequireCode()
+ + "return require('" + getRuntime() + "')"
+ + "('" + filename + "', " + code + ");"
+ + "});";
+ break;
+
+
+ // RequireJS 模块格式
+ case 'amd':
+ var dependencies = (requires.length > 0 )? "['" + getRuntime() + "','" + requires.join("','") + "']," : "['" + getRuntime() + "'],";
+ code
+ = "define("
+ + dependencies
+ + "function(template){"
+ + "return template('" + filename + "', " + code + ");"
+ + "});";
+ break;
+
+
+ // NodeJS 模块格式
+ case 'commonjs':
+
+ code
+ = "var template=require('" + getRuntime() + "');"
+ + getRequireCode()
+ + "module.exports=template('" + filename + "'," + code + ");";
+ break;
+
+
+ default:
+
+ code = "template('" + filename + "'," + code + ");";
+
+ }
+
+
+ return {
+
+ // 编译结果
+ code: code,
+
+ // 依赖的子模板
+ requires: requires.map(function (subUir) {
+ testUri(subUir, uri, source);
+ return resolve(dir + subUir);
+ })
+
+ };
+
+ };
+
+
+
+ template.config('cache', false);
+ template.onerror = function (e) {
+ throw e;
+ };
+
+
+ var SLASH_RE = /\\\\/g;
+ var DOT_RE = /\/\.\//g;
+ var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
+ var DIRNAME_RE = /[^/]+$/;
+ var ANONYMOUS_RE = /^function\s+anonymous/;
+ var EXTNAME_RE = /\.(html|htm|tpl)$/i;
+ var INCLUDE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*include|(?:^|[^$])\binclude\s*\(\s*(["'])(.+?)\1/g; //"
+
+
+ // 编译模板
+ var compile = function (source, options) {
+
+ var render = template.compile(source, options);
+
+ return render
+ .toString()
+ .replace(ANONYMOUS_RE, 'function');
+ };
+
+
+ // 检查模板是否有逻辑语法
+ var testTemplateSyntax = function (source, options) {
+ return source.indexOf(options.openTag) !== -1;
+ };
+
+
+ // 模板 filename 规范检查
+ // 保证 include 语法引用的是相对路径
+ var testUri = function (uri, fromUri, source) {
+ if (!/^\./.test(uri) || EXTNAME_RE.test(uri)) {
+
+ var line;
+
+ // 如果只出现一次这个字符串,很容易确认模板错误行
+ if (source.split(uri).length === 2) {
+ source.split(/\n/).forEach(function (code, index) {
+ if (code.indexOf(uri) !== -1) {
+ line = index + 1;
+ source = code.trim();
+ }
+ });
+ }
+
+ var error = {
+ name: 'Syntax Error',
+ line: line,
+ source: source,
+ message: 'Template must be a relative path, and can not have a suffix.'
+ };
+
+ if (fromUri) {
+ error.filename = fromUri;
+ }
+
+ throw error;
+ }
+ };
+
+
+ // 依赖分析
+ var parseDependencies = function (code) {
+ var list = [];
+ var uniq = {};
+
+ code
+ .replace(SLASH_RE, '')
+ .replace(INCLUDE_RE, function(m, m1, m2) {
+ if (m2 && !uniq.hasOwnProperty(m2)) {
+ list.push(m2);
+ uniq[m2] = true;
+ }
+ });
+
+ return list;
+ };
+
+
+ // 获取上一层 uri
+ var dirname = function (uri) {
+ return uri.replace(DIRNAME_RE, '');
+ };
+
+
+ // 分解为标准化 uri
+ var resolve = function (uri) {
+ uri = uri.replace(DOT_RE, '/');
+ while (uri.match(DOUBLE_DOT_RE)) {
+ uri = uri.replace(DOUBLE_DOT_RE, '/');
+ }
+ return uri;
+ };
+
+
+ // 构造字符串表达式
+ var stringify = function (code) {
+ return "'" + code
+ .replace(/('|\\)/g, '\\$1')
+ .replace(/\r/g, '\\r')
+ .replace(/\n/g, '\\n')
+ + "'";
+ };
+
+
+ // 压缩 HTML 字符串
+ var compressHTML = function (code) {
+ code = code
+ .replace(/\s+/g, ' ')
+ .replace(//g, '');
+
+ return code;
+ };
+
+
+ return template;
+};
+
+
+
+
+
+
diff --git a/src/defaults.js b/src/defaults.js
new file mode 100644
index 0000000..71ae5c6
--- /dev/null
+++ b/src/defaults.js
@@ -0,0 +1,54 @@
+module.exports = {
+
+ // 模板根目录
+ //base: './',
+
+ // 编译输出目录设置
+ output: './build',
+
+ // 模板使用的编码。(只支持 utf-8)
+ charset: 'utf-8',
+
+ // 定义模板采用哪种语法,内置可选:
+ // simple: 默认语法,易于读写。可参看语法文档
+ // native: 功能丰富,灵活多变。语法类似微型模板引擎 tmpl
+ syntax: 'simple',
+
+ // 自定义辅助方法路径
+ helpers: null,
+
+ // 是否过滤 XSS
+ // 如果后台给出的数据已经进行了 XSS 过滤,就可以关闭模板的过滤以提升模板渲染效率
+ escape: true,
+
+ // 是否压缩 HTML 多余空白字符
+ compress: true,
+
+ // 输出的模块类型,可选:
+ // default: 模板目录将会打包后输出,可使用 script 标签直接引入,也支持 NodeJS/RequireJS/SeaJS。
+ // cmd: 这是一种兼容 RequireJS/SeaJS 的模块(类似 atc v1版本编译结果)
+ // amd: 支持 RequireJS 等流行加载器
+ // commonjs: 编译为 NodeJS 模块
+ type: 'default',
+
+ // 设置输出的运行时文件名
+ runtime: 'template.js',
+
+ // 设置模块依赖的运行时路径
+ // 仅针对于非``type:'default'``的模块配置字段。如果不指定模块内部会自动使用相对``runtime``的路径
+ alias: null,
+
+ // 是否合并模板
+ // 仅针对于``type:'default'``的模块
+ combo: true,
+
+ // 是否输出为压缩的格式
+ minify: true,
+
+ // 是否开启编译缓存
+ cache: true,
+
+ // 是否输出日志
+ verbose: true
+
+};
diff --git a/src/path.js b/src/path.js
new file mode 100644
index 0000000..ac6af7d
--- /dev/null
+++ b/src/path.js
@@ -0,0 +1,34 @@
+/*!
+ * NodeJS path 跨平台支持(让 windows 路径分隔与 linux 保持一致,统一为:“/”)
+ * https://github.com/aui/tmodjs
+ * Released under the MIT, BSD, and GPL Licenses
+ */
+
+'use strict';
+
+var path = require('path');
+
+if (!/\\/.test(path.resolve())) {
+ module.exports = path;
+} else {
+ var oldPath = path;
+ var newPath = Object.create(oldPath);
+ var proxy = function (name) {
+ return function () {
+ var value = oldPath[name].apply(oldPath, arguments);
+ if (typeof value === 'string') {
+ value = value.split(oldPath.sep).join('/');
+ }
+ return value;
+ };
+ };
+
+ for (var name in newPath) {
+ if (typeof oldPath[name] === 'function') {
+ newPath[name] = proxy(name);
+ }
+ }
+
+ module.exports = newPath;
+}
+
diff --git a/src/runtime.js b/src/runtime.js
new file mode 100644
index 0000000..866d2e7
--- /dev/null
+++ b/src/runtime.js
@@ -0,0 +1,244 @@
+/*! Template Runtime */
+
+var runtime = function () {
+
+ function template (filename, content) {
+ return (
+ /string|function/.test(typeof content)
+ ? compile : renderFile
+ )(filename, content);
+ };
+
+
+ var cache = template.cache = {};
+ var String = this.String;
+
+ function toString (value, type) {
+
+ if (typeof value !== 'string') {
+
+ type = typeof value;
+ if (type === 'number') {
+ value += '';
+ } else if (type === 'function') {
+ value = toString(value.call(value));
+ } else {
+ value = '';
+ }
+ }
+
+ return value;
+
+ };
+
+
+ var escapeMap = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "&": "&"
+ };
+
+
+ function escapeFn (s) {
+ return escapeMap[s];
+ }
+
+
+ function escapeHTML (content) {
+ return toString(content)
+ .replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
+ };
+
+
+ var isArray = Array.isArray || function(obj) {
+ return ({}).toString.call(obj) === '[object Array]';
+ };
+
+
+ function each (data, callback) {
+ if (isArray(data)) {
+ for (var i = 0, len = data.length; i < len; i++) {
+ callback.call(data, data[i], i, data);
+ }
+ } else {
+ for (i in data) {
+ callback.call(data, data[i], i);
+ }
+ }
+ };
+
+
+ function resolve (from, to) {
+ var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
+ var dirname = ('./' + from).replace(/[^/]+$/, "");
+ var filename = dirname + to;
+ filename = filename.replace(/\/\.\//g, "/");
+ while (filename.match(DOUBLE_DOT_RE)) {
+ filename = filename.replace(DOUBLE_DOT_RE, "/");
+ }
+ return filename;
+ };
+
+
+ var utils = template.utils = {
+
+ $helpers: {},
+
+ $include: function (filename, data, from) {
+ filename = resolve(from, filename);
+ return renderFile(filename, data);
+ },
+
+ $string: toString,
+
+ $escape: escapeHTML,
+
+ $each: each
+
+ };
+
+
+ var helpers = template.helpers = utils.$helpers;
+
+
+ function renderFile (filename, data) {
+ var fn = template.get(filename) || showDebugInfo({
+ filename: filename,
+ name: 'Render Error',
+ message: 'Template not found'
+ });
+ return data ? fn(data) : fn;
+ };
+
+
+ function compile (filename, fn) {
+
+ if (typeof fn === 'string') {
+ var string = fn;
+ fn = function () {
+ return new String(string);
+ };
+ }
+
+ var render = cache[filename] = function (data) {
+ try {
+ return new fn(data, filename) + '';
+ } catch (e) {
+ return showDebugInfo(e)();
+ }
+ };
+
+ render.prototype = fn.prototype = utils;
+ render.toString = function () {
+ return fn + '';
+ };
+
+ return render;
+ };
+
+
+ function showDebugInfo (e) {
+
+ var type = "{Template Error}";
+ var message = e.stack || '';
+
+ if (message) {
+ // 利用报错堆栈信息
+ message = message.split('\n').slice(0,2).join('\n');
+ } else {
+ // 调试版本,直接给出模板语句行
+ for (var name in e) {
+ message += "<" + name + ">\n" + e[name] + "\n\n";
+ }
+ }
+
+ return function () {
+ if (typeof console === "object") {
+ console.error(type + "\n\n" + message);
+ }
+ return type;
+ };
+ };
+
+
+ template.get = function (filename) {
+ return cache[filename.replace(/^\.\//, '')];
+ };
+
+
+ template.helper = function (name, helper) {
+ helpers[name] = helper;
+ };
+
+
+ '<:namespace:>'
+ '<:helpers:>'
+ '<:templates:>'
+
+}.toString();
+
+
+var getNamespaceCode = function (type) {
+ var code = '';
+
+ switch (type) {
+
+ // RequireJS / SeaJS 兼容模块格式
+ case 'cmd':
+ // RequireJS 模块格式
+ case 'amd':
+
+ code
+ = "define(function(){"
+ + "return template;"
+ + "});";
+ break;
+
+ // NodeJS 模块格式
+ case 'commonjs':
+
+ code = "module.exports = template;"
+ break;
+
+ // 在全局定义
+ case 'global':
+
+ code = 'this.template = template;';
+ break;
+
+ // 自适应格式
+ default:
+
+ code
+ = "if (typeof define === 'function') {"
+ + "define(function() {"
+ + "return template;"
+ + "});"
+ + "} else if (typeof exports !== 'undefined') {"
+ + "module.exports = template;"
+ + "} else {"
+ + "this.template = template;"
+ + "}";
+
+ }
+
+ return code;
+};
+
+
+var VAR_RE = /['"]<\:(.*?)\:>['"]/g;
+
+module.exports = function (data) {
+
+ data.namespace = getNamespaceCode(data.type);
+
+ var code = runtime
+ .replace(VAR_RE, function ($1, $2) {
+ return data[$2] || '';
+ });
+
+ code = '!' + code + '()';
+ return code;
+};
diff --git a/src/stdout.js b/src/stdout.js
new file mode 100644
index 0000000..f7b10ad
--- /dev/null
+++ b/src/stdout.js
@@ -0,0 +1,42 @@
+/*!
+ * 在控制台支持多彩日志显示
+ * https://github.com/aui/tmodjs
+ * Released under the MIT, BSD, and GPL Licenses
+ */
+
+'use strict';
+
+var styles = {
+ // styles
+ 'bold' : ['\x1B[1m', '\x1B[22m'],
+ 'italic' : ['\x1B[3m', '\x1B[23m'],
+ 'underline' : ['\x1B[4m', '\x1B[24m'],
+ 'inverse' : ['\x1B[7m', '\x1B[27m'],
+ // colors
+ 'white' : ['\x1B[37m', '\x1B[39m'],
+ 'grey' : ['\x1B[90m', '\x1B[39m'],
+ 'black' : ['\x1B[30m', '\x1B[39m'],
+ 'blue' : ['\x1B[34m', '\x1B[39m'],
+ 'cyan' : ['\x1B[36m', '\x1B[39m'],
+ 'green' : ['\x1B[32m', '\x1B[39m'],
+ 'magenta' : ['\x1B[35m', '\x1B[39m'],
+ 'red' : ['\x1B[31m', '\x1B[39m'],
+ 'yellow' : ['\x1B[33m', '\x1B[39m']
+};
+
+
+styles['b'] = styles['bold'];
+styles['i'] = styles['italic'];
+styles['u'] = styles['underline'];
+
+module.exports = function (message) {
+ message = message.replace(/\[([^\]]*?)\]/igm, function ($1, $2) {
+ return $2.indexOf('/') === 0
+ ? styles[$2.slice(1)][1]
+ : styles[$2][0];
+ });
+
+
+ process.stdout.write(message);
+};
+
diff --git a/src/syntax/native.js b/src/syntax/native.js
new file mode 100644
index 0000000..9fb8aef
--- /dev/null
+++ b/src/syntax/native.js
@@ -0,0 +1 @@
+module.exports = require('art-template/dist/template-native-debug.js');
\ No newline at end of file
diff --git a/src/syntax/simple.js b/src/syntax/simple.js
new file mode 100644
index 0000000..2751f29
--- /dev/null
+++ b/src/syntax/simple.js
@@ -0,0 +1 @@
+module.exports = require('art-template/dist/template-debug.js');
\ No newline at end of file
diff --git a/src/tmod.js b/src/tmod.js
new file mode 100644
index 0000000..cfc0c67
--- /dev/null
+++ b/src/tmod.js
@@ -0,0 +1,1209 @@
+/*!
+ * TmodJS - AOT Template Compiler
+ * https://github.com/aui/tmodjs
+ * Released under the MIT, BSD, and GPL Licenses
+ */
+
+'use strict';
+
+var version = require('../package.json').version;
+var AOTcompile = require('./AOTcompile.js');
+var defaults = require('./defaults.js');
+var runtime = require('./runtime.js');
+var uglify2 = require('./uglify2.js');
+var stdout = require('./stdout.js');
+var watch = require('./watch.js');
+var path = require('./path.js');
+var semver = require('semver');
+
+
+
+var fs = require('fs');
+var vm = require('vm');
+var events = require('events');
+var crypto = require('crypto');
+var child_process = require('child_process');
+var exec = child_process.exec;
+//var execSync = child_process.execSync;
+
+
+// 调试脚本
+var DEBUG_File = '.debug.js';
+
+// 缓存目录
+var CACHE_DIR = '.cache';
+
+var log = function (message) {
+ console.log(message);
+};
+
+
+var Tmod = function (base, options) {
+
+
+ // 模板项目路径
+ this.base = path.resolve(base);
+
+
+ // 项目配置选项
+ this.options = options = this.getConfig(options);
+
+ if (options.output !== false) {
+ // 输出路径
+ this.output = path.resolve(this.base, options.output);
+
+
+ // 运行时输出路径
+ this.runtime = path.resolve(this.output, options.runtime);
+ } else {
+ this.output = defaults.output
+ }
+
+
+ // 编译结果存储
+ this._cache = {};
+
+
+ // 清理模板项目临时文件
+ this._clear();
+
+
+ // 初始化模板引擎
+ this._initEngine();
+
+
+ // 初始化事件系统
+ events.EventEmitter.call(this);
+
+
+ // 初始化 watch 事件,修复 watch 的跨平台的 BUG
+ this.on('newListener', function (event, listener) {
+
+ if (/*watch && */event === 'watch') {
+
+ this.log('\n[green]Waiting...[/green]\n\n');
+
+ watch(this.base, function (data) {
+ this.emit('watch', data);
+ }.bind(this), function (folderPath) {
+ return this.filter(folderPath) && folderPath !== this.output;
+
+ }.bind(this), fs);
+
+ //watch = null;
+ }
+
+ });
+
+
+ // 监听模板修改事件
+ this.on('change', function (data) {
+ var time = (new Date).toLocaleTimeString();
+ this.log('[grey]' + time + '[/grey]\n');
+ });
+
+
+ // 监听模板删除事件(Windows NodeJS 暂时无法做到)
+ this.on('delete', function (data) {
+ var time = (new Date).toLocaleTimeString();
+ this.log('[grey]' + time + '[/grey]\n');
+ this.log('[red]-[/red] ' + data.id + '\n');
+ });
+
+
+ // 监听模板加载事件
+ this.on('load', function (error, data) {
+
+ if (error) {
+ this.log('[red]•[/red] ');
+ this.log(data.id);
+ return;
+ }
+
+ if (data.modified) {
+ this.log('[green]•[/green] ');
+ } else {
+ this.log('[grey]•[/grey] ');
+ }
+
+ this.log(data.id);
+ });
+
+
+ // 监听模板编译事件
+ this.on('compile', function (error, data) {
+
+ if (error) {
+ this.log(' [inverse][red]{{Syntax Error}}[/red][/inverse]\n\n');
+ } else {
+
+ this.log(this.options.debug ? ' [grey][/grey]' : '');
+ this.log(' [grey]:v' + data.version + '[/grey]');
+ this.log('\n');
+
+ }
+
+
+ });
+
+
+ // 调试事件(异步事件)
+ this.on('debug', function (error) {
+
+ this.log('[red]Debug info:[/red]\n');
+
+ if (error.line && error.source) {
+ this.log('[red]' + error.line + ': ' + error.source + '[/red]\n');
+ }
+
+ this.log('[red]' + error.message + '[/red]\n');
+ });
+
+
+
+ // 监听模板合并事件
+ this.on('combo', function (error, data) {
+
+ if (error) {
+ this.log('[red]' + error + '[/red]\n');
+ } else {
+ // this.log('[grey]»[/grey] ');
+ // this.log('[grey]' + this.options.runtime + '[/grey]');
+ // this.log(' [grey]:build' + data.version + '[/grey]');
+ // this.log('\n');
+ }
+
+ });
+
+ // 输出运行时 TODO: 这个时机需要优化
+ this._buildRuntime();
+};
+
+
+// 默认配置
+// 用户配置将保存到模板根目录 package.json 文件中
+Tmod.defaults = defaults;
+
+
+Tmod.prototype = {
+
+ __proto__: events.EventEmitter.prototype,
+
+
+ // 获取用户配置
+ getConfig: function () {
+
+ var options = arguments[0];
+
+ if (!options) {
+ return this.options;
+ }
+
+ var file = path.join(this.base, 'package.json');
+
+ var defaults = Tmod.defaults;
+ var json = null;
+ var name = null;
+ var config = {};
+
+
+ // 读取目录中 package.json
+ if (fs.existsSync(file)) {
+ var fileContent = fs.readFileSync(file, 'utf-8');
+
+ if (fileContent) {
+ json = JSON.parse(fileContent);
+ }
+ }
+
+
+ if (!json) {
+
+ json = {
+ "name": 'template',
+ "version": '1.0.0',
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {}
+ };
+
+ }
+
+ //有些项目的package.json里只有devDependencies而没有dependencies
+ //那么下面的replace那行代码就会出现can't read property 'tmodjs' of undefined的错误
+ //这里添加容错逻辑
+
+ if (!json.dependencies) {
+ json.dependencies = json.devDependencies;
+ }
+
+ var targetVersion = json.dependencies.tmodjs.replace(/^~/, '');
+
+
+
+ try {
+ // 比较模板项目版本号
+ if (semver.lt(version, targetVersion)) {
+ this.log('[red]You must upgrade to the latest version of tmodjs![/red]\n');
+ this.log('Local: ' + version + '\n')
+ this.log('Target: ' + targetVersion + '\n');
+ process.exit(1);
+ }
+ } catch (e) {}
+
+
+
+ // 更新模板项目的依赖版本信息
+ json.dependencies.tmodjs = version;
+
+
+ // 来自 Tmod.defaults
+ for (name in defaults) {
+ config[name] = defaults[name];
+ }
+
+
+ // 来自 package.json 文件
+ for (name in json['tmodjs-config']) {
+ config[name] = json['tmodjs-config'][name];
+ }
+
+
+ // 来自 Tmod(base, options) 的配置
+ for (name in options) {
+ if (options[name] !== undefined) {
+ config[name] = options[name];
+ }
+ }
+
+
+ config = this._fixConfig(config, defaults, json['tmodjs-config'], options);
+
+ json['tmodjs-config'] = config;
+ this['package.json'] = json;
+ this.projectVersion = json.version;
+
+ return config;
+ },
+
+
+ /**
+ * 保存用户配置
+ * @return {String} 用户配置文件路径
+ */
+ saveConfig: function () {
+
+ var file = path.join(this.base, 'package.json');
+ var configName = 'tmodjs-config';
+ var json = this['package.json'];
+
+ var options = json[configName];
+ var userConfigList = Object.keys(Tmod.defaults);
+
+
+ // 只保存指定的字段
+ json[configName] = JSON.parse(
+ JSON.stringify(options, userConfigList)
+ );
+
+
+ var text = JSON.stringify(json, null, 4);
+
+
+ fs.writeFileSync(file, text, 'utf-8');
+
+ return file;
+ },
+
+
+ /**
+ * 编译模板
+ * @param {String, ArrayList} 模板文件相对路径。无此参数则编译目录所有模板
+ */
+ compile: function (file) {
+
+ var that = this;
+ var error = false;
+ var walk;
+
+ if (file) {
+
+
+ var fileList = typeof file === 'string' ? [file] : file;
+
+ fileList = fileList.map(function (file) {
+ return path.resolve(that.base, file);
+ });
+
+ walk = function (list) {
+
+ list.forEach(function (file) {
+
+ if (error) {
+ return;
+ }
+
+ error = !that._compile(file);
+
+ });
+ };
+
+
+ walk(fileList);
+
+ if (!error && this.options.combo) {
+ this._combo();
+ }
+
+ } else {
+
+
+ walk = function (dir) {
+
+ if (dir === that.output) {
+ return;
+ }
+
+ var dirList = fs.readdirSync(dir);
+
+ dirList.forEach(function (item) {
+
+ if (error) {
+ return;
+ }
+
+ if (fs.statSync(path.join(dir, item)).isDirectory()) {
+ walk(path.join(dir, item));
+ } else if (that.filterBasename(item) && that.filterExtname(item)) {
+ error = !that._compile(path.join(dir, item));
+ }
+
+ });
+ };
+
+
+ walk(this.base);
+
+ if (!error && this.options.combo) {
+ this._combo();
+ }
+ }
+
+ },
+
+
+ /**
+ * 文件与路径筛选器
+ * @param {String} 绝对路径
+ * @return {Boolean}
+ */
+ filter: function (file) {
+
+ if (fs.existsSync(file)) {
+ var stat = fs.statSync(file);
+ if (stat.isDirectory()) {
+
+ var dirs = file.split(path.sep);
+ var basedir = dirs[dirs.length - 1];
+
+ return this.filterBasename(basedir) ? true : false;
+
+ } else {
+
+ return this.filterBasename(path.basename(file))
+ && this.filterExtname(path.extname(file));
+ }
+
+ } else {
+ return false;
+ }
+ },
+
+
+ /**
+ * 名称筛选器
+ * @param {String}
+ * @return {Boolean}
+ */
+ filterBasename: function (name) {
+ // 英文、数字、点、中划线、下划线的组合,且不能以点开头
+ var FILTER_RE = /^\.|[^\w\.\-$]/;
+
+ return !FILTER_RE.test(name);
+ },
+
+
+ /**
+ * 后缀名筛选器
+ * @param {String}
+ * @return {Boolean}
+ */
+ filterExtname: function (name) {
+ // 支持的后缀名
+ var EXTNAME_RE = /\.(html|htm|tpl)$/i;
+ return EXTNAME_RE.test(name);
+ },
+
+
+ /**
+ * 启动即时编译,监听文件修改自动编译
+ */
+ watch: function () {
+
+ // 监控模板目录
+ this.on('watch', function (data) {
+
+ var type = data.type;
+ var fstype = data.fstype;
+ var target = data.target;
+ var parent = data.parent;
+ var fullname = path.join(parent, target);
+
+
+ if (target && fstype === 'file' && this.filter(fullname)) {//
+
+ if (type === 'delete') {
+
+ this.emit('delete', {
+ id: this._toId(target),
+ sourceFile: target
+ });
+
+ var jsFile = fullname.replace(path.extname(fullname), '');
+ jsFile = jsFile.replace(this.base, this.output) + '.js'
+
+ this._fsUnlink(jsFile);
+
+ this._removeCache(target);
+
+ if (this.options.combo) {
+ this._combo();
+ }
+
+ } else if (/updated|create/.test(type)) {
+
+ this.emit('change', {
+ id: this._toId(target),
+ sourceFile: target
+ });
+
+ if (this._compile(fullname)) {
+ if (this.options.combo) {
+ this._combo();
+ }
+ }
+
+ }
+ }
+
+ });
+
+ },
+
+
+ /**
+ * 打印日志
+ * @param {String} 消息
+ */
+ log: function (message) {
+ if (this.options.verbose) {
+ stdout(message);
+ }
+ },
+
+
+ // 修正配置-版本兼容
+ _fixConfig: function (options, defaultsConfig, projectConfig, inputConfig) {
+
+ var cwd = process.cwd();
+ var base = this.base;
+
+ // 忽略大小写
+ options.type = options.type.toLowerCase();
+
+
+ // 模板合并规则
+ // 兼容 0.0.3-rc3 之前的配置
+ if (Array.isArray(options.combo) && !options.combo.length) {
+ options.combo = false;
+ } else {
+ options.combo = !!options.combo;
+ }
+
+
+ // 兼容 0.1.0 之前的配置
+ if (options.type === 'templatejs') {
+ options.type = 'default';
+ }
+
+
+ // 根据生成模块的类型删除不支持的配置字段
+ if (options.type === 'default' || options.type === 'global') {
+ delete options.alias;
+ } else {
+ delete options.combo;
+ }
+
+
+ // 处理外部输入:转换成相对于 base 的路径
+
+ if (inputConfig.output) {
+ options.output = path.relative(base, path.resolve(cwd, inputConfig.output));
+ }
+
+ if (inputConfig.syntax && /\.js$/.test(inputConfig.syntax)) {// 值可能为内置名称:native || simple
+ options.syntax = path.relative(base, path.resolve(cwd, inputConfig.syntax));
+ }
+
+ if (inputConfig.helpers) {
+ options.helpers = path.relative(base, path.resolve(cwd, inputConfig.helpers));
+ }
+
+
+ return options;
+ },
+
+
+ // 文件写入
+ _fsWrite: function (file, data, charset) {
+ this._fsMkdir(path.dirname(file));
+ fs.writeFileSync(file, data, charset || 'utf-8');
+ },
+
+
+ // 文件读取
+ _fsRead: function (file, charset) {
+ if (fs.existsSync(file)) {
+ return fs.readFileSync(file, charset || 'utf-8');
+ }
+ },
+
+
+ // 创建目录,包括子文件夹
+ _fsMkdir: function (dir) {
+
+ var currPath = dir;
+ var toMakeUpPath = [];
+
+ while (!fs.existsSync(currPath)) {
+ toMakeUpPath.unshift(currPath);
+ currPath = path.dirname(currPath);
+ }
+
+ toMakeUpPath.forEach(function (pathItem) {
+ fs.mkdirSync(pathItem);
+ });
+
+ },
+
+
+ // 删除文件夹,包括子文件夹
+ _fsRmdir: function (dir) {
+
+ var walk = function (dir) {
+
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
+ return;
+ }
+
+ var files = fs.readdirSync(dir);
+
+ if (!files.length) {
+ fs.rmdirSync(dir);
+ return;
+ } else {
+ files.forEach(function (file) {
+ var fullName = path.join(dir, file);
+ if (fs.statSync(fullName).isDirectory()) {
+ walk(fullName);
+ } else {
+ fs.unlinkSync(fullName);
+ }
+ });
+ }
+
+ fs.rmdirSync(dir);
+ };
+
+ walk(dir);
+ },
+
+
+ // 删除模板文件
+ _fsUnlink: function (file) {
+ return fs.existsSync(file) && fs.unlinkSync(file);
+ },
+
+
+ // 获取字符串 md5 值
+ _getMd5: function (text) {
+ return crypto.createHash('md5').update(text).digest('hex');
+ },
+
+
+ // 获取元数据
+ _getMetadata: function (js) {
+ var data = js.match(/\/\*TMODJS\:(.*?)\*\//);
+ if (data) {
+ return JSON.parse(data[1]);
+ }
+ },
+
+
+ // 删除元数据
+ _removeMetadata: function (js) {
+ var data = this._getMetadata(js) || {};
+ var newText = '';
+
+ // 文件末尾设置一个空注释,然后让 UglifyJS 不压缩它,避免很多文件挤成一行
+ if (data.version) {
+ newText = '/*v:' + data.version + '*/';
+ }
+
+ return js.replace(/^\/\*TMODJS\:(?:.*)\*\//, newText);
+ },
+
+
+ // 设置元数据
+ _setMetadata: function (js, data) {
+ data = JSON.stringify(data || {});
+ js = '/*TMODJS:' + data + '*/\n' + js
+ .replace(/\/\*TMODJS\:(.*?)\*\//, '');
+ return js;
+ },
+
+
+ // 调试语法错误
+ _debug: function (error, callback) {
+
+ var debugFile = error.debugFile;
+ var code = error.temp;
+
+ code = "/*! */\n' + code;
+
+ this._fsWrite(debugFile, code, this.options.charset);
+
+ // 启动子进程进行调试,从根本上避免影响当前进程
+
+ exec('node ' + debugFile, function (error, stdout, stderr) {
+ var message = error ? error.message : '';
+ message = message
+ .replace(/^Command\sfailed\:|\s*SyntaxError[\w\W]*$/g, '')
+ .trim();
+ callback(message);
+ });
+
+ },
+
+
+ // 编译运行时
+ _buildRuntime: function (templates, metadata, callback) {
+
+ templates = templates || '';
+ metadata = metadata || {};
+ callback = callback || function () {};
+
+ var error = null;
+
+ var runtimeCode = runtime({
+ type: this.options.type,
+ helpers: this._helpersCode,
+ templates: templates
+ });
+
+
+ runtimeCode = this._setMetadata(runtimeCode, metadata);
+
+ if (this.options.output !== false) {
+ try {
+ this._fsMkdir(path.dirname(this.runtime));
+ fs.writeFileSync(this.runtime, runtimeCode, this.options.charset);
+ } catch (e) {
+ error = e;
+ }
+
+
+ if (this.options.debug) {
+ this._beautify(this.runtime);
+ }
+
+ if (!this.options.debug && this.options.minify) {
+ this._minify(this.runtime);
+ }
+ }
+
+ callback.call(this, error, runtimeCode);
+ },
+
+
+ _getUglifyOptions: function () {
+ return {
+ // require 变量是 AMD 、CMD 模块需要硬解析的字符
+ reserved: 'require',
+ // 忽略压缩的注释
+ comments: '/TMODJS\\:|^v\\:\\d+/',
+ compress: {
+ warnings: false
+ }
+ };
+ },
+
+
+ _uglify: function (file, options) {
+
+ var result;
+
+ try {
+ result = uglify2(file, file, options);
+ } catch (e) {
+ var err = new Error('Uglification failed.');
+ if (e.message) {
+ err.message += '\n' + e.message + '. \n';
+ if (e.line) {
+ err.message += 'Line ' + e.line + ' in ' + file + '\n';
+ }
+ }
+ err.origError = e;
+ console.log(err);
+ }
+
+ try {
+ if (result) {
+ fs.writeFileSync(file, result.output, this.options.charset);
+ }
+ } catch (e) {}
+ },
+
+
+ // 格式化 js
+ _beautify: function (file) {
+ var options = this._getUglifyOptions();
+ options.mangle = false;
+ options.beautify = true;
+ this._uglify(file, options);
+ },
+
+
+ // 压缩 js
+ _minify: function (file) {
+ var options = this._getUglifyOptions();
+ options.mangle = {};
+ options.beautify = false;
+ options.ascii_only = true;
+ this._uglify(file, options);
+ },
+
+
+ // 打包模板
+ _combo: function () {
+
+ var files = [];
+ var combo = '';
+ var cache = this._getCache();
+ var code = '';
+ var build = Date.now();
+
+ for (var i in cache) {
+
+ code = cache[i];
+ code = this._removeMetadata(code);
+ combo += code;
+
+ files.push(i);
+ }
+
+
+ var metadata = {};
+ if (this.options.debug) {
+ metadata.debug = true;
+ }
+
+ if (this.options.combo) {
+ metadata.version = this.projectVersion;
+ }
+
+
+ this._buildRuntime(combo, metadata, function (error, data) {
+
+ // 广播:合并事件
+ this.emit('combo', error, {
+
+ // 编译时间
+ build: build,
+
+ // 打包的代码
+ output: data,
+
+ // 输出的文件路径
+ outputFile: this.runtime,
+
+ // 被合并的文件列表
+ sourcefiles: files
+
+ });
+
+ });
+
+ },
+
+
+ // 路径转换为模板 ID
+ // base: /Users/tangbin/Documents/web/tpl
+ // file: /Users/tangbin/Documents/web/tpl/index/main.html
+ // >>>>> index/main
+ _toId: function (file) {
+ var extname = path.extname(file);
+ var id = file.replace(this.base + '/', '').replace(extname, '');
+ return id;
+ },
+
+
+ // 编译单个模板
+ // file: /Users/tangbin/Documents/web/tpl/index/main.html
+ _compile: function (file) {
+
+
+ // 模板字符串
+ var source = '';
+
+ var readError = null;
+ var compileError = null;
+ var writeError = null;
+
+ // 目标路径
+ var target = file
+ .replace(path.extname(file), '.js')
+ .replace(this.base, this.output);
+
+ var mod = this._getCache(file);
+ var modObject = {};
+ var metadata = {};
+ var count = 0;
+
+ var isDebug = this.options.debug;
+ var isCacheDir = this.options.combo;
+
+
+ try {
+ source = fs.readFileSync(file, this.options.charset);
+ } catch (e) {
+ readError = e;
+ }
+
+
+ var newMd5 = this._getMd5(source + JSON.stringify(this['package.json']));
+
+ // 如果开启了合并,编译后的文件使用缓存目录保存
+ if (isCacheDir) {
+ target = target.replace(this.output, path.join(this.output, CACHE_DIR));
+ }
+
+
+ // 尝试从文件中读取上一次编译的结果
+ if (!mod && fs.existsSync(target)) {
+ mod = this._fsRead(target, this.options.charset);
+ }
+
+
+ // 获取缓存的元数据
+ if (mod) {
+ metadata = this._getMetadata(mod) || {};
+ count = metadata.version || 0;
+ }
+
+
+ // 检查是否需要编译
+ var modified = !this.options.cache
+ || !mod // 从来没有编译过
+ || metadata.debug // 上个版本为调试版
+ || isDebug // 当前配置为调试版
+ || newMd5 !== metadata.md5; // 模板已经发生了修改(包括配置文件)
+
+
+ // 获取模板 ID
+ var id = this._toId(file);
+
+
+ // 广播:模板加载事件
+ this.emit('load', readError, {
+
+ // 模板 ID
+ id: id,
+
+ // 模板是否需要重新编译
+ modified: modified,
+
+ // 原始文件路径
+ sourceFile: file,
+
+ // 模板源代码
+ source: source,
+
+ // 输出路径
+ outputFile: target
+
+ });
+
+
+ if (readError) {
+ return;
+ }
+
+
+ try {
+
+ // 编译模板
+ if (modified) {
+ modObject = this.template.AOTcompile(source, {
+ filename: id,
+ alias: this.options.alias,
+ type: this.options.type,
+ compress: this.options.compress,
+ escape: this.options.escape,
+ runtime: this.options.runtime,
+ debug: isDebug
+ });
+ mod = modObject.code;
+ }
+
+ } catch (e) {
+ compileError = e;
+ }
+
+
+ // 不输出的情况:遇到错误 || 文件或配置没有更新
+ if (!compileError && modified) {
+
+ count ++;
+
+ mod = this._setMetadata(mod, {
+ debug: isDebug,
+ version: count,
+ md5: newMd5
+ });
+
+ if (this.options.output !== false) {
+ try {
+ this._fsMkdir(path.dirname(target));//////
+ fs.writeFileSync(target, mod, this.options.charset);
+ } catch (e) {
+ writeError = e;
+ }
+
+
+ if (!isCacheDir && !writeError) {
+ if (isDebug) {
+ this._beautify(target);
+ }
+
+ if (!isDebug && this.options.minify) {
+ this._minify(target);
+ }
+ }
+ }
+
+ }
+
+
+ var compileInfo = {
+
+ // 模板 ID
+ id: id,
+
+ // 版本
+ version: count,
+
+ // 源码
+ source: source,
+
+ // 模板文件路径
+ sourceFile: file,
+
+ // 编译结果代码
+ output: mod,
+
+ // 编译输出文件路径
+ outputFile: target,
+
+ // 是否被修改
+ modified: modified,
+
+ // 依赖的子模板 ID 列表
+ requires: modObject.requires || []
+ };
+
+
+ if (compileError && !compileError.source) {
+
+ // 语法错误,目前只能对比生成后的 js 来查找错误的模板语法
+
+ compileError.debugFile = path.join(this.base, DEBUG_File);
+
+ this.debuging = true;
+
+ this._debug(compileError, function (message) {
+
+ var e = {
+
+ // 错误名称
+ name: compileError.name,
+
+ // 报错信息
+ message: message,
+
+ // 调试文件地址
+ debugFile: compileError.debugFile,
+
+ // 编译器输出的临时文件
+ temp: compileError.temp
+
+ };
+
+ for (var name in e) {
+ compileError[name] = e[name];
+ }
+
+ this.emit('debug', compileError);
+
+ }.bind(this));
+
+
+ } else {
+
+ // 删除上次遗留的调试文件
+ if (this.debuging) {
+ this._fsUnlink(path.join(this.base, DEBUG_File));
+ delete this.debuging;
+ }
+
+ // 缓存编译好的模板
+ this._setCache(file, mod);
+ }
+
+
+ this.emit('compile', compileError || writeError, compileInfo);
+
+
+ if (compileError || writeError) {
+ this.emit('debug', compileError || writeError);
+ return null;
+ } else {
+ return compileInfo;
+ }
+ },
+
+
+ // 计算字节长度
+ _getByteLength: function (content) {
+ return content.replace(/[^\x00-\xff]/gi, '--').length;
+ },
+
+
+ // 获取缓存
+ _getCache: function (id) {
+ if (typeof id === 'undefined') {
+ return this._cache;
+ } else {
+ return this._cache[id];
+ }
+ },
+
+
+ // 设置缓存
+ _setCache: function (id, data) {
+ this._cache[id] = data;
+ },
+
+
+ // 删除缓存
+ _removeCache: function (id) {
+ delete this._cache[id];
+ },
+
+
+ // 初始化模板引擎
+ _initEngine: function () {
+ var options = this.options;
+ var template;
+
+ switch (String(options.syntax)) {
+ case 'native':
+ template = require('./syntax/native.js');
+ break;
+
+ case 'simple':
+ template = require('./syntax/simple.js');
+ break;
+
+ // 不再推荐使用动态加载自定义语法
+ // 为了兼容 < v1.0 的功能
+ default:
+
+ var syntaxFile = path.resolve(this.base, options.syntax);
+
+ if (fs.existsSync(syntaxFile)) {
+
+ template = require('./syntax/native.js');
+
+ var syntaxCode = fs.readFileSync(syntaxFile, 'utf-8');
+
+ vm.runInNewContext(syntaxCode, {
+ console: console,
+ template: template
+ });
+
+ } else {
+
+ this.log('[red]Not found: ' + syntaxFile + '[/red]');
+ process.exit(1);
+
+ }
+ }
+
+
+ // 配置模板引擎:辅助方法
+ if (options.helpers) {
+
+ var helpersFile = path.resolve(this.base, options.helpers);
+
+ if (fs.existsSync(helpersFile)) {
+
+ this._helpersCode = fs.readFileSync(helpersFile, 'utf-8');
+ vm.runInNewContext(this._helpersCode, {
+ console: console,
+ template: template
+ });
+
+ } else {
+
+ this.log('[red]Not found: ' + helpersFile + '[/red]');
+ process.exit(1);
+
+ }
+ }
+
+
+ this.template = AOTcompile(template);
+
+ },
+
+
+ // 清理项目临时文件
+ _clear: function () {
+
+ // 删除上次遗留的调试文件
+ this._fsUnlink(path.join(this.base, DEBUG_File));
+
+
+ // 删除不必要的缓存目录
+ if (!this.options.combo) {
+ this._fsRmdir(path.join(this.output, CACHE_DIR));
+ }
+
+ }
+
+};
+
+module.exports = Tmod;
+
diff --git a/src/uglify2.js b/src/uglify2.js
new file mode 100644
index 0000000..5ae63a6
--- /dev/null
+++ b/src/uglify2.js
@@ -0,0 +1,182 @@
+/*
+ * uglify
+ * https://gruntjs.com/
+ *
+ * Copyright (c) 2014 "Cowboy" Ben Alman, contributors
+ * Licensed under the MIT license.
+ */
+'use strict';
+
+// External libs.
+var path = require('path');
+var fs = require('fs');
+var UglifyJS = require('uglify-js');
+
+// Minify with UglifyJS.
+// From https://github.com/mishoo/UglifyJS2
+// API docs at http://lisperator.net/uglifyjs/
+module.exports = function (files, dest, options) {
+ options = options || {};
+
+ var topLevel = null;
+ var sourcesContent = {};
+
+ var outputOptions = getOutputOptions(options, dest);
+ var output = UglifyJS.OutputStream(outputOptions);
+
+ if (!Array.isArray(files)) {
+ files = [files];
+ }
+
+ // Grab and parse all source files
+ files.forEach(function (file) {
+
+ var code = fs.readFileSync(file, 'utf-8');
+
+ // The src file name must be relative to the source map for things to work
+ var basename = path.basename(file);
+ var fileDir = path.dirname(file);
+ var sourceMapDir = path.dirname(options.generatedSourceMapName || '');
+ var relativePath = path.relative(sourceMapDir, fileDir);
+ var pathPrefix = relativePath ? (relativePath + path.sep) : '';
+
+ // Convert paths to use forward slashes for sourcemap use in the browser
+ file = (pathPrefix + basename).replace(/\\/g, '/');
+
+ sourcesContent[file] = code;
+ topLevel = UglifyJS.parse(code, {
+ filename: file,
+ toplevel: topLevel
+ });
+ });
+
+ // Wrap code in a common js wrapper.
+ if (options.wrap) {
+ topLevel = topLevel.wrap_commonjs(options.wrap, options.exportAll);
+ }
+
+ // Wrap code in closure with configurable arguments/parameters list.
+ if (options.enclose) {
+ var argParamList = options.enclose.map(function (val, key) {
+ return key + ':' + val;
+ });
+
+ topLevel = topLevel.wrap_enclose(argParamList);
+ }
+
+ // Need to call this before we mangle or compress,
+ // and call after any compression or ast altering
+ topLevel.figure_out_scope();
+
+ if (options.compress !== false) {
+ if (options.compress.warnings !== true) {
+ options.compress.warnings = false;
+ }
+ var compressor = UglifyJS.Compressor(options.compress);
+ topLevel = topLevel.transform(compressor);
+
+ // Need to figure out scope again after source being altered
+ topLevel.figure_out_scope();
+ }
+
+ if (options.mangle !== false) {
+ // disabled due to:
+ // 1) preserve stable name mangling
+ // 2) it increases gzipped file size, see https://github.com/mishoo/UglifyJS2#mangler-options
+ // // compute_char_frequency optimizes names for compression
+ // topLevel.compute_char_frequency(options.mangle);
+
+ // Requires previous call to figure_out_scope
+ // and should always be called after compressor transform
+ topLevel.mangle_names(options.mangle);
+ }
+
+ if (options.sourceMap && options.sourceMapIncludeSources) {
+ for (var file in sourcesContent) {
+ if (sourcesContent.hasOwnProperty(file)) {
+ outputOptions.source_map.get().setSourceContent(file, sourcesContent[file]);
+ }
+ }
+ }
+
+ // Print the ast to OutputStream
+ topLevel.print(output);
+
+ var output = output.get();
+
+ // Add the source map reference to the end of the file
+ if (options.sourceMap) {
+ // Set all paths to forward slashes for use in the browser
+ output += "\n//# sourceMappingURL=" + options.destToSourceMap.replace(/\\/g, '/');
+ }
+
+ var result = {
+ output: output,
+ sourceMap: outputOptions.source_map
+ };
+
+ return result;
+};
+
+var getOutputOptions = function (options, dest) {
+ var outputOptions = {
+ beautify: false,
+ source_map: null,
+ ascii_only: false
+ };
+
+
+ if (/^\//.test(options.comments)) {
+ outputOptions.comments = new Function("return(" + options.comments + ")")();
+ } else if (options.comments == "all") {
+ outputOptions.comments = true;
+ } else {
+ outputOptions.comments = function(node, comment) {
+ var text = comment.value;
+ var type = comment.type;
+ if (type == "comment2") {
+ // multiline comment
+ return /@preserve|@license|@cc_on/i.test(text);
+ }
+ }
+ }
+
+ if (options.banner && options.sourceMap) {
+ outputOptions.preamble = options.banner;
+ }
+
+ if (options.beautify) {
+ if (typeof options.beautify === 'object') {
+ // beautify options sent as an object are merged
+ // with outputOptions and passed to the OutputStream
+ outputOptions.beautify = Object.create(options.beautify);
+ } else {
+ outputOptions.beautify = options.beautify;
+ }
+ }
+
+
+ if (options.sourceMap) {
+
+ var destBasename = path.basename(dest);
+ var destPath = path.dirname(dest);
+ outputOptions.source_map = UglifyJS.SourceMap({
+ file: destBasename
+ });
+
+ }
+
+ if (options.indentLevel !== undefined) {
+ outputOptions.indent_level = options.indentLevel;
+ }
+
+ if (options.ascii_only !== undefined) {
+ outputOptions.ascii_only = options.ascii_only;
+ }
+
+ if (options.mangle && options.reserved !== undefined) {
+ options.mangle.except = options.reserved.replace(/^\s+|\s+$/g).split(/\s*,+\s*/);
+ }
+
+ return outputOptions;
+};
diff --git a/src/watch.js b/src/watch.js
new file mode 100644
index 0000000..42dbd9e
--- /dev/null
+++ b/src/watch.js
@@ -0,0 +1,131 @@
+/*!
+ * fs watch 子目录的监听与跨平台支持(让 windows 的行为与 linux 保持一致)
+ * https://github.com/aui/tmodjs
+ * Released under the MIT, BSD, and GPL Licenses
+ */
+
+'use strict';
+
+var fs = require('fs');
+var path = require('path');
+var os = require('os');
+
+
+var watchList = {};
+var timer = {};
+
+
+var walk = function (dir, callback, filter) {
+ fs.readdirSync(dir).forEach(function (item) {
+ var fullname = path.join(dir, item);
+
+ if (fs.statSync(fullname).isDirectory()){
+
+ if (!filter(fullname)){
+ return;
+ }
+
+ watch(fullname, callback, filter);
+ walk(fullname, callback, filter);
+ }
+ });
+};
+
+
+var watch = function (parent, callback, filter) {
+
+ if (watchList[parent]) {
+ watchList[parent].close();
+ }
+
+ watchList[parent] = fs.watch(parent, function (event, filename) {
+
+
+ // @see http://stackoverflow.com/questions/9629307/fs-watch-is-returning-null-for-the-file-name
+ if (filename === null) {
+ return;
+ }
+
+ var fullname = path.join(parent, filename);
+ var type;
+ var fstype;
+
+ if (!filter(fullname)) {
+ return;
+ }
+
+ // 检查文件、目录是否存在
+ if (!fs.existsSync(fullname)) {
+
+ // 如果目录被删除则关闭监视器
+ if (watchList[fullname]) {
+ fstype = 'directory';
+ watchList[fullname].close();
+ delete watchList[fullname];
+ } else {
+ fstype = 'file';
+ }
+
+ type = 'delete';
+
+ } else {
+
+ // 文件
+ if (fs.statSync(fullname).isFile()) {
+
+ fstype = 'file';
+ type = event == 'rename' ? 'create' : 'updated';
+
+ // 文件夹
+ } else if (event === 'rename') {
+
+ fstype = 'directory';
+ type = 'create';
+
+ watch(fullname, callback, filter);
+ walk(fullname, callback, filter);
+ }
+
+ }
+
+ var eventData = {
+ type: type,
+ target: filename,
+ parent: parent,
+ fstype: fstype
+ };
+
+
+ if (/windows/i.test(os.type())) {
+ // window 下 nodejs fs.watch 方法尚未稳定
+ clearTimeout(timer[fullname]);
+ timer[fullname] = setTimeout(function() {
+ callback(eventData);
+ }, 16);
+
+ } else {
+ callback(eventData);
+ }
+
+
+ });
+
+};
+
+
+/**
+ * @param {String} 要监听的目录
+ * @param {Function} 文件、目录改变后的回调函数
+ * @param {Function} 过滤器(可选)
+ */
+module.exports = function (dir, callback, filter) {
+
+ // 排除“.”、“_”开头或者非英文命名的目录
+ var FILTER_RE = /[^\w\.\-$]/;
+ filter = filter || function (name) {
+ return !FILTER_RE.test(name);
+ };
+
+ watch(dir, callback, filter);
+ walk(dir, callback, filter);
+};
\ No newline at end of file
diff --git a/test/README.md b/test/README.md
index 40f1427..6c4b13d 100644
--- a/test/README.md
+++ b/test/README.md
@@ -1,21 +1,20 @@
# TmodJS-运行测试例子
-切换到当前目录,执行 tmod 命令
+## 一、运行默认设置测试例子
+
+### 编译
+
+切换到当前目录
+
+ $ cd test/tpl
+
+然后执行 tmod 命令
```
-$ cd tmodjs/test/tpl
$ tmod
```
-如果终端返回如下信息,表示模板已经编译完成
-```
-./copyright.html √
-./index.html √
-./public/footer.html √
-./public/header.html √
-./public/logo.html √
-```
-## 在浏览器中加载模板
+### 在浏览器中加载模板
模板经过编译后,支持普通脚本引入与 SeaJS 、RequireJS 等模块管理器调用。
@@ -23,8 +22,12 @@ $ tmod
* 使用 RequireJS 加载模板:[requirejs.html](requirejs.html)
* 使用 SeaJS 加载模板:[seajs.html](seajs.html)
-## 在 NodeJS 中加载模板
+### 在 NodeJS 中加载模板
运行示例:
- $ node node.js
\ No newline at end of file
+ $ node node.js
+
+## 二、运行其他设置测试例子
+
+``test/test-all`` 下每个目录都是一个独立的模板项目,它们设置了不同的配置属性,切换到目录后可以单独编译。目录有对应的同名``.html``文件,可以用来查看效果。
diff --git a/test/test-all/amd.html b/test/test-all/amd.html
new file mode 100644
index 0000000..fa763bb
--- /dev/null
+++ b/test/test-all/amd.html
@@ -0,0 +1,51 @@
+
+
+
+
+RequireJS - 调用模板演示
+
+
+
+
+
+loading..
+
+
+
+
+
+
+
diff --git a/test/test-all/amd/build/copyright.js b/test/test-all/amd/build/copyright.js
new file mode 100644
index 0000000..06ced26
--- /dev/null
+++ b/test/test-all/amd/build/copyright.js
@@ -0,0 +1,4 @@
+/*TMODJS:{"version":10,"md5":"dab122db77ff232c93de9fb84a59141d"}*/
+define([ "./template", "" ], function(template) {
+ return template("copyright", "(c) 2013");
+});
\ No newline at end of file
diff --git a/test/test-all/amd/build/index.js b/test/test-all/amd/build/index.js
new file mode 100644
index 0000000..cfd6da2
--- /dev/null
+++ b/test/test-all/amd/build/index.js
@@ -0,0 +1,17 @@
+/*TMODJS:{"version":10,"md5":"8804d1e566c02e1752278f059521998e"}*/
+define([ "./template", "./public/header", "./public/footer" ], function(template) {
+ return template("index", function($data, $filename) {
+ "use strict";
+ var $utils = this, include = ($utils.$helpers, function(filename, data) {
+ data = data || $data;
+ var text = $utils.$include(filename, data, $filename);
+ return $out += text;
+ }), $escape = $utils.$escape, title = $data.title, $each = $utils.$each, list = $data.list, $out = ($data.$value,
+ $data.$index, "");
+ return include("./public/header"), $out += ' ', $out += $escape(title),
+ $out += " ", include("./public/footer"), new String($out);
+ });
+});
\ No newline at end of file
diff --git a/test/test-all/amd/build/public/footer.js b/test/test-all/amd/build/public/footer.js
new file mode 100644
index 0000000..1e14ce4
--- /dev/null
+++ b/test/test-all/amd/build/public/footer.js
@@ -0,0 +1,13 @@
+/*TMODJS:{"version":10,"md5":"59c2faf9fb8603adb836ad83babc2cd3"}*/
+define([ "../template", "../copyright" ], function(template) {
+ return template("public/footer", function($data, $filename) {
+ "use strict";
+ var $utils = this, time = ($utils.$helpers, $data.time), $escape = $utils.$escape, include = function(filename, data) {
+ data = data || $data;
+ var text = $utils.$include(filename, data, $filename);
+ return $out += text;
+ }, $out = "";
+ return $out += '", new String($out);
+ });
+});
\ No newline at end of file
diff --git a/test/test-all/amd/build/public/header.js b/test/test-all/amd/build/public/header.js
new file mode 100644
index 0000000..7e770db
--- /dev/null
+++ b/test/test-all/amd/build/public/header.js
@@ -0,0 +1,13 @@
+/*TMODJS:{"version":10,"md5":"f131eb7300f7037adc98bcd30bfc38db"}*/
+define([ "../template", "./logo" ], function(template) {
+ return template("public/header", function($data, $filename) {
+ "use strict";
+ var $utils = this, include = ($utils.$helpers, function(filename, data) {
+ data = data || $data;
+ var text = $utils.$include(filename, data, $filename);
+ return $out += text;
+ }), $out = "";
+ return $out += ' ',
+ new String($out);
+ });
+});
\ No newline at end of file
diff --git a/test/test-all/amd/build/public/logo.js b/test/test-all/amd/build/public/logo.js
new file mode 100644
index 0000000..43e1209
--- /dev/null
+++ b/test/test-all/amd/build/public/logo.js
@@ -0,0 +1,4 @@
+/*TMODJS:{"version":10,"md5":"72904bcb9592df68a9bd08dca5c1f013"}*/
+define([ "../template", "" ], function(template) {
+ return template("public/logo", "");
+});
\ No newline at end of file
diff --git a/test/test-all/amd/build/template.js b/test/test-all/amd/build/template.js
new file mode 100644
index 0000000..3b4ffe7
--- /dev/null
+++ b/test/test-all/amd/build/template.js
@@ -0,0 +1,81 @@
+/*TMODJS:{}*/
+!function() {
+ function template(filename, content) {
+ return (/string|function/.test(typeof content) ? compile : renderFile)(filename, content);
+ }
+ function toString(value, type) {
+ return "string" != typeof value && (type = typeof value, "number" === type ? value += "" : value = "function" === type ? toString(value.call(value)) : ""),
+ value;
+ }
+ function escapeFn(s) {
+ return escapeMap[s];
+ }
+ function escapeHTML(content) {
+ return toString(content).replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
+ }
+ function each(data, callback) {
+ if (isArray(data)) for (var i = 0, len = data.length; len > i; i++) callback.call(data, data[i], i, data); else for (i in data) callback.call(data, data[i], i);
+ }
+ function resolve(from, to) {
+ var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/, dirname = ("./" + from).replace(/[^/]+$/, ""), filename = dirname + to;
+ for (filename = filename.replace(/\/\.\//g, "/"); filename.match(DOUBLE_DOT_RE); ) filename = filename.replace(DOUBLE_DOT_RE, "/");
+ return filename;
+ }
+ function renderFile(filename, data) {
+ var fn = template.get(filename) || showDebugInfo({
+ filename: filename,
+ name: "Render Error",
+ message: "Template not found"
+ });
+ return data ? fn(data) : fn;
+ }
+ function compile(filename, fn) {
+ if ("string" == typeof fn) {
+ var string = fn;
+ fn = function() {
+ return new String(string);
+ };
+ }
+ var render = cache[filename] = function(data) {
+ try {
+ return new fn(data, filename) + "";
+ } catch (e) {
+ return showDebugInfo(e)();
+ }
+ };
+ return render.prototype = fn.prototype = utils, render.toString = function() {
+ return fn + "";
+ }, render;
+ }
+ function showDebugInfo(e) {
+ var type = "{Template Error}", message = e.stack || "";
+ if (message) message = message.split("\n").slice(0, 2).join("\n"); else for (var name in e) message += "<" + name + ">\n" + e[name] + "\n\n";
+ return function() {
+ return "object" == typeof console && console.error(type + "\n\n" + message), type;
+ };
+ }
+ var cache = template.cache = {}, String = this.String, escapeMap = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "&": "&"
+ }, isArray = Array.isArray || function(obj) {
+ return "[object Array]" === {}.toString.call(obj);
+ }, utils = template.utils = {
+ $helpers: {},
+ $include: function(filename, data, from) {
+ return filename = resolve(from, filename), renderFile(filename, data);
+ },
+ $string: toString,
+ $escape: escapeHTML,
+ $each: each
+ }, helpers = template.helpers = utils.$helpers;
+ template.get = function(filename) {
+ return cache[filename.replace(/^\.\//, "")];
+ }, template.helper = function(name, helper) {
+ helpers[name] = helper;
+ }, define(function() {
+ return template;
+ });
+}();
\ No newline at end of file
diff --git a/test/test-all/amd/copyright.html b/test/test-all/amd/copyright.html
new file mode 100644
index 0000000..4c65dfa
--- /dev/null
+++ b/test/test-all/amd/copyright.html
@@ -0,0 +1 @@
+(c) 2013
\ No newline at end of file
diff --git a/test/test-all/amd/index.html b/test/test-all/amd/index.html
new file mode 100644
index 0000000..fc67525
--- /dev/null
+++ b/test/test-all/amd/index.html
@@ -0,0 +1,12 @@
+{{include './public/header'}}
+
+
+
+{{include './public/footer'}}
\ No newline at end of file
diff --git a/test/test-all/amd/package.json b/test/test-all/amd/package.json
new file mode 100644
index 0000000..a3dc82a
--- /dev/null
+++ b/test/test-all/amd/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.1-rc5"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "amd",
+ "runtime": "template.js",
+ "alias": null,
+ "minify": false,
+ "cache": true
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/amd/public/footer.html b/test/test-all/amd/public/footer.html
new file mode 100644
index 0000000..956c23e
--- /dev/null
+++ b/test/test-all/amd/public/footer.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/test/test-all/amd/public/header.html b/test/test-all/amd/public/header.html
new file mode 100644
index 0000000..d93e780
--- /dev/null
+++ b/test/test-all/amd/public/header.html
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/amd/public/logo.html b/test/test-all/amd/public/logo.html
new file mode 100644
index 0000000..b02f7cb
--- /dev/null
+++ b/test/test-all/amd/public/logo.html
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/cmd-alias.html b/test/test-all/cmd-alias.html
new file mode 100644
index 0000000..f51d8c9
--- /dev/null
+++ b/test/test-all/cmd-alias.html
@@ -0,0 +1,59 @@
+
+
+
+
+SeaJS - 调用模板演示
+
+
+
+
+
+
+loading..
+
+
+
+
+
+
+
diff --git a/test/test-all/cmd-alias/build/copyright.js b/test/test-all/cmd-alias/build/copyright.js
new file mode 100644
index 0000000..816bf49
--- /dev/null
+++ b/test/test-all/cmd-alias/build/copyright.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":12,"md5":"5321700567c2e26459e3d59c3a4ebca7"}*/
+define(function(require){return require("template")("copyright","(c) 2013")});
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/build/index.js b/test/test-all/cmd-alias/build/index.js
new file mode 100644
index 0000000..1201b36
--- /dev/null
+++ b/test/test-all/cmd-alias/build/index.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":12,"md5":"79a9fd45feb82ea2f2ad71d32d66a6dc"}*/
+define(function(require){return require("./public/header"),require("./public/footer"),require("template")("index",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,e){e=e||a;var f=c.$include(d,e,b);return i+=f}),e=c.$escape,f=a.title,g=c.$each,h=a.list,i=(a.$value,a.$index,"");return d("./public/header"),i+=' ",d("./public/footer"),new String(i)})});
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/build/public/footer.js b/test/test-all/cmd-alias/build/public/footer.js
new file mode 100644
index 0000000..95d870f
--- /dev/null
+++ b/test/test-all/cmd-alias/build/public/footer.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":12,"md5":"931ccc67fe62475d7ad7c1465718a38b"}*/
+define(function(require){return require("../copyright"),require("template")("public/footer",function(a,b){"use strict";var c=this,d=(c.$helpers,a.time),e=c.$escape,f=function(d,e){e=e||a;var f=c.$include(d,e,b);return g+=f},g="";return g+='",new String(g)})});
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/build/public/header.js b/test/test-all/cmd-alias/build/public/header.js
new file mode 100644
index 0000000..1150c87
--- /dev/null
+++ b/test/test-all/cmd-alias/build/public/header.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":12,"md5":"0df0ce2d0e617583da35ea1e65fa8c8e"}*/
+define(function(require){return require("./logo"),require("template")("public/header",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,f){f=f||a;var g=c.$include(d,f,b);return e+=g}),e="";return e+=' ',new String(e)})});
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/build/public/logo.js b/test/test-all/cmd-alias/build/public/logo.js
new file mode 100644
index 0000000..56236a1
--- /dev/null
+++ b/test/test-all/cmd-alias/build/public/logo.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":12,"md5":"8f7949ea743db268e3b8cd95fbf99ba0"}*/
+define(function(require){return require("template")("public/logo",' ')});
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/build/template.js b/test/test-all/cmd-alias/build/template.js
new file mode 100644
index 0000000..b108b5f
--- /dev/null
+++ b/test/test-all/cmd-alias/build/template.js
@@ -0,0 +1,2 @@
+/*TMODJS:{}*/
+!function(a){function b(a,b){return(/string|function/.test(typeof b)?i:h)(a,b)}function c(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?c(a.call(a)):""),a}function d(a){return l[a]}function e(a){return c(a).replace(/&(?![\w#]+;)|[<>"']/g,d)}function f(a,b){if(m(a))for(var c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)}function g(a,b){var c=/(\/)[^/]+\1\.\.\1/,d=("./"+a).replace(/[^/]+$/,""),e=d+b;for(e=e.replace(/\/\.\//g,"/");e.match(c);)e=e.replace(c,"/");return e}function h(a,c){var d=b.get(a)||j({filename:a,name:"Render Error",message:"Template not found"});return c?d(c):d}function i(b,c){if("string"==typeof c){var d=c;c=function(){return new a(d)}}var e=k[b]=function(a){try{return new c(a,b)+""}catch(d){return j(d)()}};return e.prototype=c.prototype=n,e.toString=function(){return c+""},e}function j(a){var b="{Template Error}",c=a.stack||"";if(c)c=c.split("\n").slice(0,2).join("\n");else for(var d in a)c+="<"+d+">\n"+a[d]+"\n\n";return function(){return"object"==typeof console&&console.error(b+"\n\n"+c),b}}var k=b.cache={},a=this.String,l={"<":"<",">":">",'"':""","'":"'","&":"&"},m=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},n=b.utils={$helpers:{},$include:function(a,b,c){return a=g(c,a),h(a,b)},$string:c,$escape:e,$each:f},o=b.helpers=n.$helpers;b.get=function(a){return k[a.replace(/^\.\//,"")]},b.helper=function(a,b){o[a]=b},"function"==typeof define?define(function(){return b}):"undefined"!=typeof exports?module.exports=b:this.template=b}();
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/copyright.html b/test/test-all/cmd-alias/copyright.html
new file mode 100644
index 0000000..4c65dfa
--- /dev/null
+++ b/test/test-all/cmd-alias/copyright.html
@@ -0,0 +1 @@
+(c) 2013
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/index.html b/test/test-all/cmd-alias/index.html
new file mode 100644
index 0000000..fc67525
--- /dev/null
+++ b/test/test-all/cmd-alias/index.html
@@ -0,0 +1,12 @@
+{{include './public/header'}}
+
+
+
+{{include './public/footer'}}
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/package.json b/test/test-all/cmd-alias/package.json
new file mode 100644
index 0000000..6ac79f6
--- /dev/null
+++ b/test/test-all/cmd-alias/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "cmd",
+ "runtime": "template.js",
+ "alias": "template",
+ "minify": true,
+ "cache": true
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/public/footer.html b/test/test-all/cmd-alias/public/footer.html
new file mode 100644
index 0000000..956c23e
--- /dev/null
+++ b/test/test-all/cmd-alias/public/footer.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/public/header.html b/test/test-all/cmd-alias/public/header.html
new file mode 100644
index 0000000..d93e780
--- /dev/null
+++ b/test/test-all/cmd-alias/public/header.html
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/cmd-alias/public/logo.html b/test/test-all/cmd-alias/public/logo.html
new file mode 100644
index 0000000..b02f7cb
--- /dev/null
+++ b/test/test-all/cmd-alias/public/logo.html
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/cmd.html b/test/test-all/cmd.html
new file mode 100644
index 0000000..61e2bfe
--- /dev/null
+++ b/test/test-all/cmd.html
@@ -0,0 +1,51 @@
+
+
+
+
+SeaJS - 调用模板演示
+
+
+
+
+
+loading..
+
+
+
+
+
+
+
diff --git a/test/test-all/cmd/build/copyright.js b/test/test-all/cmd/build/copyright.js
new file mode 100644
index 0000000..6420ad6
--- /dev/null
+++ b/test/test-all/cmd/build/copyright.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":7,"md5":"a0a58807274f57184510a7a187ad3d46"}*/
+define(function(require){return require("./template")("copyright","(c) 2013")});
\ No newline at end of file
diff --git a/test/test-all/cmd/build/index.js b/test/test-all/cmd/build/index.js
new file mode 100644
index 0000000..79d35ef
--- /dev/null
+++ b/test/test-all/cmd/build/index.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":7,"md5":"508f4903c5e3ecb102ff2b3dce664de9"}*/
+define(function(require){return require("./public/header"),require("./public/footer"),require("./template")("index",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,e){e=e||a;var f=c.$include(d,e,b);return i+=f}),e=c.$escape,f=a.title,g=c.$each,h=a.list,i=(a.$value,a.$index,"");return d("./public/header"),i+=' ",d("./public/footer"),new String(i)})});
\ No newline at end of file
diff --git a/test/test-all/cmd/build/public/footer.js b/test/test-all/cmd/build/public/footer.js
new file mode 100644
index 0000000..77c1142
--- /dev/null
+++ b/test/test-all/cmd/build/public/footer.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":7,"md5":"51f9b85e31ee032bf492ad73c44bc1de"}*/
+define(function(require){return require("../copyright"),require("../template")("public/footer",function(a,b){"use strict";var c=this,d=(c.$helpers,a.time),e=c.$escape,f=function(d,e){e=e||a;var f=c.$include(d,e,b);return g+=f},g="";return g+='",new String(g)})});
\ No newline at end of file
diff --git a/test/test-all/cmd/build/public/header.js b/test/test-all/cmd/build/public/header.js
new file mode 100644
index 0000000..af17e3b
--- /dev/null
+++ b/test/test-all/cmd/build/public/header.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":7,"md5":"8661caf5ac0192c7f0b0a435c3ccbd1a"}*/
+define(function(require){return require("./logo"),require("../template")("public/header",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,f){f=f||a;var g=c.$include(d,f,b);return e+=g}),e="";return e+=' ',new String(e)})});
\ No newline at end of file
diff --git a/test/test-all/cmd/build/public/logo.js b/test/test-all/cmd/build/public/logo.js
new file mode 100644
index 0000000..c47452f
--- /dev/null
+++ b/test/test-all/cmd/build/public/logo.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":7,"md5":"d2c64bfdc524b5098ff5e30b2df1f36a"}*/
+define(function(require){return require("../template")("public/logo",' ')});
\ No newline at end of file
diff --git a/test/test-all/cmd/build/template.js b/test/test-all/cmd/build/template.js
new file mode 100644
index 0000000..b108b5f
--- /dev/null
+++ b/test/test-all/cmd/build/template.js
@@ -0,0 +1,2 @@
+/*TMODJS:{}*/
+!function(a){function b(a,b){return(/string|function/.test(typeof b)?i:h)(a,b)}function c(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?c(a.call(a)):""),a}function d(a){return l[a]}function e(a){return c(a).replace(/&(?![\w#]+;)|[<>"']/g,d)}function f(a,b){if(m(a))for(var c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)}function g(a,b){var c=/(\/)[^/]+\1\.\.\1/,d=("./"+a).replace(/[^/]+$/,""),e=d+b;for(e=e.replace(/\/\.\//g,"/");e.match(c);)e=e.replace(c,"/");return e}function h(a,c){var d=b.get(a)||j({filename:a,name:"Render Error",message:"Template not found"});return c?d(c):d}function i(b,c){if("string"==typeof c){var d=c;c=function(){return new a(d)}}var e=k[b]=function(a){try{return new c(a,b)+""}catch(d){return j(d)()}};return e.prototype=c.prototype=n,e.toString=function(){return c+""},e}function j(a){var b="{Template Error}",c=a.stack||"";if(c)c=c.split("\n").slice(0,2).join("\n");else for(var d in a)c+="<"+d+">\n"+a[d]+"\n\n";return function(){return"object"==typeof console&&console.error(b+"\n\n"+c),b}}var k=b.cache={},a=this.String,l={"<":"<",">":">",'"':""","'":"'","&":"&"},m=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},n=b.utils={$helpers:{},$include:function(a,b,c){return a=g(c,a),h(a,b)},$string:c,$escape:e,$each:f},o=b.helpers=n.$helpers;b.get=function(a){return k[a.replace(/^\.\//,"")]},b.helper=function(a,b){o[a]=b},"function"==typeof define?define(function(){return b}):"undefined"!=typeof exports?module.exports=b:this.template=b}();
\ No newline at end of file
diff --git a/test/test-all/cmd/copyright.html b/test/test-all/cmd/copyright.html
new file mode 100644
index 0000000..4c65dfa
--- /dev/null
+++ b/test/test-all/cmd/copyright.html
@@ -0,0 +1 @@
+(c) 2013
\ No newline at end of file
diff --git a/test/test-all/cmd/index.html b/test/test-all/cmd/index.html
new file mode 100644
index 0000000..fc67525
--- /dev/null
+++ b/test/test-all/cmd/index.html
@@ -0,0 +1,12 @@
+{{include './public/header'}}
+
+
+
+{{include './public/footer'}}
\ No newline at end of file
diff --git a/test/test-all/cmd/package.json b/test/test-all/cmd/package.json
new file mode 100644
index 0000000..e917e91
--- /dev/null
+++ b/test/test-all/cmd/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.2",
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "cmd",
+ "runtime": "template.js",
+ "alias": null,
+ "minify": true,
+ "cache": false
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/cmd/public/footer.html b/test/test-all/cmd/public/footer.html
new file mode 100644
index 0000000..956c23e
--- /dev/null
+++ b/test/test-all/cmd/public/footer.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/test/test-all/cmd/public/header.html b/test/test-all/cmd/public/header.html
new file mode 100644
index 0000000..d93e780
--- /dev/null
+++ b/test/test-all/cmd/public/header.html
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/cmd/public/logo.html b/test/test-all/cmd/public/logo.html
new file mode 100644
index 0000000..b02f7cb
--- /dev/null
+++ b/test/test-all/cmd/public/logo.html
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/combo-off.html b/test/test-all/combo-off.html
new file mode 100644
index 0000000..1e9cb70
--- /dev/null
+++ b/test/test-all/combo-off.html
@@ -0,0 +1,55 @@
+
+
+
+
+TemplateJS - 调用模板演示
+
+
+
+
+loading..
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/test-all/combo-off/build/copyright.js b/test/test-all/combo-off/build/copyright.js
new file mode 100644
index 0000000..4bfff06
--- /dev/null
+++ b/test/test-all/combo-off/build/copyright.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":8,"md5":"8f56742619a6a26ee7b0175e0bead0b0"}*/
+template("copyright","(c) 2013");
\ No newline at end of file
diff --git a/test/test-all/combo-off/build/index.js b/test/test-all/combo-off/build/index.js
new file mode 100644
index 0000000..58bb1e6
--- /dev/null
+++ b/test/test-all/combo-off/build/index.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":8,"md5":"f8c225edd0fd3d0d5ca75bf53e01ee76"}*/
+template("index",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,e){e=e||a;var f=c.$include(d,e,b);return i+=f}),e=c.$escape,f=a.title,g=c.$each,h=a.list,i=(a.$value,a.$index,"");return d("./public/header"),i+=' \u6807\u9898 ',i+=e(f),i+=" ",d("./public/footer"),new String(i)});
\ No newline at end of file
diff --git a/test/test-all/combo-off/build/public/footer.js b/test/test-all/combo-off/build/public/footer.js
new file mode 100644
index 0000000..2dd3cc7
--- /dev/null
+++ b/test/test-all/combo-off/build/public/footer.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":8,"md5":"3fb66ac398d81b747a6d2762473553f3"}*/
+template("public/footer",function(a,b){"use strict";var c=this,d=(c.$helpers,a.time),e=c.$escape,f=function(d,e){e=e||a;var f=c.$include(d,e,b);return g+=f},g="";return g+='",new String(g)});
\ No newline at end of file
diff --git a/test/test-all/combo-off/build/public/header.js b/test/test-all/combo-off/build/public/header.js
new file mode 100644
index 0000000..729ee44
--- /dev/null
+++ b/test/test-all/combo-off/build/public/header.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":8,"md5":"4b6070718603e5122466d0edb80e2cf4"}*/
+template("public/header",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,f){f=f||a;var g=c.$include(d,f,b);return e+=g}),e="";return e+=' ',new String(e)});
\ No newline at end of file
diff --git a/test/test-all/combo-off/build/public/logo.js b/test/test-all/combo-off/build/public/logo.js
new file mode 100644
index 0000000..9824b47
--- /dev/null
+++ b/test/test-all/combo-off/build/public/logo.js
@@ -0,0 +1,2 @@
+/*TMODJS:{"version":8,"md5":"20761918c020bdffb2696447d5d07310"}*/
+template("public/logo",' ');
\ No newline at end of file
diff --git a/test/test-all/combo-off/build/template.js b/test/test-all/combo-off/build/template.js
new file mode 100644
index 0000000..b108b5f
--- /dev/null
+++ b/test/test-all/combo-off/build/template.js
@@ -0,0 +1,2 @@
+/*TMODJS:{}*/
+!function(a){function b(a,b){return(/string|function/.test(typeof b)?i:h)(a,b)}function c(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?c(a.call(a)):""),a}function d(a){return l[a]}function e(a){return c(a).replace(/&(?![\w#]+;)|[<>"']/g,d)}function f(a,b){if(m(a))for(var c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)}function g(a,b){var c=/(\/)[^/]+\1\.\.\1/,d=("./"+a).replace(/[^/]+$/,""),e=d+b;for(e=e.replace(/\/\.\//g,"/");e.match(c);)e=e.replace(c,"/");return e}function h(a,c){var d=b.get(a)||j({filename:a,name:"Render Error",message:"Template not found"});return c?d(c):d}function i(b,c){if("string"==typeof c){var d=c;c=function(){return new a(d)}}var e=k[b]=function(a){try{return new c(a,b)+""}catch(d){return j(d)()}};return e.prototype=c.prototype=n,e.toString=function(){return c+""},e}function j(a){var b="{Template Error}",c=a.stack||"";if(c)c=c.split("\n").slice(0,2).join("\n");else for(var d in a)c+="<"+d+">\n"+a[d]+"\n\n";return function(){return"object"==typeof console&&console.error(b+"\n\n"+c),b}}var k=b.cache={},a=this.String,l={"<":"<",">":">",'"':""","'":"'","&":"&"},m=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},n=b.utils={$helpers:{},$include:function(a,b,c){return a=g(c,a),h(a,b)},$string:c,$escape:e,$each:f},o=b.helpers=n.$helpers;b.get=function(a){return k[a.replace(/^\.\//,"")]},b.helper=function(a,b){o[a]=b},"function"==typeof define?define(function(){return b}):"undefined"!=typeof exports?module.exports=b:this.template=b}();
\ No newline at end of file
diff --git a/test/test-all/combo-off/copyright.html b/test/test-all/combo-off/copyright.html
new file mode 100644
index 0000000..4c65dfa
--- /dev/null
+++ b/test/test-all/combo-off/copyright.html
@@ -0,0 +1 @@
+(c) 2013
\ No newline at end of file
diff --git a/test/test-all/combo-off/index.html b/test/test-all/combo-off/index.html
new file mode 100644
index 0000000..1bf411f
--- /dev/null
+++ b/test/test-all/combo-off/index.html
@@ -0,0 +1,12 @@
+{{include './public/header'}}
+
+
+
+{{include './public/footer'}}
\ No newline at end of file
diff --git a/test/test-all/combo-off/package.json b/test/test-all/combo-off/package.json
new file mode 100644
index 0000000..4fc5543
--- /dev/null
+++ b/test/test-all/combo-off/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.1-rc4"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "default",
+ "runtime": "template.js",
+ "combo": false,
+ "minify": true,
+ "cache": false
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/combo-off/public/footer.html b/test/test-all/combo-off/public/footer.html
new file mode 100644
index 0000000..956c23e
--- /dev/null
+++ b/test/test-all/combo-off/public/footer.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/test/test-all/combo-off/public/header.html b/test/test-all/combo-off/public/header.html
new file mode 100644
index 0000000..d93e780
--- /dev/null
+++ b/test/test-all/combo-off/public/header.html
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/combo-off/public/logo.html b/test/test-all/combo-off/public/logo.html
new file mode 100644
index 0000000..b02f7cb
--- /dev/null
+++ b/test/test-all/combo-off/public/logo.html
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/escape-off.html b/test/test-all/escape-off.html
new file mode 100644
index 0000000..dfccc62
--- /dev/null
+++ b/test/test-all/escape-off.html
@@ -0,0 +1,49 @@
+
+
+
+
+TemplateJS - 调用模板演示
+
+
+
+
+loading..
+
+
+
+
+
+
+
+
diff --git a/test/test-all/escape-off/build/index.js b/test/test-all/escape-off/build/index.js
new file mode 100644
index 0000000..d5a1d7e
--- /dev/null
+++ b/test/test-all/escape-off/build/index.js
@@ -0,0 +1,2 @@
+/* */
+template("index",function(i){var t=this,e=t.$string,l=t.$ubb2html,n=i.title,a=t.$each,u=i.list,r=(i.$value,i.$index,t.$escape),h="";return h+='",new String(h)});
\ No newline at end of file
diff --git a/test/test-all/escape-off/build/template.js b/test/test-all/escape-off/build/template.js
new file mode 100644
index 0000000..a64b244
--- /dev/null
+++ b/test/test-all/escape-off/build/template.js
@@ -0,0 +1,3 @@
+/*TMODJS:{"version":"1.0.0"}*/
+!function(a){function b(a,b){return(/string|function/.test(typeof b)?i:h)(a,b)}function c(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?c(a.call(a)):""),a}function d(a){return l[a]}function e(a){return c(a).replace(/&(?![\w#]+;)|[<>"']/g,d)}function f(a,b){if(m(a))for(var c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)}function g(a,b){var c=/(\/)[^/]+\1\.\.\1/,d=("./"+a).replace(/[^/]+$/,""),e=d+b;for(e=e.replace(/\/\.\//g,"/");e.match(c);)e=e.replace(c,"/");return e}function h(a,c){var d=b.get(a)||j({filename:a,name:"Render Error",message:"Template not found"});return c?d(c):d}function i(b,c){if("string"==typeof c){var d=c;c=function(){return new a(d)}}var e=k[b]=function(a){try{return new c(a,b)+""}catch(d){return j(d)()}};return e.prototype=c.prototype=n,e.toString=function(){return c+""},e}function j(a){var b="{Template Error}",c=a.stack||"";if(c)c=c.split("\n").slice(0,2).join("\n");else for(var d in a)c+="<"+d+">\n"+a[d]+"\n\n";return function(){return"object"==typeof console&&console.error(b+"\n\n"+c),b}}var k=b.cache={},a=this.String,l={"<":"<",">":">",'"':""","'":"'","&":"&"},m=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},n=b.utils={$helpers:{},$include:function(a,b,c){return a=g(c,a),h(a,b)},$string:c,$escape:e,$each:f},o=b.helpers=n.$helpers;b.get=function(a){return k[a.replace(/^\.\//,"")]},b.helper=function(a,b){o[a]=b},"function"==typeof define?define(function(){return b}):"undefined"!=typeof exports?module.exports=b:this.template=b,/*v:6*/
+b("index",function(b){"use strict";var c=this,d=(c.$helpers,c.$string),e=b.title,f=c.$each,g=b.list,h=(b.$value,b.$index,"");return h+='",new a(h)})}();
\ No newline at end of file
diff --git a/test/test-all/escape-off/index.html b/test/test-all/escape-off/index.html
new file mode 100644
index 0000000..f85da91
--- /dev/null
+++ b/test/test-all/escape-off/index.html
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/test/test-all/escape-off/package.json b/test/test-all/escape-off/package.json
new file mode 100644
index 0000000..253cb0a
--- /dev/null
+++ b/test/test-all/escape-off/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": null,
+ "escape": false,
+ "compress": true,
+ "type": "default",
+ "runtime": "template.js",
+ "combo": true,
+ "minify": true,
+ "cache": false
+ }
+}
\ No newline at end of file
diff --git a/test/node.js b/test/test-all/global.html
similarity index 70%
rename from test/node.js
rename to test/test-all/global.html
index f1746ac..6d571e1 100644
--- a/test/node.js
+++ b/test/test-all/global.html
@@ -1,4 +1,16 @@
-var template = require('./tpl/build/template');
+
+
+
+
+TemplateJS - 调用模板演示
+
+
+
+
+loading..
+
+
+
+
+
+
+
-var html = template('index', data);
-console.log(html)
\ No newline at end of file
diff --git a/test/test-all/global/build/template.js b/test/test-all/global/build/template.js
new file mode 100644
index 0000000..cb8928d
--- /dev/null
+++ b/test/test-all/global/build/template.js
@@ -0,0 +1,115 @@
+/*TMODJS:{"version":"1.0.1"}*/
+!function() {
+ function template(filename, content) {
+ return (/string|function/.test(typeof content) ? compile : renderFile)(filename, content);
+ }
+ function toString(value, type) {
+ return "string" != typeof value && (type = typeof value, "number" === type ? value += "" : value = "function" === type ? toString(value.call(value)) : ""),
+ value;
+ }
+ function escapeFn(s) {
+ return escapeMap[s];
+ }
+ function escapeHTML(content) {
+ return toString(content).replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
+ }
+ function each(data, callback) {
+ if (isArray(data)) for (var i = 0, len = data.length; len > i; i++) callback.call(data, data[i], i, data); else for (i in data) callback.call(data, data[i], i);
+ }
+ function resolve(from, to) {
+ var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/, dirname = ("./" + from).replace(/[^/]+$/, ""), filename = dirname + to;
+ for (filename = filename.replace(/\/\.\//g, "/"); filename.match(DOUBLE_DOT_RE); ) filename = filename.replace(DOUBLE_DOT_RE, "/");
+ return filename;
+ }
+ function renderFile(filename, data) {
+ var fn = template.get(filename) || showDebugInfo({
+ filename: filename,
+ name: "Render Error",
+ message: "Template not found"
+ });
+ return data ? fn(data) : fn;
+ }
+ function compile(filename, fn) {
+ if ("string" == typeof fn) {
+ var string = fn;
+ fn = function() {
+ return new String(string);
+ };
+ }
+ var render = cache[filename] = function(data) {
+ try {
+ return new fn(data, filename) + "";
+ } catch (e) {
+ return showDebugInfo(e)();
+ }
+ };
+ return render.prototype = fn.prototype = utils, render.toString = function() {
+ return fn + "";
+ }, render;
+ }
+ function showDebugInfo(e) {
+ var type = "{Template Error}", message = e.stack || "";
+ if (message) message = message.split("\n").slice(0, 2).join("\n"); else for (var name in e) message += "<" + name + ">\n" + e[name] + "\n\n";
+ return function() {
+ return "object" == typeof console && console.error(type + "\n\n" + message), type;
+ };
+ }
+ var cache = template.cache = {}, String = this.String, escapeMap = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "&": "&"
+ }, isArray = Array.isArray || function(obj) {
+ return "[object Array]" === {}.toString.call(obj);
+ }, utils = template.utils = {
+ $helpers: {},
+ $include: function(filename, data, from) {
+ return filename = resolve(from, filename), renderFile(filename, data);
+ },
+ $string: toString,
+ $escape: escapeHTML,
+ $each: each
+ }, helpers = template.helpers = utils.$helpers;
+ template.get = function(filename) {
+ return cache[filename.replace(/^\.\//, "")];
+ }, template.helper = function(name, helper) {
+ helpers[name] = helper;
+ }, this.template = template, /*v:1*/
+ template("copyright", "(c) 2013"), /*v:1*/
+ template("index", function($data, $filename) {
+ "use strict";
+ var $utils = this, include = ($utils.$helpers, function(filename, data) {
+ data = data || $data;
+ var text = $utils.$include(filename, data, $filename);
+ return $out += text;
+ }), $escape = $utils.$escape, title = $data.title, $each = $utils.$each, list = $data.list, $out = ($data.$value,
+ $data.$index, "");
+ return include("./public/header"), $out += ' ', $out += $escape(title),
+ $out += " ", include("./public/footer"), new String($out);
+ }), /*v:1*/
+ template("public/footer", function($data, $filename) {
+ "use strict";
+ var $utils = this, time = ($utils.$helpers, $data.time), $escape = $utils.$escape, include = function(filename, data) {
+ data = data || $data;
+ var text = $utils.$include(filename, data, $filename);
+ return $out += text;
+ }, $out = "";
+ return $out += '", new String($out);
+ }), /*v:1*/
+ template("public/header", function($data, $filename) {
+ "use strict";
+ var $utils = this, include = ($utils.$helpers, function(filename, data) {
+ data = data || $data;
+ var text = $utils.$include(filename, data, $filename);
+ return $out += text;
+ }), $out = "";
+ return $out += ' ',
+ new String($out);
+ }), /*v:1*/
+ template("public/logo", "");
+}();
\ No newline at end of file
diff --git a/test/test-all/global/copyright.html b/test/test-all/global/copyright.html
new file mode 100644
index 0000000..4c65dfa
--- /dev/null
+++ b/test/test-all/global/copyright.html
@@ -0,0 +1 @@
+(c) 2013
\ No newline at end of file
diff --git a/test/test-all/global/index.html b/test/test-all/global/index.html
new file mode 100644
index 0000000..fc67525
--- /dev/null
+++ b/test/test-all/global/index.html
@@ -0,0 +1,12 @@
+{{include './public/header'}}
+
+
+
+{{include './public/footer'}}
\ No newline at end of file
diff --git a/test/test-all/global/package.json b/test/test-all/global/package.json
new file mode 100644
index 0000000..fd2b961
--- /dev/null
+++ b/test/test-all/global/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.1",
+ "dependencies": {
+ "tmodjs": "1.0.1-rc5"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "global",
+ "runtime": "template.js",
+ "combo": true,
+ "minify": false,
+ "cache": true
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/global/public/footer.html b/test/test-all/global/public/footer.html
new file mode 100644
index 0000000..956c23e
--- /dev/null
+++ b/test/test-all/global/public/footer.html
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/test/test-all/global/public/header.html b/test/test-all/global/public/header.html
new file mode 100644
index 0000000..267582e
--- /dev/null
+++ b/test/test-all/global/public/header.html
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/global/public/logo.html b/test/test-all/global/public/logo.html
new file mode 100644
index 0000000..b02f7cb
--- /dev/null
+++ b/test/test-all/global/public/logo.html
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/test/test-all/helper.html b/test/test-all/helper.html
new file mode 100644
index 0000000..568cace
--- /dev/null
+++ b/test/test-all/helper.html
@@ -0,0 +1,26 @@
+
+
+
+
+TemplateJS - 调用模板演示
+
+
+
+
+loading..
+
+
+
+
+
+
+
+
diff --git a/test/test-all/helper/build/index.js b/test/test-all/helper/build/index.js
new file mode 100644
index 0000000..d5a1d7e
--- /dev/null
+++ b/test/test-all/helper/build/index.js
@@ -0,0 +1,2 @@
+/* */
+template("index",function(i){var t=this,e=t.$string,l=t.$ubb2html,n=i.title,a=t.$each,u=i.list,r=(i.$value,i.$index,t.$escape),h="";return h+='",new String(h)});
\ No newline at end of file
diff --git a/test/test-all/helper/build/template.js b/test/test-all/helper/build/template.js
new file mode 100644
index 0000000..6e9ff22
--- /dev/null
+++ b/test/test-all/helper/build/template.js
@@ -0,0 +1,104 @@
+/*TMODJS:{"version":"1.0.0"}*/
+!function(String) {
+ function template(filename, content) {
+ return (/string|function/.test(typeof content) ? compile : renderFile)(filename, content);
+ }
+ function toString(value, type) {
+ return "string" != typeof value && (type = typeof value, "number" === type ? value += "" : value = "function" === type ? toString(value.call(value)) : ""),
+ value;
+ }
+ function escapeFn(s) {
+ return escapeMap[s];
+ }
+ function escapeHTML(content) {
+ return toString(content).replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
+ }
+ function each(data, callback) {
+ if (isArray(data)) for (var i = 0, len = data.length; len > i; i++) callback.call(data, data[i], i, data); else for (i in data) callback.call(data, data[i], i);
+ }
+ function resolve(from, to) {
+ var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/, dirname = ("./" + from).replace(/[^/]+$/, ""), filename = dirname + to;
+ for (filename = filename.replace(/\/\.\//g, "/"); filename.match(DOUBLE_DOT_RE); ) filename = filename.replace(DOUBLE_DOT_RE, "/");
+ return filename;
+ }
+ function renderFile(filename, data) {
+ var fn = template.get(filename) || showDebugInfo({
+ filename: filename,
+ name: "Render Error",
+ message: "Template not found"
+ });
+ return data ? fn(data) : fn;
+ }
+ function compile(filename, fn) {
+ if ("string" == typeof fn) {
+ var string = fn;
+ fn = function() {
+ return new String(string);
+ };
+ }
+ var render = cache[filename] = function(data) {
+ try {
+ return new fn(data, filename) + "";
+ } catch (e) {
+ return showDebugInfo(e)();
+ }
+ };
+ return render.prototype = fn.prototype = utils, render.toString = function() {
+ return fn + "";
+ }, render;
+ }
+ function showDebugInfo(e) {
+ var type = "{Template Error}", message = e.stack || "";
+ if (message) message = message.split("\n").slice(0, 2).join("\n"); else for (var name in e) message += "<" + name + ">\n" + e[name] + "\n\n";
+ return function() {
+ return "object" == typeof console && console.error(type + "\n\n" + message), type;
+ };
+ }
+ var cache = template.cache = {}, String = this.String, escapeMap = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "&": "&"
+ }, isArray = Array.isArray || function(obj) {
+ return "[object Array]" === {}.toString.call(obj);
+ }, utils = template.utils = {
+ $helpers: {},
+ $include: function(filename, data, from) {
+ return filename = resolve(from, filename), renderFile(filename, data);
+ },
+ $string: toString,
+ $escape: escapeHTML,
+ $each: each
+ }, helpers = template.helpers = utils.$helpers;
+ template.get = function(filename) {
+ return cache[filename.replace(/^\.\//, "")];
+ }, template.helper = function(name, helper) {
+ helpers[name] = helper;
+ }, "function" == typeof define ? define(function() {
+ return template;
+ }) : "undefined" != typeof exports ? module.exports = template : this.template = template,
+ template.helper("dateFormat", function(date, format) {
+ date = new Date(date);
+ var map = {
+ M: date.getMonth() + 1,
+ d: date.getDate(),
+ h: date.getHours(),
+ m: date.getMinutes(),
+ s: date.getSeconds(),
+ q: Math.floor((date.getMonth() + 3) / 3),
+ S: date.getMilliseconds()
+ };
+ return format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
+ var v = map[t];
+ return void 0 !== v ? (all.length > 1 && (v = "0" + v, v = v.substr(v.length - 2)),
+ v) : "y" === t ? (date.getFullYear() + "").substr(4 - all.length) : all;
+ });
+ }), /*v:21*/
+ template("index", function($data) {
+ "use strict";
+ var $utils = this, $helpers = $utils.$helpers, $string = $utils.$string, $escape = $utils.$escape, time = $data.time, $out = "";
+ return $out += $string($helpers.dateFormat($escape(time), "yyyy-MM-dd hh:mm:ss")),
+ new String($out);
+ });
+}();
\ No newline at end of file
diff --git a/test/test-all/helper/index.html b/test/test-all/helper/index.html
new file mode 100644
index 0000000..b82d4e5
--- /dev/null
+++ b/test/test-all/helper/index.html
@@ -0,0 +1 @@
+{{time | dateFormat:'yyyy-MM-dd hh:mm:ss'}}
\ No newline at end of file
diff --git a/test/test-all/helper/package.json b/test/test-all/helper/package.json
new file mode 100644
index 0000000..3af10de
--- /dev/null
+++ b/test/test-all/helper/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "simple",
+ "helpers": "./template-helpers.js",
+ "escape": true,
+ "compress": true,
+ "type": "default",
+ "runtime": "template.js",
+ "combo": true,
+ "minify": false,
+ "cache": false
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/helper/template-helpers.js b/test/test-all/helper/template-helpers.js
new file mode 100644
index 0000000..282bfc6
--- /dev/null
+++ b/test/test-all/helper/template-helpers.js
@@ -0,0 +1,46 @@
+/**
+ * 对日期进行格式化,
+ * @param date 要格式化的日期
+ * @param format 进行格式化的模式字符串
+ * 支持的模式字母有:
+ * y:年,
+ * M:年中的月份(1-12),
+ * d:月份中的天(1-31),
+ * h:小时(0-23),
+ * m:分(0-59),
+ * s:秒(0-59),
+ * S:毫秒(0-999),
+ * q:季度(1-4)
+ * @return String
+ * @author yanis.wang
+ * @see http://yaniswang.com/frontend/2013/02/16/dateformat-performance/
+ */
+template.helper('dateFormat', function (date, format) {
+
+ date = new Date(date);
+
+ var map = {
+ "M": date.getMonth() + 1, //月份
+ "d": date.getDate(), //日
+ "h": date.getHours(), //小时
+ "m": date.getMinutes(), //分
+ "s": date.getSeconds(), //秒
+ "q": Math.floor((date.getMonth() + 3) / 3), //季度
+ "S": date.getMilliseconds() //毫秒
+ };
+ format = format.replace(/([yMdhmsqS])+/g, function(all, t){
+ var v = map[t];
+ if(v !== undefined){
+ if(all.length > 1){
+ v = '0' + v;
+ v = v.substr(v.length-2);
+ }
+ return v;
+ }
+ else if(t === 'y'){
+ return (date.getFullYear() + '').substr(4 - all.length);
+ }
+ return all;
+ });
+ return format;
+});
\ No newline at end of file
diff --git a/test/test-all/include/build/include.js b/test/test-all/include/build/include.js
new file mode 100644
index 0000000..0e220e9
--- /dev/null
+++ b/test/test-all/include/build/include.js
@@ -0,0 +1,18 @@
+/*TMODJS:{"version":3,"md5":"f4d1cc4066fb3f4e0e9bc077ebb9b79b"}*/
+define([ "./template", "./a", "./b", "./e", "./d" ], function(template) {
+ return template("include", function($data, $filename) {
+ "use strict";
+ var $utils = this, include = ($utils.$helpers, function(filename, data) {
+ data = data || $data;
+ var text = $utils.$include(filename, data, $filename);
+ return $out += text;
+ }), xxx = ($data.labe, $data.xxx), $out = "";
+ return include("./a", {
+ labe: ")"
+ }), include("./b", {
+ labe: "("
+ }), $out += " ", include("./e", {
+ include: "./v"
+ }), $out += " ", $out += " ", include("./d"), xxx.include("./c"), new String($out);
+ });
+});
\ No newline at end of file
diff --git a/test/test-all/include/build/template.js b/test/test-all/include/build/template.js
new file mode 100644
index 0000000..7afd16b
--- /dev/null
+++ b/test/test-all/include/build/template.js
@@ -0,0 +1,81 @@
+/*TMODJS:{}*/
+!function(String) {
+ function template(filename, content) {
+ return (/string|function/.test(typeof content) ? compile : renderFile)(filename, content);
+ }
+ function toString(value, type) {
+ return "string" != typeof value && (type = typeof value, "number" === type ? value += "" : value = "function" === type ? toString(value.call(value)) : ""),
+ value;
+ }
+ function escapeFn(s) {
+ return escapeMap[s];
+ }
+ function escapeHTML(content) {
+ return toString(content).replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
+ }
+ function each(data, callback) {
+ if (isArray(data)) for (var i = 0, len = data.length; len > i; i++) callback.call(data, data[i], i, data); else for (i in data) callback.call(data, data[i], i);
+ }
+ function resolve(from, to) {
+ var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/, dirname = ("./" + from).replace(/[^/]+$/, ""), filename = dirname + to;
+ for (filename = filename.replace(/\/\.\//g, "/"); filename.match(DOUBLE_DOT_RE); ) filename = filename.replace(DOUBLE_DOT_RE, "/");
+ return filename;
+ }
+ function renderFile(filename, data) {
+ var fn = template.get(filename) || showDebugInfo({
+ filename: filename,
+ name: "Render Error",
+ message: "Template not found"
+ });
+ return data ? fn(data) : fn;
+ }
+ function compile(filename, fn) {
+ if ("string" == typeof fn) {
+ var string = fn;
+ fn = function() {
+ return new String(string);
+ };
+ }
+ var render = cache[filename] = function(data) {
+ try {
+ return new fn(data, filename) + "";
+ } catch (e) {
+ return showDebugInfo(e)();
+ }
+ };
+ return render.prototype = fn.prototype = utils, render.toString = function() {
+ return fn + "";
+ }, render;
+ }
+ function showDebugInfo(e) {
+ var type = "{Template Error}", message = e.stack || "";
+ if (message) message = message.split("\n").slice(0, 2).join("\n"); else for (var name in e) message += "<" + name + ">\n" + e[name] + "\n\n";
+ return function() {
+ return "object" == typeof console && console.error(type + "\n\n" + message), type;
+ };
+ }
+ var cache = template.cache = {}, String = this.String, escapeMap = {
+ "<": "<",
+ ">": ">",
+ '"': """,
+ "'": "'",
+ "&": "&"
+ }, isArray = Array.isArray || function(obj) {
+ return "[object Array]" === {}.toString.call(obj);
+ }, utils = template.utils = {
+ $helpers: {},
+ $include: function(filename, data, from) {
+ return filename = resolve(from, filename), renderFile(filename, data);
+ },
+ $string: toString,
+ $escape: escapeHTML,
+ $each: each
+ }, helpers = template.helpers = utils.$helpers;
+ template.get = function(filename) {
+ return cache[filename.replace(/^\.\//, "")];
+ }, template.helper = function(name, helper) {
+ helpers[name] = helper;
+ }, "function" == typeof define ? define(function() {
+ return template;
+ }) : "undefined" != typeof exports ? module.exports = template : this.template = template;
+}();
\ No newline at end of file
diff --git a/test/test/include/include.html b/test/test-all/include/include.html
similarity index 100%
rename from test/test/include/include.html
rename to test/test-all/include/include.html
diff --git a/test/test/include/package.json b/test/test-all/include/package.json
similarity index 57%
rename from test/test/include/package.json
rename to test/test-all/include/package.json
index 73ab260..f02aaee 100644
--- a/test/test/include/package.json
+++ b/test/test-all/include/package.json
@@ -2,19 +2,19 @@
"name": "template",
"version": "1.0.0",
"dependencies": {
- "tmodjs": "~0.0.2"
+ "tmodjs": "1.0.0"
},
"tmodjs-config": {
"output": "./build",
"charset": "utf-8",
- "combo": [
- "*"
- ],
"syntax": "native",
"helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "amd",
+ "runtime": "template.js",
+ "alias": null,
"minify": false,
- "async": false,
- "engine": false,
- "type": "amd"
+ "cache": false
}
}
\ No newline at end of file
diff --git a/test/test-all/syntax-native.html b/test/test-all/syntax-native.html
new file mode 100644
index 0000000..198d33c
--- /dev/null
+++ b/test/test-all/syntax-native.html
@@ -0,0 +1,49 @@
+
+
+
+
+TemplateJS - 调用模板演示
+
+
+
+
+loading..
+
+
+
+
+
+
+
+
diff --git a/test/test-all/syntax-native/build/index.js b/test/test-all/syntax-native/build/index.js
new file mode 100644
index 0000000..24ee584
--- /dev/null
+++ b/test/test-all/syntax-native/build/index.js
@@ -0,0 +1,2 @@
+/* */
+template("index",function(i){var t=this,e=t.$escape,l=i.title,n=i.i,r=i.list,a="";a+='",new String(a)});
\ No newline at end of file
diff --git a/test/test-all/syntax-native/build/template.js b/test/test-all/syntax-native/build/template.js
new file mode 100644
index 0000000..206014e
--- /dev/null
+++ b/test/test-all/syntax-native/build/template.js
@@ -0,0 +1,3 @@
+/*TMODJS:{"version":"1.0.0"}*/
+!function(a){function b(a,b){return(/string|function/.test(typeof b)?i:h)(a,b)}function c(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?c(a.call(a)):""),a}function d(a){return l[a]}function e(a){return c(a).replace(/&(?![\w#]+;)|[<>"']/g,d)}function f(a,b){if(m(a))for(var c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)}function g(a,b){var c=/(\/)[^/]+\1\.\.\1/,d=("./"+a).replace(/[^/]+$/,""),e=d+b;for(e=e.replace(/\/\.\//g,"/");e.match(c);)e=e.replace(c,"/");return e}function h(a,c){var d=b.get(a)||j({filename:a,name:"Render Error",message:"Template not found"});return c?d(c):d}function i(b,c){if("string"==typeof c){var d=c;c=function(){return new a(d)}}var e=k[b]=function(a){try{return new c(a,b)+""}catch(d){return j(d)()}};return e.prototype=c.prototype=n,e.toString=function(){return c+""},e}function j(a){var b="{Template Error}",c=a.stack||"";if(c)c=c.split("\n").slice(0,2).join("\n");else for(var d in a)c+="<"+d+">\n"+a[d]+"\n\n";return function(){return"object"==typeof console&&console.error(b+"\n\n"+c),b}}var k=b.cache={},a=this.String,l={"<":"<",">":">",'"':""","'":"'","&":"&"},m=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},n=b.utils={$helpers:{},$include:function(a,b,c){return a=g(c,a),h(a,b)},$string:c,$escape:e,$each:f},o=b.helpers=n.$helpers;b.get=function(a){return k[a.replace(/^\.\//,"")]},b.helper=function(a,b){o[a]=b},"function"==typeof define?define(function(){return b}):"undefined"!=typeof exports?module.exports=b:this.template=b,/*v:4*/
+b("index",function(b){"use strict";var c=this,d=(c.$helpers,c.$escape),e=b.title,f=b.i,g=b.list,h="";h+='",new a(h)})}();
\ No newline at end of file
diff --git a/test/test-all/syntax-native/index.html b/test/test-all/syntax-native/index.html
new file mode 100644
index 0000000..5d73bcc
--- /dev/null
+++ b/test/test-all/syntax-native/index.html
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/test/test-all/syntax-native/package.json b/test/test-all/syntax-native/package.json
new file mode 100644
index 0000000..f15decd
--- /dev/null
+++ b/test/test-all/syntax-native/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "native",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "default",
+ "runtime": "template.js",
+ "combo": true,
+ "minify": true,
+ "cache": false
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/syntax.html b/test/test-all/syntax.html
new file mode 100644
index 0000000..70a3195
--- /dev/null
+++ b/test/test-all/syntax.html
@@ -0,0 +1,49 @@
+
+
+
+
+TemplateJS - 调用模板演示
+
+
+
+
+loading..
+
+
+
+
+
+
+
+
diff --git a/test/test-all/syntax/build/index.js b/test/test-all/syntax/build/index.js
new file mode 100644
index 0000000..dc8394c
--- /dev/null
+++ b/test/test-all/syntax/build/index.js
@@ -0,0 +1,2 @@
+/* */
+template("index",function(i){var e=this,l=i.x,t=e.$escape,n=i.title,a=e.$each,r=i.list,u=(i.$value,i.$index,""),l="hello world";return u+=" ",u+=t(l),u+=' ",new String(u)});
\ No newline at end of file
diff --git a/test/test-all/syntax/build/template.js b/test/test-all/syntax/build/template.js
new file mode 100644
index 0000000..8763b71
--- /dev/null
+++ b/test/test-all/syntax/build/template.js
@@ -0,0 +1,3 @@
+/*TMODJS:{"version":"1.0.0"}*/
+!function(a){function b(a,b){return(/string|function/.test(typeof b)?i:h)(a,b)}function c(a,b){return"string"!=typeof a&&(b=typeof a,"number"===b?a+="":a="function"===b?c(a.call(a)):""),a}function d(a){return l[a]}function e(a){return c(a).replace(/&(?![\w#]+;)|[<>"']/g,d)}function f(a,b){if(m(a))for(var c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)}function g(a,b){var c=/(\/)[^/]+\1\.\.\1/,d=("./"+a).replace(/[^/]+$/,""),e=d+b;for(e=e.replace(/\/\.\//g,"/");e.match(c);)e=e.replace(c,"/");return e}function h(a,c){var d=b.get(a)||j({filename:a,name:"Render Error",message:"Template not found"});return c?d(c):d}function i(b,c){if("string"==typeof c){var d=c;c=function(){return new a(d)}}var e=k[b]=function(a){try{return new c(a,b)+""}catch(d){return j(d)()}};return e.prototype=c.prototype=n,e.toString=function(){return c+""},e}function j(a){var b="{Template Error}",c=a.stack||"";if(c)c=c.split("\n").slice(0,2).join("\n");else for(var d in a)c+="<"+d+">\n"+a[d]+"\n\n";return function(){return"object"==typeof console&&console.error(b+"\n\n"+c),b}}var k=b.cache={},a=this.String,l={"<":"<",">":">",'"':""","'":"'","&":"&"},m=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},n=b.utils={$helpers:{},$include:function(a,b,c){return a=g(c,a),h(a,b)},$string:c,$escape:e,$each:f},o=b.helpers=n.$helpers;b.get=function(a){return k[a.replace(/^\.\//,"")]},b.helper=function(a,b){o[a]=b},"function"==typeof define?define(function(){return b}):"undefined"!=typeof exports?module.exports=b:this.template=b,/*v:6*/
+b("index",function(b){"use strict";var c=this,d=(c.$helpers,c.$escape),e=b.title,f=c.$each,g=b.list,h=(b.$value,b.$index,"");return h+='",new a(h)})}();
\ No newline at end of file
diff --git a/test/test-all/syntax/index.html b/test/test-all/syntax/index.html
new file mode 100644
index 0000000..602209d
--- /dev/null
+++ b/test/test-all/syntax/index.html
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/test/test-all/syntax/package.json b/test/test-all/syntax/package.json
new file mode 100644
index 0000000..137450e
--- /dev/null
+++ b/test/test-all/syntax/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "template",
+ "version": "1.0.0",
+ "dependencies": {
+ "tmodjs": "1.0.0"
+ },
+ "tmodjs-config": {
+ "output": "./build",
+ "charset": "utf-8",
+ "syntax": "./template-syntax.js",
+ "helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "default",
+ "runtime": "template.js",
+ "combo": true,
+ "minify": true,
+ "cache": false
+ }
+}
\ No newline at end of file
diff --git a/test/test-all/syntax/template-syntax.js b/test/test-all/syntax/template-syntax.js
new file mode 100644
index 0000000..61154c6
--- /dev/null
+++ b/test/test-all/syntax/template-syntax.js
@@ -0,0 +1,88 @@
+
+template.defaults.openTag = '';
+
+
+template.defaults.parser = function (code) {
+ code = code.replace(/^\s/, '');
+
+ var split = code.split(' ');
+ var key = split.shift();
+ var args = split.join(' ');
+
+ switch (key) {
+
+ case 'if':
+
+ code = 'if(' + args + '){';
+ break;
+
+ case 'else':
+
+ if (split.shift() === 'if') {
+ split = ' if(' + split.join(' ') + ')';
+ } else {
+ split = '';
+ }
+
+ code = '}else' + split + '{';
+ break;
+
+ case '/if':
+
+ code = '}';
+ break;
+
+ case 'each':
+
+ var object = split[0] || '$data';
+ var as = split[1] || 'as';
+ var value = split[2] || '$value';
+ var index = split[3] || '$index';
+
+ var param = value + ',' + index;
+
+ if (as !== 'as') {
+ object = '[]';
+ }
+
+ code = '$each(' + object + ',function(' + param + '){';
+ break;
+
+ case '/each':
+
+ code = '});';
+ break;
+
+ case 'echo':
+
+ code = 'print(' + args + ');';
+ break;
+
+ case 'print':
+ case 'include':
+
+ code = key + '(' + split.join(',') + ');';
+ break;
+
+ default:
+
+ if (template.helpers.hasOwnProperty(key)) {
+
+ code = '=#' + key + '(' + split.join(',') + ');';
+
+ } else {
+
+ code = code.replace(/[\s;]*$/, '');
+ code = '=' + code;
+ }
+
+ break;
+ }
+
+
+ return code;
+};
+
+
+
diff --git a/test/test/include/build/include.js b/test/test/include/build/include.js
deleted file mode 100644
index c79a7f5..0000000
--- a/test/test/include/build/include.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/*! */
-define([ "./template", "./a", "./b", "./e", "./d" ], function(template) {
- return template("./include", function($data, $id) {
- var $helpers = this, include = function(id, data) {
- data = data || $data;
- var content = $helpers.$include(id, data, $id);
- if (content !== undefined) {
- $out += content;
- return content;
- }
- }, labe = $data.labe, xxx = $data.xxx, $out = "";
- include("./a", {
- labe: ")"
- });
- include("./b", {
- labe: "("
- });
- $out += " ";
- include("./e", {
- include: "./v"
- });
- $out += " ";
- if ("include('./n')") {}
- $out += " ";
- include("./d");
- xxx.include("./c");
- return new String($out);
- });
-});
\ No newline at end of file
diff --git a/test/test/include/build/template.js b/test/test/include/build/template.js
deleted file mode 100644
index 6df046e..0000000
--- a/test/test/include/build/template.js
+++ /dev/null
@@ -1,106 +0,0 @@
-/*! */
-!function(global) {
- var template = function(path, content) {
- return template[/string|function/.test(typeof content) ? "compile" : "render"].apply(template, arguments);
- };
- var cache = template.cache = {};
- var helpers = template.helpers = {
- $string: function(value) {
- var type = typeof value;
- if (!/string|number/.test(type)) {
- value = type === "function" ? helpers.$string(value()) : "";
- }
- return value + "";
- },
- $escape: function(content) {
- var m = {
- "<": "<",
- ">": ">",
- '"': """,
- "'": "'",
- "&": "&"
- };
- return helpers.$string(content).replace(/&(?![\w#]+;)|[<>"']/g, function(s) {
- return m[s];
- });
- },
- $each: function(data, callback) {
- var isArray = Array.isArray || function(obj) {
- return {}.toString.call(obj) === "[object Array]";
- };
- if (isArray(data)) {
- for (var i = 0, len = data.length; i < len; i++) {
- callback.call(data, data[i], i, data);
- }
- } else {
- for (i in data) {
- callback.call(data, data[i], i);
- }
- }
- },
- $resolve: function(from, to) {
- var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
- var dirname = from.replace(/[^/]+$/, "");
- var id = dirname + to;
- id = id.replace(/\/\.\//g, "/");
- while (id.match(DOUBLE_DOT_RE)) {
- id = id.replace(DOUBLE_DOT_RE, "/");
- }
- return id;
- },
- $include: function(path, data, from) {
- var id = helpers.$resolve(from, path);
- return template.render(id, data);
- }
- };
- var debug = function(e) {
- var message = "";
- for (var name in e) {
- message += "<" + name + ">\n" + e[name] + "\n\n";
- }
- if (message && global.console) {
- console.error("Template Error\n\n" + message);
- }
- return function() {
- return "{Template Error}";
- };
- };
- template.render = function(path, data) {
- var fn = template.get(path) || debug({
- id: path,
- name: "Render Error",
- message: "No Template"
- });
- return data ? fn(data) : fn;
- };
- template.compile = function(path, fn) {
- var isFunction = typeof fn === "function";
- var render = cache[path] = function(data) {
- try {
- return isFunction ? new fn(data, path) + "" : fn;
- } catch (e) {
- return debug(e)();
- }
- };
- render.prototype = fn.prototype = helpers;
- render.toString = function() {
- return fn + "";
- };
- return render;
- };
- template.get = function(id) {
- return cache[id.replace(/^([^.])/, "./$1")];
- };
- template.helper = function(name, helper) {
- helpers[name] = helper;
- };
- if (typeof define === "function") {
- define(function() {
- return template;
- });
- } else if (typeof exports !== "undefined") {
- module.exports = template;
- } else {
- global.template = template;
- }
-}(this);
\ No newline at end of file
diff --git a/test/tpl/build/copyright.js b/test/tpl/build/copyright.js
deleted file mode 100644
index 17eb557..0000000
--- a/test/tpl/build/copyright.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! */
-template("./copyright","(c) 2013");
\ No newline at end of file
diff --git a/test/tpl/build/index.js b/test/tpl/build/index.js
deleted file mode 100644
index 4036827..0000000
--- a/test/tpl/build/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! */
-template("./index",function(i,e){var t=this,include=function(l,n){n=n||i;var r=t.$include(l,n,e);return void 0!==r?(a+=r,r):void 0},l=t.$escape,n=i.title,r=t.$each,u=i.list,a=(i.$value,i.$index,"");return include("./public/header"),a+=' ",include("./public/footer"),new String(a)});
\ No newline at end of file
diff --git a/test/tpl/build/public/footer.js b/test/tpl/build/public/footer.js
deleted file mode 100644
index 18580e2..0000000
--- a/test/tpl/build/public/footer.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! */
-template("./public/footer",function(i,e){var t=this,n=i.time,r=t.$escape,include=function(n,r){r=r||i;var u=t.$include(n,r,e);return void 0!==u?(l+=u,u):void 0},l="";return l+='",new String(l)});
\ No newline at end of file
diff --git a/test/tpl/build/public/header.js b/test/tpl/build/public/header.js
deleted file mode 100644
index 319f5f8..0000000
--- a/test/tpl/build/public/header.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! */
-template("./public/header",function(i,e){var t=this,include=function(n,l){l=l||i;var a=t.$include(n,l,e);return void 0!==a?(r+=a,a):void 0},r="";return r+=' ',new String(r)});
\ No newline at end of file
diff --git a/test/tpl/build/public/logo.js b/test/tpl/build/public/logo.js
deleted file mode 100644
index 63da4db..0000000
--- a/test/tpl/build/public/logo.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! */
-template("./public/logo",' ');
\ No newline at end of file
diff --git a/test/tpl/build/template.js b/test/tpl/build/template.js
index 6da88c5..2263cd3 100644
--- a/test/tpl/build/template.js
+++ b/test/tpl/build/template.js
@@ -1,7 +1,7 @@
-/*! */
-!function(e){var t=function(e,i){return t[/string|function/.test(typeof i)?"compile":"render"].apply(t,arguments)},i=t.cache={},r=t.helpers={$string:function(e){var t=typeof e;return/string|number/.test(t)||(e="function"===t?r.$string(e()):""),e+""},$escape:function(e){var t={"<":"<",">":">",'"':""","'":"'","&":"&"};return r.$string(e).replace(/&(?![\w#]+;)|[<>"']/g,function(e){return t[e]})},$each:function(e,t){var i=Array.isArray||function(e){return"[object Array]"==={}.toString.call(e)};if(i(e))for(var r=0,n=e.length;n>r;r++)t.call(e,e[r],r,e);else for(r in e)t.call(e,e[r],r)},$resolve:function(e,t){var i=/(\/)[^/]+\1\.\.\1/,r=e.replace(/[^/]+$/,""),n=r+t;for(n=n.replace(/\/\.\//g,"/");n.match(i);)n=n.replace(i,"/");return n},$include:function(e,i,n){var o=r.$resolve(n,e);return t.render(o,i)}},n=function(t){var i="";for(var r in t)i+="<"+r+">\n"+t[r]+"\n\n";return i&&e.console&&console.error("Template Error\n\n"+i),function(){return"{Template Error}"}};t.render=function(e,i){var r=t.get(e)||n({id:e,name:"Render Error",message:"No Template"});return i?r(i):r},t.compile=function(e,t){var o="function"==typeof t,a=i[e]=function(i){try{return o?new t(i,e)+"":t}catch(r){return n(r)()}};return a.prototype=t.prototype=r,a.toString=function(){return t+""},a},t.get=function(e){return i[e.replace(/^([^.])/,"./$1")]},t.helper=function(e,t){r[e]=t},/**/
-t("./copyright","(c) 2013"),/**/
-t("./index",function(e,t){var i=this,include=function(r,n){n=n||e;var o=i.$include(r,n,t);return void 0!==o?(l+=o,o):void 0},r=i.$escape,n=e.title,o=i.$each,a=e.list,l=(e.$value,e.$index,"");return include("./public/header"),l+=' ",include("./public/footer"),new String(l)}),/**/
-t("./public/footer",function(e,t){var i=this,r=e.time,n=i.$escape,include=function(r,n){n=n||e;var a=i.$include(r,n,t);return void 0!==a?(o+=a,a):void 0},o="";return o+='",new String(o)}),/**/
-t("./public/header",function(e,t){var i=this,include=function(n,o){o=o||e;var a=i.$include(n,o,t);return void 0!==a?(r+=a,a):void 0},r="";return r+=' ',new String(r)}),/**/
-t("./public/logo",' '),"function"==typeof define?define(function(){return t}):"undefined"!=typeof exports?module.exports=t:e.template=t}(this);
\ No newline at end of file
+/*TMODJS:{"version":"1.0.1"}*/
+!function(){function a(a,b){return(/string|function/.test(typeof b)?h:g)(a,b)}function b(a,c){return"string"!=typeof a&&(c=typeof a,"number"===c?a+="":a="function"===c?b(a.call(a)):""),a}function c(a){return l[a]}function d(a){return b(a).replace(/&(?![\w#]+;)|[<>"']/g,c)}function e(a,b){if(m(a))for(var c=0,d=a.length;d>c;c++)b.call(a,a[c],c,a);else for(c in a)b.call(a,a[c],c)}function f(a,b){var c=/(\/)[^/]+\1\.\.\1/,d=("./"+a).replace(/[^/]+$/,""),e=d+b;for(e=e.replace(/\/\.\//g,"/");e.match(c);)e=e.replace(c,"/");return e}function g(b,c){var d=a.get(b)||i({filename:b,name:"Render Error",message:"Template not found"});return c?d(c):d}function h(a,b){if("string"==typeof b){var c=b;b=function(){return new k(c)}}var d=j[a]=function(c){try{return new b(c,a)+""}catch(d){return i(d)()}};return d.prototype=b.prototype=n,d.toString=function(){return b+""},d}function i(a){var b="{Template Error}",c=a.stack||"";if(c)c=c.split("\n").slice(0,2).join("\n");else for(var d in a)c+="<"+d+">\n"+a[d]+"\n\n";return function(){return"object"==typeof console&&console.error(b+"\n\n"+c),b}}var j=a.cache={},k=this.String,l={"<":"<",">":">",'"':""","'":"'","&":"&"},m=Array.isArray||function(a){return"[object Array]"==={}.toString.call(a)},n=a.utils={$helpers:{},$include:function(a,b,c){return a=f(c,a),g(a,b)},$string:b,$escape:d,$each:e},o=a.helpers=n.$helpers;a.get=function(a){return j[a.replace(/^\.\//,"")]},a.helper=function(a,b){o[a]=b},"function"==typeof define?define(function(){return a}):"undefined"!=typeof exports?module.exports=a:this.template=a,/*v:25*/
+a("copyright","(c) 2013"),/*v:38*/
+a("index",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,e){e=e||a;var f=c.$include(d,e,b);return i+=f}),e=c.$escape,f=a.title,g=c.$each,h=a.list,i=(a.$value,a.$index,"");return d("./public/header"),i+=' ",d("./public/footer"),new k(i)}),/*v:26*/
+a("public/footer",function(a,b){"use strict";var c=this,d=(c.$helpers,a.time),e=c.$escape,f=function(d,e){e=e||a;var f=c.$include(d,e,b);return g+=f},g="";return g+='",new k(g)}),/*v:26*/
+a("public/header",function(a,b){"use strict";var c=this,d=(c.$helpers,function(d,f){f=f||a;var g=c.$include(d,f,b);return e+=g}),e="";return e+=' ',new k(e)}),/*v:27*/
+a("public/logo",' ')}();
\ No newline at end of file
diff --git a/test/tpl/package.json b/test/tpl/package.json
index 6bdb78b..67216ae 100644
--- a/test/tpl/package.json
+++ b/test/tpl/package.json
@@ -1,20 +1,20 @@
{
"name": "template",
- "version": "1.0.0",
+ "version": "1.0.1",
"dependencies": {
- "tmodjs": "~0.0.2"
+ "tmodjs": "1.0.1-rc9"
},
"tmodjs-config": {
"output": "./build",
"charset": "utf-8",
- "combo": [
- "*"
- ],
"syntax": "simple",
"helpers": null,
+ "escape": true,
+ "compress": true,
+ "type": "default",
+ "runtime": "template.js",
+ "combo": true,
"minify": true,
- "async": false,
- "engine": false,
- "type": "templatejs"
+ "cache": false
}
}
\ No newline at end of file
diff --git a/test/tpl/public/header.html b/test/tpl/public/header.html
index d93e780..267582e 100644
--- a/test/tpl/public/header.html
+++ b/test/tpl/public/header.html
@@ -6,6 +6,6 @@
新闻
图片
军事
-
+
\ No newline at end of file
diff --git a/tmod.js b/tmod.js
deleted file mode 100644
index cfe0c28..0000000
--- a/tmod.js
+++ /dev/null
@@ -1,958 +0,0 @@
-/*!
- * TmodJS - AOT Template Compiler
- * https://github.com/aui/tmodjs
- * Released under the MIT, BSD, and GPL Licenses
- */
-
-'use strict';
-
-var version = require('./package.json').version;
-var template = require('./lib/template-AOTcompile.js');
-var uglifyjs = require("./lib/uglify.js");
-
-var fs = require('fs');
-var path = require('path');
-var events = require('events');
-var crypto = require('crypto');
-var vm = require("vm");
-
-
-// 跨平台 path 接口,统一 windows 与 linux 的路径分隔符,
-// 避免不同平台模板编译后其 id 不一致
-(function () {
-
- if (!/\\/.test(path.resolve())) {
- return path;
- }
-
- var oldPath = path;
- var newPath = Object.create(oldPath);
- var proxy = function (name) {
- return function () {
- var value = oldPath[name].apply(oldPath, arguments);
- if (typeof value === 'string') {
- value = value.split(oldPath.sep).join('/');
- }
- return value;
- }
- };
-
- for (var name in newPath) {
- if (typeof oldPath[name] === 'function') {
- newPath[name] = proxy(name);
- }
- };
-
- path = newPath;
-})();
-
-
-var RUNTIME = 'template';
-var EXTNAME_RE = /\.(html|htm|tpl)$/i;
-var FILTER_RE = /[^\w\.\-$]/;
-var DIRNAME_RE = /[^\/]*/;
-
-
-module.exports = {
-
- __proto__: events.EventEmitter.prototype,
-
-
- // 默认配置
- // 用户配置将保存到模板根目录 package.json 文件中
- defaults: {
-
- // 编译输出目录设置
- output: './build',
-
- // 模板使用的编码。(注意:非 utf-8 编码的模板缺乏测试)
- charset: 'utf-8',
-
- // 模板合并规则
- // 注意:type 参数的值为 templatejs 才会生效
- combo: ['*'],
-
- // 定义模板采用哪种语法,可选:
- // simple: 默认语法,易于读写。可参看语法文档
- // native: 功能丰富,灵活多变。语法类似微型模板引擎 tmpl
- syntax: 'simple',
-
- // 自定义辅助方法路径
- helpers: null,
-
- // 是否输出为压缩的格式
- minify: true,
-
- // 是否内嵌异步加载插件(beta)
- // 可以支持 template.async(path, function (render) {}) 方式异步载入模板
- // 注意:type 参数是 templatejs 的时候才生效
- async: false,
-
- // 是否嵌入模板引擎,否则编译为不依赖引擎的纯 js 代码
- // 通常来说,模板不多的情况下,编译为原生的 js 打包后体积更小,因为不必嵌入引擎
- // 当模板很多的时候,内置模板引擎,模板使用字符串存储的方案会更能节省空间
- engine: false,
-
- // 输出的模块类型(不区分大小写),可选:
- // templatejs: 模板目录将会打包后输出,可使用 script 标签直接引入,也支持 NodeJS/RequireJS/SeaJS。
- // cmd: 这是一种兼容 RequireJS/SeaJS 的模块(类似 atc v1版本编译结果)
- // amd: 支持 RequireJS 等流行加载器
- // commonjs: 编译为 NodeJS 模块
- type: 'templatejs'
-
- },
-
-
- // 获取用户配置
- getUserConfig: function (options, dir) {
- dir = this.path || dir;
- var file = dir + '/package.json';
- var defaults = this.defaults;
- var json = null;
- var name = null;
- var config = {};
-
-
- // 读取目录中 package.json
- if (fs.existsSync(file)) {
- var fileContent = fs.readFileSync(file, 'utf-8');
-
- if (fileContent) {
- json = JSON.parse(fileContent);
- }
- }
-
-
- if (!json) {
-
- json = {
- "name": 'template',
- "version": '1.0.0',
- "dependencies": {
- "tmodjs": ""
- },
- "tmodjs-config": {}
- }
-
- }
-
- json.dependencies.tmodjs = '~' + version;
-
- // 默认配置 优先级:0
- for (name in defaults) {
- config[name] = defaults[name];
- }
-
-
- // 项目配置 优先级:1
- for (name in json["tmodjs-config"]) {
- config[name] = json["tmodjs-config"][name];
- }
-
-
- // 用户配置 优先级:2
- for (name in options) {
- config[name] = options[name];
- }
-
-
- json["tmodjs-config"] = config;
- this['package.json'] = json;
-
- // 忽略大小写
- config['type'] = config['type'].toLowerCase();
- config['syntax'] = config['syntax'].toLowerCase();
-
- return config;
- },
-
-
- /** 保存用户配置 */
- saveUserConfig: function () {
-
- var file = this.path + '/package.json';
- var configName = 'tmodjs-config';
- var json = this['package.json'];
-
- var options = json[configName];
- var userConfigList = Object.keys(this.defaults);
-
-
- //if (options.output.indexOf('.') === 0) {
- // json.main = options.output + '/' + (options.runtime || RUNTIME) + '.js';
- //}
-
-
- // 只保存指定的字段
- json[configName] = JSON.parse(
- JSON.stringify(options, userConfigList)
- );
-
-
- var text = JSON.stringify(json, null, 4);
-
-
- fs.writeFileSync(file, text, 'utf-8');
- },
-
-
- // 绑定文件监听事件
- _onwatch: function (dir, callback) {
-
- var that = this;
- var watchList = {};
- var timer = {};
-
-
- var walk = function (dir) {
- fs.readdirSync(dir).forEach(function (item) {
- var fullname = dir + '/' + item;
- if (fs.statSync(fullname).isDirectory()){
- watch(fullname);
- walk(fullname);
- }
- });
- };
-
-
- // 排除“.”、“_”开头或者非英文命名的目录
- var filter = function (name) {
- return !FILTER_RE.test(name) && name !== that.output;
- };
-
-
- var watch = function (parent) {
-
- var target = path.basename(parent);
-
- if (!filter(target)){
- return;
- }
-
- if (watchList[parent]) {
- watchList[parent].close();
- }
-
- watchList[parent] = fs.watch(parent, function (event, filename) {
-
- var fullname = parent + '/' + filename;
- var type;
- var fstype;
-
- if (!filter(filename)) {
- return;
- }
-
- // 检查文件、目录是否存在
- if (!fs.existsSync(fullname)) {
-
- // 如果目录被删除则关闭监视器
- if (watchList[fullname]) {
- fstype = 'directory';
- watchList[fullname].close();
- delete watchList[fullname];
- } else {
- fstype = 'file';
- }
-
- type = 'delete';
-
- } else {
-
- // 文件
- if (fs.statSync(fullname).isFile()) {
-
- fstype = 'file';
- type = event == 'rename' ? 'create' : 'updated'
-
- // 文件夹
- } else if (event === 'rename') {
-
- fstype = 'directory';
- type = 'create'
- watch(fullname);
- walk(fullname);
- }
-
- }
-
- var eventData = {
- type: type,
- target: filename,
- parent: parent,
- fstype: fstype
- };
-
-
- if (/windows/i.test(require('os').type())) {
- // window 下 nodejs fs.watch 方法尚未稳定
- clearTimeout(timer[fullname]);
- timer[fullname] = setTimeout(function() {
- callback.call(that, eventData);
- }, 16);
-
- } else {
- callback.call(that, eventData);
- }
-
-
- });
-
- };
-
- watch(dir);
- walk(dir);
- },
-
-
- // 筛选模板文件
- _filter: function (name) {
- return !FILTER_RE.test(name) && EXTNAME_RE.test(name);
- },
-
-
- // 模板文件写入
- _fsWrite: function (file, data) {
- this._fsMkdir(path.dirname(file));
- fs.writeFileSync(file, data, this.options['charset']);
- },
-
-
- // 模板文件读取
- _fsRead: function (file) {
- return fs.readFileSync(file, this.options['charset']);
- },
-
-
- // 创建目录,包括子文件夹
- _fsMkdir: function (dir) {
-
- var currPath = dir;
- var toMakeUpPath = [];
-
- while (!fs.existsSync(currPath)) {
- toMakeUpPath.unshift(currPath);
- currPath = path.dirname(currPath);
- }
-
- toMakeUpPath.forEach(function (pathItem) {
- fs.mkdirSync(pathItem);
- });
-
- },
-
-
- // 删除文件夹,包括子文件夹
- _rmdir: function (dir) {
-
- var walk = function (dir) {
-
- if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
- return;
- }
-
- var files = fs.readdirSync(dir);
-
- if (!files.length) {
- fs.rmdirSync(dir);
- return;
- } else {
- files.forEach(function (file) {
- var fullName = path.join(dir, file);
- if (fs.statSync(fullName).isDirectory()) {
- walk(fullName);
- } else {
- fs.unlinkSync(fullName);
- }
- });
- }
-
- fs.rmdirSync(dir);
- };
-
- walk(dir);
- },
-
-
- // 删除模板文件
- _fsUnlink: function (file) {
- return fs.existsSync(file) && fs.unlinkSync(file);
- },
-
-
- // 获取字符串 md5 值
- _md5: function (text) {
- return crypto.createHash('md5').update(text).digest('hex');
- },
-
-
- // 检查模板是否更改
- _isChange: function (html, js) {
- var newMd5 = this._md5(html + JSON.stringify(this['package.json']));
- var oldMd5 = js.match(//)[1];
- return newMd5 !== oldMd5;
- },
-
-
- // 调试语法错误
- _debug: function (error, callback) {
-
- var debugFile = error.debugFile;
- var code = error.temp;
-
- code = "/*! */\n' + code;
-
- this._fsWrite(debugFile, code);
-
- // 启动子进程进行调试,从根本上避免影响当前进程
- var exec = require('child_process').exec;
- exec('node ' + debugFile, {timeout: 0}, function (error, stdout, stderr) {
- var message = error ? error.message : '';
- message = message
- .replace(/^Command\sfailed\:|\s*SyntaxError[\w\W]*$/g, '')
- .trim();
- callback(message);
- });
-
- //this._fsUnlink(debugFile);
- },
-
-
- // 在控制台显示日志(支持UBB)
- _log: function (message) {
-
- var styles = {
- // styles
- 'bold' : ['\x1B[1m', '\x1B[22m'],
- 'italic' : ['\x1B[3m', '\x1B[23m'],
- 'underline' : ['\x1B[4m', '\x1B[24m'],
- 'inverse' : ['\x1B[7m', '\x1B[27m'],
- // colors
- 'white' : ['\x1B[37m', '\x1B[39m'],
- 'grey' : ['\x1B[90m', '\x1B[39m'],
- 'black' : ['\x1B[30m', '\x1B[39m'],
- 'blue' : ['\x1B[34m', '\x1B[39m'],
- 'cyan' : ['\x1B[36m', '\x1B[39m'],
- 'green' : ['\x1B[32m', '\x1B[39m'],
- 'magenta' : ['\x1B[35m', '\x1B[39m'],
- 'red' : ['\x1B[31m', '\x1B[39m'],
- 'yellow' : ['\x1B[33m', '\x1B[39m']
- };
-
-
- styles['b'] = styles['bold'];
- styles['i'] = styles['italic'];
- styles['u'] = styles['underline'];
-
-
- message = message.replace(/\[([^\]]*?)\]/igm, function ($1, $2) {
- return $2.indexOf('/') === 0
- ? styles[$2.slice(1)][1]
- : styles[$2][0];
- });
-
-
- this.log(message);
- },
-
-
-
- log: function (message) {
- process.stdout.write(message);
- },
-
-
- // 打包模板
- _combo: function () {
-
- var that = this;
- var templates = [];
- var ignores = [];
- var isDebug = this.options.debug;
- var isWrappings = this.options.type !== 'templatejs';
- var runtime = this.options.engine ? '/lib/template-full.js' : '/lib/template-runtime.js';
-
- var template = fs.readFileSync(__dirname + runtime, 'utf-8');
-
- var combo = '';
-
-
- var walk = function (dir) {
- var dirList = fs.readdirSync(dir);
-
- dirList.forEach(function (item) {
-
- if (fs.statSync(dir + '/' + item).isDirectory()) {
- walk(dir + '/' + item);
- } else if (that._filter(item)) {
-
- var id = (dir + '/' + item)
- .replace(EXTNAME_RE, '')
- .replace(that.path + '/', '');
-
- templates.push(id);
-
- if (!that.combo.test(id)) {
- ignores.push(id);
- return;
- }
-
- var target = that.output + '/' + id + '.js';
-
-
- if (fs.existsSync(target)) {
- var code = that._fsRead(target);
-
- // 一个猥琐的实现:
- // 文件末尾设置一个空注释,然后让 UglifyJS 不压缩它,避免很多文件挤成一行
- code = code.replace(/^\/\*[\w\W]*?\*\//, '/**/');
-
-
- combo += code;
- }
-
-
- }
- });
- };
-
- if (!isWrappings) {
- walk(this.path);
- }
-
- var build = Date.now();
- var debug = isDebug ? '' : '';
-
-
- var data = {
- version: this.version,
- build: build,
- templates: combo,
- debug: debug,
- plugins: '',
- syntax: '',
- engine: '',
- helpers: this.helpers
- };
-
-
- // 嵌入异步加载插件
- if (this.options.async) {
- data.plugins = fs.readFileSync(__dirname + '/lib/template-async.js', 'utf-8');
- }
-
-
- // 嵌入引擎
- if (this.options.engine) {
- data.engine = fs.readFileSync(__dirname + '/lib/template.js', 'utf-8');
- data.syntax = fs.readFileSync(__dirname + '/lib/template-syntax.js', 'utf-8');
- }
-
-
- template = template.replace(/['"]<\:(.*?)\:>['"]/g, function ($1, $2) {
- return data[$2] || '';
- });
-
-
- var target = path.join(this.output, (this.options.runtime || RUNTIME) + '.js');
-
-
- this._fsWrite(target, template);
-
- isDebug || !this.options.minify
- ? uglifyjs.beautify(target)
- : uglifyjs.minify(target);
-
-
- this.emit('combo', {
- output: target,
- isDebug: isDebug,
- templates: templates,
- ignores: ignores,
- build: build
- });
- },
-
-
- /**
- * 监听模板的修改进行即时编译
- */
- watch: function () {
-
- // 监控模板目录
- this.on('watch', function (data) {
-
- var type = data.type;
- var fstype = data.fstype;
- var target = data.target;
- var parent = data.parent;
- var fullname = parent + '/' + target;
-
-
- if (target && fstype === 'file' && this._filter(target)) {
-
- if (type === 'delete') {
-
- this.emit('delete', {
- source: data.target
- });
- fullname = fullname.replace(EXTNAME_RE, '');
- this._fsUnlink(fullname.replace(this.path, this.output) + '.js');
- this._combo();
-
- } else if (/updated|create/.test(type)) {
-
- this.emit('change', {
- source: data.target
- });
-
- if (this._compile(fullname)) {
- this._combo();
- };
-
- }
- }
-
- });
-
- },
-
-
- // 编译单个模板
- _compile: function (file) {
-
- var that = this;
-
- // 模板字符串
- var source = this._fsRead(file);
-
- // 目标路径
- var target = file
- .replace(EXTNAME_RE, '.js')
- .replace(this.path, this.output);
-
-
- var mod = '';
- var modObject = {};
- var error = true;
- var errorInfo = null;
- var isDebug = this.options.debug;
- var isWrappings = this.options.wrappings;
- var isEngine = this.options.engine;
-
-
- // 读取上一次编译的结果
- if (fs.existsSync(target)) {
- mod = this._fsRead(target);
- }
-
-
- // 检查模板是否有改动
- var isChange = !mod
- || //.test(mod)
- || isDebug
- || this._isChange(source, mod);
-
-
- var id = file
- .replace(this.path + '/', './');
-
-
- var extname = id.match(EXTNAME_RE)[1];
- id = id.replace(EXTNAME_RE, '');
-
-
- // 模板加载事件
- this.emit('load', {
- id: id,
- file: file,
- extname: extname,
- isChange: isChange,
- source: source,
- target: target
- });
-
-
- try {
-
- if (isChange) {
- modObject = template.AOTcompile(id, source, {
- runtime: this.options.runtime,
- engine: this.options.engine,
- type: this.options.type,
- debug: isDebug
- });
- mod = modObject.code;
- }
-
- error = false;
-
- } catch (e) {
- errorInfo = e;
-
- }
-
-
- if (!error && isChange) {
-
- var md5 = this._md5(source + JSON.stringify(this['package.json']));
- mod = '/*! '
- + (isDebug ? ' ' : '')
- + '*/\n' + mod;
-
- this._fsWrite(target, mod);
- uglifyjs[isDebug || !this.options.minify ? 'beautify' : 'minify'](target);
-
-
- }
-
-
- var compileInfo = {
- id: id,
- file: file,
- extname: extname,
- isChange: isChange,
- error: error,
- source: source,
- target: target,
- code: mod,
- requires: modObject.requires || []
- };
-
-
- if (error) {
- errorInfo.debugFile = this.path + '/.debug.js';
-
- this._debug(errorInfo, function (message) {
-
- var e = {
- name: errorInfo.name,
- type: 'compileError',
- message: message,
- debugFile: errorInfo.debugFile,
- temp: errorInfo.temp
- };
- for (var name in compileInfo) {
- e[name] = compileInfo[name];
- };
-
- // 模板编译错误事件
- this.emit('compileError', e);
-
- this.emit('error', e);
-
- }.bind(this));
-
- } else {
-
- // 模板编译成功事件
- this.emit('compile', compileInfo);
- }
-
-
- if (error) {
- return false;
- } else {
- return compileInfo;
- }
- },
-
-
- /**
- * 编译模板
- * @param {String} 模板文件路径,无此参数则编译目录所有模板
- * @param {Boolean} 是否递归编译 include 依赖
- */
- compile: function (file, recursion) {
-
- var that = this;
- var error = false;
-
- if (file) {
-
- var extname = path.extname(file);
-
- var walk = function (list) {
-
- list.forEach(function (file) {
-
- if (error) {
- return;
- }
-
- var info = that._compile(file);
-
- error = !info;
-
-
- if (!error && recursion !== false && info.requires.length) {
-
- list = info.requires.map(function (id) {
- var target = path.resolve(that.path, id + extname);
- return target;
- });
-
- walk(list);
-
- };
-
- });
- };
-
- walk(typeof file === 'string' ? [file] : file);
-
- !error && this._combo();
-
- } else {
-
-
- var walk = function (dir) {
- var dirList = fs.readdirSync(dir);
-
- dirList.forEach(function (item) {
-
- if (error) {
- return;
- }
-
- if (fs.statSync(dir + '/' + item).isDirectory()) {
- walk(dir + '/' + item);
- } else if (that._filter(item)) {
- error = !that._compile(dir + '/' + item);
- }
-
- });
- };
-
-
- walk(this.path);
- !error && this._combo();
- }
-
- },
-
-
- init: function (input, options) {
-
- events.EventEmitter.call(this);
-
- options = this.options = this.getUserConfig(options, input);
-
- // 模板目录
- this.path = path.resolve(input);
-
- // 输出目录
- this.output = path.resolve(path.join(this.path, options.output));
-
- // 辅助方法
- this.helpers = '';
-
-
- // 加载辅助方法
- if (options['helpers']) {
- var helpersPath = path.join(this.path, options['helpers']);
-
- if (fs.existsSync(helpersPath)) {
- this.helpers = fs.readFileSync(helpersPath, 'utf-8');
- vm.runInNewContext(this.helpers, {
- template: template
- });
- }
- }
-
-
- // 加载模板语法设置
- if (options['syntax'] && options['syntax'] !== 'native') {
- var syntaxPath = options['syntax'] === 'simple'
- ? __dirname + '/lib/template-syntax.js'
- : path.join(this.path, options['syntax']);
-
- if (fs.existsSync(syntaxPath)) {
- vm.runInNewContext(fs.readFileSync(syntaxPath, 'utf-8'), {
- template: template
- });
- }
- }
-
-
- // 模板合并规则
- if (options.combo.length) {
- var reg = [];
- options.combo.forEach(function (combo) {
- combo = combo
- .replace(/([\$\.])/g, '\\$1')
- .replace(/\*/g, '(.*?)');
-
- reg.push('^' + combo + '$');
- });
- this.combo = new RegExp(reg.join('|'));
- }
-
-
- // 初始化 watch 事件
- this.on('newListener', function (event, listener) {
- if (event === 'watch') {
- this._log('\n[inverse]Waiting..[/inverse]\n\n');
- this._onwatch(this.path, function (data) {
- this.emit('watch', data);
- });
- this._onwatch = function () {};
- }
- });
-
-
- // 监听模板修改事件
- this.on('change', function (data) {
- var time = (new Date).toLocaleTimeString();
- this._log('[grey]' + time + '[/grey] ');
- this._log('[grey]Template has been updated[/grey]\n');
- });
-
-
- // 监听模板加载事件
- this.on('load', function (data) {
- this._log(data.id + '[grey].' + data.extname + '[/grey]');
- });
-
-
- // 监听模板编译事件
- this.on('compile', function (data) {
- if (data.isChange) {
- this._log(' [green]√[/green]\n');
- } else {
- this._log(' [grey]√[/grey]\n');
- }
- });
-
-
- // 监听编译错误事件
- this.on('compileError', function (data) {
- this._log(' [inverse][red]Syntax Error[/red][/inverse]\n');
- this._log('\n[red]Template ID: ' + data.id + '[/red]\n');
- this._log('[red]' + data.message + '[/red]\n');
- });
-
-
- // 监听模板合并事件
- this.on('combo', function (data) {
-
- this._log('\n');
-
- var iLength = data.ignores.length;
- var tLengtn = data.templates.length;
-
- if (data.ignores.length) {
- this._log('[grey]Ignore(' + iLength + '/' + tLengtn + '): '
- + data.ignores.join(', ') + '[/grey]\n');
- }
-
- this._log('[grey]>>> [/grey][green]' + data.output + '[/green]');
- this._log(this.options.debug ? ' [red][/red]\n' : '\n');
-
- });
-
-
- }
-
-};
-