Skip to content

Commit a7cf52b

Browse files
committed
wip
1 parent c436875 commit a7cf52b

File tree

7 files changed

+175
-5
lines changed

7 files changed

+175
-5
lines changed

packages/alpinejs/src/alpine.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { mapAttributes, directive, setPrefix as prefix, prefix as prefixed } fro
33
import { start, addRootSelector, addInitSelector, closestRoot, findClosest, initTree, destroyTree, interceptInit } from './lifecycle'
44
import { onElRemoved, onAttributeRemoved, onAttributesAdded, mutateDom, deferMutations, flushAndStopDeferringMutations, startObservingMutations, stopObservingMutations } from './mutation'
55
import { mergeProxies, closestDataStack, addScopeToNode, scope as $data } from './scope'
6-
import { setEvaluator, evaluate, evaluateLater, dontAutoEvaluateFunctions, evaluateRaw } from './evaluator'
6+
import { setEvaluator, setRawEvaluator, evaluate, evaluateLater, dontAutoEvaluateFunctions, evaluateRaw } from './evaluator'
77
import { transition } from './directives/x-transition'
88
import { clone, cloneNode, skipDuringClone, onlyDuringClone, interceptClone } from './clone'
99
import { interceptor, initInterceptors } from './interceptor'
@@ -50,6 +50,7 @@ let Alpine = {
5050
initInterceptors,
5151
injectMagics,
5252
setEvaluator,
53+
setRawEvaluator,
5354
mergeProxies,
5455
extractProp,
5556
findClosest,

packages/alpinejs/src/evaluator.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ export function setEvaluator(newEvaluator) {
3434
theEvaluatorFunction = newEvaluator
3535
}
3636

37+
let theRawEvaluatorFunction
38+
39+
export function setRawEvaluator(newEvaluator) {
40+
theRawEvaluatorFunction = newEvaluator
41+
}
42+
3743
export function normalEvaluator(el, expression) {
3844
let overriddenMagics = {}
3945

@@ -157,7 +163,11 @@ export function runIfTypeOfFunction(receiver, value, scope, params, el) {
157163
}
158164
}
159165

160-
export function evaluateRaw(el, expression, extras = {}) {
166+
export function evaluateRaw(...args) {
167+
return theRawEvaluatorFunction(...args)
168+
}
169+
170+
export function normalRawEvaluator(el, expression, extras = {}) {
161171
let overriddenMagics = {}
162172

163173
injectMagics(overriddenMagics, el)

packages/alpinejs/src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@ import Alpine from './alpine'
2323
* It's the function that converts raw JavaScript string
2424
* expressions like @click="toggle()", into actual JS.
2525
*/
26-
import { normalEvaluator } from './evaluator'
26+
import { normalEvaluator, normalRawEvaluator } from './evaluator'
2727

2828
Alpine.setEvaluator(normalEvaluator)
29+
Alpine.setRawEvaluator(normalRawEvaluator)
2930

3031
/**
3132
* _______________________________________________________

packages/csp/src/evaluator.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ import { tryCatch } from 'alpinejs/src/utils/error'
44
import { generateRuntimeFunction } from './parser'
55
import { injectMagics } from 'alpinejs/src/magics'
66

7+
export function cspRawEvaluator(el, expression, extras = {}) {
8+
let dataStack = generateDataStack(el)
9+
10+
let scope = mergeProxies([extras.scope ?? {}, ...dataStack])
11+
12+
let evaluate = generateRuntimeFunction(expression)
13+
14+
let result = evaluate({
15+
scope,
16+
forceBindingRootScopeToFunctions: true,
17+
})
18+
19+
// If the result is a function, call it
20+
if (typeof result === 'function' && shouldAutoEvaluateFunctions) {
21+
return result()
22+
}
23+
24+
return result
25+
}
26+
727
export function cspEvaluator(el, expression) {
828
let dataStack = generateDataStack(el)
929

packages/csp/src/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import Alpine from 'alpinejs/src/alpine'
1717
* interpret strings as runtime JS. We're going to use
1818
* a more CSP-friendly evaluator for this instead.
1919
*/
20-
import { cspEvaluator } from './evaluator'
20+
import { cspEvaluator, cspRawEvaluator } from './evaluator'
2121

2222
Alpine.setEvaluator(cspEvaluator)
23+
Alpine.setRawEvaluator(cspRawEvaluator)
2324

2425
/**
2526
* The rest of this file bootstraps Alpine the way it is

tests/vitest/csp-evaluator.spec.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// @vitest-environment jsdom
2+
3+
import { describe, it, expect, beforeAll } from 'vitest';
4+
import Alpine from '../../packages/csp/src/index.js';
5+
import { cspRawEvaluator } from '../../packages/csp/src/evaluator.js';
6+
7+
beforeAll(() => Alpine.start())
8+
9+
describe('cspRawEvaluator', () => {
10+
it('simple expression', () => {
11+
let element = { parentNode: null, _x_dataStack: [] }
12+
13+
expect(cspRawEvaluator(element, '42')).toBe(42)
14+
});
15+
16+
it('with scope', () => {
17+
let element = { parentNode: null, _x_dataStack: [] }
18+
19+
expect(cspRawEvaluator(element, 'foo', { scope: { foo: 42 } })).toBe(42)
20+
});
21+
22+
it('auto-evaluating function expression', () => {
23+
let element = { parentNode: null, _x_dataStack: [] }
24+
25+
let scope = { getAnswer: () => 42 }
26+
27+
expect(cspRawEvaluator(element, 'getAnswer()', { scope })).toBe(42)
28+
});
29+
30+
it('non auto-evaluating function expression', () => {
31+
let element = { parentNode: null, _x_dataStack: [] }
32+
33+
let scope = { getAnswer: () => 42 }
34+
35+
Alpine.dontAutoEvaluateFunctions(() => {
36+
let fn = cspRawEvaluator(element, 'getAnswer', { scope })
37+
expect(fn()).toBe(42)
38+
})
39+
});
40+
41+
it('property access', () => {
42+
let element = { parentNode: null, _x_dataStack: [] }
43+
44+
let scope = { user: { name: 'John' } }
45+
46+
expect(cspRawEvaluator(element, 'user.name', { scope })).toBe('John')
47+
});
48+
49+
it('method calls preserve context', () => {
50+
let element = { parentNode: null, _x_dataStack: [] }
51+
52+
let scope = {
53+
counter: {
54+
count: 5,
55+
getCount() { return this.count }
56+
}
57+
}
58+
59+
expect(cspRawEvaluator(element, 'counter.getCount()', { scope })).toBe(5)
60+
});
61+
62+
it('ternary expressions', () => {
63+
let element = { parentNode: null, _x_dataStack: [] }
64+
65+
expect(cspRawEvaluator(element, 'true ? 1 : 2')).toBe(1)
66+
expect(cspRawEvaluator(element, 'false ? 1 : 2')).toBe(2)
67+
});
68+
69+
it('arithmetic operations', () => {
70+
let element = { parentNode: null, _x_dataStack: [] }
71+
72+
expect(cspRawEvaluator(element, '2 + 3 * 4')).toBe(14)
73+
expect(cspRawEvaluator(element, '(2 + 3) * 4')).toBe(20)
74+
});
75+
76+
it('comparison operations', () => {
77+
let element = { parentNode: null, _x_dataStack: [] }
78+
79+
expect(cspRawEvaluator(element, '5 > 3')).toBe(true)
80+
expect(cspRawEvaluator(element, '5 === 5')).toBe(true)
81+
expect(cspRawEvaluator(element, '5 === "5"')).toBe(false)
82+
});
83+
84+
it('logical operations', () => {
85+
let element = { parentNode: null, _x_dataStack: [] }
86+
87+
expect(cspRawEvaluator(element, 'true && false')).toBe(false)
88+
expect(cspRawEvaluator(element, 'true || false')).toBe(true)
89+
expect(cspRawEvaluator(element, '!false')).toBe(true)
90+
});
91+
});

tests/vitest/evaluator.spec.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { describe, it, expect, beforeAll } from 'vitest';
44
import Alpine from '../../packages/alpinejs/src/index.js';
5-
import { evaluate, evaluateLater } from '../../packages/alpinejs/src/evaluator.js';
5+
import { evaluate, evaluateLater, evaluateRaw } from '../../packages/alpinejs/src/evaluator.js';
66

77
beforeAll(() => Alpine.start())
88

@@ -151,4 +151,50 @@ describe('evaluateLater([Function])', () => {
151151
expect(value).toBe(42)
152152
})
153153
});
154+
})
155+
156+
describe('evaluateRaw([String])', () => {
157+
it('simple expression', () => {
158+
let element = { parentNode: null, _x_dataStack: [] }
159+
160+
expect(evaluateRaw(element, '42')).toBe(42)
161+
});
162+
163+
it('with scope', () => {
164+
let element = { parentNode: null, _x_dataStack: [] }
165+
166+
expect(evaluateRaw(element, 'foo', { scope: { foo: 42 } })).toBe(42)
167+
});
168+
169+
it('auto-evaluating function expression', () => {
170+
let element = { parentNode: null, _x_dataStack: [] }
171+
172+
expect(evaluateRaw(element, '() => 42')).toBe(42)
173+
});
174+
175+
it('non auto-evaluating function expression', () => {
176+
let element = { parentNode: null, _x_dataStack: [] }
177+
178+
Alpine.dontAutoEvaluateFunctions(() => {
179+
expect(evaluateRaw(element, '() => 42')()).toBe(42)
180+
})
181+
});
182+
183+
it('await returns promise directly', async () => {
184+
let element = { parentNode: null, _x_dataStack: [] }
185+
186+
let result = evaluateRaw(element, 'await Promise.resolve(42)')
187+
188+
expect(result).toBeInstanceOf(Promise)
189+
expect(await result).toBe(42)
190+
});
191+
192+
it('promise is returned directly', async () => {
193+
let element = { parentNode: null, _x_dataStack: [] }
194+
195+
let result = evaluateRaw(element, '(() => { let promise = new Promise(() => {}); promise.foo = "bar"; return promise })()')
196+
197+
expect(result).toBeInstanceOf(Promise)
198+
expect(result.foo).toBe('bar')
199+
});
154200
})

0 commit comments

Comments
 (0)