ruact 0.0.19 → 0.0.21
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 +57 -1
- data/CONTRIBUTING.md +10 -3
- data/README.md +1 -1
- data/lib/generators/ruact/install/templates/AGENTS.md.tt +13 -13
- data/lib/generators/ruact/install/templates/initializer.rb.tt +4 -1
- data/lib/ruact/cached_blocks.rb +30 -0
- data/lib/ruact/configuration.rb +6 -1
- data/lib/ruact/controller/delivery.rb +262 -0
- data/lib/ruact/controller/document_rendering.rb +15 -12
- data/lib/ruact/controller/streaming.rb +305 -0
- data/lib/ruact/controller.rb +34 -51
- data/lib/ruact/declared_content.rb +33 -0
- data/lib/ruact/deferred_render.rb +254 -0
- data/lib/ruact/doctor.rb +42 -2
- data/lib/ruact/erb_preprocessor.rb +211 -24
- data/lib/ruact/flight/react_element.rb +22 -2
- data/lib/ruact/flight/renderer.rb +94 -41
- data/lib/ruact/flight/request.rb +71 -27
- data/lib/ruact/flight/serializer.rb +3 -10
- data/lib/ruact/html_converter.rb +44 -34
- data/lib/ruact/inline_script.rb +24 -0
- data/lib/ruact/load_async_note.rb +51 -0
- data/lib/ruact/markers.rb +98 -0
- data/lib/ruact/navigation_boundary.rb +2 -2
- data/lib/ruact/query_watch.rb +106 -0
- data/lib/ruact/railtie.rb +12 -0
- data/lib/ruact/render_context.rb +73 -6
- data/lib/ruact/render_pipeline.rb +32 -22
- data/lib/ruact/request_kind.rb +36 -0
- data/lib/ruact/server.rb +3 -7
- data/lib/ruact/testing/flight_extractor.rb +21 -4
- data/lib/ruact/version.rb +1 -1
- data/lib/ruact/view_helper/suspense.rb +70 -0
- data/lib/ruact/view_helper.rb +35 -33
- data/lib/ruact.rb +7 -0
- data/vendor/javascript/vite-plugin-ruact/bootstrap.test.mjs +16 -3
- data/vendor/javascript/vite-plugin-ruact/page-mount.test.mjs +75 -0
- data/vendor/javascript/vite-plugin-ruact/page-stream.test.mjs +132 -0
- data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +185 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx +23 -18
- data/vendor/javascript/vite-plugin-ruact/runtime/dev-overlay.js +53 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/page-stream.js +108 -0
- data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +74 -20
- data/vendor/javascript/vite-plugin-ruact/runtime/suspense-boundary.js +1 -0
- metadata +17 -2
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// How a page is opened from its Flight bytes, wherever they come from: the
|
|
2
|
+
// rows the document pushes onto `__FLIGHT_DATA` (the first load) or the body of
|
|
3
|
+
// the router's fetch (a navigation, a form, revalidate()).
|
|
4
|
+
//
|
|
5
|
+
// The bytes go to React's Flight client through ./transport.js, so a response
|
|
6
|
+
// cut short reaches the Suspense boundaries labelled, and a second reader
|
|
7
|
+
// settles when the last byte is in.
|
|
8
|
+
import { createFromReadableStream } from "virtual:ruact/flight-client";
|
|
9
|
+
import { labelledStream } from "./transport.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* What the router dispatches on the document before it stops one it leaves
|
|
13
|
+
* while it still loads (Story 19-2).
|
|
14
|
+
*/
|
|
15
|
+
export const LEAVE_EVENT = "ruact:leave";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The rows the document pushes onto `__FLIGHT_DATA`, as a byte stream that
|
|
19
|
+
* stays open while the document does (Story 19-1): the rows already in the
|
|
20
|
+
* queue, then each one pushed later — a page that streams pushes one script per
|
|
21
|
+
* `<Suspense>` — until the document has finished loading. A row pushed before
|
|
22
|
+
* this runs is in the queue, and read first.
|
|
23
|
+
*
|
|
24
|
+
* A document the router leaves ({@link LEAVE_EVENT}) ends the stream as an
|
|
25
|
+
* aborted navigation does: the page being left reports no response cut short.
|
|
26
|
+
*/
|
|
27
|
+
export function documentRows(queue, doc = globalThis.document) {
|
|
28
|
+
const encoder = new TextEncoder();
|
|
29
|
+
return new ReadableStream({
|
|
30
|
+
start(controller) {
|
|
31
|
+
let open = true;
|
|
32
|
+
const read = (row) => controller.enqueue(encoder.encode(row));
|
|
33
|
+
queue.forEach(read);
|
|
34
|
+
queue.length = 0;
|
|
35
|
+
queue.push = (...rows) => {
|
|
36
|
+
if (open) rows.forEach(read);
|
|
37
|
+
return 0;
|
|
38
|
+
};
|
|
39
|
+
const close = (error) => {
|
|
40
|
+
if (!open) return;
|
|
41
|
+
open = false;
|
|
42
|
+
doc?.removeEventListener?.("readystatechange", atEnd);
|
|
43
|
+
doc?.removeEventListener?.(LEAVE_EVENT, left);
|
|
44
|
+
if (error) controller.error(error);
|
|
45
|
+
else controller.close();
|
|
46
|
+
};
|
|
47
|
+
// Closed when the document stops loading: its end (`interactive`, just
|
|
48
|
+
// before DOMContentLoaded), or the router stopping it (Story 19-2).
|
|
49
|
+
const atEnd = () => {
|
|
50
|
+
if (doc.readyState !== "loading") close();
|
|
51
|
+
};
|
|
52
|
+
const left = () => close(new DOMException("the page was left while it loaded", "AbortError"));
|
|
53
|
+
if (doc?.readyState === "loading") {
|
|
54
|
+
doc.addEventListener("readystatechange", atEnd);
|
|
55
|
+
doc.addEventListener(LEAVE_EVENT, left);
|
|
56
|
+
} else {
|
|
57
|
+
close();
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The element the page mounts into. The entry is `async` and may run before
|
|
65
|
+
* the parser reaches `<div id="root">` — a layout that puts `ruact_js_assets`
|
|
66
|
+
* above it — so this waits for it, and gives up (null) when the document ends
|
|
67
|
+
* without one.
|
|
68
|
+
*/
|
|
69
|
+
export function whenRoot(doc = globalThis.document) {
|
|
70
|
+
const found = doc.getElementById("root");
|
|
71
|
+
if (found || doc.readyState !== "loading") return Promise.resolve(found);
|
|
72
|
+
return new Promise((resolve) => {
|
|
73
|
+
const observer = new MutationObserver(() => {
|
|
74
|
+
const root = doc.getElementById("root");
|
|
75
|
+
if (root) finish(root);
|
|
76
|
+
});
|
|
77
|
+
const finish = (root) => {
|
|
78
|
+
observer.disconnect();
|
|
79
|
+
doc.removeEventListener("DOMContentLoaded", atEnd);
|
|
80
|
+
resolve(root);
|
|
81
|
+
};
|
|
82
|
+
const atEnd = () => finish(doc.getElementById("root"));
|
|
83
|
+
observer.observe(doc.documentElement, { childList: true, subtree: true });
|
|
84
|
+
doc.addEventListener("DOMContentLoaded", atEnd);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Opens a page from a byte stream.
|
|
90
|
+
*
|
|
91
|
+
* `root` resolves with the tree as soon as row 0 has arrived; deferred Suspense
|
|
92
|
+
* rows keep streaming into it. A stream that ends without a root rejects it
|
|
93
|
+
* (./transport.js labels the failure). `end` settles when the last byte is in,
|
|
94
|
+
* and rejects with the stream's error; it never goes unhandled on its own.
|
|
95
|
+
*/
|
|
96
|
+
export function openPage(body) {
|
|
97
|
+
const [forClient, forEnd] = body.tee();
|
|
98
|
+
const end = drain(forEnd);
|
|
99
|
+
end.catch(() => {});
|
|
100
|
+
// React's client returns a thenable; a Promise is what callers can await,
|
|
101
|
+
// chain and assert on.
|
|
102
|
+
return { root: Promise.resolve(createFromReadableStream(labelledStream(forClient))), end };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function drain(stream) {
|
|
106
|
+
const reader = stream.getReader();
|
|
107
|
+
while (!(await reader.read()).done) { /* the client has its own copy */ }
|
|
108
|
+
}
|
|
@@ -10,9 +10,8 @@
|
|
|
10
10
|
* Also handles popstate for browser back/forward.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { createFromReadableStream } from "virtual:ruact/flight-client";
|
|
14
13
|
import { setBoundaryErrorHandler } from "./suspense-boundary.js";
|
|
15
|
-
import {
|
|
14
|
+
import { LEAVE_EVENT, openPage } from "./page-stream.js";
|
|
16
15
|
|
|
17
16
|
// Errors already reported once (by a Suspense boundary) that navigate() must
|
|
18
17
|
// not report again on their way out to a revalidate() caller.
|
|
@@ -22,17 +21,29 @@ let _onNavigate = null;
|
|
|
22
21
|
let _onError = null;
|
|
23
22
|
let _currentAbort = null;
|
|
24
23
|
|
|
24
|
+
// Story 19-2 — whether the page on screen is a document the router stopped
|
|
25
|
+
// and has not replaced yet (a new tree, a full load, a native submit); and the
|
|
26
|
+
// path of the page on screen, which a `#anchor` does not leave.
|
|
27
|
+
let _pageStopped = false;
|
|
28
|
+
let _renderedPath = null;
|
|
29
|
+
|
|
25
30
|
/**
|
|
26
31
|
* @param {object} opts
|
|
27
32
|
* @param {function} opts.onNavigate - called with the new React element tree
|
|
28
33
|
* @param {function} [opts.onError] - called with an Error when navigation fails,
|
|
29
34
|
* or when a Suspense child's row is an error
|
|
35
|
+
* @param {function} [opts.onBoundaryError] - called, too, when a Suspense child's
|
|
36
|
+
* row is an error (the bootstrap's development
|
|
37
|
+
* overlay — Story 19-1); not for a navigation
|
|
30
38
|
*/
|
|
31
|
-
export function setupRouter({ onNavigate, onError = null }) {
|
|
39
|
+
export function setupRouter({ onNavigate, onError = null, onBoundaryError = null }) {
|
|
32
40
|
_onNavigate = onNavigate;
|
|
33
41
|
_onError = onError;
|
|
42
|
+
_pageStopped = false;
|
|
43
|
+
_renderedPath = typeof location === "undefined" ? null : location.pathname + location.search;
|
|
34
44
|
setBoundaryErrorHandler((error) => {
|
|
35
45
|
console.error(error.message);
|
|
46
|
+
onBoundaryError?.(error);
|
|
36
47
|
_onError?.(error);
|
|
37
48
|
});
|
|
38
49
|
|
|
@@ -55,11 +66,15 @@ export function setupRouter({ onNavigate, onError = null }) {
|
|
|
55
66
|
// what link clicks and form submits want, but a programmatic
|
|
56
67
|
// `revalidate()` caller HAS to be able to branch on success vs.
|
|
57
68
|
// failure.
|
|
69
|
+
// `leave: false` (Story 19-2): revalidate() stays on the page, so a
|
|
70
|
+
// document still streaming is not stopped — stopping it would also fail the
|
|
71
|
+
// page's other requests in flight (a server function, a useQuery).
|
|
58
72
|
globalThis.__ruact_revalidate = (target) =>
|
|
59
73
|
navigate(target ?? location.pathname + location.search, {
|
|
60
74
|
push: false,
|
|
61
75
|
scroll: false,
|
|
62
76
|
throwOnError: true,
|
|
77
|
+
leave: false,
|
|
63
78
|
});
|
|
64
79
|
}
|
|
65
80
|
|
|
@@ -109,9 +124,12 @@ function handleClick(event) {
|
|
|
109
124
|
const anchor = event.target.closest("a[href]");
|
|
110
125
|
if (!shouldIntercept(anchor)) return;
|
|
111
126
|
|
|
112
|
-
event.preventDefault();
|
|
113
|
-
|
|
114
127
|
const url = new URL(anchor.getAttribute("href"), location.href);
|
|
128
|
+
// Story 19-2 — this page with a `#anchor`: the browser scrolls; nothing to
|
|
129
|
+
// fetch, and a page still streaming is not left.
|
|
130
|
+
if (url.hash && url.pathname + url.search === location.pathname + location.search) return;
|
|
131
|
+
|
|
132
|
+
event.preventDefault();
|
|
115
133
|
navigate(url.pathname + url.search + url.hash);
|
|
116
134
|
}
|
|
117
135
|
|
|
@@ -301,6 +319,7 @@ function _isFlight(response) {
|
|
|
301
319
|
// `response.url` never carries the #fragment (Fetch strips it), so the one
|
|
302
320
|
// the link asked for is put back.
|
|
303
321
|
function _fullLoad(response, targetUrl, { replace = false } = {}) {
|
|
322
|
+
_pageStopped = false;
|
|
304
323
|
const requested = new URL(targetUrl, location.href);
|
|
305
324
|
const destination = new URL(response.url || requested.href);
|
|
306
325
|
if (!destination.hash && requested.hash) destination.hash = requested.hash;
|
|
@@ -337,6 +356,7 @@ const _SUBMITTER_OVERRIDES = [
|
|
|
337
356
|
];
|
|
338
357
|
|
|
339
358
|
function _nativeSubmit(form, submitter) {
|
|
359
|
+
_pageStopped = false;
|
|
340
360
|
const restore = [];
|
|
341
361
|
const added = [];
|
|
342
362
|
|
|
@@ -402,6 +422,7 @@ async function _submitForm(form, submitter = null) {
|
|
|
402
422
|
// Rails _method hidden field is included in FormData automatically;
|
|
403
423
|
// Rack::MethodOverride reads it and routes to the correct action.
|
|
404
424
|
_currentAbort?.abort();
|
|
425
|
+
_leaveOpenDocument();
|
|
405
426
|
const controller = (_currentAbort = new AbortController());
|
|
406
427
|
|
|
407
428
|
const headers = { Accept: "text/x-component", "Ruact-Request": "1" };
|
|
@@ -436,6 +457,8 @@ async function _submitForm(form, submitter = null) {
|
|
|
436
457
|
if (err.name === "AbortError") return;
|
|
437
458
|
console.error("[ruact-router] Form submission error:", err);
|
|
438
459
|
_onError?.(err);
|
|
460
|
+
} finally {
|
|
461
|
+
_recoverStoppedPage(controller);
|
|
439
462
|
}
|
|
440
463
|
}
|
|
441
464
|
|
|
@@ -446,6 +469,9 @@ async function _submitForm(form, submitter = null) {
|
|
|
446
469
|
function handlePopstate() {
|
|
447
470
|
// Story 17.0f — `replace`: if this entry turns out not to be a ruact page, the
|
|
448
471
|
// full load must not push a NEW entry into the history being walked.
|
|
472
|
+
// Story 19-2 — only the hash changed (a `#anchor`, Back over one): the page
|
|
473
|
+
// on screen is this one, and the browser scrolls.
|
|
474
|
+
if (location.pathname + location.search === _renderedPath) return;
|
|
449
475
|
navigate(location.pathname + location.search + location.hash, { push: false, scroll: false, replace: true });
|
|
450
476
|
}
|
|
451
477
|
|
|
@@ -453,6 +479,38 @@ function handlePopstate() {
|
|
|
453
479
|
// Navigation
|
|
454
480
|
// ---------------------------------------------------------------------------
|
|
455
481
|
|
|
482
|
+
/**
|
|
483
|
+
* Story 19-2 — the page being left may be a document still arriving: a page
|
|
484
|
+
* that streams, whose <Suspense> rows are still to come. Nobody will see them,
|
|
485
|
+
* so stop loading it: the connection closes, and the server, which asks before
|
|
486
|
+
* each block, starts no further block for it. Only a document that streams
|
|
487
|
+
* and has rows still to come (the server marks it, `__FLIGHT_DATA.streaming`):
|
|
488
|
+
* any other document still being parsed — a slow network, a long layout — is
|
|
489
|
+
* left to load, the part of its layout after `#root` included.
|
|
490
|
+
*/
|
|
491
|
+
function _leaveOpenDocument() {
|
|
492
|
+
if (typeof document === "undefined" || document.readyState !== "loading") return;
|
|
493
|
+
if (globalThis.__FLIGHT_DATA?.streaming !== true) return;
|
|
494
|
+
// First its rows' stream ends as an aborted navigation: stopped, it would
|
|
495
|
+
// read as a response cut short, which the page being left reports.
|
|
496
|
+
if (typeof document.dispatchEvent === "function") document.dispatchEvent(new Event(LEAVE_EVENT));
|
|
497
|
+
if (typeof window !== "undefined" && typeof window.stop === "function") window.stop();
|
|
498
|
+
_pageStopped = true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Story 19-2 — a navigation ended without replacing a page the router had
|
|
503
|
+
* stopped (a failed fetch, a 403 or a 500 to a form — this navigation's, or a
|
|
504
|
+
* later one's that superseded the one that stopped it): the user is still on
|
|
505
|
+
* that page, whose boundaries would keep their fallback. Fetch it again,
|
|
506
|
+
* without stopping anything, once. Not when a newer navigation took over.
|
|
507
|
+
*/
|
|
508
|
+
function _recoverStoppedPage(controller) {
|
|
509
|
+
if (!_pageStopped || controller.signal.aborted) return;
|
|
510
|
+
_pageStopped = false;
|
|
511
|
+
navigate(location.pathname + location.search, { push: false, scroll: false, leave: false });
|
|
512
|
+
}
|
|
513
|
+
|
|
456
514
|
/**
|
|
457
515
|
* Story 8.2 review patch R7 (2026-05-17) — `throwOnError` opts a caller
|
|
458
516
|
* out of the swallow-and-forward error mode. The default (false) is the
|
|
@@ -462,9 +520,10 @@ function handlePopstate() {
|
|
|
462
520
|
* REJECTS on a failed Flight fetch — without this, callers cannot
|
|
463
521
|
* branch on success vs. failure of a programmatic refetch.
|
|
464
522
|
*/
|
|
465
|
-
async function navigate(url, { push = true, scroll = true, throwOnError = false, replace = false } = {}) {
|
|
523
|
+
async function navigate(url, { push = true, scroll = true, throwOnError = false, replace = false, leave = true } = {}) {
|
|
466
524
|
// Abort any in-flight fetch so we don't apply a stale response
|
|
467
525
|
_currentAbort?.abort();
|
|
526
|
+
if (leave) _leaveOpenDocument();
|
|
468
527
|
const controller = (_currentAbort = new AbortController());
|
|
469
528
|
|
|
470
529
|
try {
|
|
@@ -501,6 +560,8 @@ async function navigate(url, { push = true, scroll = true, throwOnError = false,
|
|
|
501
560
|
console.error("[ruact-router] Navigation error:", err);
|
|
502
561
|
_onError?.(err);
|
|
503
562
|
if (throwOnError) throw err;
|
|
563
|
+
} finally {
|
|
564
|
+
_recoverStoppedPage(controller);
|
|
504
565
|
}
|
|
505
566
|
}
|
|
506
567
|
|
|
@@ -570,15 +631,11 @@ async function _processFlightResponse(response, {
|
|
|
570
631
|
? (() => { const u = new URL(response.url); return u.pathname + u.search + u.hash; })()
|
|
571
632
|
: targetUrl;
|
|
572
633
|
|
|
573
|
-
// Two readers of one body: React's client, and a drain that settles when the
|
|
574
|
-
// last row is in — `revalidate()` resolves then, as it always has.
|
|
575
|
-
const [forClient, forEnd] = response.body.tee();
|
|
576
|
-
const streamEnd = _drain(forEnd);
|
|
577
|
-
streamEnd.catch(() => {}); // awaited below; an abort must not be "unhandled" first
|
|
578
|
-
|
|
579
634
|
// Resolves when row 0 arrives; deferred Suspense rows keep streaming into it.
|
|
580
|
-
//
|
|
581
|
-
|
|
635
|
+
// `page.end` settles when the last row is in — `revalidate()` resolves then,
|
|
636
|
+
// as it always has.
|
|
637
|
+
const page = openPage(response.body);
|
|
638
|
+
const root = await page.root;
|
|
582
639
|
|
|
583
640
|
// A newer navigation started while this one was in flight: it owns the page.
|
|
584
641
|
if (signal?.aborted) return;
|
|
@@ -595,11 +652,13 @@ async function _processFlightResponse(response, {
|
|
|
595
652
|
}
|
|
596
653
|
|
|
597
654
|
if (pushEntry) history.pushState(null, "", finalPath);
|
|
655
|
+
_pageStopped = false;
|
|
656
|
+
_renderedPath = location.pathname + location.search;
|
|
598
657
|
_onNavigate(root);
|
|
599
658
|
if (scroll) window.scrollTo(0, 0);
|
|
600
659
|
|
|
601
660
|
try {
|
|
602
|
-
await
|
|
661
|
+
await page.end;
|
|
603
662
|
} catch (err) {
|
|
604
663
|
// The page is on screen. A row that never arrived was reported by its
|
|
605
664
|
// Suspense boundary; a revalidate() caller still learns it failed.
|
|
@@ -607,8 +666,3 @@ async function _processFlightResponse(response, {
|
|
|
607
666
|
throw err;
|
|
608
667
|
}
|
|
609
668
|
}
|
|
610
|
-
|
|
611
|
-
async function _drain(stream) {
|
|
612
|
-
const reader = stream.getReader();
|
|
613
|
-
while (!(await reader.read()).done) { /* the client has its own copy */ }
|
|
614
|
-
}
|
|
@@ -27,6 +27,7 @@ export function setBoundaryErrorHandler(handler) {
|
|
|
27
27
|
// runtime/transport.js labels a response cut short the same way.
|
|
28
28
|
const DIGEST_MESSAGES = {
|
|
29
29
|
"ruact:suspense-timeout": "Suspense timeout exceeded",
|
|
30
|
+
"ruact:suspense-error": "An error occurred in a <Suspense>",
|
|
30
31
|
"ruact:connection-closed": "the response ended before the whole page arrived",
|
|
31
32
|
};
|
|
32
33
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruact
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.21
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Luiz Garcia
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|
|
@@ -71,15 +71,20 @@ files:
|
|
|
71
71
|
- lib/generators/ruact/scaffold/templates/views/new.html.erb.tt
|
|
72
72
|
- lib/generators/ruact/scaffold/templates/views/show.html.erb.tt
|
|
73
73
|
- lib/ruact.rb
|
|
74
|
+
- lib/ruact/cached_blocks.rb
|
|
74
75
|
- lib/ruact/client_manifest.rb
|
|
75
76
|
- lib/ruact/component_attributes.rb
|
|
76
77
|
- lib/ruact/component_contract.rb
|
|
77
78
|
- lib/ruact/configuration.rb
|
|
78
79
|
- lib/ruact/controller.rb
|
|
80
|
+
- lib/ruact/controller/delivery.rb
|
|
79
81
|
- lib/ruact/controller/document_rendering.rb
|
|
80
82
|
- lib/ruact/controller/format_negotiation.rb
|
|
81
83
|
- lib/ruact/controller/page_rendering.rb
|
|
82
84
|
- lib/ruact/controller/pages.rb
|
|
85
|
+
- lib/ruact/controller/streaming.rb
|
|
86
|
+
- lib/ruact/declared_content.rb
|
|
87
|
+
- lib/ruact/deferred_render.rb
|
|
83
88
|
- lib/ruact/doctor.rb
|
|
84
89
|
- lib/ruact/erb_preprocessor.rb
|
|
85
90
|
- lib/ruact/erb_preprocessor_hook.rb
|
|
@@ -93,15 +98,20 @@ files:
|
|
|
93
98
|
- lib/ruact/flight/serializer.rb
|
|
94
99
|
- lib/ruact/flight/utf8.rb
|
|
95
100
|
- lib/ruact/html_converter.rb
|
|
101
|
+
- lib/ruact/inline_script.rb
|
|
96
102
|
- lib/ruact/layout_source.rb
|
|
103
|
+
- lib/ruact/load_async_note.rb
|
|
97
104
|
- lib/ruact/manifest_resolver.rb
|
|
105
|
+
- lib/ruact/markers.rb
|
|
98
106
|
- lib/ruact/navigation_boundary.rb
|
|
99
107
|
- lib/ruact/packaging.rb
|
|
100
108
|
- lib/ruact/query.rb
|
|
109
|
+
- lib/ruact/query_watch.rb
|
|
101
110
|
- lib/ruact/railtie.rb
|
|
102
111
|
- lib/ruact/redirect_path.rb
|
|
103
112
|
- lib/ruact/render_context.rb
|
|
104
113
|
- lib/ruact/render_pipeline.rb
|
|
114
|
+
- lib/ruact/request_kind.rb
|
|
105
115
|
- lib/ruact/routing.rb
|
|
106
116
|
- lib/ruact/serializable.rb
|
|
107
117
|
- lib/ruact/server.rb
|
|
@@ -134,6 +144,7 @@ files:
|
|
|
134
144
|
- lib/ruact/validation_errors_collector.rb
|
|
135
145
|
- lib/ruact/version.rb
|
|
136
146
|
- lib/ruact/view_helper.rb
|
|
147
|
+
- lib/ruact/view_helper/suspense.rb
|
|
137
148
|
- lib/ruact/views/layouts/ruact.html.erb
|
|
138
149
|
- lib/rubocop/cop/ruact.rb
|
|
139
150
|
- lib/rubocop/cop/ruact/no_extend_self.rb
|
|
@@ -154,11 +165,15 @@ files:
|
|
|
154
165
|
- vendor/javascript/vite-plugin-ruact/manifest-http.test.mjs
|
|
155
166
|
- vendor/javascript/vite-plugin-ruact/package-lock.json
|
|
156
167
|
- vendor/javascript/vite-plugin-ruact/package.json
|
|
168
|
+
- vendor/javascript/vite-plugin-ruact/page-mount.test.mjs
|
|
169
|
+
- vendor/javascript/vite-plugin-ruact/page-stream.test.mjs
|
|
157
170
|
- vendor/javascript/vite-plugin-ruact/registry.test.mjs
|
|
158
171
|
- vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs
|
|
159
172
|
- vendor/javascript/vite-plugin-ruact/runtime/bootstrap.jsx
|
|
173
|
+
- vendor/javascript/vite-plugin-ruact/runtime/dev-overlay.js
|
|
160
174
|
- vendor/javascript/vite-plugin-ruact/runtime/flight-modules.js
|
|
161
175
|
- vendor/javascript/vite-plugin-ruact/runtime/flight-webpack-globals.js
|
|
176
|
+
- vendor/javascript/vite-plugin-ruact/runtime/page-stream.js
|
|
162
177
|
- vendor/javascript/vite-plugin-ruact/runtime/root-boundary.js
|
|
163
178
|
- vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js
|
|
164
179
|
- vendor/javascript/vite-plugin-ruact/runtime/suspense-boundary.js
|