Repository navigation
Hybrid components with ordinary Vue code - #15
Merged
Merged
Conversation
…them A computed reading a composable's result or an import failed every server render with ExpressionError, and compiling said nothing. Computeds.compile now decides, in dependency order, which computeds read only refs, props, JS globals and other such computeds; the rest are left out with a warning naming what they lack. Template expressions reading a left-out computed are unrendered, and a v-if chain stops at an unrendered condition, so the first paint shows no branch rather than a misleading v-else.
ScriptSetup.client_bindings lists top-level names bound by any call other than Vue's compiler macros, ref(), computed() and macro imports, destructured names included: composables whatever their name, reactive(), and so on. A file with such a binding is hybrid, as one with a ref() is; one whose only state came from composables used to compile as server-only.
A def in <script lang="elixir"> named after a computed, in snake_case, taking the assigns, computes it on the server on every render and in a replay, from the props, the refs, recorded or initial, and earlier computeds. Such a computed is never left out, comes after the JavaScript computeds, and counts as reading everything for change tracking. A counterpart named after a LiveView callback is a compile error. Computed names get their atoms while compiling, so rendering only looks them up.
Every ref was registered for replay, so state a template never reads, or that changes at frame rate, would flood a recording. Hybrid.build now works out what the render reads: the template's names, and through the computeds and Elixir counterparts it reads (a counterpart's from its map patterns and assigns.key access), refs and composables' results alike. Setup registers those, the bridge unwraps them with Vue's toValue and reports the latest value of what changed once per PhoenixReplay's flush interval, and a replay accepts exactly those names. The Hybrid guide explains composables and libraries: what the server has, left-out computeds and the v-if rule, Elixir counterparts and how they can drift, and callbacks in templates.
…ants
Each hybrid render lists the composable state it lacks: all of it live,
what a replay didn't record. An expression reading it isn't rendered, a
v-if chain whose condition reads it renders no branch, and an Elixir
counterpart reading it isn't called, its computed absent too; before, a
counterpart matching %{sortKey: _} raised FunctionClauseError on every
live render, and v-if="sortKey === 'name'" showed its v-else.
Top-level constants are evaluated once while compiling and known to the
server, and a computed calling a script function with an Elixir
counterpart calls the counterpart, so neither leaves a computed out.
Recording unwraps with unref and skips functions, such as useClipboard's
copy, which toValue would have called. Pending changes go out when the
page is hidden or navigates, before the replayer's last batch. Left-out
warnings point at the computed's line.
A counterpart whose pattern matches on a composable's value is never called on the live render, so its computed was silently missing from the first paint. Warn at its def, and make the convention explicit: pattern keys and assigns.key are required, assigns[:key] is optional and nil when missing. Optional reads are recorded too, so a replay has them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A spike that used ordinary libraries in the demo's contacts page (es-toolkit's
sortBy, VueUse'srefDebounced,useLocalStorage,useClipboard,onKeyStroke) turned up three gaps:debouncedSearch) or an import (sortBy) failed every server render withExpressionError, and compiling said nothing.ref()counted as client state, so a file whose state came from composables compiled as server-only, and that state was never recorded for replay.What changed
One commit each:
Hybrid.Computeds.compile/2decides, in dependency order, which computeds read only what the server has: refs, props, JS globals and other such computeds. The rest are left out with a warning naming what they lack. An expression reading a left-out computed is unrendered, and av-ifchain stops at an unrendered condition, so the first paint shows no branch rather than a misleadingv-else.ScriptSetup.client_bindingslists top-level names bound by any call other than Vue's compiler macros,ref(),computed()and macro imports, destructured names included. Such a binding makes a file hybrid.def filtered(assigns)in<script lang="elixir">computesfilteredon the server on every render and in a replay. A counterpart named after a LiveView callback is a compile error.useMouseisn't recorded, and a replay accepts exactly those names.v-ifchain stops at it, and an Elixir counterpart reading it isn't called (a counterpart matching%{sortKey: _}used to raiseFunctionClauseErrorlive).unrefand functions skipped, souseClipboard'scopyis never called; pending changes go out when the page is hidden or navigates; left-out warnings carry the computed's line.v-ifrule, counterparts and how they can drift, and callbacks in templates. Also the replay section, ARCHITECTURE.md and the CHANGELOG.Verification
mix lintandmix test --include e2e: 356 tests. Fixtures from the spike assert the composable case and the import case, the first paint, counterparts live and in a replay (including one reading composable state, absent live),v-ifon composable state, constants and counterpart calls in computeds, the reserved-name error, and the recorded names (state read only by a left-out computed isn't recorded).searchwith its latest value, nothing before start or after stop, and a mount during a recording. The demo's other Playwright tests pass./contactsrenders (200, previously 500) with a warning per left-out computed and no misleading "No contacts match". The demo rebuild on top of this is a separate PR.