swig.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. var utils = require('./utils')
  2. var _tags = require('./tags')
  3. var _filters = require('./filters')
  4. var parser = require('./parser')
  5. var dateformatter = require('./dateformatter')
  6. var loaders = require('./loaders')
  7. /**
  8. * Swig version number as a string.
  9. * @example
  10. * if (swig.version === "1.4.2") { ... }
  11. *
  12. * @type {String}
  13. */
  14. exports.version = '1.4.2'
  15. /**
  16. * Swig Options Object. This object can be passed to many of the API-level Swig methods to control various aspects of the engine. All keys are optional.
  17. * @typedef {Object} SwigOpts
  18. * @property {boolean} autoescape Controls whether or not variable output will automatically be escaped for safe HTML output. Defaults to <code data-language="js">true</code>. Functions executed in variable statements will not be auto-escaped. Your application/functions should take care of their own auto-escaping.
  19. * @property {array} varControls Open and close controls for variables. Defaults to <code data-language="js">['{{', '}}']</code>.
  20. * @property {array} tagControls Open and close controls for tags. Defaults to <code data-language="js">['{%', '%}']</code>.
  21. * @property {array} cmtControls Open and close controls for comments. Defaults to <code data-language="js">['{#', '#}']</code>.
  22. * @property {object} locals Default variable context to be passed to <strong>all</strong> templates.
  23. * @property {CacheOptions} cache Cache control for templates. Defaults to saving in <code data-language="js">'memory'</code>. Send <code data-language="js">false</code> to disable. Send an object with <code data-language="js">get</code> and <code data-language="js">set</code> functions to customize.
  24. * @property {TemplateLoader} loader The method that Swig will use to load templates. Defaults to <var>swig.loaders.fs</var>.
  25. */
  26. var defaultOptions = {
  27. autoescape: true,
  28. varControls: ['{{', '}}'],
  29. tagControls: ['{%', '%}'],
  30. cmtControls: ['{#', '#}'],
  31. locals: {},
  32. /**
  33. * Cache control for templates. Defaults to saving all templates into memory.
  34. * @typedef {boolean|string|object} CacheOptions
  35. * @example
  36. * // Default
  37. * swig.setDefaults({ cache: 'memory' });
  38. * @example
  39. * // Disables caching in Swig.
  40. * swig.setDefaults({ cache: false });
  41. * @example
  42. * // Custom cache storage and retrieval
  43. * swig.setDefaults({
  44. * cache: {
  45. * get: function (key) { ... },
  46. * set: function (key, val) { ... }
  47. * }
  48. * });
  49. */
  50. cache: 'memory',
  51. /**
  52. * Configure Swig to use either the <var>swig.loaders.fs</var> or <var>swig.loaders.memory</var> template loader. Or, you can write your own!
  53. * For more information, please see the <a href="../loaders/">Template Loaders documentation</a>.
  54. * @typedef {class} TemplateLoader
  55. * @example
  56. * // Default, FileSystem loader
  57. * swig.setDefaults({ loader: swig.loaders.fs() });
  58. * @example
  59. * // FileSystem loader allowing a base path
  60. * // With this, you don't use relative URLs in your template references
  61. * swig.setDefaults({ loader: swig.loaders.fs(__dirname + '/templates') });
  62. * @example
  63. * // Memory Loader
  64. * swig.setDefaults({ loader: swig.loaders.memory({
  65. * layout: '{% block foo %}{% endblock %}',
  66. * page1: '{% extends "layout" %}{% block foo %}Tacos!{% endblock %}'
  67. * })});
  68. */
  69. loader: loaders.fs()
  70. }
  71. var defaultInstance
  72. /**
  73. * Empty function, used in templates.
  74. * @return {string} Empty string
  75. * @private
  76. */
  77. function efn () {
  78. return ''
  79. }
  80. /**
  81. * Validate the Swig options object.
  82. * @param {?SwigOpts} options Swig options object.
  83. * @return {undefined} This method will throw errors if anything is wrong.
  84. * @private
  85. */
  86. function validateOptions (options) {
  87. if (!options) {
  88. return
  89. }
  90. utils.each(['varControls', 'tagControls', 'cmtControls'], function (key) {
  91. if (!options.hasOwnProperty(key)) {
  92. return
  93. }
  94. if (!utils.isArray(options[key]) || options[key].length !== 2) {
  95. throw new Error(
  96. 'Option "' +
  97. key +
  98. '" must be an array containing 2 different control strings.'
  99. )
  100. }
  101. if (options[key][0] === options[key][1]) {
  102. throw new Error(
  103. 'Option "' + key + '" open and close controls must not be the same.'
  104. )
  105. }
  106. utils.each(options[key], function (a, i) {
  107. if (a.length < 2) {
  108. throw new Error(
  109. 'Option "' +
  110. key +
  111. '" ' +
  112. (i ? 'open ' : 'close ') +
  113. 'control must be at least 2 characters. Saw "' +
  114. a +
  115. '" instead.'
  116. )
  117. }
  118. })
  119. })
  120. if (options.hasOwnProperty('cache')) {
  121. if (options.cache && options.cache !== 'memory') {
  122. if (!options.cache.get || !options.cache.set) {
  123. throw new Error(
  124. 'Invalid cache option ' +
  125. JSON.stringify(options.cache) +
  126. ' found. Expected "memory" or { get: function (key) { ... }, set: function (key, value) { ... } }.'
  127. )
  128. }
  129. }
  130. }
  131. if (options.hasOwnProperty('loader')) {
  132. if (options.loader) {
  133. if (!options.loader.load || !options.loader.resolve) {
  134. throw new Error(
  135. 'Invalid loader option ' +
  136. JSON.stringify(options.loader) +
  137. ' found. Expected { load: function (pathname, cb) { ... }, resolve: function (to, from) { ... } }.'
  138. )
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Set defaults for the base and all new Swig environments.
  145. *
  146. * @example
  147. * swig.setDefaults({ cache: false });
  148. * // => Disables Cache
  149. *
  150. * @example
  151. * swig.setDefaults({ locals: { now: function () { return new Date(); } }});
  152. * // => sets a globally accessible method for all template
  153. * // contexts, allowing you to print the current date
  154. * // => {{ now()|date('F jS, Y') }}
  155. *
  156. * @param {SwigOpts} [options={}] Swig options object.
  157. * @return {undefined}
  158. */
  159. exports.setDefaults = function (options) {
  160. validateOptions(options)
  161. defaultInstance.options = utils.extend(defaultInstance.options, options)
  162. }
  163. /**
  164. * Set the default TimeZone offset for date formatting via the date filter. This is a global setting and will affect all Swig environments, old or new.
  165. * @param {number} offset Offset from GMT, in minutes.
  166. * @return {undefined}
  167. */
  168. exports.setDefaultTZOffset = function (offset) {
  169. dateformatter.tzOffset = offset
  170. }
  171. /**
  172. * Create a new, separate Swig compile/render environment.
  173. *
  174. * @example
  175. * var swig = require('swig');
  176. * var myswig = new swig.Swig({varControls: ['<%=', '%>']});
  177. * myswig.render('Tacos are <%= tacos =>!', { locals: { tacos: 'delicious' }});
  178. * // => Tacos are delicious!
  179. * swig.render('Tacos are <%= tacos =>!', { locals: { tacos: 'delicious' }});
  180. * // => 'Tacos are <%= tacos =>!'
  181. *
  182. * @param {SwigOpts} [opts={}] Swig options object.
  183. * @return {object} New Swig environment.
  184. */
  185. exports.Swig = function (opts) {
  186. validateOptions(opts)
  187. this.options = utils.extend({}, defaultOptions, opts || {})
  188. this.cache = {}
  189. this.extensions = {}
  190. var self = this
  191. var tags = _tags
  192. var filters = _filters
  193. /**
  194. * Get combined locals context.
  195. * @param {?SwigOpts} [options] Swig options object.
  196. * @return {object} Locals context.
  197. * @private
  198. */
  199. function getLocals (options) {
  200. if (!options || !options.locals) {
  201. return self.options.locals
  202. }
  203. return utils.extend({}, self.options.locals, options.locals)
  204. }
  205. /**
  206. * Determine whether caching is enabled via the options provided and/or defaults
  207. * @param {SwigOpts} [options={}] Swig Options Object
  208. * @return {boolean}
  209. * @private
  210. */
  211. function shouldCache (options) {
  212. options = options || {}
  213. return (
  214. (options.hasOwnProperty('cache') && !options.cache) || !self.options.cache
  215. )
  216. }
  217. /**
  218. * Get compiled template from the cache.
  219. * @param {string} key Name of template.
  220. * @return {object|undefined} Template function and tokens.
  221. * @private
  222. */
  223. function cacheGet (key, options) {
  224. if (shouldCache(options)) {
  225. return
  226. }
  227. if (self.options.cache === 'memory') {
  228. return self.cache[key]
  229. }
  230. return self.options.cache.get(key)
  231. }
  232. /**
  233. * Store a template in the cache.
  234. * @param {string} key Name of template.
  235. * @param {object} val Template function and tokens.
  236. * @return {undefined}
  237. * @private
  238. */
  239. function cacheSet (key, options, val) {
  240. if (shouldCache(options)) {
  241. return
  242. }
  243. if (self.options.cache === 'memory') {
  244. self.cache[key] = val
  245. return
  246. }
  247. self.options.cache.set(key, val)
  248. }
  249. /**
  250. * Clears the in-memory template cache.
  251. *
  252. * @example
  253. * swig.invalidateCache();
  254. *
  255. * @return {undefined}
  256. */
  257. this.invalidateCache = function () {
  258. if (self.options.cache === 'memory') {
  259. self.cache = {}
  260. }
  261. }
  262. /**
  263. * Add a custom filter for swig variables.
  264. *
  265. * @example
  266. * function replaceMs(input) { return input.replace(/m/g, 'f'); }
  267. * swig.setFilter('replaceMs', replaceMs);
  268. * // => {{ "onomatopoeia"|replaceMs }}
  269. * // => onofatopeia
  270. *
  271. * @param {string} name Name of filter, used in templates. <strong>Will</strong> overwrite previously defined filters, if using the same name.
  272. * @param {function} method Function that acts against the input. See <a href="/docs/filters/#custom">Custom Filters</a> for more information.
  273. * @return {undefined}
  274. */
  275. this.setFilter = function (name, method) {
  276. if (typeof method !== 'function') {
  277. throw new Error('Filter "' + name + '" is not a valid function.')
  278. }
  279. filters[name] = method
  280. }
  281. /**
  282. * Add a custom tag. To expose your own extensions to compiled template code, see <code data-language="js">swig.setExtension</code>.
  283. *
  284. * For a more in-depth explanation of writing custom tags, see <a href="../extending/#tags">Custom Tags</a>.
  285. *
  286. * @example
  287. * var tacotag = require('./tacotag');
  288. * swig.setTag('tacos', tacotag.parse, tacotag.compile, tacotag.ends, tacotag.blockLevel);
  289. * // => {% tacos %}Make this be tacos.{% endtacos %}
  290. * // => Tacos tacos tacos tacos.
  291. *
  292. * @param {string} name Tag name.
  293. * @param {function} parse Method for parsing tokens.
  294. * @param {function} compile Method for compiling renderable output.
  295. * @param {boolean} [ends=false] Whether or not this tag requires an <i>end</i> tag.
  296. * @param {boolean} [blockLevel=false] If false, this tag will not be compiled outside of <code>block</code> tags when extending a parent template.
  297. * @return {undefined}
  298. */
  299. this.setTag = function (name, parse, compile, ends, blockLevel) {
  300. if (typeof parse !== 'function') {
  301. throw new Error(
  302. 'Tag "' + name + '" parse method is not a valid function.'
  303. )
  304. }
  305. if (typeof compile !== 'function') {
  306. throw new Error(
  307. 'Tag "' + name + '" compile method is not a valid function.'
  308. )
  309. }
  310. tags[name] = {
  311. parse: parse,
  312. compile: compile,
  313. ends: ends || false,
  314. block: !!blockLevel
  315. }
  316. }
  317. /**
  318. * Add extensions for custom tags. This allows any custom tag to access a globally available methods via a special globally available object, <var>_ext</var>, in templates.
  319. *
  320. * @example
  321. * swig.setExtension('trans', function (v) { return translate(v); });
  322. * function compileTrans(compiler, args, content, parent, options) {
  323. * return '_output += _ext.trans(' + args[0] + ');'
  324. * };
  325. * swig.setTag('trans', parseTrans, compileTrans, true);
  326. *
  327. * @param {string} name Key name of the extension. Accessed via <code data-language="js">_ext[name]</code>.
  328. * @param {*} object The method, value, or object that should be available via the given name.
  329. * @return {undefined}
  330. */
  331. this.setExtension = function (name, object) {
  332. self.extensions[name] = object
  333. }
  334. /**
  335. * Parse a given source string into tokens.
  336. *
  337. * @param {string} source Swig template source.
  338. * @param {SwigOpts} [options={}] Swig options object.
  339. * @return {object} parsed Template tokens object.
  340. * @private
  341. */
  342. this.parse = function (source, options) {
  343. validateOptions(options)
  344. var locals = getLocals(options)
  345. var opt = {}
  346. var k
  347. for (k in options) {
  348. if (options.hasOwnProperty(k) && k !== 'locals') {
  349. opt[k] = options[k]
  350. }
  351. }
  352. options = utils.extend({}, self.options, opt)
  353. options.locals = locals
  354. return parser.parse(this, source, options, tags, filters)
  355. }
  356. /**
  357. * Parse a given file into tokens.
  358. *
  359. * @param {string} pathname Full path to file to parse.
  360. * @param {SwigOpts} [options={}] Swig options object.
  361. * @return {object} parsed Template tokens object.
  362. * @private
  363. */
  364. this.parseFile = function (pathname, options) {
  365. var src
  366. if (!options) {
  367. options = {}
  368. }
  369. pathname = self.options.loader.resolve(pathname, options.resolveFrom)
  370. src = self.options.loader.load(pathname)
  371. if (!options.filename) {
  372. options = utils.extend({ filename: pathname }, options)
  373. }
  374. return self.parse(src, options)
  375. }
  376. /**
  377. * Re-Map blocks within a list of tokens to the template's block objects.
  378. * @param {array} tokens List of tokens for the parent object.
  379. * @param {object} template Current template that needs to be mapped to the parent's block and token list.
  380. * @return {array}
  381. * @private
  382. */
  383. function remapBlocks (blocks, tokens) {
  384. return utils.map(tokens, function (token) {
  385. var args = token.args ? token.args.join('') : ''
  386. if (token.name === 'block' && blocks[args]) {
  387. token = blocks[args]
  388. }
  389. if (token.content && token.content.length) {
  390. token.content = remapBlocks(blocks, token.content)
  391. }
  392. return token
  393. })
  394. }
  395. /**
  396. * Import block-level tags to the token list that are not actual block tags.
  397. * @param {array} blocks List of block-level tags.
  398. * @param {array} tokens List of tokens to render.
  399. * @return {undefined}
  400. * @private
  401. */
  402. function importNonBlocks (blocks, tokens) {
  403. var temp = []
  404. utils.each(blocks, function (block) {
  405. temp.push(block)
  406. })
  407. utils.each(temp.reverse(), function (block) {
  408. if (block.name !== 'block') {
  409. tokens.unshift(block)
  410. }
  411. })
  412. }
  413. /**
  414. * Recursively compile and get parents of given parsed token object.
  415. *
  416. * @param {object} tokens Parsed tokens from template.
  417. * @param {SwigOpts} [options={}] Swig options object.
  418. * @return {object} Parsed tokens from parent templates.
  419. * @private
  420. */
  421. function getParents (tokens, options) {
  422. var parentName = tokens.parent
  423. var parentFiles = []
  424. var parents = []
  425. var parentFile
  426. var parent
  427. var l
  428. while (parentName) {
  429. if (!options || !options.filename) {
  430. throw new Error(
  431. 'Cannot extend "' +
  432. parentName +
  433. '" because current template has no filename.'
  434. )
  435. }
  436. parentFile = parentFile || options.filename
  437. parentFile = self.options.loader.resolve(parentName, parentFile)
  438. parent =
  439. cacheGet(parentFile, options) ||
  440. self.parseFile(
  441. parentFile,
  442. utils.extend({}, options, { filename: parentFile })
  443. )
  444. parentName = parent.parent
  445. if (parentFiles.indexOf(parentFile) !== -1) {
  446. throw new Error('Illegal circular extends of "' + parentFile + '".')
  447. }
  448. parentFiles.push(parentFile)
  449. parents.push(parent)
  450. }
  451. // Remap each parents'(1) blocks onto its own parent(2), receiving the full token list for rendering the original parent(1) on its own.
  452. l = parents.length
  453. for (l = parents.length - 2; l >= 0; l -= 1) {
  454. parents[l].tokens = remapBlocks(parents[l].blocks, parents[l + 1].tokens)
  455. importNonBlocks(parents[l].blocks, parents[l].tokens)
  456. }
  457. return parents
  458. }
  459. /**
  460. * Pre-compile a source string into a cache-able template function.
  461. *
  462. * @example
  463. * swig.precompile('{{ tacos }}');
  464. * // => {
  465. * // tpl: function (_swig, _locals, _filters, _utils, _fn) { ... },
  466. * // tokens: {
  467. * // name: undefined,
  468. * // parent: null,
  469. * // tokens: [...],
  470. * // blocks: {}
  471. * // }
  472. * // }
  473. *
  474. * In order to render a pre-compiled template, you must have access to filters and utils from Swig. <var>efn</var> is simply an empty function that does nothing.
  475. *
  476. * @param {string} source Swig template source string.
  477. * @param {SwigOpts} [options={}] Swig options object.
  478. * @return {object} Renderable function and tokens object.
  479. */
  480. this.precompile = function (source, options) {
  481. var tokens = self.parse(source, options)
  482. var parents = getParents(tokens, options)
  483. var tpl
  484. if (parents.length) {
  485. // Remap the templates first-parent's tokens using this template's blocks.
  486. tokens.tokens = remapBlocks(tokens.blocks, parents[0].tokens)
  487. importNonBlocks(tokens.blocks, tokens.tokens)
  488. }
  489. try {
  490. tpl = new Function( // eslint-disable-line
  491. '_swig',
  492. '_ctx',
  493. '_filters',
  494. '_utils',
  495. '_fn',
  496. ' var _ext = _swig.extensions,\n' +
  497. ' _output = "";\n' +
  498. parser.compile(tokens, parents, options) +
  499. '\n' +
  500. ' return _output;\n'
  501. )
  502. } catch (e) {
  503. utils.throwError(e, null, options.filename)
  504. }
  505. return { tpl: tpl, tokens: tokens }
  506. }
  507. /**
  508. * Compile and render a template string for final output.
  509. *
  510. * When rendering a source string, a file path should be specified in the options object in order for <var>extends</var>, <var>include</var>, and <var>import</var> to work properly. Do this by adding <code data-language="js">{ filename: '/absolute/path/to/mytpl.html' }</code> to the options argument.
  511. *
  512. * @example
  513. * swig.render('{{ tacos }}', { locals: { tacos: 'Tacos!!!!' }});
  514. * // => Tacos!!!!
  515. *
  516. * @param {string} source Swig template source string.
  517. * @param {SwigOpts} [options={}] Swig options object.
  518. * @return {string} Rendered output.
  519. */
  520. this.render = function (source, options) {
  521. return self.compile(source, options)()
  522. }
  523. /**
  524. * Compile and render a template file for final output. This is most useful for libraries like Express.js.
  525. *
  526. * @example
  527. * swig.renderFile('./template.html', {}, function (err, output) {
  528. * if (err) {
  529. * throw err;
  530. * }
  531. * console.log(output);
  532. * });
  533. *
  534. * @example
  535. * swig.renderFile('./template.html', {});
  536. * // => output
  537. *
  538. * @param {string} pathName File location.
  539. * @param {object} [locals={}] Template variable context.
  540. * @param {Function} [cb] Asyncronous callback function. If not provided, <var>compileFile</var> will run syncronously.
  541. * @return {string} Rendered output.
  542. */
  543. this.renderFile = function (pathName, locals, cb) {
  544. if (cb) {
  545. self.compileFile(pathName, {}, function (err, fn) {
  546. var result
  547. if (err) {
  548. cb(err)
  549. return
  550. }
  551. try {
  552. result = fn(locals)
  553. } catch (err2) {
  554. cb(err2)
  555. return
  556. }
  557. cb(null, result)
  558. })
  559. return
  560. }
  561. return self.compileFile(pathName)(locals)
  562. }
  563. /**
  564. * Compile string source into a renderable template function.
  565. *
  566. * @example
  567. * var tpl = swig.compile('{{ tacos }}');
  568. * // => {
  569. * // [Function: compiled]
  570. * // parent: null,
  571. * // tokens: [{ compile: [Function] }],
  572. * // blocks: {}
  573. * // }
  574. * tpl({ tacos: 'Tacos!!!!' });
  575. * // => Tacos!!!!
  576. *
  577. * When compiling a source string, a file path should be specified in the options object in order for <var>extends</var>, <var>include</var>, and <var>import</var> to work properly. Do this by adding <code data-language="js">{ filename: '/absolute/path/to/mytpl.html' }</code> to the options argument.
  578. *
  579. * @param {string} source Swig template source string.
  580. * @param {SwigOpts} [options={}] Swig options object.
  581. * @return {function} Renderable function with keys for parent, blocks, and tokens.
  582. */
  583. this.compile = function (source, options) {
  584. var key = options ? options.filename : null
  585. var cached = key ? cacheGet(key, options) : null
  586. var context
  587. var contextLength
  588. var pre
  589. if (cached) {
  590. return cached
  591. }
  592. context = getLocals(options)
  593. contextLength = utils.keys(context).length
  594. pre = self.precompile(source, options)
  595. function compiled (locals) {
  596. var lcls
  597. if (locals && contextLength) {
  598. lcls = utils.extend({}, context, locals)
  599. } else if (locals && !contextLength) {
  600. lcls = locals
  601. } else if (!locals && contextLength) {
  602. lcls = context
  603. } else {
  604. lcls = {}
  605. }
  606. return pre.tpl(self, lcls, filters, utils, efn)
  607. }
  608. utils.extend(compiled, pre.tokens)
  609. if (key) {
  610. cacheSet(key, options, compiled)
  611. }
  612. return compiled
  613. }
  614. /**
  615. * Compile a source file into a renderable template function.
  616. *
  617. * @example
  618. * var tpl = swig.compileFile('./mytpl.html');
  619. * // => {
  620. * // [Function: compiled]
  621. * // parent: null,
  622. * // tokens: [{ compile: [Function] }],
  623. * // blocks: {}
  624. * // }
  625. * tpl({ tacos: 'Tacos!!!!' });
  626. * // => Tacos!!!!
  627. *
  628. * @example
  629. * swig.compileFile('/myfile.txt', { varControls: ['<%=', '=%>'], tagControls: ['<%', '%>']});
  630. * // => will compile 'myfile.txt' using the var and tag controls as specified.
  631. *
  632. * @param {string} pathname File location.
  633. * @param {SwigOpts} [options={}] Swig options object.
  634. * @param {Function} [cb] Asyncronous callback function. If not provided, <var>compileFile</var> will run syncronously.
  635. * @return {function} Renderable function with keys for parent, blocks, and tokens.
  636. */
  637. this.compileFile = function (pathname, options, cb) {
  638. var src, cached
  639. if (!options) {
  640. options = {}
  641. }
  642. pathname = self.options.loader.resolve(pathname, options.resolveFrom)
  643. if (!options.filename) {
  644. options = utils.extend({ filename: pathname }, options)
  645. }
  646. cached = cacheGet(pathname, options)
  647. if (cached) {
  648. if (cb) {
  649. cb(null, cached)
  650. return
  651. }
  652. return cached
  653. }
  654. if (cb) {
  655. self.options.loader.load(pathname, function (err, src) {
  656. if (err) {
  657. cb(err)
  658. return
  659. }
  660. var compiled
  661. try {
  662. compiled = self.compile(src, options)
  663. } catch (err2) {
  664. cb(err2)
  665. return
  666. }
  667. cb(err, compiled)
  668. })
  669. return
  670. }
  671. src = self.options.loader.load(pathname)
  672. return self.compile(src, options)
  673. }
  674. /**
  675. * Run a pre-compiled template function. This is most useful in the browser when you've pre-compiled your templates with the Swig command-line tool.
  676. *
  677. * @example
  678. * $ swig compile ./mytpl.html --wrap-start="var mytpl = " > mytpl.js
  679. * @example
  680. * <script src="mytpl.js"></script>
  681. * <script>
  682. * swig.run(mytpl, {});
  683. * // => "rendered template..."
  684. * </script>
  685. *
  686. * @param {function} tpl Pre-compiled Swig template function. Use the Swig CLI to compile your templates.
  687. * @param {object} [locals={}] Template variable context.
  688. * @param {string} [filepath] Filename used for caching the template.
  689. * @return {string} Rendered output.
  690. */
  691. this.run = function (tpl, locals, filepath) {
  692. var context = getLocals({ locals: locals })
  693. if (filepath) {
  694. cacheSet(filepath, {}, tpl)
  695. }
  696. return tpl(self, context, filters, utils, efn)
  697. }
  698. }
  699. /*!
  700. * Export methods publicly
  701. */
  702. defaultInstance = new exports.Swig()
  703. exports.setFilter = defaultInstance.setFilter
  704. exports.setTag = defaultInstance.setTag
  705. exports.setExtension = defaultInstance.setExtension
  706. exports.parseFile = defaultInstance.parseFile
  707. exports.precompile = defaultInstance.precompile
  708. exports.compile = defaultInstance.compile
  709. exports.compileFile = defaultInstance.compileFile
  710. exports.render = defaultInstance.render
  711. exports.renderFile = defaultInstance.renderFile
  712. exports.run = defaultInstance.run
  713. exports.invalidateCache = defaultInstance.invalidateCache
  714. exports.loaders = loaders