tmod.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  1. /*!
  2. * TmodJS - AOT Template Compiler
  3. * https://github.com/aui/tmodjs
  4. * Released under the MIT, BSD, and GPL Licenses
  5. */
  6. 'use strict';
  7. var version = require('./package.json').version;
  8. var template = require('./lib/AOTcompile.js');
  9. var uglifyjs = require('./lib/uglify.js');
  10. var fs = require('fs');
  11. var path = require('path');
  12. var events = require('events');
  13. var crypto = require('crypto');
  14. var vm = require('vm');
  15. var exec = require('child_process').exec;
  16. var os = require('os');
  17. var engineDirname = path.dirname(require.resolve('art-template'));
  18. // 跨平台 path 接口,统一 windows 与 linux 的路径分隔符,
  19. // 避免不同平台模板编译后其 id 不一致
  20. ;(function () {
  21. if (!/\\/.test(path.resolve())) {
  22. return path;
  23. }
  24. var oldPath = path;
  25. var newPath = Object.create(oldPath);
  26. var proxy = function (name) {
  27. return function () {
  28. var value = oldPath[name].apply(oldPath, arguments);
  29. if (typeof value === 'string') {
  30. value = value.split(oldPath.sep).join('/');
  31. }
  32. return value;
  33. }
  34. };
  35. for (var name in newPath) {
  36. if (typeof oldPath[name] === 'function') {
  37. newPath[name] = proxy(name);
  38. }
  39. }
  40. path = newPath;
  41. })();
  42. var RUNTIME = 'template';
  43. var EXTNAME_RE = /\.(html|htm|tpl)$/i;
  44. var FILTER_RE = /[^\w\.\-$]/;
  45. var DIRNAME_RE = /[^\/]*/;
  46. module.exports = {
  47. __proto__: events.EventEmitter.prototype,
  48. // 默认配置
  49. // 用户配置将保存到模板根目录 package.json 文件中
  50. defaults: {
  51. // 编译输出目录设置
  52. output: './build',
  53. // 模板使用的编码。(注意:非 utf-8 编码的模板缺乏测试)
  54. charset: 'utf-8',
  55. // 定义模板采用哪种语法,内置可选:
  56. // simple: 默认语法,易于读写。可参看语法文档
  57. // native: 功能丰富,灵活多变。语法类似微型模板引擎 tmpl
  58. // 或者指定语法解析器路径,参考:
  59. // https://github.com/aui/artTemplate/blob/master/src/template-syntax.js
  60. syntax: 'simple',
  61. // 自定义辅助方法路径
  62. helpers: null,
  63. // 是否过滤 XSS
  64. // 如果后台给出的数据已经进行了 XSS 过滤,就可以关闭模板的过滤以提升模板渲染效率
  65. escape: true,
  66. // 是否嵌入模板引擎,否则编译为不依赖引擎的纯 js 代码
  67. // 选择嵌入模板引擎后,模板以字符串存储并浏览器中执行编译
  68. engine: false,
  69. // 输出的模块类型,可选:
  70. // templatejs: 模板目录将会打包后输出,可使用 script 标签直接引入,也支持 NodeJS/RequireJS/SeaJS。
  71. // cmd: 这是一种兼容 RequireJS/SeaJS 的模块(类似 atc v1版本编译结果)
  72. // amd: 支持 RequireJS 等流行加载器
  73. // commonjs: 编译为 NodeJS 模块
  74. type: 'templatejs',
  75. // 运行时别名
  76. // 仅针对于非 templatejs 的类型模块
  77. alias: null,
  78. // 是否合并模板
  79. // 仅针对于 templatejs 类型的模块
  80. combo: true,
  81. // 是否输出为压缩的格式
  82. minify: true
  83. },
  84. // 获取用户配置
  85. getUserConfig: function (options, dir) {
  86. dir = this.path || dir;
  87. var file = dir + '/package.json';
  88. var defaults = this.defaults;
  89. var json = null;
  90. var name = null;
  91. var config = {};
  92. // 读取目录中 package.json
  93. if (fs.existsSync(file)) {
  94. var fileContent = fs.readFileSync(file, 'utf-8');
  95. if (fileContent) {
  96. json = JSON.parse(fileContent);
  97. }
  98. }
  99. if (!json) {
  100. json = {
  101. "name": 'template',
  102. "version": '1.0.0',
  103. "dependencies": {
  104. "tmodjs": ""
  105. },
  106. "tmodjs-config": {}
  107. }
  108. }
  109. json.dependencies.tmodjs = '~' + version;
  110. // 默认配置 优先级:0
  111. for (name in defaults) {
  112. config[name] = defaults[name];
  113. }
  114. // 项目配置 优先级:1
  115. for (name in json['tmodjs-config']) {
  116. config[name] = json['tmodjs-config'][name];
  117. }
  118. // 用户配置 优先级:2
  119. for (name in options) {
  120. config[name] = options[name];
  121. }
  122. json['tmodjs-config'] = config;
  123. this['package.json'] = json;
  124. // 忽略大小写
  125. config.type = config.type.toLowerCase();
  126. // 模板合并规则
  127. // 兼容 0.0.3-rc3 之前的配置
  128. if (Array.isArray(config.combo) && !config.combo.length) {
  129. config.combo = false;
  130. } else {
  131. config.combo = !!config.combo;
  132. }
  133. // 根据生成模块的类型删除不支持的配置字段
  134. if (config.type === 'templatejs') {
  135. delete config.alias;
  136. } else {
  137. delete config.combo;
  138. }
  139. return config;
  140. },
  141. /**
  142. * 保存用户配置
  143. * @return {String} 用户配置文件路径
  144. */
  145. saveUserConfig: function () {
  146. var file = this.path + '/package.json';
  147. var configName = 'tmodjs-config';
  148. var json = this['package.json'];
  149. var options = json[configName];
  150. var userConfigList = Object.keys(this.defaults);
  151. // 只保存指定的字段
  152. json[configName] = JSON.parse(
  153. JSON.stringify(options, userConfigList)
  154. );
  155. var text = JSON.stringify(json, null, 4);
  156. fs.writeFileSync(file, text, 'utf-8');
  157. return file;
  158. },
  159. // 绑定文件监听事件
  160. _onwatch: function (dir, callback) {
  161. var that = this;
  162. var watchList = {};
  163. var timer = {};
  164. var walk = function (dir) {
  165. fs.readdirSync(dir).forEach(function (item) {
  166. var fullname = dir + '/' + item;
  167. if (fs.statSync(fullname).isDirectory()){
  168. watch(fullname);
  169. walk(fullname);
  170. }
  171. });
  172. };
  173. // 排除“.”、“_”开头或者非英文命名的目录
  174. var filter = function (name) {
  175. return !FILTER_RE.test(name) && name !== that.output;
  176. };
  177. var watch = function (parent) {
  178. var target = path.basename(parent);
  179. if (!filter(target)){
  180. return;
  181. }
  182. if (watchList[parent]) {
  183. watchList[parent].close();
  184. }
  185. watchList[parent] = fs.watch(parent, function (event, filename) {
  186. var fullname = parent + '/' + filename;
  187. var type;
  188. var fstype;
  189. if (!filter(filename)) {
  190. return;
  191. }
  192. // 检查文件、目录是否存在
  193. if (!fs.existsSync(fullname)) {
  194. // 如果目录被删除则关闭监视器
  195. if (watchList[fullname]) {
  196. fstype = 'directory';
  197. watchList[fullname].close();
  198. delete watchList[fullname];
  199. } else {
  200. fstype = 'file';
  201. }
  202. type = 'delete';
  203. } else {
  204. // 文件
  205. if (fs.statSync(fullname).isFile()) {
  206. fstype = 'file';
  207. type = event == 'rename' ? 'create' : 'updated'
  208. // 文件夹
  209. } else if (event === 'rename') {
  210. fstype = 'directory';
  211. type = 'create'
  212. watch(fullname);
  213. walk(fullname);
  214. }
  215. }
  216. var eventData = {
  217. type: type,
  218. target: filename,
  219. parent: parent,
  220. fstype: fstype
  221. };
  222. if (/windows/i.test(os.type())) {
  223. // window 下 nodejs fs.watch 方法尚未稳定
  224. clearTimeout(timer[fullname]);
  225. timer[fullname] = setTimeout(function() {
  226. callback.call(that, eventData);
  227. }, 16);
  228. } else {
  229. callback.call(that, eventData);
  230. }
  231. });
  232. };
  233. watch(dir);
  234. walk(dir);
  235. },
  236. // 筛选模板文件
  237. _filter: function (name) {
  238. return !FILTER_RE.test(name) && EXTNAME_RE.test(name);
  239. },
  240. // 模板文件写入
  241. _fsWrite: function (file, data) {
  242. this._fsMkdir(path.dirname(file));
  243. fs.writeFileSync(file, data, this.options['charset']);
  244. },
  245. // 模板文件读取
  246. _fsRead: function (file) {
  247. return fs.readFileSync(file, this.options['charset']);
  248. },
  249. // 创建目录,包括子文件夹
  250. _fsMkdir: function (dir) {
  251. var currPath = dir;
  252. var toMakeUpPath = [];
  253. while (!fs.existsSync(currPath)) {
  254. toMakeUpPath.unshift(currPath);
  255. currPath = path.dirname(currPath);
  256. }
  257. toMakeUpPath.forEach(function (pathItem) {
  258. fs.mkdirSync(pathItem);
  259. });
  260. },
  261. // 删除文件夹,包括子文件夹
  262. _rmdir: function (dir) {
  263. var walk = function (dir) {
  264. if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) {
  265. return;
  266. }
  267. var files = fs.readdirSync(dir);
  268. if (!files.length) {
  269. fs.rmdirSync(dir);
  270. return;
  271. } else {
  272. files.forEach(function (file) {
  273. var fullName = path.join(dir, file);
  274. if (fs.statSync(fullName).isDirectory()) {
  275. walk(fullName);
  276. } else {
  277. fs.unlinkSync(fullName);
  278. }
  279. });
  280. }
  281. fs.rmdirSync(dir);
  282. };
  283. walk(dir);
  284. },
  285. // 删除模板文件
  286. _fsUnlink: function (file) {
  287. return fs.existsSync(file) && fs.unlinkSync(file);
  288. },
  289. // 获取字符串 md5 值
  290. _md5: function (text) {
  291. return crypto.createHash('md5').update(text).digest('hex');
  292. },
  293. // 检查模板是否更改
  294. _isChange: function (html, js) {
  295. var newMd5 = this._md5(html + JSON.stringify(this['package.json']));
  296. var oldMd5 = js.match(/<MD5:(\w*)>/)[1];
  297. return newMd5 !== oldMd5;
  298. },
  299. // 调试语法错误
  300. _debug: function (error, callback) {
  301. var debugFile = error.debugFile;
  302. var code = error.temp;
  303. code = "/*! <DEBUG:" + error.id + '> */\n' + code;
  304. this._fsWrite(debugFile, code);
  305. // 启动子进程进行调试,从根本上避免影响当前进程
  306. exec('node ' + debugFile, {timeout: 0}, function (error, stdout, stderr) {
  307. var message = error ? error.message : '';
  308. message = message
  309. .replace(/^Command\sfailed\:|\s*SyntaxError[\w\W]*$/g, '')
  310. .trim();
  311. callback(message);
  312. });
  313. //this._fsUnlink(debugFile);
  314. },
  315. // 在控制台显示日志(支持UBB)
  316. _log: function (message) {
  317. var styles = {
  318. // styles
  319. 'bold' : ['\x1B[1m', '\x1B[22m'],
  320. 'italic' : ['\x1B[3m', '\x1B[23m'],
  321. 'underline' : ['\x1B[4m', '\x1B[24m'],
  322. 'inverse' : ['\x1B[7m', '\x1B[27m'],
  323. // colors
  324. 'white' : ['\x1B[37m', '\x1B[39m'],
  325. 'grey' : ['\x1B[90m', '\x1B[39m'],
  326. 'black' : ['\x1B[30m', '\x1B[39m'],
  327. 'blue' : ['\x1B[34m', '\x1B[39m'],
  328. 'cyan' : ['\x1B[36m', '\x1B[39m'],
  329. 'green' : ['\x1B[32m', '\x1B[39m'],
  330. 'magenta' : ['\x1B[35m', '\x1B[39m'],
  331. 'red' : ['\x1B[31m', '\x1B[39m'],
  332. 'yellow' : ['\x1B[33m', '\x1B[39m']
  333. };
  334. styles['b'] = styles['bold'];
  335. styles['i'] = styles['italic'];
  336. styles['u'] = styles['underline'];
  337. message = message.replace(/\[([^\]]*?)\]/igm, function ($1, $2) {
  338. return $2.indexOf('/') === 0
  339. ? styles[$2.slice(1)][1]
  340. : styles[$2][0];
  341. });
  342. this.log(message);
  343. },
  344. log: function (message) {
  345. process.stdout.write(message);
  346. },
  347. // 打包模板
  348. _combo: function () {
  349. var that = this;
  350. var templates = [];
  351. var options = this.options;
  352. var isDebug = options.debug;
  353. var isWrappings = options.type !== 'templatejs';
  354. var runtime = options.engine ? '/lib/runtime/full.js' : '/lib/runtime/basic.js';
  355. var template = fs.readFileSync(__dirname + runtime, 'utf-8');
  356. var combo = '';
  357. var walk = function (dir) {
  358. var dirList = fs.readdirSync(dir);
  359. dirList.forEach(function (item) {
  360. if (fs.statSync(dir + '/' + item).isDirectory()) {
  361. walk(dir + '/' + item);
  362. } else if (that._filter(item)) {
  363. var id = (dir + '/' + item)
  364. .replace(EXTNAME_RE, '')
  365. .replace(that.path + '/', '');
  366. templates.push(id);
  367. var target = that.output + '/' + id + '.js';
  368. if (fs.existsSync(target)) {
  369. var code = that._fsRead(target);
  370. // 一个猥琐的实现:
  371. // 文件末尾设置一个空注释,然后让 UglifyJS 不压缩它,避免很多文件挤成一行
  372. code = code.replace(/^\/\*[\w\W]*?\*\//, '/**/');
  373. combo += code;
  374. }
  375. }
  376. });
  377. };
  378. if (!isWrappings && options.combo) {
  379. walk(this.path);
  380. }
  381. var build = Date.now();
  382. var debug = isDebug ? '<DEBUG>' : '';
  383. var data = {
  384. version: this.version,
  385. build: build,
  386. templates: combo,
  387. debug: debug,
  388. syntax: '',
  389. engine: '',
  390. helpers: this.helpers
  391. };
  392. // 嵌入引擎
  393. if (this.options.engine) {
  394. data.engine = fs.readFileSync(engineDirname + '/template.js', 'utf-8');
  395. data.syntax = this.syntax;
  396. }
  397. template = template.replace(/['"]<\:(.*?)\:>['"]/g, function ($1, $2) {
  398. return data[$2] || '';
  399. });
  400. var target = path.join(this.output, RUNTIME + '.js');
  401. this._fsWrite(target, template);
  402. isDebug || !this.options.minify
  403. ? uglifyjs.beautify(target)
  404. : uglifyjs.minify(target);
  405. this.emit('combo', {
  406. output: target,
  407. name: RUNTIME,
  408. fullname: RUNTIME + '.js',
  409. extname: '.js',
  410. isDebug: isDebug,
  411. templates: templates,
  412. build: build
  413. });
  414. },
  415. /**
  416. * 监听模板的修改进行即时编译
  417. */
  418. watch: function () {
  419. // 监控模板目录
  420. this.on('watch', function (data) {
  421. var type = data.type;
  422. var fstype = data.fstype;
  423. var target = data.target;
  424. var parent = data.parent;
  425. var fullname = parent + '/' + target;
  426. if (target && fstype === 'file' && this._filter(target)) {
  427. if (type === 'delete') {
  428. this.emit('delete', {
  429. source: data.target
  430. });
  431. fullname = fullname.replace(EXTNAME_RE, '');
  432. this._fsUnlink(fullname.replace(this.path, this.output) + '.js');
  433. this._combo();
  434. } else if (/updated|create/.test(type)) {
  435. this.emit('change', {
  436. source: data.target
  437. });
  438. if (this._compile(fullname)) {
  439. this._combo();
  440. };
  441. }
  442. }
  443. });
  444. },
  445. // 编译单个模板
  446. _compile: function (file) {
  447. var that = this;
  448. // 模板字符串
  449. var source = this._fsRead(file);
  450. // 目标路径
  451. var target = file
  452. .replace(EXTNAME_RE, '.js')
  453. .replace(this.path, this.output);
  454. var mod = '';
  455. var modObject = {};
  456. var error = true;
  457. var errorInfo = null;
  458. var isDebug = this.options.debug;
  459. var isWrappings = this.options.wrappings;
  460. var isEngine = this.options.engine;
  461. // 读取上一次编译的结果
  462. if (fs.existsSync(target)) {
  463. mod = this._fsRead(target);
  464. }
  465. // 检查模板是否有改动
  466. var isChange = !mod
  467. || /<DEBUG>/.test(mod)
  468. || isDebug
  469. || this._isChange(source, mod);
  470. var id = file
  471. .replace(this.path + '/', './');
  472. var extname = id.match(EXTNAME_RE)[1];
  473. id = id.replace(EXTNAME_RE, '');
  474. // 模板加载事件
  475. this.emit('load', {
  476. id: id,
  477. file: file,
  478. extname: extname,
  479. isChange: isChange,
  480. source: source,
  481. target: target
  482. });
  483. try {
  484. if (isChange) {
  485. modObject = template.AOTcompile(id, source, {
  486. alias: this.options.alias,
  487. engine: this.options.engine,
  488. type: this.options.type,
  489. debug: isDebug
  490. });
  491. mod = modObject.code;
  492. }
  493. error = false;
  494. } catch (e) {
  495. errorInfo = e;
  496. }
  497. if (!error && isChange) {
  498. var md5 = this._md5(source + JSON.stringify(this['package.json']));
  499. mod = '/*<TMODJS> <MD5:' + md5 + '>' + (isDebug ? ' <DEBUG>' : '') + '*/\n' + mod;
  500. this._fsWrite(target, mod);
  501. uglifyjs[isDebug || !this.options.minify ? 'beautify' : 'minify'](target);
  502. }
  503. var compileInfo = {
  504. id: id,
  505. file: file,
  506. extname: extname,
  507. isChange: isChange,
  508. error: error,
  509. source: source,
  510. target: target,
  511. code: mod,
  512. requires: modObject.requires || []
  513. };
  514. if (error) {
  515. errorInfo.debugFile = this.path + '/.debug.js';
  516. this._debug(errorInfo, function (message) {
  517. var e = {
  518. name: errorInfo.name,
  519. type: 'compileError',
  520. message: message,
  521. debugFile: errorInfo.debugFile,
  522. temp: errorInfo.temp
  523. };
  524. for (var name in compileInfo) {
  525. e[name] = compileInfo[name];
  526. }
  527. // 模板编译错误事件
  528. this.emit('compileError', e);
  529. this.emit('error', e);
  530. }.bind(this));
  531. } else {
  532. // 模板编译成功事件
  533. this.emit('compile', compileInfo);
  534. }
  535. if (error) {
  536. return false;
  537. } else {
  538. return compileInfo;
  539. }
  540. },
  541. /**
  542. * 编译模板
  543. * @param {String} 模板文件路径,无此参数则编译目录所有模板
  544. * @param {Boolean} 是否递归编译 include 依赖
  545. */
  546. compile: function (file, recursion) {
  547. var that = this;
  548. var error = false;
  549. if (file) {
  550. var extname = path.extname(file);
  551. var walk = function (list) {
  552. list.forEach(function (file) {
  553. if (error) {
  554. return;
  555. }
  556. var info = that._compile(file);
  557. error = !info;
  558. if (!error && recursion !== false && info.requires.length) {
  559. list = info.requires.map(function (id) {
  560. var target = path.resolve(that.path, id + extname);
  561. return target;
  562. });
  563. walk(list);
  564. };
  565. });
  566. };
  567. walk(typeof file === 'string' ? [file] : file);
  568. !error && this._combo();
  569. } else {
  570. var walk = function (dir) {
  571. if (dir === that.output) {
  572. return;
  573. }
  574. var dirList = fs.readdirSync(dir);
  575. dirList.forEach(function (item) {
  576. if (error) {
  577. return;
  578. }
  579. if (fs.statSync(dir + '/' + item).isDirectory()) {
  580. walk(dir + '/' + item);
  581. } else if (that._filter(item)) {
  582. error = !that._compile(dir + '/' + item);
  583. }
  584. });
  585. };
  586. walk(this.path);
  587. !error && this._combo();
  588. }
  589. },
  590. init: function (input, options) {
  591. events.EventEmitter.call(this);
  592. options = this.options = this.getUserConfig(options, input);
  593. // 模板目录
  594. this.path = path.resolve(input);
  595. // 输出目录
  596. this.output = path.resolve(path.join(this.path, options.output));
  597. // 辅助方法
  598. this.helpers = '';
  599. // 加载辅助方法
  600. if (options['helpers']) {
  601. var helpersFile = path.join(this.path, options['helpers']);
  602. if (fs.existsSync(helpersFile)) {
  603. this.helpers = fs.readFileSync(helpersFile, 'utf-8');
  604. vm.runInNewContext(this.helpers, {
  605. template: template
  606. });
  607. }
  608. }
  609. // 加载模板语法设置
  610. if (options['syntax'] && options['syntax'] !== 'native') {
  611. var syntaxFile = options['syntax'] === 'simple'
  612. ? engineDirname + '/template-syntax.js'
  613. : path.join(this.path, options['syntax']);
  614. if (fs.existsSync(syntaxFile)) {
  615. this.syntax = fs.readFileSync(syntaxFile, 'utf-8');
  616. vm.runInNewContext(this.syntax, {
  617. template: template
  618. });
  619. }
  620. }
  621. // 是否过滤 XSS 的开关
  622. template.isEscape = options.escape;
  623. // 初始化 watch 事件
  624. this.on('newListener', function (event, listener) {
  625. if (event === 'watch') {
  626. this._log('\n[inverse]Waiting..[/inverse]\n\n');
  627. this._onwatch(this.path, function (data) {
  628. this.emit('watch', data);
  629. });
  630. this._onwatch = function () {};
  631. }
  632. });
  633. // 监听模板修改事件
  634. this.on('change', function (data) {
  635. var time = (new Date).toLocaleTimeString();
  636. this._log('[grey]' + time + '[/grey] ');
  637. this._log('[grey]Template has been updated[/grey]\n');
  638. });
  639. // 监听模板加载事件
  640. this.on('load', function (data) {
  641. if (data.isChange) {
  642. this._log('[green]●[/green] ');
  643. } else {
  644. this._log('[grey]○[/grey] ');
  645. }
  646. this._log(data.id.replace(/^\.\//, '') + '[grey].' + data.extname + '[/grey]');
  647. });
  648. // 监听模板编译事件
  649. this.on('compile', function (data) {
  650. this._log('\n');
  651. });
  652. // 监听编译错误事件
  653. this.on('compileError', function (data) {
  654. this._log(' [inverse][red]Syntax Error[/red][/inverse]\n');
  655. this._log('\n[red]Template ID: ' + data.id + '[/red]\n');
  656. this._log('[red]' + data.message + '[/red]\n');
  657. });
  658. // 监听模板合并事件
  659. this.on('combo', function (data) {
  660. var output = options.type === 'templatejs'
  661. ? options.output + '/' + data.fullname
  662. : options.output + '/';
  663. output = output.replace(/^\.\//, '');
  664. this._log('[grey]> [/grey]');
  665. this._log(this.options.debug ? '[inverse]<DEBUG>[/inverse] ' : '');
  666. this._log(output);
  667. this._log('\n');
  668. });
  669. }
  670. };