pure-engine
Advanced tools
Comparing version 0.9.13 to 0.9.14
{ | ||
"name": "pure-engine", | ||
"version": "0.9.13", | ||
"version": "0.9.14", | ||
"description": "Compile HTML templates into JS", | ||
@@ -13,4 +13,4 @@ "main": "index.js", | ||
"engines": { | ||
"node": ">= 8.11.3", | ||
"npm": ">= 6.2.0" | ||
"node": ">= 10.14.0", | ||
"npm": ">= 6.4.1" | ||
}, | ||
@@ -57,2 +57,4 @@ "repository": { | ||
"ava": "^0.25.0", | ||
"babel-loader": "^8.0.4", | ||
"babel-plugin-transform-react-jsx": "^6.24.1", | ||
"benchmark": "^2.1.4", | ||
@@ -65,2 +67,7 @@ "coffeescript": "^2.3.2", | ||
"preact": "^8.3.1", | ||
"preact-compat": "^3.18.4", | ||
"react": "^16.6.3", | ||
"react-bootstrap": "^0.32.4", | ||
"react-dom": "^16.6.3", | ||
"react-text-mask": "^5.4.3", | ||
"rollup": "^0.67.1", | ||
@@ -74,3 +81,4 @@ "rollup-plugin-babel": "^4.0.3", | ||
"svelte": "^2.15.3", | ||
"underscore": "^1.9.1" | ||
"underscore": "^1.9.1", | ||
"webpack": "^4.26.1" | ||
}, | ||
@@ -77,0 +85,0 @@ "standard": { |
@@ -45,13 +45,82 @@ # pure-engine | ||
* import and require tags | ||
```html | ||
<import layout from="./layouts/default.html"/> | ||
<import form from="./components/form.html"/> | ||
<import input from="./components/input.html"/> | ||
<import button from="./components/button.html"/> | ||
<layout> | ||
<h1>Hello, world!</h1> | ||
<form> | ||
<input name="foo" /> | ||
<button>Submit</button> | ||
</form> | ||
</layout> | ||
``` | ||
* conditional tags: if, else, elseif, unless, elseunless | ||
```html | ||
<if foo>bar</if> | ||
``` | ||
* loops: for, each, foreach | ||
* import and require tags | ||
```html | ||
<for car in cars> | ||
{car.brand} | ||
</for> | ||
``` | ||
* filters for strings, numbers, arrays, objects and more | ||
```html | ||
{title | capitalize} | ||
``` | ||
* special attributes, e.g. inline for asset inline, width="auto" for auto sizing and more | ||
```html | ||
<img src="./foo.png" inline> | ||
``` | ||
* built-in i18n support (translate tag and filter) | ||
```html | ||
<i18n yaml> | ||
hello: | ||
- 'Hej!' | ||
- 'Hello!' | ||
</i18n> | ||
<h1><translate hello /></h1> | ||
``` | ||
* compiler tag for scripts (allows custom compilers) | ||
```html | ||
<div id="app"></div> | ||
<script compiler="preact"> | ||
import { render } from "preact" | ||
const Foo = ({ bar }) => { | ||
return (<span>{bar}</span>) | ||
} | ||
render( | ||
<Foo bar="baz" />, | ||
document.getElementById("app") | ||
) | ||
</script> | ||
``` | ||
* error handling: rescue tag | ||
## Input / Output Examples | ||
```html | ||
<h1>{person.title}</h1> | ||
<rescue>:(</rescue> | ||
``` | ||
## Input / Output | ||
``` | ||
@@ -105,7 +174,7 @@ <if foo is present>{bar}</if> | ||
``` | ||
pure-engine x 3,504,768 ops/sec ±1.80% (85 runs sampled) | ||
underscore x 210,428 ops/sec ±1.60% (90 runs sampled) | ||
lodash x 249,232 ops/sec ±1.10% (91 runs sampled) | ||
handlebars x 1,883,683 ops/sec ±1.70% (84 runs sampled) | ||
mustache x 450,925 ops/sec ±2.56% (87 runs sampled) | ||
pure-engine x 4,053,839 ops/sec ±0.91% (87 runs sampled) | ||
underscore x 161,728 ops/sec ±0.88% (91 runs sampled) | ||
lodash x 204,561 ops/sec ±0.73% (90 runs sampled) | ||
handlebars x 1,699,469 ops/sec ±1.12% (85 runs sampled) | ||
mustache x 447,168 ops/sec ±1.09% (82 runs sampled) | ||
Fastest is pure-engine | ||
@@ -112,0 +181,0 @@ ``` |
@@ -26,6 +26,7 @@ const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const { join, dirname } = require('path') | ||
const { parse } = require('himalaya') | ||
const { parse } = require('./parser') | ||
const yaml = require('yaml-js') | ||
const size = require('image-size') | ||
const { normalize } = require('./array') | ||
const { clone } = require('./object') | ||
let asyncCounter = 0 | ||
@@ -97,6 +98,2 @@ | ||
let paths = [] | ||
if (SELF_CLOSING_TAGS.includes(name)) { | ||
throw new Error(`Forbidden component name: ${name}. Reason: this tag is self closing.`) | ||
} | ||
if (options.paths) { | ||
@@ -191,3 +188,4 @@ paths = paths.concat(options.paths) | ||
const currentComponents = [] | ||
walk(htmlTree, async current => { | ||
let slots = 0 | ||
walk(htmlTree, async (current, parent) => { | ||
if (current.tagName === 'import' || current.tagName === 'require') { | ||
@@ -205,5 +203,10 @@ collectComponentsFromImport(current, statistics, currentComponents, component, options) | ||
} | ||
if (current.tagName === 'slot' || current.tagName === 'yield') { | ||
if (current.attributes.length === 0) { // <slot></slot> | ||
current.children = children | ||
if ((current.tagName === 'slot' || current.tagName === 'yield') && current.children.length === 0) { | ||
if (current.attributes.length === 0) { | ||
if (slots === 0) { | ||
current.children = children | ||
} else { | ||
current.children = clone(children) | ||
} | ||
slots += 1 | ||
} else { | ||
@@ -213,3 +216,10 @@ const name = current.attributes[0].key | ||
if ((leaf.tagName === 'slot' || leaf.tagName === 'yield') && leaf.attributes.length > 0 && leaf.attributes[0].key === name) { | ||
current.children = leaf.children | ||
// the following might not be super performant in case of components with multiple slots | ||
// we could do this only if a slot with given name is not unique (e.g. in if / else statements) | ||
if (slots > 2) { | ||
current.children = clone(leaf.children) | ||
} else { | ||
current.children = leaf.children | ||
} | ||
slots += 1 | ||
} | ||
@@ -937,5 +947,2 @@ }) | ||
collectComponentsFromPartialOrRender(fragment, statistics, options) | ||
walk(fragment, async node => { | ||
await collect(tree, node, variables, filters, components, statistics, translations, store, depth, options, promises, errors) | ||
}) | ||
} | ||
@@ -942,0 +949,0 @@ depth -= 1 |
const AbstractSyntaxTree = require('abstract-syntax-tree') | ||
const { parse } = require('himalaya') | ||
const { parse } = require('./parser') | ||
const walk = require('himalaya-walk') | ||
@@ -9,2 +9,4 @@ const { TEMPLATE_VARIABLE, OBJECT_VARIABLE, ESCAPE_VARIABLE, GLOBAL_VARIABLES } = require('./enum') | ||
const { array: { unique } } = require('pure-utilities') | ||
const Analyzer = require('./Analyzer') | ||
const Optimizer = require('./Optimizer') | ||
const Statistics = require('./Statistics') | ||
@@ -117,3 +119,9 @@ | ||
} | ||
const compiled = new Function(`return function render(${OBJECT_VARIABLE}, ${ESCAPE_VARIABLE}) {\n${program.toString()}}`)() // eslint-disable-line | ||
const analyzer = new Analyzer(program) | ||
const params = analyzer.params() | ||
const optimizer = new Optimizer(program) | ||
optimizer.optimize() | ||
const compiled = new Function(`return function render(${params}) {\n${program.toString()}}`)() // eslint-disable-line | ||
return { template: compiled, statistics, errors } | ||
@@ -120,0 +128,0 @@ } |
@@ -1,2 +0,2 @@ | ||
const { OBJECT_VARIABLE, ESCAPE_VARIABLE, BOOLEAN_ATTRIBUTES, UNESCAPED_NAMES, GLOBAL_VARIABLES } = require('./enum') | ||
const { OBJECT_VARIABLE, ESCAPE_VARIABLE, BOOLEAN_ATTRIBUTES, UNESCAPED_NAMES, GLOBAL_VARIABLES, RESERVED_KEYWORDS } = require('./enum') | ||
const { | ||
@@ -47,4 +47,23 @@ getLiteral, getIdentifier, getObjectMemberExpression, | ||
function placeholderName (keyword) { | ||
return `__${keyword.toUpperCase()}_PLACEHOLDER__` | ||
} | ||
function convertToExpression (string) { | ||
RESERVED_KEYWORDS.forEach(keyword => { | ||
string = string.replace(new RegExp(`\\b${keyword}\\b`, 'g'), placeholderName(keyword)) | ||
}) | ||
const tree = new AbstractSyntaxTree(string) | ||
tree.replace({ | ||
enter: node => { | ||
if (node.type === 'Identifier') { | ||
RESERVED_KEYWORDS.forEach(keyword => { | ||
if (node.name === placeholderName(keyword)) { | ||
node.name = keyword | ||
} | ||
}) | ||
} | ||
return node | ||
} | ||
}) | ||
const { expression } = tree.ast.body[0] | ||
@@ -128,95 +147,19 @@ return expression | ||
function getTemplateNode (expression, variables, unescape) { | ||
if (expression.type === 'Identifier') { | ||
if (expression.type === 'Literal') { | ||
return expression | ||
} else if (expression.type === 'Identifier') { | ||
const node = convertIdentifier(expression, variables) | ||
if (unescape) return node | ||
return getEscapeCallExpression(node) | ||
} | ||
if (expression.type === 'Literal') { | ||
return expression | ||
} else if (expression.type === 'BinaryExpression') { | ||
} else if ([ | ||
'MemberExpression', | ||
'CallExpression', | ||
'ArrayExpression', | ||
'NewExpression', | ||
'ConditionalExpression', | ||
'ObjectExpression', | ||
'LogicalExpression', | ||
'BinaryExpression' | ||
].includes(expression.type)) { | ||
AbstractSyntaxTree.replace(expression, (node, parent) => { | ||
if (node.type === 'MemberExpression' && node.property.type === 'Identifier') { | ||
node.property.omit = true | ||
} else if (node.type === 'Identifier' && !node.omit) { | ||
node.omit = true | ||
if (!variables.includes(node.name)) { | ||
const object = getIdentifier(OBJECT_VARIABLE) | ||
object.omit = true | ||
node = { | ||
type: 'MemberExpression', | ||
object, | ||
property: node | ||
} | ||
} | ||
} | ||
return node | ||
}) | ||
if (!unescape) { | ||
expression = getEscapeCallExpression(expression) | ||
} | ||
return expression | ||
} else if (expression.type === 'MemberExpression') { | ||
if (expression.object.type === 'Identifier') { | ||
if (variables.includes(expression.object.name)) { | ||
if (unescape) return expression | ||
return getEscapeCallExpression(expression) | ||
} | ||
if (expression.computed && expression.property.type === 'Identifier') { | ||
expression.property = convertIdentifier(expression.property, variables) | ||
} | ||
let leaf = { | ||
type: 'MemberExpression', | ||
object: getObjectMemberExpression(expression.object.name), | ||
property: expression.property, | ||
computed: expression.computed || expression.property.type === 'Literal' | ||
} | ||
if (unescape) return leaf | ||
return getEscapeCallExpression(leaf) | ||
} else if (expression.object.type === 'MemberExpression') { | ||
if (expression.computed && expression.property.type === 'Identifier') { | ||
expression.property = convertIdentifier(expression.property, variables) | ||
} | ||
let leaf = expression.object | ||
if (leaf.computed && leaf.property.type === 'Identifier') { | ||
leaf.property = convertIdentifier(leaf.property, variables) | ||
} | ||
while (leaf.object.type === 'MemberExpression') { | ||
leaf = leaf.object | ||
if (leaf.computed && leaf.property.type === 'Identifier') { | ||
leaf.property = convertIdentifier(leaf.property, variables) | ||
} | ||
} | ||
if (!variables.includes(leaf.object.name)) { | ||
leaf.object = getObjectMemberExpression(leaf.object.name) | ||
} | ||
if (unescape) return expression | ||
return getEscapeCallExpression(expression) | ||
} | ||
} else if (expression.type === 'CallExpression') { | ||
expression.arguments = expression.arguments.map(node => getTemplateNode(node, variables, true)) | ||
if (expression.callee.type === 'Identifier') { | ||
expression.callee = convertIdentifier(expression.callee, variables) | ||
if (unescape) return expression | ||
return getEscapeCallExpression(expression) | ||
} else if (expression.callee.type === 'MemberExpression') { | ||
let node = expression.callee.object | ||
if (expression.callee.object.type === 'Identifier') { | ||
expression.callee.object = convertIdentifier(expression.callee.object, variables) | ||
if (unescape) return expression | ||
return getEscapeCallExpression(expression) | ||
} else { | ||
while (node.object && node.object.type === 'MemberExpression') { | ||
node = node.object | ||
} | ||
if (node.type === 'CallExpression') { | ||
node.callee = convertIdentifier(node.callee, variables) | ||
} else { | ||
node.object = convertIdentifier(node.object, variables) | ||
} | ||
if (unescape) return expression | ||
return getEscapeCallExpression(expression) | ||
} | ||
} | ||
} else if (['ArrayExpression', 'NewExpression', 'ConditionalExpression', 'ObjectExpression'].includes(expression.type)) { | ||
AbstractSyntaxTree.replace(expression, (node, parent) => { | ||
if (node.type === 'Property' && node.key === node.value) { | ||
@@ -237,3 +180,3 @@ if (!variables.includes(node.key.name)) { | ||
node.omit = true | ||
} else if (node.type === 'MemberExpression' && node.property.type === 'Identifier') { | ||
} else if (node.type === 'MemberExpression' && node.property.type === 'Identifier' && !node.computed) { | ||
node.property.omit = true | ||
@@ -254,17 +197,6 @@ } else if (node.type === 'Identifier' && !node.omit && !GLOBAL_VARIABLES.includes(node.name)) { | ||
}) | ||
if (!unescape) { | ||
return getEscapeCallExpression(expression) | ||
if (unescape) { | ||
return expression | ||
} | ||
return expression | ||
} else if (expression.type === 'LogicalExpression') { | ||
if (expression.left.type === 'Identifier' && !variables.includes(expression.left.name)) { | ||
expression.left = getObjectMemberExpression(expression.left.name) | ||
} | ||
if (expression.right.type === 'Identifier' && !variables.includes(expression.right.name)) { | ||
expression.right = getObjectMemberExpression(expression.right.name) | ||
} | ||
if (!unescape) { | ||
return getEscapeCallExpression(expression) | ||
} | ||
return expression | ||
return getEscapeCallExpression(expression) | ||
} else { | ||
@@ -271,0 +203,0 @@ throw new Error(`Expression type: ${expression.type} isn't supported yet.`) |
@@ -85,3 +85,7 @@ module.exports = { | ||
'and' | ||
], | ||
RESERVED_KEYWORDS: [ | ||
'class', | ||
'for' | ||
] | ||
} |
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
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
87934
21
2188
184
28