hibiki_phlex 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +117 -0
- data/lib/hibiki/phlex/rerenderable.rb +24 -0
- data/lib/hibiki/phlex/version.rb +7 -0
- data/lib/hibiki/phlex.rb +41 -0
- data/lib/hibiki_phlex.rb +5 -0
- metadata +85 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 4c2497270017a9a8000df2a65a725d71a330698a5bd1ecba91b7e17d535ad625
|
|
4
|
+
data.tar.gz: ab335047166028c98ea49b12d784660f3667ceff5c5b980a1bfdd4c9402f971e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 01cb9954a404539ed1c78c9c55542f2b2fa4b2c28d8828e04ae0ceafa0d0352db2664f7519f95f53cb6fdcae538250d8b5d7a471332e1bb8077ad3d868b65af2
|
|
7
|
+
data.tar.gz: 81a1c655c2e8a4b29e922dd2f397015972a148d2a68ae73150eaed3551a54c2fe0fa77c5afb5705475da70f5f61abd5a07779763ad292b629d65fdd4ea3ca736
|
data/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# hibiki_phlex
|
|
2
|
+
|
|
3
|
+
Phlex glue for [hibiki](https://github.com/planetaska/hibiki): reactive
|
|
4
|
+
components. A Phlex component is a plain Ruby object, so
|
|
5
|
+
`Hibiki::Reactive` gives it per-instance signals read as ordinary method
|
|
6
|
+
calls — no `.value` anywhere in `view_template` — and a **render effect**
|
|
7
|
+
re-renders it whenever one of those signals changes.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
signal write → render effect re-runs → component re-renders
|
|
11
|
+
(same instance) → fresh HTML handed to your block
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Transport-agnostic on purpose: the gem depends only on hibiki and phlex.
|
|
15
|
+
The block receiving the HTML decides where it goes — broadcast it over
|
|
16
|
+
Turbo Streams (see [hibiki_rails](../hibiki_rails)), write it to a file,
|
|
17
|
+
diff it in a test.
|
|
18
|
+
|
|
19
|
+
Incubating inside the hibiki repo while the core gem is pre-release; will
|
|
20
|
+
be extracted to its own repository once hibiki 0.1.0 ships and this API
|
|
21
|
+
stabilizes. Phlex >= 2.4, < 2.5 (see the version-pin rationale below),
|
|
22
|
+
Ruby >= 3.4.
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class TodoList < Phlex::HTML
|
|
28
|
+
include Hibiki::Reactive
|
|
29
|
+
include Hibiki::Phlex::Rerenderable
|
|
30
|
+
|
|
31
|
+
state(:items) { [] }
|
|
32
|
+
derived(:remaining) { items.count { |item| !item[:done] } }
|
|
33
|
+
|
|
34
|
+
def view_template
|
|
35
|
+
div(id: "todos") do
|
|
36
|
+
h2 { "Todos — #{remaining} remaining" }
|
|
37
|
+
ul { items.each { |item| li { item[:title] } } }
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Signals compare with ==; build new values instead of mutating in place.
|
|
42
|
+
def add(title) = self.items = items + [{ title:, done: false }]
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
list = TodoList.new
|
|
48
|
+
|
|
49
|
+
effect = Hibiki::Phlex.render_effect(list) do |html|
|
|
50
|
+
# your transport — e.g. inside a Hibiki::Rails::Channel:
|
|
51
|
+
# broadcast_replace target: "todos", html:
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
list.add("write docs") # → the block runs again with fresh HTML
|
|
55
|
+
effect.dispose # or let an enclosing Hibiki.root own teardown
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- The effect's **first run is the dependency-collecting initial render**:
|
|
59
|
+
signals read inside `view_template` subscribe it through plain method
|
|
60
|
+
calls, and that first HTML is yielded too.
|
|
61
|
+
- Re-renders happen **on the same instance** — signal identity lives in
|
|
62
|
+
the instance, so a fresh instance per render would reset every signal.
|
|
63
|
+
That's what `Rerenderable` exists for.
|
|
64
|
+
- `render_effect(component, scheduler:)` passes the scheduler through to
|
|
65
|
+
`Hibiki::Effect`, so reruns can be deferred or debounced (e.g.
|
|
66
|
+
`Hibiki::Rails::Debounce`).
|
|
67
|
+
- Returns the `Hibiki::Effect`. Created inside `Hibiki.root` (or another
|
|
68
|
+
effect), the owner tree disposes it automatically; a bare caller calls
|
|
69
|
+
`#dispose`.
|
|
70
|
+
|
|
71
|
+
## With hibiki_rails
|
|
72
|
+
|
|
73
|
+
The combination is the LiveView-ish loop — one signal graph per cable
|
|
74
|
+
connection, one render effect per component:
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
class TodosChannel < ApplicationCable::Channel
|
|
78
|
+
include Hibiki::Rails::Channel
|
|
79
|
+
|
|
80
|
+
def build_graph
|
|
81
|
+
@list = TodoList.new
|
|
82
|
+
Hibiki::Phlex.render_effect(@list) do |html|
|
|
83
|
+
broadcast_replace target: "todos", html:
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def add(data) = @list.add(data["title"])
|
|
88
|
+
end
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Granularity is inherent and worth knowing: the ERB style in hibiki_rails
|
|
92
|
+
is one effect **per partial** (fine-grained), a Phlex render effect is one
|
|
93
|
+
effect **per component** (component-grained). Both are correct; pick per
|
|
94
|
+
page. The initial-state pattern (server-rendered placeholder + subscribe
|
|
95
|
+
after the Turbo stream confirms) is transport-side — see the hibiki_rails
|
|
96
|
+
README.
|
|
97
|
+
|
|
98
|
+
## Why the strict Phlex pin
|
|
99
|
+
|
|
100
|
+
Phlex components are one-shot: an instance renders once, and a second
|
|
101
|
+
`call` raises `Phlex::DoubleRenderError`. Phlex 2 tracks "spent" purely
|
|
102
|
+
through the private `@_state` ivar — everything else about a render is
|
|
103
|
+
per-call — so `Rerenderable#rerender` clears it and calls again. That is
|
|
104
|
+
the entire adapter, but it leans on a private implementation detail, so:
|
|
105
|
+
|
|
106
|
+
- the gemspec allows only verified Phlex minors (`>= 2.4, < 2.5`), and
|
|
107
|
+
- `spec/phlex_contract_spec.rb` pins the upstream contract itself, so
|
|
108
|
+
bumping the bound fails loudly in CI if the internals moved.
|
|
109
|
+
|
|
110
|
+
## Development
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
bundle exec rake # specs + rubocop (same as CI)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Plain RSpec — no Rails; the live end-to-end proof app is `spike/` in the
|
|
117
|
+
parent repo (the `/phlex` page).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hibiki
|
|
4
|
+
module Phlex
|
|
5
|
+
# Lets the same component instance render more than once — required for
|
|
6
|
+
# render_effect, where signal identity lives in the instance (a fresh
|
|
7
|
+
# instance per render would reset every signal).
|
|
8
|
+
#
|
|
9
|
+
# Phlex 2 marks an instance as spent purely through @_state —
|
|
10
|
+
# SGML#internal_call raises DoubleRenderError when it is set, and it is
|
|
11
|
+
# deliberately never cleared after a render (see `rendering?`).
|
|
12
|
+
# Everything else (output buffer, capture state, the thread-local
|
|
13
|
+
# current component) is per-call, so clearing @_state before calling
|
|
14
|
+
# again is the ENTIRE adapter. It leans on a private ivar, which is why
|
|
15
|
+
# this gem pins Phlex minors and spec/phlex_contract_spec.rb pins the
|
|
16
|
+
# contract itself.
|
|
17
|
+
module Rerenderable
|
|
18
|
+
def rerender(...)
|
|
19
|
+
@_state = nil
|
|
20
|
+
call(...)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/hibiki/phlex.rb
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# NOTE: for everything under lib/hibiki/phlex/ — inside this namespace a bare
|
|
4
|
+
# `Phlex` constant resolves to Hibiki::Phlex, not the framework. Always
|
|
5
|
+
# write `::Phlex.` when you mean the framework.
|
|
6
|
+
module Hibiki
|
|
7
|
+
# Phlex glue for hibiki: a component is a plain Ruby object, so
|
|
8
|
+
# Hibiki::Reactive gives it signals read as ordinary method calls, and a
|
|
9
|
+
# render effect re-renders the same instance (signal identity lives in
|
|
10
|
+
# the instance) whenever one of them changes.
|
|
11
|
+
module Phlex
|
|
12
|
+
# Wrap a component's render in an effect. The effect's first run is the
|
|
13
|
+
# dependency-collecting initial render — signals read inside
|
|
14
|
+
# view_template subscribe it through plain method calls — and its HTML
|
|
15
|
+
# is yielded like every rerun's. On each signal change the component
|
|
16
|
+
# re-renders ON THE SAME INSTANCE and the fresh HTML goes to the block;
|
|
17
|
+
# the block owns the transport (broadcast it, print it, diff it).
|
|
18
|
+
#
|
|
19
|
+
# Hibiki::Phlex.render_effect(@list) do |html|
|
|
20
|
+
# broadcast_replace target: "todos", html:
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# Component-grained by design: one effect per component (the ERB style
|
|
24
|
+
# in hibiki_rails is one effect per partial — both are correct).
|
|
25
|
+
# `scheduler:` passes through to Hibiki::Effect, so reruns can be
|
|
26
|
+
# deferred/debounced (e.g. Hibiki::Rails::Debounce). Returns the Effect;
|
|
27
|
+
# inside a Hibiki.root or another effect the owner tree adopts it, a
|
|
28
|
+
# bare caller disposes it by hand.
|
|
29
|
+
def self.render_effect(component, scheduler: nil, &sink)
|
|
30
|
+
unless component.respond_to?(:rerender)
|
|
31
|
+
raise ArgumentError,
|
|
32
|
+
"#{component.class} has no #rerender — include Hibiki::Phlex::Rerenderable"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
Effect.new(scheduler:) { sink.call(component.rerender) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
require_relative "phlex/version"
|
|
41
|
+
require_relative "phlex/rerenderable"
|
data/lib/hibiki_phlex.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: hibiki_phlex
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- planetaska
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: hibiki
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.1'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.1'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: phlex
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.4'
|
|
33
|
+
- - "<"
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '2.5'
|
|
36
|
+
type: :runtime
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '2.4'
|
|
43
|
+
- - "<"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '2.5'
|
|
46
|
+
description: 'Make a Phlex component reactive: include Hibiki::Reactive for signals
|
|
47
|
+
read as plain method calls, Hibiki::Phlex::Rerenderable so the same instance can
|
|
48
|
+
render again, and wrap it in Hibiki::Phlex.render_effect to get fresh HTML on every
|
|
49
|
+
signal change — transport-agnostic, the block decides where the HTML goes.'
|
|
50
|
+
email:
|
|
51
|
+
- planetaska@gmail.com
|
|
52
|
+
executables: []
|
|
53
|
+
extensions: []
|
|
54
|
+
extra_rdoc_files: []
|
|
55
|
+
files:
|
|
56
|
+
- README.md
|
|
57
|
+
- lib/hibiki/phlex.rb
|
|
58
|
+
- lib/hibiki/phlex/rerenderable.rb
|
|
59
|
+
- lib/hibiki/phlex/version.rb
|
|
60
|
+
- lib/hibiki_phlex.rb
|
|
61
|
+
homepage: https://github.com/planetaska/hibiki-phlex
|
|
62
|
+
licenses:
|
|
63
|
+
- MIT
|
|
64
|
+
metadata:
|
|
65
|
+
homepage_uri: https://github.com/planetaska/hibiki-phlex
|
|
66
|
+
source_code_uri: https://github.com/planetaska/hibiki-phlex
|
|
67
|
+
rubygems_mfa_required: 'true'
|
|
68
|
+
rdoc_options: []
|
|
69
|
+
require_paths:
|
|
70
|
+
- lib
|
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '3.4'
|
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
requirements: []
|
|
82
|
+
rubygems_version: 4.0.16
|
|
83
|
+
specification_version: 4
|
|
84
|
+
summary: 'Phlex glue for hibiki: reactive components re-rendered by a render effect.'
|
|
85
|
+
test_files: []
|