Comparing version 2.1.0 to 3.0.0
@@ -0,1 +1,8 @@ | ||
# 3.0.0 | ||
## Breaking Changes | ||
* Changed: EJS eval tags `<% %>` are no longer allowed in attribute values, for safety and simplicity, use escaped tags `<%= %>` | ||
* Removed: `standAlone` option to `compile()`. Use new function `compile.standAlone()` for similar effect | ||
* Added: `compile.standAlone()`. It returns the JS render function body as a string. The string can be trasmitted to a client and then the render function reconstructed with `new Function('locals, customRender', code)` | ||
# 2.1.0 | ||
@@ -2,0 +9,0 @@ * Added: exposed `getSnippet()` |
@@ -12,2 +12,3 @@ 'use strict' | ||
* @param {boolean} [options.standAlone=false] | ||
* @returns {string} | ||
*/ | ||
@@ -14,0 +15,0 @@ module.exports.render = function (source, locals, options) { |
@@ -1,2 +0,1 @@ | ||
/*jshint evil:true*/ | ||
'use strict' | ||
@@ -10,16 +9,2 @@ | ||
/** | ||
* Prepare render source for stand-alone function | ||
* @var {string} | ||
*/ | ||
let standAloneRenderCode = render.toString() | ||
// Remove function signature and closing '}' | ||
standAloneRenderCode = standAloneRenderCode | ||
.substring(standAloneRenderCode.indexOf('\n') + 1, standAloneRenderCode.length - 2) | ||
// Add stand-alone versions of escapeHTML | ||
standAloneRenderCode = `var escapeHTML = ${escape.html.standAlone.toString()} | ||
${standAloneRenderCode}` | ||
/** | ||
* A function that may transform the parsed tree before the compilation continues. | ||
@@ -34,2 +19,16 @@ * This should return a new array of tokens or `undefined` to use the same (in case | ||
/** | ||
* @callback Render | ||
* @param {Object} locals | ||
* @param {CustomRender} customRender | ||
* @returns {string} | ||
*/ | ||
/** | ||
* @callback CustomRender | ||
* @param {string} elementName | ||
* @param {Object} locals | ||
* @returns {string} | ||
*/ | ||
/** | ||
* @param {string} source | ||
@@ -39,5 +38,4 @@ * @param {Object} [options] | ||
* @param {string} [options.filename='ejs'] | ||
* @param {boolean} [options.standAlone=false] | ||
* @param {TransformerFn} [options.transformer] | ||
* @returns {function(Object):string} | ||
* @returns {Render} | ||
*/ | ||
@@ -47,2 +45,54 @@ module.exports = function (source, options) { | ||
let jsCode = prepareInternalJSCode(source, options), | ||
filename = options.filename || 'ejs' | ||
let internalRender | ||
try { | ||
/*jshint evil:true*/ | ||
internalRender = new Function('locals, renderCustom, __escape, __line', jsCode) | ||
} catch (e) { | ||
e.message += ` (in ${filename}, while compiling ejs)` | ||
throw e | ||
} | ||
return function (locals, renderCustom) { | ||
let line = { | ||
start: 0, | ||
end: 0 | ||
} | ||
try { | ||
return internalRender(locals, renderCustom, escape.html, line) | ||
} catch (err) { | ||
let snippet = getSnippet(source, line.start, line.end) | ||
err.path = filename | ||
err.message = `${filename}:${line.start}\n${snippet}\n\n${err.message}` | ||
throw err | ||
} | ||
} | ||
} | ||
/** | ||
* Much like {@link compile}, but returns a stand-alone JS source code, | ||
* that can be exported to another JS VM. When there, turn this into a function | ||
* with: render = new Function('locals, customRender', returnedCode) | ||
* @returns {string} | ||
*/ | ||
module.exports.standAlone = function (source, options) { | ||
let jsCode = prepareInternalJSCode(source, options) | ||
return `var __escape = ${escape.html.standAloneCode}, __line = {}; ${jsCode}` | ||
} | ||
/** | ||
* Common logic for `compile` and `compile.standAlone` | ||
* @private | ||
* @param {string} source | ||
* @param {Object} [options] | ||
* @param {boolean} [options.debug=false] | ||
* @param {string} [options.filename='ejs'] | ||
* @param {TransformerFn} [options.transformer] | ||
* @returns {Render} | ||
*/ | ||
function prepareInternalJSCode(source, options) { | ||
options = options || {} | ||
// Parse | ||
@@ -57,6 +107,3 @@ let tokens = parse(source) | ||
let reducedTokens = reduce(tokens), | ||
jsCode = createCode(reducedTokens), | ||
escapeHTML = escape.html, | ||
filename = options.filename || 'ejs', | ||
internalRender | ||
jsCode = createCode(reducedTokens) | ||
@@ -67,24 +114,3 @@ if (options.debug) { | ||
if (options.standAlone) { | ||
// internalRender, source, filename | ||
let standAloneCode = `var internalRender = function (locals, __escape, __line) { | ||
${jsCode} | ||
} | ||
var source = "${escape.js(source)}" | ||
var filename = "${escape.js(filename)}" | ||
var getSnippet = ${getSnippet} | ||
${standAloneRenderCode}` | ||
return new Function('locals, renderCustom', standAloneCode) | ||
} | ||
try { | ||
internalRender = new Function('locals, __escape, __line, __renderCustom', jsCode) | ||
} catch (e) { | ||
e.message += ` (in ${filename}, while compiling ejs)` | ||
throw e | ||
} | ||
return function (locals, renderCustom) { | ||
return render(locals, escapeHTML, renderCustom, internalRender, source, filename) | ||
} | ||
return jsCode | ||
} | ||
@@ -130,28 +156,2 @@ | ||
module.exports.createCode = createCode | ||
/** | ||
* @param {Object} locals | ||
* @param {function(string):string} escapeHTML | ||
* @param {Function} renderCustom | ||
* @param {Function} internalRender | ||
* @param {string} source | ||
* @param {string} filename | ||
* @returns {string} | ||
*/ | ||
function render(locals, escapeHTML, renderCustom, internalRender, source, filename) { | ||
/*jshint esnext:false*/ | ||
var line = { | ||
start: 0, | ||
end: 0 | ||
} | ||
try { | ||
return internalRender(locals, escapeHTML, line, renderCustom) | ||
} catch (err) { | ||
var snippet = getSnippet(source, line.start, line.end) | ||
err.path = filename | ||
err.message = filename + ':' + | ||
line.start + '\n' + snippet + '\n\n' + err.message | ||
throw err | ||
} | ||
} | ||
module.exports.createCode = createCode |
@@ -12,3 +12,3 @@ 'use strict' | ||
module.exports.prepareContent = function (element) { | ||
let jsCode = '__renderCustom(' | ||
let jsCode = 'renderCustom(' | ||
@@ -15,0 +15,0 @@ // First parameter: tag name |
@@ -31,27 +31,13 @@ 'use strict' | ||
/** | ||
* Stand-alone version of html escape function | ||
* This is less efficient then the original version, but can be exported | ||
* to the browser | ||
* @param {string} [str] | ||
* @returns {string} | ||
* @type {string} | ||
*/ | ||
module.exports.html.standAlone = function escape(str) { | ||
/*jshint esnext:false*/ | ||
var htmlCharMap = { | ||
'&': '&', | ||
'<': '<', | ||
'>': '>', | ||
'"': '"', | ||
'\'': ''' | ||
} | ||
module.exports.html.standAloneCode = `function escape(str) { | ||
return str == null ? '' : String(str) | ||
.replace(/&/g, '&') | ||
.replace(/</g, '<') | ||
.replace(/>/g, '>') | ||
.replace(/'/g, ''') | ||
.replace(/"/g, '"') | ||
}` | ||
if (str === undefined || str === null) { | ||
return '' | ||
} | ||
return String(str).replace(/[&<>"']/g, function encodeHTMLChar(c) { | ||
return htmlCharMap[c] | ||
}) | ||
} | ||
/** | ||
@@ -58,0 +44,0 @@ * Escape as to make safe to put inside double quotes: x = "..." |
@@ -233,7 +233,7 @@ 'use strict' | ||
if (match[1] === '<%-') { | ||
throwSyntaxError('EJS unescaped tags are not allowed here') | ||
throwSyntaxError('EJS unescaped tags are not allowed inside open tags') | ||
} else if (match[1] === '<%=') { | ||
throwSyntaxError('EJS escaped tags are not allowed here') | ||
throwSyntaxError('EJS escaped tags are not allowed inside open tags') | ||
} else if (match[1] === '<%') { | ||
throwSyntaxError('EJS eval tags are not allowed here') | ||
throwSyntaxError('EJS eval tags are not allowed inside open tags') | ||
} else if (match[1] === '>') { | ||
@@ -347,7 +347,7 @@ break | ||
if (match[0] === '<%-') { | ||
throwSyntaxError('Invalid quoted attribute value') | ||
throwSyntaxError('EJS unescaped tags are not allowed inside attribute values') | ||
} else if (match[0] === '<%=') { | ||
parts.push(readSimpleToken('ejs-escaped', ejsEndRegex)) | ||
} else if (match[0] === '<%') { | ||
parts.push(readSimpleToken('ejs-eval', ejsEndRegex)) | ||
throwSyntaxError('EJS eval tags are not allowed inside attribute values') | ||
} else { | ||
@@ -354,0 +354,0 @@ // End quote |
{ | ||
"name": "ejs-html", | ||
"version": "2.1.0", | ||
"version": "3.0.0", | ||
"author": "Sitegui <sitegui@sitegui.com.br>", | ||
"description": "Embedded JavaScript HTML templates. Another implementation of EJS, focused on run-time performance, HTML syntax checking and outputting minified HTML.", | ||
"description": "Embedded JavaScript HTML templates. An implementation of EJS focused on run-time performance, HTML syntax checking, minified HTML output and custom HTML elements.", | ||
"main": "./index.js", | ||
@@ -11,3 +11,3 @@ "repository": { | ||
}, | ||
"keywords": ["ejs", "html", "template", "engine", "minification"], | ||
"keywords": ["ejs", "html", "template", "engine", "minification", "custom elements", "web components"], | ||
"dependencies": {}, | ||
@@ -19,7 +19,5 @@ "license": "MIT", | ||
"scripts": { | ||
"test": "mocha test", | ||
"docs": "jsdoc -d docs -c docs.json -R README.md -t jaguarjs-jsdoc" | ||
"test": "mocha test" | ||
}, | ||
"devDependencies": { | ||
"jsdoc": "^3.4.0", | ||
"mocha": "^2.3.4", | ||
@@ -26,0 +24,0 @@ "should": "^8.0.2" |
@@ -6,3 +6,3 @@ # EJS HTML | ||
Embedded JavaScript HTML templates. Another implementation of EJS, focused on run-time performance, HTML syntax checking and outputting minified HTML. | ||
Embedded JavaScript HTML templates. An implementation of EJS focused on run-time performance, HTML syntax checking, minified HTML output and custom HTML elements. | ||
@@ -38,3 +38,3 @@ ## Usage | ||
* Normalize attributes spaces: `<input \n required>` → `<input required>` | ||
* Normalize class spaces: `<div class=" a b">` → `<div class="a b">` | ||
* Normalize class spaces: `<div class=" a b ">` → `<div class="a b">` | ||
* Simplify boolean attributes: `<input required="oh-yeah!">` → `<input required>` | ||
@@ -59,3 +59,3 @@ * Remove self-close slash: `<br />` → `<br>` | ||
```js | ||
// change I tags for EM | ||
// change I elements for EM | ||
@@ -111,3 +111,3 @@ var render = ejs.compile('<i>Hi</i> <p><i>Deep</i></p>', { | ||
This is the most basic usage of this feature. For more (like passing JS values and multiple content areas), see [custom-tags.md](https://github.com/sitegui/ejs-html/blob/master/custom-tags.md) | ||
This is the most basic usage of this feature. For more (like passing JS values and multiple content areas), see [custom-els.md](https://github.com/sitegui/ejs-html/blob/master/custom-els.md) | ||
@@ -131,7 +131,18 @@ ## Missing features | ||
* `filename`: used to name the file in render-time error's stack trace | ||
* `standAlone`: if `true`, return a function that can be exported somewhere else, for example, to compile in the server and render in the browser, use this. When `false` (default), the generated function will be optimized to run in the same VM it was compiled | ||
* `transformer`: a function that can transform the parsed HTML element tree, before the minification and compilation. This should return a new array of tokens or `undefined` to use the same (in case of in-place changes). Consult the definition of a `Token` in the [parse.js](https://github.com/sitegui/ejs-html/blob/master/lib/parse.js) file. | ||
This will return a compiled render function that can then be called like: `render(locals[, customRender])`. `locals` is the data object used to fill the template. `customRender` is an optional function used to render custom elements, see [custom-tags.md](https://github.com/sitegui/ejs-html/blob/master/custom-tags.md) for more info about it. | ||
This will return a compiled render function that can then be called like: `render(locals[, customRender])`. `locals` is the data object used to fill the template. `customRender` is an optional function used to render custom elements, see [custom-els.md](https://github.com/sitegui/ejs-html/blob/master/custom-els.md) for more info about it. | ||
### compile.standAlone(source[, options]) | ||
Like `compile()`, but returns the function body code as a string, so that it can be exported somewhere else. A use case for this is compile the EJS template in the server, export the function to the client and render in the browser: | ||
```js | ||
// On the server | ||
let functionBody = ejs.compile.standAlone('<p>Hi <%=name%></p>') | ||
// On the client | ||
var render = new Function('locals, customRender', functionBody) | ||
render({name: 'you'}) // <p>Hi you</p> | ||
``` | ||
### render(source[, locals[, options]]) | ||
@@ -147,8 +158,8 @@ Just a convinience for `compile(source, options)(locals)`. | ||
### escape.html(str) | ||
Make HTML-safe | ||
Return a HTML-safe version of `str`, escaping &, <, >, " and ' | ||
### escape.js(str) | ||
Escape as to make safe to put inside double quotes: `x = "..."` | ||
Escape as to make safe to put inside double quotes: `x = "..."`, escaping \, \n, \r and " | ||
### escape.getSnippet(source, lineStart, lineEnd) | ||
Extract the code snippet in the given region (used internally to create error messages) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
44872
2
159
1
917