runtime.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. import * as Utils from './utils';
  2. import Exception from './exception';
  3. import { COMPILER_REVISION, REVISION_CHANGES, createFrame } from './base';
  4. export function checkRevision(compilerInfo) {
  5. const compilerRevision = compilerInfo && compilerInfo[0] || 1,
  6. currentRevision = COMPILER_REVISION;
  7. if (compilerRevision !== currentRevision) {
  8. if (compilerRevision < currentRevision) {
  9. const runtimeVersions = REVISION_CHANGES[currentRevision],
  10. compilerVersions = REVISION_CHANGES[compilerRevision];
  11. throw new Exception('Template was precompiled with an older version of Handlebars than the current runtime. ' +
  12. 'Please update your precompiler to a newer version (' + runtimeVersions + ') or downgrade your runtime to an older version (' + compilerVersions + ').');
  13. } else {
  14. // Use the embedded version info since the runtime doesn't know about this revision yet
  15. throw new Exception('Template was precompiled with a newer version of Handlebars than the current runtime. ' +
  16. 'Please update your runtime to a newer version (' + compilerInfo[1] + ').');
  17. }
  18. }
  19. }
  20. export function template(templateSpec, env) {
  21. /* istanbul ignore next */
  22. if (!env) {
  23. throw new Exception('No environment passed to template');
  24. }
  25. if (!templateSpec || !templateSpec.main) {
  26. throw new Exception('Unknown template object: ' + typeof templateSpec);
  27. }
  28. templateSpec.main.decorator = templateSpec.main_d;
  29. // Note: Using env.VM references rather than local var references throughout this section to allow
  30. // for external users to override these as psuedo-supported APIs.
  31. env.VM.checkRevision(templateSpec.compiler);
  32. function invokePartialWrapper(partial, context, options) {
  33. if (options.hash) {
  34. context = Utils.extend({}, context, options.hash);
  35. if (options.ids) {
  36. options.ids[0] = true;
  37. }
  38. }
  39. partial = env.VM.resolvePartial.call(this, partial, context, options);
  40. let result = env.VM.invokePartial.call(this, partial, context, options);
  41. if (result == null && env.compile) {
  42. options.partials[options.name] = env.compile(partial, templateSpec.compilerOptions, env);
  43. result = options.partials[options.name](context, options);
  44. }
  45. if (result != null) {
  46. if (options.indent) {
  47. let lines = result.split('\n');
  48. for (let i = 0, l = lines.length; i < l; i++) {
  49. if (!lines[i] && i + 1 === l) {
  50. break;
  51. }
  52. lines[i] = options.indent + lines[i];
  53. }
  54. result = lines.join('\n');
  55. }
  56. return result;
  57. } else {
  58. throw new Exception('The partial ' + options.name + ' could not be compiled when running in runtime-only mode');
  59. }
  60. }
  61. // Just add water
  62. let container = {
  63. strict: function(obj, name) {
  64. if (!(name in obj)) {
  65. throw new Exception('"' + name + '" not defined in ' + obj);
  66. }
  67. return obj[name];
  68. },
  69. lookup: function(depths, name) {
  70. const len = depths.length;
  71. for (let i = 0; i < len; i++) {
  72. if (depths[i] && depths[i][name] != null) {
  73. return depths[i][name];
  74. }
  75. }
  76. },
  77. lambda: function(current, context) {
  78. return typeof current === 'function' ? current.call(context) : current;
  79. },
  80. escapeExpression: Utils.escapeExpression,
  81. invokePartial: invokePartialWrapper,
  82. fn: function(i) {
  83. let ret = templateSpec[i];
  84. ret.decorator = templateSpec[i + '_d'];
  85. return ret;
  86. },
  87. programs: [],
  88. program: function(i, data, declaredBlockParams, blockParams, depths) {
  89. let programWrapper = this.programs[i],
  90. fn = this.fn(i);
  91. if (data || depths || blockParams || declaredBlockParams) {
  92. programWrapper = wrapProgram(this, i, fn, data, declaredBlockParams, blockParams, depths);
  93. } else if (!programWrapper) {
  94. programWrapper = this.programs[i] = wrapProgram(this, i, fn);
  95. }
  96. return programWrapper;
  97. },
  98. data: function(value, depth) {
  99. while (value && depth--) {
  100. value = value._parent;
  101. }
  102. return value;
  103. },
  104. merge: function(param, common) {
  105. let obj = param || common;
  106. if (param && common && (param !== common)) {
  107. obj = Utils.extend({}, common, param);
  108. }
  109. return obj;
  110. },
  111. // An empty object to use as replacement for null-contexts
  112. nullContext: Object.seal({}),
  113. noop: env.VM.noop,
  114. compilerInfo: templateSpec.compiler
  115. };
  116. function ret(context, options = {}) {
  117. let data = options.data;
  118. ret._setup(options);
  119. if (!options.partial && templateSpec.useData) {
  120. data = initData(context, data);
  121. }
  122. let depths,
  123. blockParams = templateSpec.useBlockParams ? [] : undefined;
  124. if (templateSpec.useDepths) {
  125. if (options.depths) {
  126. depths = context != options.depths[0] ? [context].concat(options.depths) : options.depths;
  127. } else {
  128. depths = [context];
  129. }
  130. }
  131. function main(context/*, options*/) {
  132. return '' + templateSpec.main(container, context, container.helpers, container.partials, data, blockParams, depths);
  133. }
  134. main = executeDecorators(templateSpec.main, main, container, options.depths || [], data, blockParams);
  135. return main(context, options);
  136. }
  137. ret.isTop = true;
  138. ret._setup = function(options) {
  139. if (!options.partial) {
  140. container.helpers = container.merge(options.helpers, env.helpers);
  141. if (templateSpec.usePartial) {
  142. container.partials = container.merge(options.partials, env.partials);
  143. }
  144. if (templateSpec.usePartial || templateSpec.useDecorators) {
  145. container.decorators = container.merge(options.decorators, env.decorators);
  146. }
  147. } else {
  148. container.helpers = options.helpers;
  149. container.partials = options.partials;
  150. container.decorators = options.decorators;
  151. }
  152. };
  153. ret._child = function(i, data, blockParams, depths) {
  154. if (templateSpec.useBlockParams && !blockParams) {
  155. throw new Exception('must pass block params');
  156. }
  157. if (templateSpec.useDepths && !depths) {
  158. throw new Exception('must pass parent depths');
  159. }
  160. return wrapProgram(container, i, templateSpec[i], data, 0, blockParams, depths);
  161. };
  162. return ret;
  163. }
  164. export function wrapProgram(container, i, fn, data, declaredBlockParams, blockParams, depths) {
  165. function prog(context, options = {}) {
  166. let currentDepths = depths;
  167. if (depths && context != depths[0] && !(context === container.nullContext && depths[0] === null)) {
  168. currentDepths = [context].concat(depths);
  169. }
  170. return fn(container,
  171. context,
  172. container.helpers, container.partials,
  173. options.data || data,
  174. blockParams && [options.blockParams].concat(blockParams),
  175. currentDepths);
  176. }
  177. prog = executeDecorators(fn, prog, container, depths, data, blockParams);
  178. prog.program = i;
  179. prog.depth = depths ? depths.length : 0;
  180. prog.blockParams = declaredBlockParams || 0;
  181. return prog;
  182. }
  183. export function resolvePartial(partial, context, options) {
  184. if (!partial) {
  185. if (options.name === '@partial-block') {
  186. partial = options.data['partial-block'];
  187. } else {
  188. partial = options.partials[options.name];
  189. }
  190. } else if (!partial.call && !options.name) {
  191. // This is a dynamic partial that returned a string
  192. options.name = partial;
  193. partial = options.partials[partial];
  194. }
  195. return partial;
  196. }
  197. export function invokePartial(partial, context, options) {
  198. // Use the current closure context to save the partial-block if this partial
  199. const currentPartialBlock = options.data && options.data['partial-block'];
  200. options.partial = true;
  201. if (options.ids) {
  202. options.data.contextPath = options.ids[0] || options.data.contextPath;
  203. }
  204. let partialBlock;
  205. if (options.fn && options.fn !== noop) {
  206. options.data = createFrame(options.data);
  207. // Wrapper function to get access to currentPartialBlock from the closure
  208. let fn = options.fn;
  209. partialBlock = options.data['partial-block'] = function partialBlockWrapper(context, options = {}) {
  210. // Restore the partial-block from the closure for the execution of the block
  211. // i.e. the part inside the block of the partial call.
  212. options.data = createFrame(options.data);
  213. options.data['partial-block'] = currentPartialBlock;
  214. return fn(context, options);
  215. };
  216. if (fn.partials) {
  217. options.partials = Utils.extend({}, options.partials, fn.partials);
  218. }
  219. }
  220. if (partial === undefined && partialBlock) {
  221. partial = partialBlock;
  222. }
  223. if (partial === undefined) {
  224. throw new Exception('The partial ' + options.name + ' could not be found');
  225. } else if (partial instanceof Function) {
  226. return partial(context, options);
  227. }
  228. }
  229. export function noop() { return ''; }
  230. function initData(context, data) {
  231. if (!data || !('root' in data)) {
  232. data = data ? createFrame(data) : {};
  233. data.root = context;
  234. }
  235. return data;
  236. }
  237. function executeDecorators(fn, prog, container, depths, data, blockParams) {
  238. if (fn.decorator) {
  239. let props = {};
  240. prog = fn.decorator(prog, props, container, depths && depths[0], data, blockParams, depths);
  241. Utils.extend(prog, props);
  242. }
  243. return prog;
  244. }