Inline JS Expressions in Interactivity API - #79780
Conversation
|
Warning: Type of PR label mismatch To merge this PR, it requires exactly 1 label indicating the type of PR. Other labels are optional and not being checked here.
Read more about Type labels in Gutenberg. Don't worry if you don't have the required permissions to add labels; the PR reviewer should be able to help with the task. |
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
|
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @nickchomey! In case you missed it, we'd love to have you join us in our Slack community. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
0098c6a to
7c6535d
Compare
7c6535d to
a6762f8
Compare
|
I should note that I took the liberty of addressing the comments to remove the hasNegativeOperator mechanism for function calls. It is isolated in its own commit for review, reversion etc... |
a6762f8 to
d30ef0e
Compare
luisherranz
left a comment
There was a problem hiding this comment.
Thanks for the substantial work you’ve put into this exploration. I want to clarify the direction, because my Slack comments seem to have been interpreted as a broader green light than I intended.
I understand the motivation. However, I don’t support allowing arbitrary JavaScript expressions in directive values, and I don’t think these PRs should move forward in their current direction.
What I said could be worth exploring was a very small expression syntax that:
- Does not resemble JavaScript or PHP.
- Is limited to simple cases such as comparisons and conditionals.
- Has identical semantics in JavaScript and PHP.
- Remains compatible with strict CSP.
- Does not become a general-purpose code evaluator.
This proposal is fundamentally different. It compiles directive values using new Function(). The browser therefore accepts arbitrary JavaScript while PHP understands only a subset, creating inconsistent server and client behavior.
The CSP implications are particularly problematic. Sites using these expressions would need to permit 'unsafe-eval' or have them fail. In the WordPress ecosystem, that means a plugin could either be incompatible with a site’s security policy or require weakening that policy for the entire document. Core should not establish a public API with that requirement. A future WordPress CSP system should also never silently reduce a site’s protection simply because an installed plugin requires it, so it wouldn't solve this problem either.
There is a broader security concern as well. WordPress permits data-* attributes in sanitized post content, and directive markup can be produced by blocks, plugins, themes, filters, and dynamic data. Turning those values into executable source changes the trust boundary significantly. HTML escaping such as esc_attr() does not make a value safe to compile as JavaScript.
Finally, although this proposal offers some convenience, it adds no new capability: the same logic can be placed in a JavaScript store, where it benefits from normal linting, bundling, caching, type checking, and debugging tools. I don’t think avoiding a few small getters justifies the security, compatibility, and maintenance costs.
I would still consider a separate proposal for a small, read-only DSL with syntax distinct from JavaScript and PHP. It could support state and context paths, literals, comparisons, simple conditionals… Its grammar and behavior would need to be exactly the same in PHP and JavaScript.
For those reasons, I don’t support this proposal or merging the current PRs. If there is interest in that narrower alternative, I would welcome a design-focused exploration of it.
|
Thanks for the review. Since this is really a discussion about the proposal rather than the implementation, I've replied on #79765 to keep the design discussion in one place. |
- Tests for splitStatements: null cases, basic split, string/regex/template literal handling, mixed content, non-string input - Tests for getEvaluate full-expression path: context comparisons (!==, ===), ternary, negation, logical operators, array filter, multi-statement, invalid expressions, backward-compatible fast path
…ake sure that hasNegativeOperator is working
…ting shows it is even fast
…ass now as they go through full expression path
d30ef0e to
00cdc67
Compare
|
As per discussion in #79765, this is simply not a direction that the iAPI maintainers are willing to consider. Closing this. |
What?
Closes #79765
Why?
As described in detail in the associated issue, this PR adds support for evaluating js expressions contained inline in the iAPI HTML attribute directives. This simplifies development and maintainance and can improve performance by reducing the amount of external js that needs to be fetched and parsed.
How?
Inspiration and code implementation on the client-side for this comes directly from the fantastic Datastar framework, which allows an arbitrary amount of JS expressions to be provided in an attribute, delimited by
;. This framework received significant scrutiny by many highly experienced developers over the course of its multi-year pre-v1 period.The following args are exposed to each expression:
actions,callbacks,context,state,el(for triggering element) andevt(for the triggering event). This allows the expressions to be that much more... expressive...Testing Instructions
packages/interactivity/src/test/hooks.ts- 36 tests:splitStatements: 15 tests covering empty strings, no-semicolons, simple splits, string/regex/template-literal semicolons, IIFEs, escaped quotes, division-not-mistaken-for-regex, trailing semicolons, whitespace handling, and non-string inputs.getEvaluatefull-expression path: 21 tests covering comparisons, logical operators, ternaries, array filters, multi-statement "last value wins", direct state/context mutation, short-circuit operand return, JS truthiness (empty arrays,'0'), loose vs strict equality, nullish coalescing, string concatenation, bitwise/shift/exponentiation, and nested ternaries.Unit Tests
cd gutenberg npm run test:unit -- packages/interactivity/src/test/hooks.ts --runInBandA simple "real-world" test:
elandevtelarg available to the expressions and multi-expression directive capabilitiesfnCacheMap used ingetEvaluate()prevents new functions from being created for identical expressions. Screen recording shows this as well.Kooha-2026-07-03-18-17-55.mp4
Additionally, you could try moving any existing plugin's simple getter, actions, callback etc.. function from an external js file to the html callsite. It should evaluate as-normal.
Use of AI Tools
AI assistance: Yes
Tool(s): VSCode GitHub Copilot
Model(s): GPT-5.4, Deepseek V4 Flash and Pro
Used for: Frankly everything. But I iterated with it as a pair programmer.