Socket
Socket
Sign inDemoInstall

ejs-html

Package Overview
Dependencies
1
Maintainers
2
Versions
21
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 5.0.0 to 5.1.0

.vscode/launch.json

3

HISTORY.md

@@ -0,1 +1,4 @@

# 5.1.0
* Added: `sourceMap` option to create source maps
# 5.0.0

@@ -2,0 +5,0 @@

197

lib/compile.js

@@ -7,2 +7,3 @@ 'use strict'

prepareOptions = require('./prepareOptions'),
sourceBuilder = require('./sourceBuilder'),
reduce

@@ -41,3 +42,7 @@

let jsCode = prepareInternalJSCode(source, options)
let {
code,
map,
mapWithCode
} = prepareInternalJSCode(source, options).build(source)

@@ -47,3 +52,3 @@ let internalRender

// eslint-disable-next-line no-new-func
internalRender = new Function('locals, renderCustom, __e, __l', jsCode)
internalRender = new Function('locals, renderCustom, __e, __l', code)
} catch (e) {

@@ -54,23 +59,32 @@ e.message += ` (in ${options.filename}, while compiling ejs)`

let fn
if (!options.compileDebug) {
// No special exception handling
return function (locals, renderCustom) {
fn = function (locals, renderCustom) {
return internalRender(locals, renderCustom, escape.html)
}
} else {
fn = function (locals, renderCustom) {
let line = {
s: 0,
e: 0
}
try {
return internalRender(locals, renderCustom, escape.html, line)
} catch (err) {
let snippet = getSnippet(source, line.s, line.e)
err.path = options.filename
err.message = `${options.filename}:${line.s}\n${snippet}\n\n${err.message}`
throw err
}
}
}
return function (locals, renderCustom) {
let line = {
s: 0,
e: 0
}
try {
return internalRender(locals, renderCustom, escape.html, line)
} catch (err) {
let snippet = getSnippet(source, line.s, line.e)
err.path = options.filename
err.message = `${options.filename}:${line.s}\n${snippet}\n\n${err.message}`
throw err
}
if (options.sourceMap) {
fn.code = code
fn.map = map
fn.mapWithCode = mapWithCode
}
return fn
}

@@ -87,26 +101,37 @@

module.exports.standAlone = function (source, options) {
return module.exports.standAloneAsObject(source, options).code
}
/**
* 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, renderCustom', returnedCode)
* @param {string} source
* @param {Object} [options] - see {@link prepareOptions}
* @returns {{code: string, map: ?string, mapWithCode: ?string}}
*/
module.exports.standAloneAsObject = function (source, options) {
options = prepareOptions(options)
let jsCode = prepareInternalJSCode(source, options)
let subBuilder = prepareInternalJSCode(source, options),
builder = sourceBuilder(options)
if (!options.compileDebug) {
// No special exception handling
return `let __e = ${escape.html.standAloneCode};
${jsCode}`
builder.add(`${escape.html.standAloneCode}\n`)
builder.addBuilder(subBuilder)
} else {
builder.add(`${escape.html.standAloneCode}\n`)
builder.add(`let __gS=${getSnippet.toString()},__l={s:0,e:0},__s="${escape.js(source)}";`)
builder.add('try {')
builder.addBuilder(subBuilder)
builder.add('}catch(e){')
builder.add('let s=__gS(__s,__l.s,__l.e);')
builder.add(`e.path="${escape.js(options.filename)}";`)
builder.add(`e.message="${escape.js(options.filename)}:"+__l.s+"\\n"+s+"\\n\\n"+e.message;`)
builder.add('throw e;')
builder.add('}')
}
return `let __e = ${escape.html.standAloneCode},
__gS = ${getSnippet.toString()},
__l = {s: 0, e: 0},
__s = "${escape.js(source)}";
try {
${jsCode}
} catch (__x) {
let __snippet = __gS(__s, __l.s, __l.e);
__x.path = "${escape.js(options.filename)}";
__x.message = "${escape.js(options.filename)}:" + __l.s + "\\n" +
__snippet + "\\n\\n" +
__x.message;
throw __x;
}`
return builder.build(source)
}

@@ -119,3 +144,3 @@

* @param {Object} options - already prepared
* @returns {Render}
* @returns {SourceBuilder}
*/

@@ -131,6 +156,5 @@ function prepareInternalJSCode(source, options) {

let reducedTokens = reduce(tokens, options),
jsCode = createCode(reducedTokens, options, false)
let reducedTokens = reduce(tokens, options)
return jsCode
return createCode(reducedTokens, options, false)
}

@@ -145,12 +169,14 @@

* @param {boolean} asInnerExpression - whether to return code to be used inside a parent createCode() context
* @returns {string}
* @returns {SourceBuilder}
*/
function createCode(tokens, options, asInnerExpression) {
let builder = sourceBuilder(options)
if (!tokens.length || (tokens.length === 1 && typeof tokens[0] === 'string')) {
// Special case for static string
return `${asInnerExpression ? '' : 'return'}"${escape.js(tokens[0] || '')}"`
builder.add(`${asInnerExpression ? '' : 'return'}"${escape.js(tokens[0] || '')}"`)
return builder
}
let hasStatements = tokens.some(t => typeof t === 'object' && t.type === 'ejs-eval'),
code = ''
let hasStatements = tokens.some(t => typeof t === 'object' && t.type === 'ejs-eval')

@@ -170,11 +196,11 @@ // Current print position for an expression, possible values for hasStatements:

if (options.strictMode) {
code += '"use strict";'
builder.add('"use strict";')
}
code += 'locals=locals||{};let __c=locals.__contents||{};'
builder.add('locals=locals||{};let __c=locals.__contents||{};')
if (!options.strictMode) {
code += 'with(locals){'
builder.add('with(locals){')
}
if (options.vars.length) {
code += 'let ' +
options.vars.map(each => `${each}=locals.${each}`).join(',') + ';'
builder.add('let ' +
options.vars.map(each => `${each}=locals.${each}`).join(',') + ';')
}

@@ -186,7 +212,7 @@ }

// Wrap in an immediate-invocated function
code += '(function(){'
builder.add('(function(){')
}
code += 'let __o='
builder.add('let __o=')
} else if (!asInnerExpression) {
code += 'return '
builder.add('return ')
}

@@ -199,9 +225,11 @@

if (typeof token === 'string') {
appendExpression(`"${escape.js(token)}"`, true, null)
appendExpression(`"${escape.js(token)}"`, null, null, true)
} else if (token.type === 'ejs-eval') {
appendStatement(token.content.trim(), token)
appendStatement(token)
} else if (token.type === 'ejs-escaped') {
appendExpression(`__e(${token.content.trim()})`, true, token)
appendExpression('__e(', token, ')', true)
} else if (token.type === 'ejs-raw') {
appendExpression(`(${token.content.trim()})`, false, token)
appendExpression('(', token, ')', false)
} else if (token.type === 'source-builder') {
appendExpression(null, token, null, true)
}

@@ -212,37 +240,52 @@ }

if (state === 'rest' && !asInnerExpression) {
code += ';'
builder.add(';')
}
if (hasStatements) {
code += 'return __o;'
builder.add('return __o;')
if (asInnerExpression) {
code += '})()'
builder.add('})()')
}
}
if (!asInnerExpression && !options.strictMode) {
code += '}' // close with(locals)
builder.add('}') // close with(locals)
}
return code
return builder
/**
* Append an expression that contributes directly to the output
* @param {string} exp
* @param {?string} prefix
* @param {?Token|SourceBuilder} token
* @param {?string} suffix
* @param {boolean} isString - whether this expression certainly evaluates to a string
* @param {?Token} token
*/
function appendExpression(exp, isString, token) {
function appendExpression(prefix, token, suffix, isString) {
if (state === 'very-first') {
if (!isString) {
code += '""+'
builder.add('""+')
}
} else if (state === 'first') {
code += '__o+='
builder.add('__o+=')
} else {
code += '+'
builder.add('+')
}
if (options.compileDebug && token) {
code += `(${getDebugMarker(token)},${exp})`
} else {
code += exp
builder.add(`(${getDebugMarker(token)},`)
}
if (prefix) {
builder.add(prefix)
}
if (token) {
if (token.type === 'source-builder') {
builder.addBuilder(token.sourceBuilder)
} else {
builder.addToken(token)
}
}
if (suffix) {
builder.add(suffix)
}
if (options.compileDebug && token) {
builder.add(')')
}

@@ -255,16 +298,20 @@ state = 'rest'

* (This won't be called if !hasStatements)
* @param {string} st
* @param {?Token} token
* @param {Token} token
*/
function appendStatement(st, token) {
function appendStatement(token) {
if (state === 'very-first') {
code += '"";'
builder.add('"";')
} else if (state === 'rest') {
code += ';'
builder.add(';')
}
if (options.compileDebug && token) {
code += `${getDebugMarker(token)};`
if (options.compileDebug) {
builder.add(`${getDebugMarker(token)};`)
}
code += `${st}\n`
if (token.type === 'source-builder') {
builder.addBuilder(token.sourceBuilder)
} else {
builder.addToken(token)
}
builder.add('\n')

@@ -271,0 +318,0 @@ state = 'first'

@@ -5,3 +5,4 @@ 'use strict'

reduce = require('./reduce'),
compile = require('./compile')
compile = require('./compile'),
sourceBuilder = require('./sourceBuilder')

@@ -15,6 +16,7 @@ /**

module.exports.prepareContent = function (element, options) {
let jsCode = 'renderCustom('
let builder = sourceBuilder(options)
// First parameter: tag name
jsCode += `"${jsEscape(element.name)}",{`
builder.add('renderCustom(')
builder.add(`"${jsEscape(element.name)}",{`)

@@ -24,3 +26,3 @@ // Second argument: locals

let attribute = element.attributes[i]
jsCode += `"${jsEscape(makeCamelCase(attribute.name))}":`
builder.add(`"${jsEscape(makeCamelCase(attribute.name))}":`)

@@ -30,5 +32,5 @@ if (attribute.type === 'attribute-simple') {

// Pseudo-boolean attribute
jsCode += 'true'
builder.add('true')
} else {
jsCode += `"${jsEscape(attribute.value)}"`
builder.add(`"${jsEscape(attribute.value)}"`)
}

@@ -47,22 +49,29 @@ } else if (attribute.type === 'attribute') {

}
jsCode += ','
builder.add(',')
}
jsCode += '__contents:{'
builder.add('__contents:{')
let contents = prepareContents(element.children)
let contents = prepareContents(element.children),
firstContent = true
contents.forEach((tokens, name) => {
let content = compile._createCode(reduce(tokens, options), options, true)
jsCode += `"${jsEscape(name)}":${content},`
if (!firstContent) {
builder.add(',')
} else {
firstContent = false
}
let subBuilder = compile._createCode(reduce(tokens, options), options, true)
builder.add(`"${jsEscape(name)}":`)
builder.addBuilder(subBuilder)
})
jsCode += options.compileDebug ?
builder.add(options.compileDebug ?
`}},${compile._getDebugMarker(element)})` :
'}})'
'}})')
return {
type: 'ejs-raw',
type: 'source-builder',
start: element.start,
end: element.end,
content: jsCode
sourceBuilder: builder
}

@@ -77,10 +86,10 @@

if (i) {
jsCode += '+'
builder.add('+')
}
if (part.type === 'text') {
jsCode += `"${jsEscape(part.content)}"`
builder.add(`"${jsEscape(part.content)}"`)
} else if (part.type === 'ejs-escaped') {
jsCode += 'String('
builder.add('String(')
appendJSValue(part)
jsCode += ')'
builder.add(')')
} else if (part.type === 'ejs-eval') {

@@ -97,7 +106,8 @@ throw new Error('EJS eval tags are not allowed inside attribute values in custom elements')

function appendJSValue(token) {
builder.add('(')
if (options.compileDebug) {
jsCode += `(${compile._getDebugMarker(token)},${token.content})`
} else {
jsCode += `(${token.content})`
builder.add(`${compile._getDebugMarker(token)},`)
}
builder.addToken(token)
builder.add(')')
}

@@ -114,8 +124,12 @@ }

escapedName = jsEscape(name),
content = compile._createCode(reduce(element.children, options), options, true)
subBuilder = compile._createCode(reduce(element.children, options), options, true),
builder = sourceBuilder(options)
builder.add(`__c["${escapedName}"]&&/\\S/.test(__c["${escapedName}"])?__c["${escapedName}"]:`)
builder.addBuilder(subBuilder)
return {
type: 'ejs-raw',
type: 'source-builder',
start: element.start,
end: element.end,
content: `__c["${escapedName}"]&&/\\S/.test(__c["${escapedName}"])?__c["${escapedName}"]:${content}`
sourceBuilder: builder
}

@@ -122,0 +136,0 @@ }

@@ -33,10 +33,10 @@ 'use strict'

*/
module.exports.html.standAloneCode = `function escape(str) {
return str == null ? '' : String(str)
.replace(/&/g, '&')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/'/g, '&#39;')
.replace(/"/g, '&#34;')
}`
module.exports.html.standAloneCode = 'function __e(s) {' +
'return s==null?"":String(s)' +
'.replace(/&/g,"&amp;")' +
'.replace(/</g,"&lt;")' +
'.replace(/>/g,"&gt;")' +
'.replace(/\'/g,"&#39;")' +
'.replace(/"/g,"&#34;")' +
'}'

@@ -43,0 +43,0 @@ /**

@@ -12,3 +12,3 @@ 'use strict'

// Match the end of the current ejs-* token
ejsEndRegex = /%>/g,
ejsEndRegex = /\s*%>/g,
// Match the end of the current doctype or close-tag

@@ -41,3 +41,3 @@ tagEndRegex = />/g,

* @typedef {Object} Token
* @property {string} type - one of: text, ejs-eval, ejs-escaped, ejs-raw, comment, doctype, element
* @property {string} type - one of: text, ejs-eval, ejs-escaped, ejs-raw, comment, doctype, element, source-builder
* @property {SourcePoint} start - inclusive

@@ -50,2 +50,3 @@ * @property {SourcePoint} end - non inclusive

* @property {?Array<Token>} children - present for type: element
* @property {?SourceBuilder} sourceBuilder - present for type source-builder
*/

@@ -130,7 +131,7 @@

} else if (match[1] === '%=') {
tokens.push(readSimpleToken('ejs-escaped', ejsEndRegex))
tokens.push(readSimpleToken('ejs-escaped', ejsEndRegex, true))
} else if (match[1] === '%-') {
tokens.push(readSimpleToken('ejs-raw', ejsEndRegex))
tokens.push(readSimpleToken('ejs-raw', ejsEndRegex, true))
} else if (match[1] === '%') {
tokens.push(readSimpleToken('ejs-eval', ejsEndRegex))
tokens.push(readSimpleToken('ejs-eval', ejsEndRegex, true))
} else if (match[1] === '/') {

@@ -174,5 +175,6 @@ let closeTag = readCloseTag(),

* @param {RegExp} endRegex
* @param {boolean} trimRight - ignore starting empty spaces (\s)
* @returns {Token}
*/
function readSimpleToken(type, endRegex) {
function readSimpleToken(type, endRegex, trimRight) {
let match = exec(endRegex)

@@ -182,2 +184,8 @@ if (!match) {

}
if (trimRight) {
let matchTrim = exec(/\S/g)
if (matchTrim) {
advanceTo(matchTrim.index)
}
}
let start = getSourcePoint()

@@ -354,3 +362,3 @@ advanceTo(match.index)

} else if (match[0] === '<%=') {
parts.push(readSimpleToken('ejs-escaped', ejsEndRegex))
parts.push(readSimpleToken('ejs-escaped', ejsEndRegex, true))
} else if (match[0] === '<%') {

@@ -357,0 +365,0 @@ throwSyntaxError('EJS eval tags are not allowed inside attribute values')

@@ -10,2 +10,3 @@ 'use strict'

* @param {Array<string>} [options.vars=[]]
* @param {boolean} [options.sourceMap=false]
* @returns {Object}

@@ -26,4 +27,7 @@ */

}
if (options.sourceMap === undefined) {
options.sourceMap = false
}
return options
}
'use strict'
let prepareOptions = require('./prepareOptions'),
sourceBuilder = require('./sourceBuilder'),
escape = require('./escape'),
validUnquotedRegex = /^[^\s>"'<=`]*$/,

@@ -12,3 +14,3 @@ // Elements to keep inner whitespaces

* The returned array has strings for fixed content and Token instances for dynamic ones
* The token types on the resulting array may have one of the types: ejs-eval, ejs-escaped, ejs-raw
* The token types on the resulting array may have one of the types: ejs-eval, ejs-escaped, ejs-raw, source-builder
* @param {Array<Token>} tokens

@@ -142,18 +144,16 @@ * @param {Object} [options] - see {@link prepareOptions}

// Special case for <tag attr="<%=value%>">, treat this as:
// <tag<%if (value) {%> attr<%}%>>
// <tag<%if(value){%> attr<%}%>>
// <tag<%-(value)?' attr':''%>>
// Since attr is boolean, we don't want to output it
// when `value` is falsy
let subBuilder = sourceBuilder(options)
subBuilder.add('(')
subBuilder.addToken(firstPart)
subBuilder.add(`)?" ${escape.js(attribute.name)}":""`)
newTokens.push({
type: 'ejs-eval',
type: 'source-builder',
start: firstPart.start,
end: firstPart.end,
content: `if (${firstPart.content}) {`
sourceBuilder: subBuilder
})
appendText(` ${attribute.name}`, false)
newTokens.push({
type: 'ejs-eval',
start: firstPart.start,
end: firstPart.end,
content: '}'
})
continue

@@ -160,0 +160,0 @@ }

{
"name": "ejs-html",
"version": "5.0.0",
"version": "5.1.0",
"author": "Sitegui <sitegui@sitegui.com.br>",

@@ -20,3 +20,5 @@ "description": "Embedded JavaScript HTML templates. An implementation of EJS focused on run-time performance, HTML syntax checking, minified HTML output and custom HTML elements.",

],
"dependencies": {},
"dependencies": {
"source-map": "^0.7.0"
},
"license": "MIT",

@@ -30,5 +32,5 @@ "engines": {

"devDependencies": {
"mocha": "^4.1.0",
"mocha": "^5.0.0",
"should": "^13.2.1"
}
}

@@ -143,2 +143,12 @@ # EJS HTML

## Source maps
Compile with support for source map generation
```js
let fn = ejs.compile('Hello <%= locals.world %>', {sourceMap: true})
// The actual result may vary
fn.code // "use strict";locals=locals||{};let __c=locals.__contents||{};return "Hello "+(__l.s=__l.e=1,__e(locals.world));
fn.map // {"version":3,"sources":["ejs"],"names":[],"mappings":"gGAAU,Y","file":"ejs.js"}
fn.mapWithCode // {"version":3,"sources":["ejs"],"names":[],"mappings":"gGAAU,Y","file":"ejs.js","sourcesContent":["Hello <%= locals.world %>"]}
```
## Missing features

@@ -164,5 +174,11 @@ The following list of features are supported in other EJS implementations, but not by this one (at least, yet):

* `vars`: an array of var names that will be exposed from `locals` (defaults to `[]`).
* `sourceMap`: if `true`, create and return the source map
This will return a compiled render function that can then be called like: `render(locals[, renderCustom])`. `locals` is the data object used to fill the template. `renderCustom` 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.
The returned function has three extra properties if `sourceMap` is active:
* `fn.code`: compiled JS code
* `fn.map`: source map without the source code
* `fn.mapWithCode`: source map with the source code
### compile.standAlone(source[, options])

@@ -173,3 +189,3 @@ 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:

// On the server
let functionBody = ejs.compile.standAlone('<p>Hi <%=name%></p>')
let functionBody = ejs.compile.standAlone('<p>Hi <%=name%></p>', {vars: ['name']})

@@ -181,2 +197,7 @@ // On the client

### compile.standAloneAsObject(source[, options])
Like `compile.standAlone()`, but returns an object with three properties:
* `obj.code`: the compiled code, the same value returned by `compile.standAlone()`
* `obj.map` and `obj.mapWithCode`: extra properties when `sourceMap` option is active
### render(source[, locals[, options]])

@@ -183,0 +204,0 @@ Just a convinience for `compile(source, options)(locals)`.

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc