Skip to content

Commit da251b2

Browse files
committed
feat(beta): add filters and rate limiters and update for NX beta
1 parent 04eab31 commit da251b2

File tree

11 files changed

+557
-120
lines changed

11 files changed

+557
-120
lines changed

README.md

Lines changed: 99 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
# nx-compile
1+
# The compiler util
22

33
This library is part of the [NX framework](http://nx-framework.com).
44

5-
The purpose of this library is to allow the execution of strings as code in the
6-
context of an object. It combines ES6 Proxies with the JavaScript `with` keyword to achieve this.
5+
The main purpose of this library is to allow the execution of strings as code in the
6+
context of an object.
77

88
## Installation
99

1010
```
11-
$ npm install @risingstack/nx-compile
11+
$ npm install @nx-js/compiler-util
1212
```
1313

1414
## Platform support
@@ -24,24 +24,24 @@ $ npm install @risingstack/nx-compile
2424
## Usage
2525

2626
```js
27-
const compiler = require('@risingstack/nx-compile')
27+
const compiler = require('@nx-js/compiler-util')
2828
```
2929

30-
## API
30+
### Compiling code
3131

32-
### compiler.compileCode(String)
33-
34-
This method creates a function out of a string and returns it. The returned function takes
32+
`compiler.compileCode(string)` creates a function from a string. The returned function takes
3533
an object as argument and executes the string as code in the context of the passed object.
36-
The string can be any valid javascript code.
34+
The string can be any valid JavaScript code.
3735

3836
```js
3937
const code = compiler.compileCode('return prop1 + prop2')
4038
const sum = code({prop1: 1, prop2: 2}) // sum is 3
4139
```
4240

41+
#### Temporary variables
42+
4343
The returned function also accepts a second object argument, that may contain temporary variables.
44-
Temporary variables are added to the context object while the code is executing and removed after.
44+
Temporary variables are added to the context object while the code is executing.
4545
They are favored over the permanent context variables.
4646

4747
```js
@@ -51,9 +51,58 @@ const temporary = {prop1: 2}
5151
const sum = code(context, temporary) // sum is 4, context is still {prop1: 1, prop2: 2}
5252
```
5353

54-
### compiler.compileExpression(String)
54+
#### Limiters
55+
56+
Limiters are functions, which can defer or block code execution. Some popular limiters are debounce and throttle for example. Limiters can be registered by name with `compiler.limiter(name, function)` and used at the end of the code with the `&` symbol.
57+
58+
```js
59+
// next is the code or the next limiter
60+
compiler.limiter('delay', next => setTimeout(next, 1000))
61+
62+
const code = compiler.compileCode('console.log(message) & delay')
63+
const context = {message: 'Hello World'}
64+
code(context) // prints 'Hello World' to the console after a second
65+
```
66+
67+
Limiters accept a context object, which can be used to share a context between executions of the code. It makes the creation of rate limiters - like throttle and debounce - straightforward.
68+
69+
```js
70+
compiler.limiter('debounce', debounce)
71+
72+
function debounce (next, context) {
73+
clearTimeout(context.timer)
74+
context.timer = setTimeout(next, 200)
75+
}
76+
```
77+
78+
After the context argument limiters accept any number of custom arguments. These can be passed after the limiter name in the code, separated by spaces.
79+
80+
```js
81+
compiler.limiter('delay', (next, context, amount) => setTimeout(next, amount))
82+
83+
const code = compiler.compileCode('console.log(message) & delay 2000')
84+
const code2 = compiler.compileCode('console.log(message) & delay amount')
85+
86+
const context = {message: 'Hello World', amount: 3000}
87+
code(context) // prints 'Hello World' to the console after 2 seconds
88+
code2(context) // prints 'Hello World' to the console after 3 seconds
89+
```
90+
91+
Multiple limiters can be piped with the `&` symbol.
92+
93+
```js
94+
const code = compiler.compileCode('console.log(message) & delay 1000 & throttle 100')
95+
96+
// this logs 'Hello World' a second after you click the button
97+
// and it logs a message once per 100 milliseconds at most, excess messages are not logged
98+
button.addEventListener('code', () => code({message: 'Hello World'}))
99+
```
100+
101+
You can find some commonly used limiters in [this repo](https://github.com/nx-js/limiters).
55102

56-
This method creates a function out of a string and returns it. The returned function takes
103+
### Compiling expressions
104+
105+
`compiler.compileExpression(string)` creates a function from a string. The returned function takes
57106
an object as argument and executes the string as an expression in the context of the passed object.
58107
It returns the result of the evaluated expression. The string can be any javascript expression
59108
that may come after a return statement.
@@ -76,49 +125,65 @@ context.item = {name: 'item name'}
76125
result = expression(context) // result is 'item name'
77126
```
78127

79-
### compiler.expose(String, String, String, ...)
128+
#### Filters
80129

81-
Use this method to expose globals to the compiler. Non of the globals are exposed by default.
130+
Filters are functions, which can filter and modify expression result. Some popular filters are upperCase and trim for example. Filters can be registered by name with `compiler.filter(name, function)` and used at the end of the expression with the `|` symbol.
82131

83132
```js
84-
const code = compiler.compileCode('console.log(Math.round(num))')
85-
compiler.expose('console', 'Math')
86-
code({num: 1.8}) // logs 2 to the console
133+
// txt is the result of the expression
134+
compiler.filter('upperCase', txt => txt.toUpperCase())
135+
136+
const expr = compiler.compileExpression('message | upperCase')
137+
const context = {message: 'Hello World'}
138+
console.log(expr(context)) // prints 'HELLO WORLD' to the console
87139
```
88140

89-
Context variables are always favored over global ones, when both are present (with the same name).
141+
Filters accept any number of custom arguments. These can be passed after the filter name in the expression, separated by spaces.
142+
143+
```js
144+
compiler.filter('splice', (txt, start, end) => txt.splice(start, end))
90145

91-
### compiler.hide(String, String, String, ...)
146+
const expr = compiler.compileExpression('message | splice 0 6')
147+
const context = {message: 'Hello World'}
148+
console.log(expr(context)) // prints 'Hello' to the console
149+
```
92150

93-
Use this method to hide exposed globals from the compiler.
151+
Multiple filters can be piped with the `|` symbol.
152+
153+
```js
154+
const expr = compiler.compileExpression('message | splice 0 6 | upperCase')
155+
const context = {message: 'Hello World'}
156+
console.log(expr(context)) // prints 'HELLO' to the console
157+
```
158+
159+
You can find some commonly used filters in [this repo](https://github.com/nx-js/filters).
160+
161+
### Handling globals
162+
163+
`compiler.expose('String, String, ...')` exposes globals by name for the compiler. Non of the globals are exposed by default.
94164

95165
```js
96166
const code = compiler.compileCode('console.log(Math.round(num))')
97167
compiler.expose('console', 'Math')
98168
code({num: 1.8}) // logs 2 to the console
99-
compiler.hide('console', 'Math')
100-
code({num: 1.8}) // throws an error, console and Math are undefined
101169
```
102170

103-
Context variables are always favored over global ones when both are present (with the same name).
171+
Context variables are always favored over global ones, when both are present with the same name.
104172

105-
## Example
173+
`compiler.hide(String, String, ...)` hides globals by name, while `compiler.hideAll()` hides all globals.
106174

107175
```js
108-
const compiler = require('@risingstack/nx-compile')
109-
110-
compiler.expose('console')
111-
const context = {name: 'nx-compile'}
112-
const tempVars = {version: '4.2.0'}
113-
const code = compiler.compileCode('console.log(name + version)')
114-
115-
code(context, tempVars) // outputs 'nx-compile4.2.0' to console
176+
const code = compiler.compileCode('console.log(Math.round(num))')
177+
compiler.expose('console', 'Math')
178+
code({num: 1.8}) // logs 2 to the console
179+
compiler.hide('console', 'Math')
180+
code({num: 1.8}) // throws an error, console and Math are undefined
116181
```
117182

118183
## Contributions
119184

120185
This library has the very specific purpose of supporting the
121-
[NX framework](https://github.com/RisingStack/nx-framework).
186+
[NX framework](https://github.com/nx-js/framework).
122187
Features should only be added, if they are used by the framework. Otherwise please fork.
123188

124189
Bug fixes, tests and doc updates are always welcome.

index.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
'use strict'
2+
3+
const context = require('./src/context')
4+
const modifiers = require('./src/modifiers')
5+
const compiler = require('./src/compiler')
6+
7+
module.exports = {
8+
compileExpression: compiler.compileExpression,
9+
compileCode: compiler.compileCode,
10+
expose: context.expose,
11+
hide: context.hide,
12+
hideAll: context.hideAll,
13+
filter: modifiers.filter,
14+
limiter: modifiers.limiter
15+
}

package.json

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,33 @@
11
{
2-
"name": "@risingstack/nx-compile",
3-
"version": "4.2.0",
4-
"description": "Execution of string as code in the context of an object, similarly to Object.eval().",
5-
"main": "compiler.js",
2+
"name": "@nx-js/compiler-util",
3+
"version": "1.0.0",
4+
"description": "An NX util, responsible for executing code in the context of an object.",
5+
"main": "index.js",
66
"scripts": {
7-
"test": "mocha compiler.test.js",
8-
"lint": "standard compiler.js"
7+
"test": "mocha test",
8+
"lint": "standard"
99
},
1010
"author": {
1111
"name": "Miklos Bertalan",
1212
"email": "[email protected]"
1313
},
1414
"repository": {
1515
"type": "git",
16-
"url": "[email protected]:RisingStack/nx-compile.git"
16+
"url": "[email protected]:nx-js/compiler-util.git"
1717
},
1818
"bugs": {
19-
"url": "https://github.com/RisingStack/nx-compile/issues"
19+
"url": "https://github.com/nx-js/compiler-util/issues"
2020
},
21-
"homepage": "https://github.com/RisingStack/nx-compile#readme",
21+
"homepage": "https://github.com/nx-js/compiler-util#readme",
2222
"license": "MIT",
2323
"keywords": [
2424
"nx",
25+
"util",
2526
"compile",
2627
"context",
2728
"eval",
28-
"execution",
29-
"code"
29+
"code",
30+
"expression"
3031
],
3132
"devDependencies": {
3233
"chai": "3.5.0",
@@ -37,6 +38,11 @@
3738
"engines": {
3839
"node": ">=6.0.0"
3940
},
41+
"standard": {
42+
"ignore": [
43+
"test"
44+
]
45+
},
4046
"pre-push": [
4147
"lint",
4248
"test"

src/compiler.js

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict'
2+
3+
const parser = require('./parser')
4+
5+
const expressionCache = new Map()
6+
const codeCache = new Map()
7+
8+
module.exports = {
9+
compileExpression,
10+
compileCode
11+
}
12+
13+
function compileExpression (src) {
14+
if (typeof src !== 'string') {
15+
throw new TypeError('First argument must be a string.')
16+
}
17+
let expression = expressionCache.get(src)
18+
if (!expression) {
19+
expression = parser.parseExpression(src)
20+
expressionCache.set(src, expression)
21+
}
22+
23+
if (typeof expression === 'function') {
24+
return expression
25+
}
26+
27+
return function evaluateExpression (context) {
28+
let value = expression.exec(context)
29+
for (let filter of expression.filters) {
30+
const args = filter.argExpressions.map(evaluateArgExpression, context)
31+
value = filter.effect(value, ...args)
32+
}
33+
return value
34+
}
35+
}
36+
37+
function compileCode (src) {
38+
if (typeof src !== 'string') {
39+
throw new TypeError('First argument must be a string.')
40+
}
41+
let code = codeCache.get(src)
42+
if (!code) {
43+
code = parser.parseCode(src)
44+
codeCache.set(src, code)
45+
}
46+
47+
if (typeof code === 'function') {
48+
return code
49+
}
50+
51+
const context = {}
52+
return function evaluateCode (state, tempVars) {
53+
let i = 0
54+
function next () {
55+
Object.assign(context, tempVars)
56+
if (i < code.limiters.length) {
57+
const limiter = code.limiters[i++]
58+
const args = limiter.argExpressions.map(evaluateArgExpression, state)
59+
limiter.effect(next, context, ...args)
60+
} else {
61+
code.exec(state, tempVars)
62+
}
63+
}
64+
next()
65+
}
66+
}
67+
68+
function evaluateArgExpression (argExpression) {
69+
return argExpression(this)
70+
}

0 commit comments

Comments
 (0)