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'}} + +
    +

    {{title}}

    + +
    + +{{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+='

    ',i+=e(f),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'}} + +
    +

    {{title}}

    + +
    + +{{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+='

    ',i+=e(f),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'}} + +
    +

    {{title}}

    + +
    + +{{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",'

    \u817e\u8baf\u7f51

    '); \ 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'}} + +
    +

    标题 {{title}}

    + +
    + +{{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+='

    ',h+=e(l(n)),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+='

    ',h+=d(e),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 @@ +
    +

    {{title}}

    + +
    \ 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'}} + +
    +

    {{title}}

    + +
    + +{{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+='

    ',h+=e(l(n)),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+='

    ',a+=e(l),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+='

    ',h+=d(e),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 @@ +
    +

    <%=title%>

    + +
    \ 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+='

    ',u+=t(n),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+='

    ',h+=d(e),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+='

    ',a+=l(n),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",'

    \u817e\u8baf\u7f51

    '); \ 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+='

    ',l+=r(n),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",'

    \u817e\u8baf\u7f51

    '),"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+='

    ',i+=e(f),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",'

    \u817e\u8baf\u7f51

    ')}(); \ 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'); - - }); - - - } - -}; -