stimulusreflex / stimulus_reflex

Build reactive applications with the Rails tooling you already know and love.
https://docs.stimulusreflex.com
MIT License
2.28k stars 172 forks source link

SR 4 can safely shrink request and response payload sizes #598

Closed leastbad closed 1 year ago

leastbad commented 2 years ago

With a major version bump on the horizon, we can take a hard look at our payload sizes. IMO, much of the information that we're passing is redundant. I'm going to do a breakdown here so that we can discuss and share our approach with folks like @joshleblanc and @jonathan-s who write code that interfaces with our data structures.

Note that several points assume that #592 will be merged.

Request

Today:

Details ```ruby { "attrs" => { "class" => "btn btn-dark", "type" => "button", "data-reflex" => "click->example#ping", "data-action" => "click->example#__perform", "data-controller" => "stimulus-reflex", "checked" => false, "selected" => false, "tag_name" => "BUTTON", "value" => "" }, "dataset" => { "dataset" => { "data-reflex" => "click->example#ping", "data-action" => "click->example#__perform" }, "datasetAll" => { } }, "selectors" => [], "reflexId" => "22d1b880-417d-4a9b-b4b3-b5339562368f", "resolveLate" => false, "suppressLogging" => false, "xpathController" => "//*[@id='poop']/form[1]/button[1]", "xpathElement" => "//*[@id='poop']/form[1]/button[1]", "inner_html" => "", "text_content" => "", "reflexController" => "stimulus-reflex", "permanentAttributeName" => "data-reflex-permanent", "target" => "example#ping", "args" => [], "url" => "http://localhost:3000/", "tabId" => "c146c7c3-7914-4dfb-adc1-5f6e70f57ce4", "version" => "3.5.0-pre9", "params" => { "last_name" => "bad", "bar" => true }, "formData" => "first_name=&last_name=" } ```

Proposed:

Details ```ruby { "attrs" => { "class" => "btn btn-dark", "type" => "button", "data-reflex" => "click->example#ping", "checked" => false, "selected" => false, "tag_name" => "BUTTON", "value" => "" }, "reflexId" => "22d1b880-417d-4a9b-b4b3-b5339562368f", "target" => "example#ping", "url" => "http://localhost:3000/", "tabId" => "c146c7c3-7914-4dfb-adc1-5f6e70f57ce4", "version" => "3.5.0-pre9" } ```

That has to look nicer, no?

Response

Today:

Details ```ruby { "cableReady" => true, "operations" => [ { "value" => 0, "reflexId" => "22d1b880-417d-4a9b-b4b3-b5339562368f", "selector" => "#my_val", "operation" => "setValue" }, { "name" => "stimulus-reflex:morph-nothing", "selector" => nil, "payload" => { }, "stimulusReflex" => { "attrs" => { "class" => "btn btn-dark", "type" => "button", "data-reflex-serialize-form" => "true", "data-reflex" => "fart->example#ping", "data-action" => "example#ping fart->example#__perform", "data-controller" => "example", "checked" => false, "selected" => false, "tagName" => "BUTTON", "value" => "" }, "dataset" => { "dataset" => { "data-reflex-serialize-form" => "true", "data-reflex" => "fart->example#ping", "data-action" => "example#ping fart->example#__perform", "data-controller" => "example" }, "datasetAll" => { } }, "selectors" => ["body"], "reflexId" => "22d1b880-417d-4a9b-b4b3-b5339562368f", "resolveLate" => false, "suppressLogging" => false, "xpathController" => "//*[@id='poop']/form[1]/button[1]", "xpathElement" => "//*[@id='poop']/form[1]/button[1]", "innerHtml" => "", "textContent" => "", "reflexController" => "example", "permanentAttributeName" => "data-reflex-permanent", "target" => "example#ping", "args" => [], "url" => "http://localhost:3000/", "tabId" => "c146c7c3-7914-4dfb-adc1-5f6e70f57ce4", "version" => "3.5.0-pre9", "params" => { "lastName" => "bad", "bar" => true }, "formData" => "first_name=&last_name=", "morph" => :nothing }, "reflexId" => "22d1b880-417d-4a9b-b4b3-b5339562368f", "operation" => "dispatchEvent" } ], "version" => "5.0.0.pre9" } ```

With the introduction of the Reflex class on the client, there is basically no need to send the stimulusReflex object back to the client. We are already tracking almost everything in the hash on the Reflex instance. In fact, so far as I can tell, the only thing that is new is the morph type. For page and selector morphs, html, permanent_attribute_name and children_only are passed as options to CableReady itself.

Note that we might even be able to default the permanent_attribute_name to data-reflex-permanent and save another 45 bytes for every Reflex operation.

Proposed:

Details ```ruby { "cableReady" => true, "operations" => [ { "value" => 0, "reflexId" => "22d1b880-417d-4a9b-b4b3-b5339562368f", "selector" => "#my_val", "operation" => "setValue" }, { "name" => "stimulus-reflex:morph-nothing", "morph" => :nothing, "reflexId" => "22d1b880-417d-4a9b-b4b3-b5339562368f", "operation" => "dispatchEvent" } ], "version" => "5.0.0.pre9" } ```

Sign me up!

leastbad commented 2 years ago

If we capture the permanent flagged DOM elements during the Reflex init and store references, we can do permanent protection entirely on the client and not even need to send the permanentAttributeName at all.

julianrubisch commented 1 year ago

I think we can close this for now, as our attention is on building up TurboBoost. We can reopen this if the need arises.