diff --git a/package.json b/package.json index 0ad5e62..e0a762b 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "klaw": "^1.3.1", "lodash": "^4.17.4", "node-exceptions": "^2.0.0", + "require-uncached": "^1.0.3", "upcast": "^1.0.4" } } diff --git a/src/Edge/index.js b/src/Edge/index.js index 2430927..c9f2242 100644 --- a/src/Edge/index.js +++ b/src/Edge/index.js @@ -26,6 +26,9 @@ class Edge { this._tags = {} this._globals = require('../Globals') this._loader = new Loader() + this._options = { + cache: false + } this._boot() } @@ -54,7 +57,9 @@ class Edge { * @private */ _getTemplate () { - return new Template(this._tags, this._globals, this._loader) + return new Template(this._tags, { + cache: this._options.cache + }, this._globals, this._loader) } /** @@ -100,6 +105,19 @@ class Edge { tag.run(Context) } + /** + * Configure edge by passing object of options. + * + * @method configure + * + * @param {Object} options + * + * @return {void} + */ + configure (options) { + this._options = _.merge(this._options, options) + } + /** * Register a new global. * diff --git a/src/Loader/index.js b/src/Loader/index.js index d6a7f79..2c69521 100644 --- a/src/Loader/index.js +++ b/src/Loader/index.js @@ -11,6 +11,7 @@ const fs = require('fs') const path = require('path') +const requireUncached = require('require-uncached') const CE = require('../Exceptions') /** @@ -147,7 +148,7 @@ class Loader { * * @return {String} */ - loadPresenter (presenter) { + loadPresenter (presenter, clearCache = false) { /** * Presenters path has not been registered and trying * to load a presenter. @@ -157,7 +158,8 @@ class Loader { } try { - return require(path.join(this.presentersPath, presenter)) + const presenterPath = path.join(this.presentersPath, presenter) + return clearCache ? requireUncached(presenterPath) : require(presenterPath) } catch (error) { if (error.code === 'MODULE_NOT_FOUND') { throw CE.RuntimeException.missingPresenter(presenter, this.presentersPath) diff --git a/src/Template/index.js b/src/Template/index.js index a59497c..329a015 100644 --- a/src/Template/index.js +++ b/src/Template/index.js @@ -38,12 +38,13 @@ const cache = require('../Cache') * @constructor */ class Template { - constructor (tags, globals = {}, loader = null) { + constructor (tags, options, globals = {}, loader = null) { this._tags = tags this._globals = globals this._loader = loader this._viewName = 'raw string' this._runTimeViews = [] + this._options = options this._locals = {} this._presenter = null @@ -111,7 +112,7 @@ class Template { * @private */ _makeContext (data) { - const Presenter = this._presenter ? this._loader.loadPresenter(this._presenter) : BasePresenter + const Presenter = this._presenter ? this._loader.loadPresenter(this._presenter, !this._options.cache) : BasePresenter const presenter = new Presenter(data, this._locals) /** * We should always make the context with the original view @@ -126,6 +127,8 @@ class Template { * @method _addRunTimeView * * @param {String} view + * + * @private */ _addRunTimeView (view) { this._runTimeViews.push(view) @@ -138,11 +141,52 @@ class Template { * @method _removeRunTimeView * * @return {void} + * + * @private */ _removeRunTimeView () { this._runTimeViews.pop() } + /** + * Return the view from cache if cachining is + * turned on. + * + * @method _getFromCache + * + * @param {String} view + * + * @return {String|Null} + * + * @private + */ + _getFromCache (view) { + if (!this._options.cache) { + return null + } + return cache.get(view) + } + + /** + * Save view to cache when caching is turned on + * + * @method _saveToCache + * + * @param {String} view + * @param {String} output + * + * @return {void} + * + * @private + */ + _saveToCache (view, output) { + if (!this._options.cache) { + return + } + cache.add(view, output) + debug('adding view %s to cache', view) + } + /** * Compile a view by loading it from the disk and * cache the view when caching is set to true. @@ -155,7 +199,7 @@ class Template { * @return {String} */ _compileView (view, asFunction = true) { - const preCompiledView = cache.get(view) + const preCompiledView = this._getFromCache(view) /** * Return the precompiled view from the cache if @@ -170,13 +214,7 @@ class Template { try { const compiledView = compiler.compile(view) - - /** - * Adding view to cache - */ - cache.add(view, compiledView) - debug('adding view %s to cache', view) - + this._saveToCache(view, compiledView) return compiledView } catch (error) { throw this._prepareStack(view, error) @@ -378,7 +416,7 @@ class Template { return result }, {}) - const template = new Template(this._tags, this._globals, this._loader) + const template = new Template(this._tags, this._options, this._globals, this._loader) template.presenter(presenter) template._makeContext(data) return template diff --git a/test/unit/tags/component.spec.js b/test/unit/tags/component.spec.js index 1e3af01..61cb620 100644 --- a/test/unit/tags/component.spec.js +++ b/test/unit/tags/component.spec.js @@ -25,7 +25,7 @@ test.group('Tags | Component ', (group) => { }) test('parse a simple component without any slots', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert')

Hello dude

@@ -45,7 +45,7 @@ test.group('Tags | Component ', (group) => { }) test('parse a simple component with props', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert', username = 'virk')

Hello dude

@@ -65,7 +65,7 @@ test.group('Tags | Component ', (group) => { }) test('parse a simple component with dynamic props', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert', username = username)

Hello dude

@@ -85,7 +85,7 @@ test.group('Tags | Component ', (group) => { }) test('parse a simple component with object as props', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert', { username })

Hello dude

@@ -105,7 +105,7 @@ test.group('Tags | Component ', (group) => { }) test('parse a simple component with dynamic slots', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert', { username }) @slot('header') @@ -131,7 +131,7 @@ test.group('Tags | Component ', (group) => { }) test('parse a simple component with one or more dynamic slots', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert', { username }) @slot('header') @@ -169,7 +169,7 @@ test.group('Tags | Component ', (group) => { @endslot @endcomponent ` - const output = new Template(this.tags, {}, loader).renderString(statement) + const output = new Template(this.tags, {}, {}, loader).renderString(statement) assert.equal(output.trim(), dedent`
@@ -186,7 +186,7 @@ test.group('Tags | Component ', (group) => { @component('components.user', username = 'virk') @endcomponent ` - const output = new Template(this.tags, {}, loader).renderString(statement, { + const output = new Template(this.tags, {}, {}, loader).renderString(statement, { username: 'nikk' }) assert.equal(output.trim(), '

Hello virk

') @@ -197,7 +197,7 @@ test.group('Tags | Component ', (group) => { @component('components.user', username = username) @endcomponent ` - const output = new Template(this.tags, {}, loader).renderString(statement, { + const output = new Template(this.tags, {}, {}, loader).renderString(statement, { username: 'nikk' }) assert.equal(output.trim(), '

Hello nikk

') @@ -215,7 +215,7 @@ test.group('Tags | Component ', (group) => { @endslot @endcomponent ` - const output = () => new Template(this.tags, {}, loader).compileString(statement) + const output = () => new Template(this.tags, {}, {}, loader).compileString(statement) assert.throw(output, 'lineno:6 charno:0 E_INVALID_EXPRESSION: Invalid name passed to slot. Only strings are allowed') }) @@ -228,13 +228,13 @@ test.group('Tags | Component ', (group) => { @endslot @endcomponent ` - const output = new Template(this.tags, {}, loader).renderString(statement) + const output = new Template(this.tags, {}, {}, loader).renderString(statement) const $ = cheerio.load(output) assert.equal($('.body').html().trim(), '

Hello joe

') }) test('pass multiple props to a component', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.user', username = 'virk', age = 22) @endcomponent @@ -246,7 +246,7 @@ test.group('Tags | Component ', (group) => { }) test('component slots should have access to parent template scope', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert')

{{ username }}

@@ -260,7 +260,7 @@ test.group('Tags | Component ', (group) => { }) test('include inside the components', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.alert') @slot('body') @@ -276,7 +276,7 @@ test.group('Tags | Component ', (group) => { }) test('include component inside component', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.modal') @slot('header') @@ -295,7 +295,7 @@ test.group('Tags | Component ', (group) => { }) test('deeply nested tags inside slots', (assert) => { - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @component('components.modal') @slot('body') @@ -326,7 +326,7 @@ test.group('Tags | Component ', (group) => { test('pass presenter to component', (assert) => { loader.presentersPath = path.join(__dirname, '../../../test-helpers/presenters') - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const statement = dedent` @!component('components.user', presenter = 'User', username = 'virk') ` diff --git a/test/unit/tags/include.spec.js b/test/unit/tags/include.spec.js index 6519f3d..667bc51 100644 --- a/test/unit/tags/include.spec.js +++ b/test/unit/tags/include.spec.js @@ -15,7 +15,7 @@ test.group('Tags | Include ', (group) => { const statement = dedent` @include('includes.users.edge') ` - const output = new Template(this.tags, {}, loader).compileString(statement) + const output = new Template(this.tags, {}, {}, loader).compileString(statement) assert.equal(output, dedent` return (function templateFn () { let out = new String() @@ -29,7 +29,7 @@ test.group('Tags | Include ', (group) => { const statement = dedent` @include(user.profile) ` - const output = new Template(this.tags, {}, loader).compileString(statement) + const output = new Template(this.tags, {}, {}, loader).compileString(statement) assert.equal(output, dedent` return (function templateFn () { let out = new String() @@ -43,7 +43,7 @@ test.group('Tags | Include ', (group) => { const statement = dedent` @include(usersPartial) ` - const output = new Template(this.tags, {}, loader).renderString(statement, { + const output = new Template(this.tags, {}, {}, loader).renderString(statement, { usersPartial: 'includes.users' }) assert.equal(output.trim(), '

Hello

') @@ -55,7 +55,7 @@ test.group('Tags | Include ', (group) => { @include(usersPartial) @endif ` - const output = new Template(this.tags, {}, loader).renderString(statement, { + const output = new Template(this.tags, {}, {}, loader).renderString(statement, { usersPartial: 'includes.users', username: 'virk' }) @@ -68,7 +68,7 @@ test.group('Tags | Include ', (group) => { @include(usersPartial) @endif ` - const output = new Template(this.tags, {}, loader).renderString(statement, { + const output = new Template(this.tags, {}, {}, loader).renderString(statement, { usersPartial: 'includes.users' }) assert.equal(output.trim(), '') @@ -77,7 +77,7 @@ test.group('Tags | Include ', (group) => { test('work fine with nested includes', (assert) => { const statement = `@include('includes.user-profile')` - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement, { username: 'Foo' }) assert.equal(output.trim(), dedent`

User Profile

Foo

diff --git a/test/unit/template.spec.js b/test/unit/template.spec.js index 93d4a8e..60cffce 100644 --- a/test/unit/template.spec.js +++ b/test/unit/template.spec.js @@ -26,7 +26,7 @@ test.group('Template Compiler', (group) => { test('parse a simple template string without tags', (assert) => { const statement = `{{ username }}` - const template = new Template({}) + const template = new Template({}, {}) const output = template.compileString(statement) assert.equal(output, dedent` return (function templateFn () { @@ -43,7 +43,7 @@ test.group('Template Compiler', (group) => { {{ username }} @endif ` - const template = new Template(this.tags) + const template = new Template(this.tags, {}) const output = template.compileString(statement) assert.equal(output, dedent` return (function templateFn () { @@ -62,14 +62,14 @@ test.group('Template Compiler', (group) => { {{ username }} @endif ` - const template = new Template(this.tags) + const template = new Template(this.tags, {}) const output = () => template.compileString(statement) assert.throw(output, 'lineno:1 charno:0 E_INVALID_EXPRESSION: Invalid expression passed to (if) block') }) test('parse a template by reading it via loader', (assert) => { const loader = new Loader(path.join(__dirname, '../../test-helpers/views')) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.compile('ifView') assert.equal(output, dedent` return (function templateFn () { @@ -85,7 +85,7 @@ test.group('Template Compiler', (group) => { test('report error with correct lineno when file has error', (assert) => { assert.plan(2) const loader = new Loader(path.join(__dirname, '../../test-helpers/views')) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = () => template.compile('ifErrorView') try { output() @@ -103,7 +103,7 @@ test.group('Template Compiler', (group) => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement) assert.equal(output, dedent` Hello world @@ -120,7 +120,7 @@ test.group('Template Compiler', (group) => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) try { template.renderString(statement) } catch (error) { @@ -133,14 +133,14 @@ test.group('Template Compiler', (group) => { test.group('Template Runner', () => { test('render a template by loading it from file', (assert) => { const loader = new Loader(path.join(__dirname, '../../test-helpers/views')) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.render('welcome', { username: 'virk' }) assert.equal(output.trim(), 'virk') }) test('render a template from string', (assert) => { const loader = new Loader(path.join(__dirname, '../../test-helpers/views')) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString('{{ username }}', { username: 'virk' }) assert.equal(output.trim(), 'virk') }) @@ -150,7 +150,7 @@ test.group('Template Runner', () => { path.join(__dirname, '../../test-helpers/views'), path.join(__dirname, '../../test-helpers/presenters') ) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.presenter('User').renderString('{{ username }}', { username: 'virk' }) assert.equal(output.trim(), 'VIRK') }) @@ -160,7 +160,7 @@ test.group('Template Runner', () => { path.join(__dirname, '../../test-helpers/views'), path.join(__dirname, '../../test-helpers/presenters') ) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.share({username: 'virk'}).renderString('{{ username }}') assert.equal(output.trim(), 'virk') }) @@ -173,7 +173,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement) const $ = cheerio.load(output) assert.equal($('h2').length, 0) @@ -189,7 +189,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement) const $ = cheerio.load(output) assert.equal($('h2').length, 1) @@ -211,7 +211,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = () => template.renderString(statement) assert.throw(output, `lineno:6 charno:0 E_INVALID_EXPRESSION: Section <@section('content')> has been called multiple times. A section can only be called once`) }) @@ -226,7 +226,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = () => template.renderString(statement) assert.throw(output, `lineno:2 charno:0 E_INVALID_EXPRESSION: Invalid expression passed to a section. Make sure section name must be a valid string`) }) @@ -242,7 +242,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) try { template.renderString(statement) } catch (error) { @@ -261,7 +261,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement, { masterLayout: 'layouts.master' }) @@ -282,7 +282,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement) const $ = cheerio.load(output) assert.equal($('h2').length, 1) @@ -299,7 +299,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) template.renderString(statement) }) @@ -313,7 +313,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement, { username: 'virk' }) assert.equal(output.trim(), dedent`

Hey virk

@@ -332,7 +332,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement, { username: 'virk' }) assert.equal(output.trim(), dedent`

Hey virk

@@ -341,14 +341,14 @@ test.group('Template Runner', () => { test('add template to cache after compile', (assert) => { const loader = new Loader(path.join(__dirname, '../../test-helpers/views')) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {cache: true}, {}, loader) template.compile('welcome') assert.isDefined(cache._items['welcome.edge']) }) test('rendering a view multiple times should get it from the cache', (assert) => { const loader = new Loader(path.join(__dirname, '../../test-helpers/views')) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {cache: true}, {}, loader) template.compile('welcome') const existingCompile = TemplateCompiler.prototype.compile TemplateCompiler.prototype.compile = function () { @@ -367,7 +367,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement, { outputViewName () { return this.$viewName @@ -389,7 +389,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement, { outputViewName () { return this.$viewName @@ -413,7 +413,7 @@ test.group('Template Runner', () => { ` this.tags.section.run(Context) - const template = new Template(this.tags, {}, loader) + const template = new Template(this.tags, {}, {}, loader) const output = template.renderString(statement, { outputViewName () { return this.$viewName