ruact 0.0.11 → 0.0.13
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 +4 -4
- data/CHANGELOG.md +147 -48
- data/CONTRIBUTING.md +233 -0
- data/README.md +18 -8
- data/RELEASING.md +125 -139
- data/lib/generators/ruact/install/install_generator.rb +305 -126
- data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
- data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
- data/lib/generators/ruact/install/templates/tsconfig.json.tt +3 -0
- data/lib/generators/ruact/layout/layout_generator.rb +52 -0
- data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
- data/lib/ruact/configuration.rb +65 -13
- data/lib/ruact/controller/document_rendering.rb +72 -16
- data/lib/ruact/controller/page_rendering.rb +134 -0
- data/lib/ruact/controller/pages.rb +116 -0
- data/lib/ruact/controller.rb +78 -11
- data/lib/ruact/doctor.rb +233 -25
- data/lib/ruact/layout_source.rb +29 -7
- data/lib/ruact/navigation_boundary.rb +240 -0
- data/lib/ruact/packaging.rb +68 -0
- data/lib/ruact/railtie.rb +30 -0
- data/lib/ruact/server.rb +10 -1
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper.rb +158 -1
- data/lib/ruact/views/layouts/ruact.html.erb +32 -0
- data/lib/ruact.rb +29 -0
- data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
- data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
- data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
- data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
- metadata +12 -107
- data/.codecov.yml +0 -31
- data/.github/workflows/ci.yml +0 -284
- data/.github/workflows/server-functions-bench.yml +0 -54
- data/.rubocop.yml +0 -107
- data/.rubocop_todo.yml +0 -63
- data/Rakefile +0 -10
- data/bench/server_functions_dispatch_bench.rb +0 -276
- data/bench/server_functions_dispatch_bench.results.md +0 -150
- data/docs/internal/README.md +0 -9
- data/docs/internal/decisions/server-functions-api.md +0 -2236
- data/spec/benchmarks/baseline.json +0 -12
- data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
- data/spec/fixtures/flight/README.md +0 -136
- data/spec/fixtures/flight/array.txt +0 -1
- data/spec/fixtures/flight/as_json_object.txt +0 -2
- data/spec/fixtures/flight/bigint.txt +0 -1
- data/spec/fixtures/flight/boolean_false.txt +0 -1
- data/spec/fixtures/flight/boolean_true.txt +0 -1
- data/spec/fixtures/flight/client_component_with_props.txt +0 -2
- data/spec/fixtures/flight/client_reference.txt +0 -2
- data/spec/fixtures/flight/hash.txt +0 -1
- data/spec/fixtures/flight/infinity.txt +0 -1
- data/spec/fixtures/flight/nan.txt +0 -1
- data/spec/fixtures/flight/negative_infinity.txt +0 -1
- data/spec/fixtures/flight/nil.txt +0 -1
- data/spec/fixtures/flight/number_float.txt +0 -1
- data/spec/fixtures/flight/number_integer.txt +0 -1
- data/spec/fixtures/flight/react_element_no_props.txt +0 -1
- data/spec/fixtures/flight/redirect_row.txt +0 -1
- data/spec/fixtures/flight/serializable_object.txt +0 -2
- data/spec/fixtures/flight/string_basic.txt +0 -1
- data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
- data/spec/fixtures/flight/undefined.txt +0 -1
- data/spec/fixtures/readme/children-error.html.erb +0 -3
- data/spec/fixtures/readme/children-error.txt +0 -1
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
- data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
- data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
- data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
- data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
- data/spec/readme_demo_message_spec.rb +0 -67
- data/spec/readme_spec.rb +0 -282
- data/spec/ruact/client_manifest_spec.rb +0 -270
- data/spec/ruact/component_contract_spec.rb +0 -119
- data/spec/ruact/configuration_spec.rb +0 -518
- data/spec/ruact/controller_request_spec.rb +0 -671
- data/spec/ruact/controller_spec.rb +0 -343
- data/spec/ruact/doctor_spec.rb +0 -769
- data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
- data/spec/ruact/erb_preprocessor_spec.rb +0 -361
- data/spec/ruact/errors_spec.rb +0 -93
- data/spec/ruact/flight/renderer_spec.rb +0 -133
- data/spec/ruact/flight/serializer_spec.rb +0 -494
- data/spec/ruact/html_converter_spec.rb +0 -375
- data/spec/ruact/install_generator_spec.rb +0 -1549
- data/spec/ruact/layout_source_spec.rb +0 -108
- data/spec/ruact/manifest_resolver_spec.rb +0 -174
- data/spec/ruact/query_request_spec.rb +0 -706
- data/spec/ruact/query_spec.rb +0 -105
- data/spec/ruact/railtie_spec.rb +0 -155
- data/spec/ruact/render_context_spec.rb +0 -58
- data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
- data/spec/ruact/render_pipeline_spec.rb +0 -928
- data/spec/ruact/scaffold_generator_spec.rb +0 -1849
- data/spec/ruact/serializable_spec.rb +0 -179
- data/spec/ruact/server_bucket_request_spec.rb +0 -785
- data/spec/ruact/server_function_name_spec.rb +0 -53
- data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
- data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
- data/spec/ruact/server_functions/codegen_spec.rb +0 -397
- data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
- data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
- data/spec/ruact/server_functions/introspection_spec.rb +0 -135
- data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
- data/spec/ruact/server_functions/query_context_spec.rb +0 -72
- data/spec/ruact/server_functions/query_source_spec.rb +0 -193
- data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
- data/spec/ruact/server_functions/rake_spec.rb +0 -86
- data/spec/ruact/server_functions/route_source_spec.rb +0 -202
- data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
- data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
- data/spec/ruact/server_rescue_request_spec.rb +0 -416
- data/spec/ruact/server_spec.rb +0 -179
- data/spec/ruact/server_upload_request_spec.rb +0 -311
- data/spec/ruact/signed_references_spec.rb +0 -164
- data/spec/ruact/string_distance_spec.rb +0 -38
- data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
- data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
- data/spec/ruact/testing/no_production_load_spec.rb +0 -41
- data/spec/ruact/validation_errors_spec.rb +0 -116
- data/spec/ruact/view_helper_spec.rb +0 -131
- data/spec/spec_helper.rb +0 -77
- data/spec/support/fixtures/pixel.png +0 -0
- data/spec/support/flight_wire_parser.rb +0 -21
- data/spec/support/flight_wire_parser_spec.rb +0 -93
- data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
- data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
- data/spec/support/rails_stub.rb +0 -115
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# Contributing to ruact
|
|
2
|
+
|
|
3
|
+
Thanks for wanting to change something here. This guide takes you from a fresh clone to an open pull
|
|
4
|
+
request, and everything it asks you to run, you can run — with this repository and nothing else.
|
|
5
|
+
|
|
6
|
+
## What this repository is
|
|
7
|
+
|
|
8
|
+
`luizcg/ruact` is one repository holding three things that ship together:
|
|
9
|
+
|
|
10
|
+
| Directory | What lives there |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `lib/` | the Ruby library — the ERB preprocessor, the render pipeline, the Flight wire format, the Rails integration, the generators and the Rake tasks |
|
|
13
|
+
| `vendor/javascript/vite-plugin-ruact/` | the Vite plugin — it scans for `"use client"`, emits the client manifest, and generates the typed server-function accessors |
|
|
14
|
+
| `vendor/javascript/ruact-server-functions-runtime/` | the browser-side runtime those generated accessors call |
|
|
15
|
+
|
|
16
|
+
The two JavaScript packages are **bundled, not published**. A host app never installs them from npm:
|
|
17
|
+
`rails generate ruact:install` writes a `vite.config.js` that imports the plugin by absolute path off the
|
|
18
|
+
installed gem. That has one consequence worth stating up front, because it is what makes the rest of this
|
|
19
|
+
document short — **pointing a Rails app at your checkout of the gem points it at your checkout of the Vite
|
|
20
|
+
plugin too.** There is nothing to link separately, and nothing else to clone. This repository has no
|
|
21
|
+
submodules.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
| Tool | Version | Why |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| Ruby | >= 3.2 | the floor in `ruact.gemspec`; CI runs 3.2 and 3.3 |
|
|
28
|
+
| Node.js | 20 | the JavaScript suite and the asset build only — ruact runs no Node process in production |
|
|
29
|
+
| Bundler | any recent | `gem install bundler` |
|
|
30
|
+
|
|
31
|
+
You do not need a Rails **app** to work on this gem, and you do not install Rails by hand: the `Gemfile`
|
|
32
|
+
pins it to `RAILS_VERSION`, defaulting to 8.0, so `bundle install` brings it. What keeps the suite fast is
|
|
33
|
+
that `spec/support/rails_stub.rb` loads only Rails' core and adds test-only writers on top — the request
|
|
34
|
+
cycle (`action_controller`, `action_view`) is loaded by the handful of specs that actually need it, and by
|
|
35
|
+
nothing else.
|
|
36
|
+
|
|
37
|
+
## Setup
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
git clone https://github.com/luizcg/ruact.git
|
|
41
|
+
cd ruact
|
|
42
|
+
bundle install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
That is the whole setup for a Ruby-side change. If you are touching the Vite plugin or the browser
|
|
46
|
+
runtime, install their dependencies too:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
cd vendor/javascript/ruact-server-functions-runtime && npm install
|
|
50
|
+
cd ../vite-plugin-ruact && npm ci
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The first install is not decoration: the plugin's test run reaches into the runtime package, which keeps
|
|
54
|
+
`react` in its own dependencies.
|
|
55
|
+
|
|
56
|
+
## Running the checks
|
|
57
|
+
|
|
58
|
+
These are the jobs in [`.github/workflows/ci.yml`](.github/workflows/ci.yml), which runs on every push and
|
|
59
|
+
every pull request. Running them locally first is the difference between one round and four.
|
|
60
|
+
|
|
61
|
+
<!-- ci-jobs:begin -->
|
|
62
|
+
|
|
63
|
+
| CI job | What it checks |
|
|
64
|
+
|---|---|
|
|
65
|
+
| `rspec` | the Ruby suite, on Ruby 3.2/3.3 × Rails 7.0/7.1/7.2/8.0 — eight cells |
|
|
66
|
+
| `rubocop` | style, plus the three custom cops below |
|
|
67
|
+
| `yard` | every public method carries YARD documentation |
|
|
68
|
+
| `js` | the Vite plugin's vitest suite, including its Ruby↔JS byte-parity cases, and a type-level test over the generated accessors |
|
|
69
|
+
| `benchmark` | render-pipeline allocations against a recorded baseline |
|
|
70
|
+
| `name-propagation` | greps the tree for names retired before v0.1.0 |
|
|
71
|
+
| `gate` | that `CHANGELOG.md` gained something if this branch changed what the published gem contains, and that a version bump is one legal semver step to a version nothing has released |
|
|
72
|
+
|
|
73
|
+
<!-- ci-jobs:end -->
|
|
74
|
+
|
|
75
|
+
There is one more job, `release`, which publishes to RubyGems. It never runs on a pull request, and on a
|
|
76
|
+
push to `main` it publishes only when `lib/ruact/version.rb` names a version that carries no tag — which is
|
|
77
|
+
a thing a maintainer does deliberately, in a pull request of its own. See [RELEASING.md](RELEASING.md) for
|
|
78
|
+
the release process itself — this document deliberately describes none of it, so the two cannot drift apart.
|
|
79
|
+
|
|
80
|
+
Locally:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
bundle exec rspec
|
|
84
|
+
bundle exec rubocop --format github
|
|
85
|
+
rm -rf .yardoc doc && bundle exec yard --fail-on-warning
|
|
86
|
+
bundle exec rake benchmark:memory
|
|
87
|
+
bin/release-gate origin/main HEAD
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
and, for the `js` job:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
cd vendor/javascript/vite-plugin-ruact
|
|
94
|
+
npm test
|
|
95
|
+
npm run typecheck
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
To reproduce one matrix cell other than the default — `RAILS_VERSION` is what the eight `rspec` cells vary,
|
|
99
|
+
and unset means 8.0:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
RAILS_VERSION=7.2 bundle install && bundle exec rspec
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Trying your working tree in a real app
|
|
106
|
+
|
|
107
|
+
The suite is fast and the gates are strict, and neither tells you whether your change works in a Rails
|
|
108
|
+
app. This does, in about two minutes. `RUACT` is the absolute path to your clone.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
export RUACT=/absolute/path/to/your/ruact
|
|
112
|
+
|
|
113
|
+
rails new myapp --skip-javascript
|
|
114
|
+
cd myapp
|
|
115
|
+
printf '\ngem "ruact", path: "%s"\n' "$RUACT" >> Gemfile
|
|
116
|
+
bundle install
|
|
117
|
+
bin/rails generate ruact:install
|
|
118
|
+
bin/dev
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
No published gem and no published npm package take part: the `path:` source points Bundler at your
|
|
122
|
+
checkout, and the generated `vite.config.js` imports the Vite plugin from inside it. Both halves of your
|
|
123
|
+
change are live.
|
|
124
|
+
|
|
125
|
+
Give it something to render — a component in `app/javascript/components/`:
|
|
126
|
+
|
|
127
|
+
```jsx
|
|
128
|
+
"use client";
|
|
129
|
+
import { useState } from "react";
|
|
130
|
+
|
|
131
|
+
export function LikeButton({ likes }) {
|
|
132
|
+
const [count, setCount] = useState(likes);
|
|
133
|
+
return <button onClick={() => setCount(count + 1)}>{count} likes</button>;
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
a controller action, and a view that uses the component by name:
|
|
138
|
+
|
|
139
|
+
```erb
|
|
140
|
+
<h1>Home</h1>
|
|
141
|
+
<LikeButton likes={@likes} />
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`bin/dev` runs Rails and Vite together. Load the page and the button counts up.
|
|
145
|
+
|
|
146
|
+
## Four gates that fail for reasons your diff does not show
|
|
147
|
+
|
|
148
|
+
Most of CI fails where you expect. These four do not, so they are worth knowing before they surprise you.
|
|
149
|
+
|
|
150
|
+
1. **`spec/readme_spec.rb`** — `README.md` is pinned **byte for byte** in places: the quick-start block and
|
|
151
|
+
the demo image reference are compared against literals in the spec, the file is allowed exactly one raw
|
|
152
|
+
`<img>`, and a `path:` gem source is forbidden there because a reader who copies one installs nothing.
|
|
153
|
+
Edit the README casually and this goes red.
|
|
154
|
+
|
|
155
|
+
⚠️ **One of its assertions is repository-wide despite living in a README spec.** It runs `git ls-files`
|
|
156
|
+
over the whole repository and fails if any tracked file is a `.gif`, `.mp4`, `.webm` or `.webp` — media
|
|
157
|
+
committed here would ship inside every built `.gem` and stay in the clone history forever. So **no
|
|
158
|
+
document in this repository can carry an image**, including this one. Media lives with the documentation
|
|
159
|
+
site and is referenced by absolute URL.
|
|
160
|
+
|
|
161
|
+
⚠️ **YARD reads `README.md`.** Curly braces in its prose — `{@likes}`, `{Foo#bar}` — are link macros to
|
|
162
|
+
YARD, and an unresolvable one turns the `yard` job red for a README edit. Write them as `{` and
|
|
163
|
+
`}` there.
|
|
164
|
+
|
|
165
|
+
2. **`spec/readme_demo_message_spec.rb`** — the second half of that pin, covering the demo's message.
|
|
166
|
+
|
|
167
|
+
3. **`spec/benchmarks/render_pipeline_benchmark_spec.rb`** — an **allocation** baseline at ×1.20 tolerance
|
|
168
|
+
against `spec/benchmarks/baseline.json`, shared by matrix cells that legitimately differ by around 9%.
|
|
169
|
+
When it fails, read the header comment at the top of that file before doing anything: it explains how to
|
|
170
|
+
tell a real regression from accumulated drift, and how to regenerate the baseline if it is drift.
|
|
171
|
+
|
|
172
|
+
4. **Three custom RuboCop cops**, in `lib/rubocop/cop/ruact/` and enabled in `.rubocop.yml` —
|
|
173
|
+
`Ruact/NoSharedState`, `Ruact/NoIoInFlight` and `Ruact/NoExtendSelf`. Between them they say that data
|
|
174
|
+
flows through explicit arguments rather than shared mutable state, that `lib/ruact/flight/**` stays a
|
|
175
|
+
pure value transformation, and that modules expose explicit class methods.
|
|
176
|
+
|
|
177
|
+
Custom cops are the least discoverable failure a newcomer can hit, which is why they are named here —
|
|
178
|
+
but **the cop source is the authority for what each one actually detects**, and each detects a subset of
|
|
179
|
+
the rule it is named for rather than the rule entire. `Ruact/NoExtendSelf` also rejects a bare
|
|
180
|
+
`module_function`, which its name does not suggest. Read the file before assuming a pattern is either
|
|
181
|
+
caught or allowed.
|
|
182
|
+
|
|
183
|
+
And one habit rather than a gate: run `yard` after `rm -rf .yardoc doc`, as the block above does. A warm
|
|
184
|
+
cache hides warnings that CI, which checks out fresh, still reports.
|
|
185
|
+
|
|
186
|
+
## Where a change goes
|
|
187
|
+
|
|
188
|
+
Every destination below is one you can reach.
|
|
189
|
+
|
|
190
|
+
| Change | Where |
|
|
191
|
+
|---|---|
|
|
192
|
+
| anything under `lib/`, `app/`, `exe/`, `sig/`, `spec/` | **here** — `luizcg/ruact` |
|
|
193
|
+
| a typo in `README.md`, `CHANGELOG.md` or this file | **here** |
|
|
194
|
+
| the Vite plugin | **here**, at `vendor/javascript/vite-plugin-ruact/` — see the note below |
|
|
195
|
+
| the browser runtime for server functions | **here**, at `vendor/javascript/ruact-server-functions-runtime/` |
|
|
196
|
+
| the documentation site at `ruact.dev` | not in this repository — [open an issue](https://github.com/luizcg/ruact/issues) |
|
|
197
|
+
| roadmap, architecture decisions, design | not in this repository — [open an issue](https://github.com/luizcg/ruact/issues) or a discussion |
|
|
198
|
+
|
|
199
|
+
**About the Vite plugin.** There is a `vite-plugin-ruact` package on npm. It is **superseded**: nothing in
|
|
200
|
+
ruact installs it, and the plugin that actually runs is the vendored one in this repository. Send plugin
|
|
201
|
+
changes here.
|
|
202
|
+
|
|
203
|
+
## Where the design work happens
|
|
204
|
+
|
|
205
|
+
Roadmap, architecture decisions and story planning live in a private repository, so the reasoning behind a
|
|
206
|
+
change is not always visible from here. That does not gate anything: open an issue or a discussion, and the
|
|
207
|
+
outcome of that thinking comes back to you in the thread — you do not need access to propose a change,
|
|
208
|
+
argue for one, or land one.
|
|
209
|
+
|
|
210
|
+
## Opening the pull request
|
|
211
|
+
|
|
212
|
+
Branch off `main`, keep one topic per pull request, and open it against `luizcg/ruact`. Before you do:
|
|
213
|
+
|
|
214
|
+
- [ ] `bundle exec rspec` passes.
|
|
215
|
+
- [ ] `bundle exec rubocop --format github` is clean.
|
|
216
|
+
- [ ] `rm -rf .yardoc doc && bundle exec yard --fail-on-warning` passes, and every new public method has a
|
|
217
|
+
YARD comment with `@param` and `@return`.
|
|
218
|
+
- [ ] `# frozen_string_literal: true` is the first line of every new Ruby file.
|
|
219
|
+
- [ ] Nothing you added lives in `Ruact::`'s way: gem code stays under `Ruact::`, wire-format code under
|
|
220
|
+
`Ruact::Flight::`, and no Ruby or Rails core class is reopened.
|
|
221
|
+
- [ ] No `extend self` and no bare `module_function` — explicit class methods or a class instead.
|
|
222
|
+
- [ ] If you touched the Vite plugin or the runtime, `npm test` and `npm run typecheck` pass in
|
|
223
|
+
`vendor/javascript/vite-plugin-ruact`.
|
|
224
|
+
- [ ] `bin/release-gate origin/main HEAD` passes. If your branch changes anything that goes inside the
|
|
225
|
+
published gem, it asks [CHANGELOG.md](CHANGELOG.md) for a new bullet under `[Unreleased]` — written
|
|
226
|
+
for a reader who did not see the diff.
|
|
227
|
+
|
|
228
|
+
Describe what the change does and why in the pull request body. If it fixes a reported bug, link the issue.
|
|
229
|
+
|
|
230
|
+
Security issues do **not** go in a pull request — see [SECURITY.md](SECURITY.md) for private reporting.
|
|
231
|
+
|
|
232
|
+
By contributing you agree that your contribution is licensed under the MIT license in
|
|
233
|
+
[LICENSE.txt](LICENSE.txt).
|
data/README.md
CHANGED
|
@@ -22,14 +22,14 @@ rails new myapp --skip-javascript && cd myapp
|
|
|
22
22
|
# 2. Add the gem
|
|
23
23
|
bundle add ruact
|
|
24
24
|
|
|
25
|
-
# 3. Write the config
|
|
25
|
+
# 3. Write the config and an AGENTS.md (no layout of yours is edited) — then run npm install
|
|
26
26
|
rails generate ruact:install
|
|
27
27
|
|
|
28
28
|
# 4. Rails + Vite, one command
|
|
29
29
|
bin/dev
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
That is the whole install. The [Getting Started guide](https://ruact.dev/docs/getting-started) picks it up from here — first component, first scaffold, `ruact:doctor`. Already have an app? Start at step 2, then read [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — ruact renders
|
|
32
|
+
That is the whole install. The [Getting Started guide](https://ruact.dev/docs/getting-started) picks it up from here — first component, first scaffold, `ruact:doctor`. Already have an app? Start at step 2, then read [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — ruact renders only the controllers you include it in and leaves the rest of your views alone.
|
|
33
33
|
|
|
34
34
|
## How it works
|
|
35
35
|
|
|
@@ -58,7 +58,9 @@ export function PostCard({ post, author }) {
|
|
|
58
58
|
}
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
Capitalized tag means React. Lowercase stays HTML. `"use client"` is the only directive you need to learn.
|
|
62
|
+
|
|
63
|
+
Your ERB view renders on the server the way it always did, and ruact sends the result to the browser as a React tree — in the same [wire format](https://ruact.dev/docs/concepts/flight-wire-format) React uses for Server Components, implemented in Ruby. The data the view needs is inlined in the page, so React renders immediately, without a fetch. Node builds the bundle with Vite and is needed nowhere else.
|
|
62
64
|
|
|
63
65
|
## Call Rails from React
|
|
64
66
|
|
|
@@ -86,17 +88,17 @@ The verb decides — there is no per-action DSL and no second endpoint. The expo
|
|
|
86
88
|
|
|
87
89
|
## What you get
|
|
88
90
|
|
|
89
|
-
Every item below is shipped in this gem at v0.0.
|
|
91
|
+
Every item below is shipped in this gem at v0.0.11:
|
|
90
92
|
|
|
91
93
|
- **ERB as server components** — `include Ruact::Controller`, then use your components by name in the views you already have: capitalized is React, lowercase stays HTML. [Docs](https://ruact.dev/docs/concepts/erb-as-server-components)
|
|
92
94
|
- **`"use client"`** — the one directive that marks a file as client-side. The bundled Vite plugin scans for it and writes the manifest. [Docs](https://ruact.dev/docs/concepts/use-client)
|
|
93
|
-
- **Server functions and queries** — `include Ruact::Server` and `Ruact::Query` + `useQuery`, both reachable through a
|
|
94
|
-
- **
|
|
95
|
+
- **Server functions and queries** — `include Ruact::Server` and `Ruact::Query` + `useQuery`, both reachable through a module generated from your route table. A query's declared keywords become an exact TypeScript signature; an action's accessor is typed to accept an object or a `FormData` and resolve whatever the action answers. [Docs](https://ruact.dev/docs/api/server-actions)
|
|
96
|
+
- **An opt-in allowlist for props** — by default a model prop is serialized with `as_json`, so every attribute crosses and the log says so. `include Ruact::Serializable` + `ruact_props :id, :title` makes it an allowlist, and `strict_serialization` (on in production) turns the permissive path into an error rather than a warning. [Docs](https://ruact.dev/docs/api/serializable)
|
|
95
97
|
- **Validation errors round-trip** — `ruact_errors(record)` hands React `{ title: ["can't be blank"] }` without a serializer. [Docs](https://ruact.dev/docs/api/server-actions)
|
|
96
98
|
- **Signed record references** — `Ruact.signed_global_id(record, for:, expires_in:)` out, `Ruact.locate_signed(token, for:)` back in; a tampered token is a `400`, not a lookup. [Docs](https://ruact.dev/docs/api/server-actions)
|
|
97
99
|
- **Client-side navigation** — link interception, scroll restoration and redirect-after-POST, derived from your Rails routes. [Docs](https://ruact.dev/docs/concepts/navigation)
|
|
98
100
|
- **A CRUD generator** — `rails generate ruact:scaffold Post title:string body:text` delegates the model, migration and route to Rails' own `resource` generator, then adds the ruact layer. Plain semantic HTML by default; `--shadcn` opts into the Tailwind/shadcn path. It does not run migrations — `rails db:migrate` is still yours. [Docs](https://ruact.dev/docs/api/scaffold)
|
|
99
|
-
- **`bin/rails ruact:doctor`** —
|
|
101
|
+
- **`bin/rails ruact:doctor`** — nine checks over the manifest, Vite, the layout, the client-component CSS and streaming; exits `1` when one fails. [Docs](https://ruact.dev/docs/api/ruact-doctor)
|
|
100
102
|
- **One runtime dependency** — `nokogiri`. Rails itself is not a declared dependency of this gem.
|
|
101
103
|
|
|
102
104
|
## AI tools and coding agents
|
|
@@ -112,6 +114,12 @@ That is the whole adaptation, and it holds for any tool that outputs standard Re
|
|
|
112
114
|
|
|
113
115
|
For agents driving the app rather than writing one component: `rails generate ruact:install` writes an `AGENTS.md` into your app, [ruact.dev/llms.txt](https://ruact.dev/llms.txt) serves the same context to tools that fetch from the web, and `bin/rails ruact:doctor -- --json` / `bin/rails ruact:routes -- --json` emit machine-readable output (experimental — `schema_version: 0`, and the `--` separator is required).
|
|
114
116
|
|
|
117
|
+
## Where ruact fits
|
|
118
|
+
|
|
119
|
+
**A good fit:** a Rails monolith that needs real React on real screens — dashboards, editors, admin tools, anything with interaction that ERB alone makes painful — with one team shipping both sides and no appetite for a second application to keep in sync.
|
|
120
|
+
|
|
121
|
+
**Not a fit yet:** pages that must render without JavaScript, or content search engines need to read out of the HTML. ruact renders client-side from a payload inlined in the page; there is no server-rendered HTML for the React tree. That is a real gap, not a roadmap wink — if the page is your SEO surface, keep it in ERB (the two coexist in the same app, per-view) or use something that server-renders.
|
|
122
|
+
|
|
115
123
|
## Compatibility
|
|
116
124
|
|
|
117
125
|
| | Version | Where that comes from |
|
|
@@ -128,7 +136,7 @@ Everything lives at [ruact.dev](https://ruact.dev):
|
|
|
128
136
|
- [Getting Started](https://ruact.dev/docs/getting-started) — from `rails new` to a rendered component
|
|
129
137
|
- [Why ruact?](https://ruact.dev/docs/why-ruact) — where it sits next to Hotwire and Inertia
|
|
130
138
|
- [Server functions & queries](https://ruact.dev/docs/api/server-actions) — the full request/response contract
|
|
131
|
-
- [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — adopting it one
|
|
139
|
+
- [Progressive migration](https://ruact.dev/docs/guides/progressive-migration) — adopting it one controller at a time
|
|
132
140
|
- [Testing](https://ruact.dev/docs/guides/testing) — render assertions on the server side
|
|
133
141
|
- [Changelog](CHANGELOG.md) — also published at [ruact.dev/docs/changelog](https://ruact.dev/docs/changelog)
|
|
134
142
|
|
|
@@ -136,6 +144,8 @@ Everything lives at [ruact.dev](https://ruact.dev):
|
|
|
136
144
|
|
|
137
145
|
Bug reports and pull requests are welcome at [github.com/luizcg/ruact/issues](https://github.com/luizcg/ruact/issues).
|
|
138
146
|
|
|
147
|
+
Setup, the checks a PR runs, and how to try your working tree in a real Rails app: [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
148
|
+
|
|
139
149
|
Release process: [RELEASING.md](RELEASING.md). Security policy and private reporting: [SECURITY.md](SECURITY.md).
|
|
140
150
|
|
|
141
151
|
## License
|