AOTcompile.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /*!
  2. * TmodJS - AOT Template Compiler
  3. * https://github.com/aui/tmodjs
  4. * Released under the MIT, BSD, and GPL Licenses
  5. */
  6. var template = require('art-template');
  7. var SLASH_RE = /\\\\/g;
  8. var DOT_RE = /\/\.\//g;
  9. var DOUBLE_DOT_RE = /(\/)[^/]+\1\.\.\1/;
  10. var DIRNAME_RE = /[^/]+$/;
  11. var ANONYMOUS_RE = /^function\s+anonymous/;
  12. var EXTNAME_RE = /\.(html|htm|tpl)$/i;
  13. var INCLUDE_RE = /"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*include|(?:^|[^$])\binclude\s*\(\s*(["'])(.+?)\1/g; //"
  14. // 编译模板
  15. var compile = function (id, source, debug) {
  16. template.isCompress = true;
  17. template.onerror = function (e) {
  18. throw e;
  19. };
  20. var render = template.compile(id, source, debug);
  21. delete template.cache[id];
  22. return render
  23. .toString()
  24. .replace(ANONYMOUS_RE, 'function');
  25. };
  26. // 检查模板是否有逻辑语法
  27. var testTemplateSyntax = function (source) {
  28. return source.indexOf(template.openTag) !== -1;
  29. };
  30. // 模板 ID 规范检查
  31. var testId = function (id, fromID) {
  32. if (!/^\./.test(id) || EXTNAME_RE.test(id)) {
  33. var error = {
  34. name: 'Syntax Error',
  35. message: id + '\n'
  36. + 'Template must be a relative path, and can not have a suffix.'
  37. };
  38. if (fromID) {
  39. error.id = fromID;
  40. }
  41. throw error;
  42. }
  43. };
  44. // 依赖分析
  45. var parseDependencies = function (code) {
  46. var list = [];
  47. var uniq = {};
  48. code
  49. .replace(SLASH_RE, '')
  50. .replace(INCLUDE_RE, function(m, m1, m2) {
  51. if (m2 && !uniq.hasOwnProperty(m2)) {
  52. list.push(m2);
  53. uniq[m2] = true;
  54. }
  55. });
  56. return list;
  57. };
  58. // 获取上一层 ID
  59. var dirname = function (id) {
  60. return id.replace(DIRNAME_RE, '');
  61. };
  62. // 分解为标准化 ID
  63. var resolve = function (id) {
  64. id = id.replace(DOT_RE, '/');
  65. while (id.match(DOUBLE_DOT_RE)) {
  66. id = id.replace(DOUBLE_DOT_RE, '/');
  67. }
  68. return id;
  69. };
  70. // 构造字符串表达式
  71. var stringify = function (code) {
  72. return "'" + code
  73. .replace(/('|\\)/g, '\\$1')
  74. .replace(/\r/g, '\\r')
  75. .replace(/\n/g, '\\n')
  76. + "'";
  77. };
  78. // 压缩 HTML 字符串
  79. var compressHTML = function (code) {
  80. code = code
  81. .replace(/[\n\r\t\s]+/g, ' ')
  82. .replace(/<!--.*?-->/g, '');
  83. return code;
  84. };
  85. /**
  86. * 模板预编译器,根据设置生成不同格式的 javascript 模块
  87. * @param {String} 模板ID,例如 ./home/list
  88. * 要求:必须是 . 的相对路径开头,且末尾不能有后缀名
  89. * @param {String} 模板源代码
  90. * @param {Object} 可选项
  91. */
  92. template.AOTcompile = function (id, source, options) {
  93. testId(id);
  94. // 是否嵌入完整模板引擎,嵌入后将把模板保存为字符串
  95. var isEngine = options.engine;
  96. // 是否为编译为调试版本
  97. var isDebug = options.debug;
  98. // 编译的模块类型
  99. var type = options.type;
  100. // 运行时模块别名
  101. var alias = options.alias;
  102. var RUNTIME = 'template';
  103. var code = '';
  104. var render = compile(id, source, isDebug);
  105. var requires = parseDependencies(render);
  106. var isLogic = testTemplateSyntax(source);
  107. var dir = dirname(id);
  108. var tid = id.replace(/^\.\//, '');
  109. var isHTML = isEngine || !isLogic;
  110. if (isHTML) {
  111. code = isDebug ? source : compressHTML(source);
  112. code = stringify(code);
  113. } else {
  114. code = render.replace(ANONYMOUS_RE, 'function');
  115. }
  116. // 计算主入口相对于当前模板路径
  117. var getRuntime = function () {
  118. if (alias) {
  119. return alias;
  120. }
  121. var prefix = './';
  122. var length = dir.split('/').length - 2;
  123. if (length) {
  124. prefix = (new Array(length + 1)).join('../');
  125. }
  126. return prefix + RUNTIME;
  127. };
  128. // 生成 require 函数依赖声明代码
  129. var getRequireCode = function () {
  130. var requiresCode = [];
  131. requires.forEach(function (id) {
  132. requiresCode.push("require('" + id + "');");
  133. });
  134. return requiresCode.join('\n');
  135. };
  136. switch (type || '') {
  137. // TemplateJS 模块格式
  138. case '':
  139. case 'templatejs':
  140. code = "template('" + tid + "'," + code + ");";
  141. break;
  142. // RequireJS / SeaJS 兼容模块格式
  143. case 'cmd':
  144. code
  145. = "define(function(require){"
  146. + getRequireCode()
  147. + "return require('" + getRuntime() + "')"
  148. + "('" + tid + "', " + code + ");"
  149. + "});";
  150. break;
  151. // RequireJS 模块格式
  152. case 'amd':
  153. code
  154. = "define("
  155. + "['" + getRuntime() + "','" + requires.join("','") + "'],"
  156. + "function(template){"
  157. + "return template('" + tid + "', " + code + ");"
  158. + "});";
  159. break;
  160. // NodeJS 模块格式
  161. case 'commonjs':
  162. code
  163. = "var template=require('" + getRuntime() + "');"
  164. + getRequireCode()
  165. + "module.exports=template('" + tid + "'," + code + ");";
  166. break;
  167. default:
  168. throw {
  169. id: id,
  170. name: 'Type Error',
  171. message: 'Unsupported type: ' + type
  172. };
  173. }
  174. return {
  175. // 编译结果
  176. code: code,
  177. // 依赖的子模板
  178. requires: requires.map(function (subId) {
  179. testId(subId, id);
  180. return resolve(dir + subId);
  181. })
  182. };
  183. };
  184. module.exports = template;