1'use strict'; 2 3function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; _setPrototypeOf(subClass, superClass); } 4function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 5var asap = require('asap'); 6var _waterfall = require('a-sync-waterfall'); 7var lib = require('./lib'); 8var compiler = require('./compiler'); 9var filters = require('./filters'); 10var _require = require('./loaders'), 11 FileSystemLoader = _require.FileSystemLoader, 12 WebLoader = _require.WebLoader, 13 PrecompiledLoader = _require.PrecompiledLoader; 14var tests = require('./tests'); 15var globals = require('./globals'); 16var _require2 = require('./object'), 17 Obj = _require2.Obj, 18 EmitterObj = _require2.EmitterObj; 19var globalRuntime = require('./runtime'); 20var handleError = globalRuntime.handleError, 21 Frame = globalRuntime.Frame; 22var expressApp = require('./express-app'); 23 24// If the user is using the async API, *always* call it 25// asynchronously even if the template was synchronous. 26function callbackAsap(cb, err, res) { 27 asap(function () { 28 cb(err, res); 29 }); 30} 31 32/** 33 * A no-op template, for use with {% include ignore missing %} 34 */ 35var noopTmplSrc = { 36 type: 'code', 37 obj: { 38 root: function root(env, context, frame, runtime, cb) { 39 try { 40 cb(null, ''); 41 } catch (e) { 42 cb(handleError(e, null, null)); 43 } 44 } 45 } 46}; 47var Environment = /*#__PURE__*/function (_EmitterObj) { 48 _inheritsLoose(Environment, _EmitterObj); 49 function Environment() { 50 return _EmitterObj.apply(this, arguments) || this; 51 } 52 var _proto = Environment.prototype; 53 _proto.init = function init(loaders, opts) { 54 var _this = this; 55 // The dev flag determines the trace that'll be shown on errors. 56 // If set to true, returns the full trace from the error point, 57 // otherwise will return trace starting from Template.render 58 // (the full trace from within nunjucks may confuse developers using 59 // the library) 60 // defaults to false 61 opts = this.opts = opts || {}; 62 this.opts.dev = !!opts.dev; 63 64 // The autoescape flag sets global autoescaping. If true, 65 // every string variable will be escaped by default. 66 // If false, strings can be manually escaped using the `escape` filter. 67 // defaults to true 68 this.opts.autoescape = opts.autoescape != null ? opts.autoescape : true; 69 70 // If true, this will make the system throw errors if trying 71 // to output a null or undefined value 72 this.opts.throwOnUndefined = !!opts.throwOnUndefined; 73 this.opts.trimBlocks = !!opts.trimBlocks; 74 this.opts.lstripBlocks = !!opts.lstripBlocks; 75 this.loaders = []; 76 if (!loaders) { 77 // The filesystem loader is only available server-side 78 if (FileSystemLoader) { 79 this.loaders = [new FileSystemLoader('views')]; 80 } else if (WebLoader) { 81 this.loaders = [new WebLoader('/views')]; 82 } 83 } else { 84 this.loaders = lib.isArray(loaders) ? loaders : [loaders]; 85 } 86 87 // It's easy to use precompiled templates: just include them 88 // before you configure nunjucks and this will automatically 89 // pick it up and use it 90 if (typeof window !== 'undefined' && window.nunjucksPrecompiled) { 91 this.loaders.unshift(new PrecompiledLoader(window.nunjucksPrecompiled)); 92 } 93 this._initLoaders(); 94 this.globals = globals(); 95 this.filters = {}; 96 this.tests = {}; 97 this.asyncFilters = []; 98 this.extensions = {}; 99 this.extensionsList = []; 100 lib._entries(filters).forEach(function (_ref) { 101 var name = _ref[0], 102 filter = _ref[1]; 103 return _this.addFilter(name, filter); 104 }); 105 lib._entries(tests).forEach(function (_ref2) { 106 var name = _ref2[0], 107 test = _ref2[1]; 108 return _this.addTest(name, test); 109 }); 110 }; 111 _proto._initLoaders = function _initLoaders() { 112 var _this2 = this; 113 this.loaders.forEach(function (loader) { 114 // Caching and cache busting 115 loader.cache = {}; 116 if (typeof loader.on === 'function') { 117 loader.on('update', function (name, fullname) { 118 loader.cache[name] = null; 119 _this2.emit('update', name, fullname, loader); 120 }); 121 loader.on('load', function (name, source) { 122 _this2.emit('load', name, source, loader); 123 }); 124 } 125 }); 126 }; 127 _proto.invalidateCache = function invalidateCache() { 128 this.loaders.forEach(function (loader) { 129 loader.cache = {}; 130 }); 131 }; 132 _proto.addExtension = function addExtension(name, extension) { 133 extension.__name = name; 134 this.extensions[name] = extension; 135 this.extensionsList.push(extension); 136 return this; 137 }; 138 _proto.removeExtension = function removeExtension(name) { 139 var extension = this.getExtension(name); 140 if (!extension) { 141 return; 142 } 143 this.extensionsList = lib.without(this.extensionsList, extension); 144 delete this.extensions[name]; 145 }; 146 _proto.getExtension = function getExtension(name) { 147 return this.extensions[name]; 148 }; 149 _proto.hasExtension = function hasExtension(name) { 150 return !!this.extensions[name]; 151 }; 152 _proto.addGlobal = function addGlobal(name, value) { 153 this.globals[name] = value; 154 return this; 155 }; 156 _proto.getGlobal = function getGlobal(name) { 157 if (typeof this.globals[name] === 'undefined') { 158 throw new Error('global not found: ' + name); 159 } 160 return this.globals[name]; 161 }; 162 _proto.addFilter = function addFilter(name, func, async) { 163 var wrapped = func; 164 if (async) { 165 this.asyncFilters.push(name); 166 } 167 this.filters[name] = wrapped; 168 return this; 169 }; 170 _proto.getFilter = function getFilter(name) { 171 if (!this.filters[name]) { 172 throw new Error('filter not found: ' + name); 173 } 174 return this.filters[name]; 175 }; 176 _proto.addTest = function addTest(name, func) { 177 this.tests[name] = func; 178 return this; 179 }; 180 _proto.getTest = function getTest(name) { 181 if (!this.tests[name]) { 182 throw new Error('test not found: ' + name); 183 } 184 return this.tests[name]; 185 }; 186 _proto.resolveTemplate = function resolveTemplate(loader, parentName, filename) { 187 var isRelative = loader.isRelative && parentName ? loader.isRelative(filename) : false; 188 return isRelative && loader.resolve ? loader.resolve(parentName, filename) : filename; 189 }; 190 _proto.getTemplate = function getTemplate(name, eagerCompile, parentName, ignoreMissing, cb) { 191 var _this3 = this; 192 var that = this; 193 var tmpl = null; 194 if (name && name.raw) { 195 // this fixes autoescape for templates referenced in symbols 196 name = name.raw; 197 } 198 if (lib.isFunction(parentName)) { 199 cb = parentName; 200 parentName = null; 201 eagerCompile = eagerCompile || false; 202 } 203 if (lib.isFunction(eagerCompile)) { 204 cb = eagerCompile; 205 eagerCompile = false; 206 } 207 if (name instanceof Template) { 208 tmpl = name; 209 } else if (typeof name !== 'string') { 210 throw new Error('template names must be a string: ' + name); 211 } else { 212 for (var i = 0; i < this.loaders.length; i++) { 213 var loader = this.loaders[i]; 214 tmpl = loader.cache[this.resolveTemplate(loader, parentName, name)]; 215 if (tmpl) { 216 break; 217 } 218 } 219 } 220 if (tmpl) { 221 if (eagerCompile) { 222 tmpl.compile(); 223 } 224 if (cb) { 225 cb(null, tmpl); 226 return undefined; 227 } else { 228 return tmpl; 229 } 230 } 231 var syncResult; 232 var createTemplate = function createTemplate(err, info) { 233 if (!info && !err && !ignoreMissing) { 234 err = new Error('template not found: ' + name); 235 } 236 if (err) { 237 if (cb) { 238 cb(err); 239 return; 240 } else { 241 throw err; 242 } 243 } 244 var newTmpl; 245 if (!info) { 246 newTmpl = new Template(noopTmplSrc, _this3, '', eagerCompile); 247 } else { 248 newTmpl = new Template(info.src, _this3, info.path, eagerCompile); 249 if (!info.noCache) { 250 info.loader.cache[name] = newTmpl; 251 } 252 } 253 if (cb) { 254 cb(null, newTmpl); 255 } else { 256 syncResult = newTmpl; 257 } 258 }; 259 lib.asyncIter(this.loaders, function (loader, i, next, done) { 260 function handle(err, src) { 261 if (err) { 262 done(err); 263 } else if (src) { 264 src.loader = loader; 265 done(null, src); 266 } else { 267 next(); 268 } 269 } 270 271 // Resolve name relative to parentName 272 name = that.resolveTemplate(loader, parentName, name); 273 if (loader.async) { 274 loader.getSource(name, handle); 275 } else { 276 handle(null, loader.getSource(name)); 277 } 278 }, createTemplate); 279 return syncResult; 280 }; 281 _proto.express = function express(app) { 282 return expressApp(this, app); 283 }; 284 _proto.render = function render(name, ctx, cb) { 285 if (lib.isFunction(ctx)) { 286 cb = ctx; 287 ctx = null; 288 } 289 290 // We support a synchronous API to make it easier to migrate 291 // existing code to async. This works because if you don't do 292 // anything async work, the whole thing is actually run 293 // synchronously. 294 var syncResult = null; 295 this.getTemplate(name, function (err, tmpl) { 296 if (err && cb) { 297 callbackAsap(cb, err); 298 } else if (err) { 299 throw err; 300 } else { 301 syncResult = tmpl.render(ctx, cb); 302 } 303 }); 304 return syncResult; 305 }; 306 _proto.renderString = function renderString(src, ctx, opts, cb) { 307 if (lib.isFunction(opts)) { 308 cb = opts; 309 opts = {}; 310 } 311 opts = opts || {}; 312 var tmpl = new Template(src, this, opts.path); 313 return tmpl.render(ctx, cb); 314 }; 315 _proto.waterfall = function waterfall(tasks, callback, forceAsync) { 316 return _waterfall(tasks, callback, forceAsync); 317 }; 318 return Environment; 319}(EmitterObj); 320var Context = /*#__PURE__*/function (_Obj) { 321 _inheritsLoose(Context, _Obj); 322 function Context() { 323 return _Obj.apply(this, arguments) || this; 324 } 325 var _proto2 = Context.prototype; 326 _proto2.init = function init(ctx, blocks, env) { 327 var _this4 = this; 328 // Has to be tied to an environment so we can tap into its globals. 329 this.env = env || new Environment(); 330 331 // Make a duplicate of ctx 332 this.ctx = lib.extend({}, ctx); 333 this.blocks = {}; 334 this.exported = []; 335 lib.keys(blocks).forEach(function (name) { 336 _this4.addBlock(name, blocks[name]); 337 }); 338 }; 339 _proto2.lookup = function lookup(name) { 340 // This is one of the most called functions, so optimize for 341 // the typical case where the name isn't in the globals 342 if (name in this.env.globals && !(name in this.ctx)) { 343 return this.env.globals[name]; 344 } else { 345 return this.ctx[name]; 346 } 347 }; 348 _proto2.setVariable = function setVariable(name, val) { 349 this.ctx[name] = val; 350 }; 351 _proto2.getVariables = function getVariables() { 352 return this.ctx; 353 }; 354 _proto2.addBlock = function addBlock(name, block) { 355 this.blocks[name] = this.blocks[name] || []; 356 this.blocks[name].push(block); 357 return this; 358 }; 359 _proto2.getBlock = function getBlock(name) { 360 if (!this.blocks[name]) { 361 throw new Error('unknown block "' + name + '"'); 362 } 363 return this.blocks[name][0]; 364 }; 365 _proto2.getSuper = function getSuper(env, name, block, frame, runtime, cb) { 366 var idx = lib.indexOf(this.blocks[name] || [], block); 367 var blk = this.blocks[name][idx + 1]; 368 var context = this; 369 if (idx === -1 || !blk) { 370 throw new Error('no super block available for "' + name + '"'); 371 } 372 blk(env, context, frame, runtime, cb); 373 }; 374 _proto2.addExport = function addExport(name) { 375 this.exported.push(name); 376 }; 377 _proto2.getExported = function getExported() { 378 var _this5 = this; 379 var exported = {}; 380 this.exported.forEach(function (name) { 381 exported[name] = _this5.ctx[name]; 382 }); 383 return exported; 384 }; 385 return Context; 386}(Obj); 387var Template = /*#__PURE__*/function (_Obj2) { 388 _inheritsLoose(Template, _Obj2); 389 function Template() { 390 return _Obj2.apply(this, arguments) || this; 391 } 392 var _proto3 = Template.prototype; 393 _proto3.init = function init(src, env, path, eagerCompile) { 394 this.env = env || new Environment(); 395 if (lib.isObject(src)) { 396 switch (src.type) { 397 case 'code': 398 this.tmplProps = src.obj; 399 break; 400 case 'string': 401 this.tmplStr = src.obj; 402 break; 403 default: 404 throw new Error("Unexpected template object type " + src.type + "; expected 'code', or 'string'"); 405 } 406 } else if (lib.isString(src)) { 407 this.tmplStr = src; 408 } else { 409 throw new Error('src must be a string or an object describing the source'); 410 } 411 this.path = path; 412 if (eagerCompile) { 413 try { 414 this._compile(); 415 } catch (err) { 416 throw lib._prettifyError(this.path, this.env.opts.dev, err); 417 } 418 } else { 419 this.compiled = false; 420 } 421 }; 422 _proto3.render = function render(ctx, parentFrame, cb) { 423 var _this6 = this; 424 if (typeof ctx === 'function') { 425 cb = ctx; 426 ctx = {}; 427 } else if (typeof parentFrame === 'function') { 428 cb = parentFrame; 429 parentFrame = null; 430 } 431 432 // If there is a parent frame, we are being called from internal 433 // code of another template, and the internal system 434 // depends on the sync/async nature of the parent template 435 // to be inherited, so force an async callback 436 var forceAsync = !parentFrame; 437 438 // Catch compile errors for async rendering 439 try { 440 this.compile(); 441 } catch (e) { 442 var err = lib._prettifyError(this.path, this.env.opts.dev, e); 443 if (cb) { 444 return callbackAsap(cb, err); 445 } else { 446 throw err; 447 } 448 } 449 var context = new Context(ctx || {}, this.blocks, this.env); 450 var frame = parentFrame ? parentFrame.push(true) : new Frame(); 451 frame.topLevel = true; 452 var syncResult = null; 453 var didError = false; 454 this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err, res) { 455 // TODO: this is actually a bug in the compiled template (because waterfall 456 // tasks are both not passing errors up the chain of callbacks AND are not 457 // causing a return from the top-most render function). But fixing that 458 // will require a more substantial change to the compiler. 459 if (didError && cb && typeof res !== 'undefined') { 460 // prevent multiple calls to cb 461 return; 462 } 463 if (err) { 464 err = lib._prettifyError(_this6.path, _this6.env.opts.dev, err); 465 didError = true; 466 } 467 if (cb) { 468 if (forceAsync) { 469 callbackAsap(cb, err, res); 470 } else { 471 cb(err, res); 472 } 473 } else { 474 if (err) { 475 throw err; 476 } 477 syncResult = res; 478 } 479 }); 480 return syncResult; 481 }; 482 _proto3.getExported = function getExported(ctx, parentFrame, cb) { 483 // eslint-disable-line consistent-return 484 if (typeof ctx === 'function') { 485 cb = ctx; 486 ctx = {}; 487 } 488 if (typeof parentFrame === 'function') { 489 cb = parentFrame; 490 parentFrame = null; 491 } 492 493 // Catch compile errors for async rendering 494 try { 495 this.compile(); 496 } catch (e) { 497 if (cb) { 498 return cb(e); 499 } else { 500 throw e; 501 } 502 } 503 var frame = parentFrame ? parentFrame.push() : new Frame(); 504 frame.topLevel = true; 505 506 // Run the rootRenderFunc to populate the context with exported vars 507 var context = new Context(ctx || {}, this.blocks, this.env); 508 this.rootRenderFunc(this.env, context, frame, globalRuntime, function (err) { 509 if (err) { 510 cb(err, null); 511 } else { 512 cb(null, context.getExported()); 513 } 514 }); 515 }; 516 _proto3.compile = function compile() { 517 if (!this.compiled) { 518 this._compile(); 519 } 520 }; 521 _proto3._compile = function _compile() { 522 var props; 523 if (this.tmplProps) { 524 props = this.tmplProps; 525 } else { 526 var source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, this.path, this.env.opts); 527 var func = new Function(source); // eslint-disable-line no-new-func 528 props = func(); 529 } 530 this.blocks = this._getBlocks(props); 531 this.rootRenderFunc = props.root; 532 this.compiled = true; 533 }; 534 _proto3._getBlocks = function _getBlocks(props) { 535 var blocks = {}; 536 lib.keys(props).forEach(function (k) { 537 if (k.slice(0, 2) === 'b_') { 538 blocks[k.slice(2)] = props[k]; 539 } 540 }); 541 return blocks; 542 }; 543 return Template; 544}(Obj); 545module.exports = { 546 Environment: Environment, 547 Template: Template 548};