Builds Alpine.js expressions safely.
The problem this solves
Alpine state used to be built by interpolating into a string:
x-data={"{ tooltipText: '#{@description}' }"}That is broken in two distinct ways:
- It breaks on ordinary input. Any apostrophe terminates the JavaScript string
literal early —
"it's fine"produces a syntax error and the component silently stops working. - It is an injection vector. A
@descriptionof'; document.location = evil; //closes the string and runs arbitrary JavaScript. HEEx escapes the attribute for HTML, which stops"and<, but the payload above needs neither.
x_data/1 avoids both by JSON-encoding a map. Every JSON object literal is also a valid
JavaScript object literal, and Jason escapes quotes and backslashes within values:
x-data={x_data(%{open: false, text: @description})}
#=> x-data="{"open":false,"text":"it's fine"}"Interpolating into expressions
x-show, x-on:click and friends take JavaScript expressions, not data, so they cannot
be wholly JSON-encoded. Interpolate values through js/1, which encodes each one as a
JavaScript literal:
x-on:click={"activePanel = activePanel === " <> js(@index) <> " ? null : " <> js(@index)}
x-on:pine:open={"if ($event.detail.id === " <> js(@id) <> ") open = true"}Never interpolate a raw string into an expression.
Summary
Functions
Encodes a term as a JavaScript literal, for interpolation into an Alpine expression.
Merges Alpine state maps, with later maps winning.
Encodes a map as an Alpine x-data object literal.
Functions
Encodes a term as a JavaScript literal, for interpolation into an Alpine expression.
iex> js("it's")
~s("it's")
iex> js(42)
"42"
iex> js(nil)
"null"Strings come back quoted and escaped, so the result is always safe to splice directly into an expression.
Merges Alpine state maps, with later maps winning.
Useful when a component has a base state object plus conditional additions:
x-data={x_data(merge(%{open: false}, @extra_state))}
Encodes a map as an Alpine x-data object literal.
Keys may be atoms or strings; they are emitted as JSON object keys, which Alpine reads as plain property names.
iex> x_data(%{open: false, count: 3})
~s({"count":3,"open":false})