Skip to content

Commit

Permalink
Merge pull request #696 from kethinov/0.6.14
Browse files Browse the repository at this point in the history
0.6.14
  • Loading branch information
kethinov authored Oct 19, 2024
2 parents 70cd079 + e88c13b commit b83b9ad
Show file tree
Hide file tree
Showing 17 changed files with 195 additions and 201 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

## Next version

- Put your changes here...

## 0.6.14

- Finsihed work on `cheerioPolyfill.js` which makes it possible for Teddy to execute in client-side contexts without using `cheerio`, allowing for a very small bundle size for client-side Teddy (17kb minified).
- Fixed client-side tests to test Teddy in the browser properly.
- Refactored tests to improve maintainability.
- Did further work on `cheerioPolyfill.js`. It's more than half finished, but it isn't fully done yet.
- Updated various dependencies.

## 0.6.13
Expand Down
39 changes: 7 additions & 32 deletions cheerioPolyfill.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,17 @@
// extend HTMLElement prototype to include cheerio properties
Object.defineProperty(window.HTMLElement.prototype, 'name', {
get: function () {
if (this.__name === undefined) this.__name = this.nodeName.toLowerCase()
return this.__name
}
})

Object.defineProperty(window.HTMLElement.prototype, 'parent', {
get: function () {
if (this.__parent === undefined) this.__parent = this.parentNode
return this.__parent
}
})

// TODO: write more HTMLElement property extensions to polyfill cheerio: this will require reading teddy.js line by line to see what properties from cheerio each method uses and adapt them like above one property at a time
// TODO: nextSibling, children?

// create a native DOMParser
const parser = new window.DOMParser()

// stub out cheerio using native dom methods for frontend so we don't have to bundle cheerio on the frontend
export function load (html) {
console.log('Loading cheerio polyfill... TODO: This is unfinished! Please use teddy.mjs or teddy.cjs via a bundle for now.')
const doc = parser.parseFromString(html, 'text/html')
console.log('doc:', doc.body.innerHTML)
doc.body.innerHTML = doc.head.innerHTML + doc.body.innerHTML

// return a querySelector function with function chains
// e.g. dom('include') or dom(el) from teddy
const $ = function (query) { // query can be a string, or a dom object
// if query is a string, we need to create a dom object from the string: an object with elements in it, e.g. a list of include tag objects
if (typeof query === 'string') {
const els = doc.querySelectorAll(query)
console.log('cheerio polyfill: dom(query)', els)
return els // return the object collection
}

Expand All @@ -41,54 +21,49 @@ export function load (html) {

// e.g. dom(el).children() from teddy
children: function () {
console.log('cheerio polyfill: children()', el.children)
return el.children
return el.childNodes
},

// e.g. dom(arg).html() from teddy
html: function () {
console.log('cheerio polyfill: html()', el.innerHTML)
return el.innerHTML
},

// e.g. dom(el).attr('teddy_deferred_dynamic_include', 'true') from teddy
attr: function (attr, val) {
console.log('cheerio polyfill: attr()', attr, val)
return el.setAttribute(attr, val)
},

// dom(el).removeAttr(attr) from teddy
removeAttr: function (attr) {
console.log('cheerio polyfill: removeAttr()', attr)
return el.removeAttribute(attr)
},

// e.g. dom(el).replaceWith(localDom.html()) from teddy
replaceWith: function (html) {
// can either be a string or an array of elements
console.log('replaceWith doc:', doc.body.innerHTML)
if (typeof html === 'object') {
let newHtml = ''
for (const el of html) newHtml += el.outerHTML
for (const el of html) {
if (el.nodeType === window.Node.COMMENT_NODE) newHtml += '<!-- ' + el.textContent + ' -->'
else newHtml += el.outerHTML || el.textContent
}
html = newHtml
}
const temp = document.createElement('div')
temp.innerHTML = html
el.replaceWith(...temp.children)
console.log('replaceWith doc:', doc.body.innerHTML)
el.replaceWith(...temp.childNodes)
},

// e.g. dom(el).remove() from teddy
remove: function () {
console.log('cheerio polyfill: remove()', el)
return el.remove()
}
}
}

// e.g. dom.html() from teddy
$.html = function () {
console.log('cheerio polyfill: dom.html()', doc.body.innerHTML)
return doc.body.innerHTML
}

Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"url": "https://github.com/rooseveltframework/teddy/graphs/contributors"
}
],
"version": "0.6.13",
"version": "0.6.14",
"files": [
"dist"
],
Expand Down
58 changes: 31 additions & 27 deletions teddy.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function replaceCacheElements (dom, model) {
const now = Date.now()
// if max age is not set, then there is no max age and the cache content is still valid
// or if last accessed + max age > now then the cache is not stale and the cache is still valid
if (!cache.maxAge || cache.entries[keyVal].lastAccessed + cache.maxAge > now) {
if (!(cache.maxAge && !cache.maxage) || cache.entries[keyVal].lastAccessed + (cache.maxAge || cache.maxage) > now) {
const cacheContent = cache.entries[keyVal].markup
cache.entries[keyVal].lastAccessed = now
dom(el).replaceWith(cacheContent)
Expand Down Expand Up @@ -160,16 +160,16 @@ function parseIncludes (dom, model, dynamic) {
// ensure this isn't the child of a no parse block
let foundBody = false
let next = false
let parent = el.parent
let parent = el.parent || el.parentNode
while (!foundBody) {
let parentName
if (!parent) parentName = 'body'
else parentName = parent.name
else parentName = parent.name || parent.nodeName?.toLowerCase()
if (parentName === 'noparse' || parentName === 'noteddy') {
next = true
break
} else if (parentName === 'body') foundBody = true
else parent = parent.parent
else parent = parent.parent || parent.parentNode
}
if (next) continue
// get attributes
Expand All @@ -187,6 +187,7 @@ function parseIncludes (dom, model, dynamic) {
const contents = templates[src]
const localModel = Object.assign({}, model)
for (const arg of dom(el).children()) {
if (browser) arg.name = arg.nodeName?.toLowerCase()
if (arg.name === 'arg') {
if (browser) arg.attribs = getAttribs(arg)
const argval = Object.keys(arg.attribs)[0]
Expand Down Expand Up @@ -217,16 +218,16 @@ function parseConditionals (dom, model) {
// ensure this isn't the child of a loop or a no parse block
let foundBody = false
let next = false
let parent = el.parent
let parent = el.parent || el.parentNode
while (!foundBody) {
let parentName
if (!parent) parentName = 'body'
else parentName = parent.name
else parentName = parent.name || parent.nodeName?.toLowerCase()
if (parentName === 'loop' || parentName === 'noparse' || parentName === 'noteddy') {
next = true
break
} else if (parentName === 'body') foundBody = true
else parent = parent.parent
else parent = parent.parent || parent.parentNode
}
if (next) continue
// get conditions
Expand All @@ -239,6 +240,7 @@ function parseConditionals (dom, model) {
}
// check if it's an if tag and not an unless tag
let isIf = true
if (browser) el.name = el.nodeName?.toLowerCase()
if (el.name === 'unless') isIf = false
// evaluate conditional
const condResult = evaluateConditional(args, model)
Expand All @@ -247,6 +249,7 @@ function parseConditionals (dom, model) {
let nextSibling = el.nextSibling
const removeStack = []
while (nextSibling) {
if (browser) nextSibling.name = nextSibling.nodeName?.toLowerCase()
switch (nextSibling.name) {
case 'elseif':
case 'elseunless':
Expand All @@ -263,12 +266,13 @@ function parseConditionals (dom, model) {
}
}
for (const element of removeStack) dom(element).replaceWith('')
dom(el).replaceWith(el.children)
dom(el).replaceWith(el.childNodes || el.children)
parsedTags++
} else {
// true block is false; find the next elseif, elseunless, or else tag to evaluate
let nextSibling = el.nextSibling
while (nextSibling) {
if (browser) nextSibling.name = nextSibling.nodeName?.toLowerCase()
switch (nextSibling.name) {
case 'elseif':
// get conditions
Expand All @@ -282,10 +286,11 @@ function parseConditionals (dom, model) {
if (evaluateConditional(args, model)) {
// render the true block and discard the elseif, elseunless, and else blocks
const replaceSibling = nextSibling
dom(replaceSibling).replaceWith(replaceSibling.children)
dom(replaceSibling).replaceWith(replaceSibling.childNodes || replaceSibling.children)
nextSibling = el.nextSibling
const removeStack = []
while (nextSibling) {
if (browser) nextSibling.name = nextSibling.nodeName?.toLowerCase()
switch (nextSibling.name) {
case 'elseif':
case 'elseunless':
Expand Down Expand Up @@ -323,10 +328,11 @@ function parseConditionals (dom, model) {
if (!evaluateConditional(args, model)) {
// render the true block and discard the elseif, elseunless, and else blocks
const replaceSibling = nextSibling
dom(replaceSibling).replaceWith(replaceSibling.children)
dom(replaceSibling).replaceWith(replaceSibling.childNodes || replaceSibling.children)
nextSibling = el.nextSibling
const removeStack = []
while (nextSibling) {
if (browser) nextSibling.name = nextSibling.nodeName?.toLowerCase()
switch (nextSibling.name) {
case 'elseif':
case 'elseunless':
Expand Down Expand Up @@ -354,7 +360,7 @@ function parseConditionals (dom, model) {
break
case 'else':
// else is always true, so if we've gotten here, then there's nothing to evaluate and we've reached the end of the conditional blocks
dom(nextSibling).replaceWith(nextSibling.children)
dom(nextSibling).replaceWith(nextSibling.childNodes || nextSibling.children)
nextSibling = false
parsedTags++
break
Expand Down Expand Up @@ -482,16 +488,16 @@ function parseOneLineConditionals (dom, model) {
// ensure this isn't the child of a loop or a no parse block
let foundBody = false
let next = false
let parent = el.parent
let parent = el.parent || el.parentNode
while (!foundBody) {
let parentName
if (!parent) parentName = 'body'
else parentName = parent.name
else parentName = parent.name || parent.nodeName?.toLowerCase()
if (parentName === 'loop' || parentName === 'noparse' || parentName === 'noteddy') {
next = true
break
} else if (parentName === 'body') foundBody = true
else parent = parent.parent
else parent = parent.parent || parent.parentNode
}
if (next) continue
// get conditions
Expand Down Expand Up @@ -673,8 +679,8 @@ function defineNewCaches (dom, model) {
if (browser) el.attribs = getAttribs(el)
const name = el.attribs.name
const key = el.attribs.key || 'none'
const maxAge = parseInt(el.attribs.maxAge) || 0
const maxCaches = parseInt(el.attribs.maxCaches) || 1000
const maxAge = parseInt(el.attribs.maxAge || el.attribs.maxage) || 0
const maxCaches = parseInt(el.attribs.maxCaches || el.attribs.maxcaches) || 1000
const timestamp = Date.now()
const markup = dom(el).html()
if (!caches[name]) {
Expand Down Expand Up @@ -711,6 +717,7 @@ function cleanupStrayTeddyTags (dom) {
const tags = dom('[teddy_deferred_one_line_conditional], include, arg, if, unless, elseif, elseunless, else, loop, cache')
if (tags.length > 0) {
for (const el of tags) {
if (browser) el.name = el.nodeName?.toLowerCase()
if (el.name === 'include' || el.name === 'arg' || el.name === 'if' || el.name === 'unless' || el.name === 'elseif' || el.name === 'elseunless' || el.name === 'else' || el.name === 'loop' || el.name === 'cache') {
dom(el).remove()
}
Expand Down Expand Up @@ -832,10 +839,7 @@ function getOrSetObjectByDotNotation (obj, dotNotation, value) {
}
}

// #endregion

// #region cheerio polyfills

// cheerio polyfill
function getAttribs (element) {
const attributes = element.attributes
const attributesObject = {}
Expand Down Expand Up @@ -918,13 +922,13 @@ function setCache (params) {
if (!templateCaches[params.template]) templateCaches[params.template] = {}
if (params.key) {
templateCaches[params.template][params.key] = {
maxAge: params.maxAge,
maxCaches: params.maxCaches || 1000,
maxAge: params.maxAge || params.maxage,
maxCaches: (params.maxCaches || params.maxcaches) || 1000,
entries: {}
}
} else {
templateCaches[params.template].none = {
maxAge: params.maxAge,
maxAge: params.maxAge || params.maxage,
markup: null,
created: null
}
Expand Down Expand Up @@ -981,11 +985,11 @@ function render (template, model, callback) {
if (singletonCache) {
// check if the timestamp exceeds max age
if (!singletonCache.created) cacheKey = 'none'
else if (!singletonCache.maxAge) {
else if (!singletonCache.maxAge && singletonCache.maxage) {
// if no max age is set, then this cache doesn't expire
if (typeof callback === 'function') return callback(null, singletonCache.markup)
else return singletonCache.markup
} else if (singletonCache.created + singletonCache.maxAge < Date.now()) cacheKey = 'none' // if yes re-render the template and cache it again
} else if (singletonCache.created + (singletonCache.maxAge || singletonCache.maxage) < Date.now()) cacheKey = 'none' // if yes re-render the template and cache it again
else {
// if no return the cached markup and skip the template render
if (typeof callback === 'function') return callback(null, singletonCache.markup)
Expand All @@ -1004,11 +1008,11 @@ function render (template, model, callback) {
if (entryKey === cacheKeyModelVal) {
// check if the timestamp exceeds max age
const entry = templateCacheAtThisKey.entries[entryKey]
if (!templateCacheAtThisKey.maxAge) {
if (!templateCacheAtThisKey.maxAge && !templateCacheAtThisKey.maxage) {
// if no max age is set, then this cache doesn't expire
if (typeof callback === 'function') return callback(null, entry.markup)
else return entry.markup
} else if (entry.created + templateCacheAtThisKey.maxAge < Date.now()) {
} else if (entry.created + (templateCacheAtThisKey.maxAge || templateCacheAtThisKey.maxage) < Date.now()) {
// if yes re-render the template and cache it again
cacheKey = key
break
Expand Down
12 changes: 10 additions & 2 deletions test/loaders/mocha.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ for (const testGroup of testsToRun) {
before(() => {
teddy.setTemplateRoot('test/templates')
model = makeModel()
if (process.env.NODE_ENV === 'test') teddy.setVerbosity(0)
if (process.env.NODE_ENV === 'test' || process.env.NODE_ENV === 'cover') teddy.setVerbosity(0)
})

for (const test of testGroup.tests) {
Expand All @@ -26,8 +26,16 @@ for (const testGroup of testsToRun) {
}

function teddyAssert (result, expected = true) {
result = ignoreSpaces(result)
if (typeof expected === 'string') expected = ignoreSpaces(expected)
assert.equal(ignoreSpaces(result), expected)
if (Array.isArray(expected)) {
let match = false
for (let acceptable of expected) {
acceptable = ignoreSpaces(acceptable)
if (result === acceptable) match = true
}
assert.equal(match, true)
} else assert.equal(result, expected)
}

function ignoreSpaces (str) {
Expand Down
Loading

0 comments on commit b83b9ad

Please sign in to comment.