ejs-html
Advanced tools
Comparing version 5.0.0 to 5.1.0
@@ -0,1 +1,4 @@ | ||
# 5.1.0 | ||
* Added: `sourceMap` option to create source maps | ||
# 5.0.0 | ||
@@ -2,0 +5,0 @@ |
@@ -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, '<') | ||
.replace(/>/g, '>') | ||
.replace(/'/g, ''') | ||
.replace(/"/g, '"') | ||
}` | ||
module.exports.html.standAloneCode = 'function __e(s) {' + | ||
'return s==null?"":String(s)' + | ||
'.replace(/&/g,"&")' + | ||
'.replace(/</g,"<")' + | ||
'.replace(/>/g,">")' + | ||
'.replace(/\'/g,"'")' + | ||
'.replace(/"/g,""")' + | ||
'}' | ||
@@ -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)`. |
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents 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
Uses eval
Supply chain riskPackage uses eval() which is a dangerous function. This prevents 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
67118
20
1450
215
1
+ Addedsource-map@^0.7.0
+ Addedsource-map@0.7.4(transitive)