<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Oleg Isonen</title>
    <description>The latest articles on Forem by Oleg Isonen (@oleg008).</description>
    <link>https://forem.com/oleg008</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F306415%2Fd0bb0016-32ce-4d3e-9fa1-be1dd5e6fb4e.jpg</url>
      <title>Forem: Oleg Isonen</title>
      <link>https://forem.com/oleg008</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/oleg008"/>
    <language>en</language>
    <item>
      <title>Synchronized immutable state with time travel</title>
      <dc:creator>Oleg Isonen</dc:creator>
      <pubDate>Tue, 05 Apr 2022 19:45:15 +0000</pubDate>
      <link>https://forem.com/oleg008/synchronized-immutable-state-with-time-travel-2c6o</link>
      <guid>https://forem.com/oleg008/synchronized-immutable-state-with-time-travel-2c6o</guid>
      <description>&lt;p&gt;I am working on &lt;a href="https://twitter.com/webstudiois"&gt;@webstudiois&lt;/a&gt; - an open-source visual development platform. &lt;br&gt;
As part of the UI for the Webstudio Designer, a very interactive interface, I needed an undo/redo functionality and created a library for synchronizing state across the client, server, and multiple clients in future releases.&lt;/p&gt;
&lt;h2&gt;
  
  
  Challenge 1 - immutability
&lt;/h2&gt;

&lt;p&gt;Is immutability a solved problem in EcmaScript? Not by a long shot. &lt;/p&gt;

&lt;p&gt;We have got far, starting with initial attempts using abstractions like &lt;a href="https://immutable-js.com/"&gt;immutable&lt;/a&gt; which has a severe downside with interoperability. Then we went over to manually cloning objects and arrays using built-in language features like the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax"&gt;spread operator&lt;/a&gt;, which makes it super unreadable to change complex structures. For example, nested objects or arrays with objects because you have to go inside whatever you want to change and then clone every object on your path to keep the object immutable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/structuredClone"&gt;Structured cloning&lt;/a&gt; is an API I look very much forward to, though it's not solving the same problem Immer does.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://immerjs.github.io/immer/"&gt;Immer&lt;/a&gt; is the first popular library to solve immutability in an interoperable way. It lets you work with regular objects, so you have no lock-in on Immer and can use vanilla functions that mutate anything you give them without causing any trouble. The most crucial feature of Immer for me, though, is patches generation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;produce&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;immer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;produce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;draft&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Mutable behavior is allowed on the draft.&lt;/span&gt;
  &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;draft&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Tweet about it&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;})(&lt;/span&gt;&lt;span class="nx"&gt;previousState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Challenge 2 - syncing UI state to the server
&lt;/h2&gt;

&lt;p&gt;When using immutable data structures, you mainly have only two options on how to save the changes to the server:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Send custom updates to the server using any APIs.&lt;/p&gt;

&lt;p&gt;This is probably the most popular approach, which may work fine in simple cases, but won't scale well as your application grows.&lt;/p&gt;

&lt;p&gt;You have to rely on each update implemented correctly because it usually ends up with custom logic for each update that prepares the data to be sent to the server. You also need to handle errors to let you reflect the server state on the client, making it hard to do, especially with an optimistic UI. Over time, this approach provides a large surface for potential errors. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It can't be undone easily.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When data you sent to the server was manually prepared for this update, undoing that update will also require you to write custom code that would undo the same update. In some cases, it becomes very complex, especially when you have to talk to multiple endpoints, and every endpoint also has to have a way to undo the change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sending entire snapshots to the server&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Challenge 3 - sending the minimal updates to the server
&lt;/h2&gt;

&lt;p&gt;It is super easy to update the server using snapshots, reflecting everything you want to be permanently stored. This makes sure the data on the server and the client is consistent, and it's super easy to keep it this way over time.&lt;/p&gt;

&lt;p&gt;Downsides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You have to potentially send massive payloads to the server, making it impossible to sync frequently and quickly&lt;/li&gt;
&lt;li&gt;You can't easily build collaborative features because if two clients can update the same state, you have to figure out how to make a conflict resolution work with a snapshot. We already lost the information about who changed what and when.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Doing it this way, you can quickly run into conflicts even by opening two separate tabs. Also, you are limited in the ability to use iframes or web workers because if any of them require access to the state, they can't get easily synchronized without sending the entire state to them. This is a real deal-breaker since it will negate any potential benefit of using a web worker.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge 4 - undo/redo the UI state
&lt;/h2&gt;

&lt;p&gt;If some centralized store-like abstraction does not manage your UI state, undoing it becomes problematic because you have to make sure that every representation of a certain state is being updated.&lt;/p&gt;

&lt;p&gt;In addition to that, you need to be able to distinguish multiple types of states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data state - essentially, what the server knows about your app and should be undoable&lt;/li&gt;
&lt;li&gt;UI state - current state of the UI that is not reflected on the server and should not be undoable, e.g., a derrived state&lt;/li&gt;
&lt;li&gt;Ephemeral state - a state that is temporarily reflected on the UI, and should not be undoable, e.g., a selected element&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having store-like abstraction to manage states requires you to manually define which state and action is undoable, which state is being reflected on the server, and which one is ephemeral if it's not a component's local state.&lt;/p&gt;

&lt;p&gt;There are many edge cases, and managing that in a large app is not trivial.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution with autogenerated patches
&lt;/h2&gt;

&lt;p&gt;All of the above challenges were a problem for me. I need a minimal amount of changes to be sent over the network. I need a way to easily undo/redo the changes. I need the ability to reflect changes inside an iframe. I need to reflect changes across tabs and windows for a collaborative editing experience.&lt;/p&gt;

&lt;p&gt;Let me introduce you &lt;a href="https://github.com/webstudio-is/immerhin"&gt;Immerhin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Immerhin is a very thin abstraction on top of Immer that lets you use basically any state management (Redux-like stores etc.). I am personally using this tiny library &lt;a href="https://github.com/kof/react-nano-state"&gt;react nano state&lt;/a&gt; with it.&lt;/p&gt;

&lt;p&gt;Immerhin lets you declare a transaction to identify a single user action, which may update multiple states. Thanks to Immer, it enables you to mutate all those states without losing immutability. It uses Immer's &lt;a href="https://immerjs.github.io/immer/patches"&gt;patches&lt;/a&gt; and revise patches to send the minimal amount of changes to any consumers and enables undo/redo out of the box.&lt;/p&gt;

&lt;p&gt;Immerhin's current development state is not battle-tested and still lacks many features, so consider this as a project to follow for future releases or use for non-production projects. You can follow the repo or &lt;a href="https://twitter.com/webstudiois"&gt;Webstudio's Twitter&lt;/a&gt; account for the updates on this.&lt;/p&gt;

&lt;p&gt;Here is a small &lt;a href="https://codesandbox.io/s/github/webstudio-is/immerhin/tree/main/examples/react"&gt;Codesandbox demo&lt;/a&gt; that shows how it can be used to have a shared state between 2 separate lists and have an undo/redo functionality with no extra logic to manage for undo/redo.&lt;/p&gt;

&lt;p&gt;Here is how to use Immerhin:&lt;/p&gt;

&lt;h3&gt;
  
  
  Create container
&lt;/h3&gt;

&lt;p&gt;Container is an object that has &lt;code&gt;.value&lt;/code&gt; reflecting the current value and a &lt;code&gt;.dispatch(nextValue)&lt;/code&gt; which will update all container subscribers. Immerhin only cares about those two properties. How you do the rest is up to you.&lt;/p&gt;

&lt;p&gt;Example using react-nano-state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;createContainer&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-nano-state&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;initialValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Create a transaction
&lt;/h3&gt;

&lt;p&gt;Transactions let Immerhin know which containers to update and which patches to apply. They are used to implement automatic undo and sync.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;store&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;immerhin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// - generate patches&lt;/span&gt;
&lt;span class="c1"&gt;// - update states&lt;/span&gt;
&lt;span class="c1"&gt;// - inform all subscribers&lt;/span&gt;
&lt;span class="c1"&gt;// - register a transaction for potential undo/redo and sync calls&lt;/span&gt;
&lt;span class="nx"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;container1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;container2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;mutateValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;mutateValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Sending the changes
&lt;/h3&gt;

&lt;p&gt;It's up to you how you send the changes. Immerhin provides a &lt;code&gt;sync()&lt;/code&gt; function that gives you all accumulated changes so far and clears the stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Setup periodic sync with a fetch, or do this with Websocket&lt;/span&gt;
&lt;span class="nx"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/patch&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Undo/redo the changes
&lt;/h3&gt;

&lt;p&gt;Since Immerhin knows everything from transactions, you just need to call &lt;code&gt;store.undo()&lt;/code&gt; or &lt;code&gt;store.redo()&lt;/code&gt; to update those containers with revised patches from Immer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Downsides with Immerhin
&lt;/h2&gt;

&lt;p&gt;Like with any solution, there are tradeoffs. Here are a few I could come up with so far:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Potentially, if one of the consumers did not receive the patches, their state is getting out of sync. Currently, Immerhin doesn't provide a way to work around this. If you have great ideas, please share them &lt;a href="https://twitter.com/kof"&gt;on Twitter&lt;/a&gt; or over the &lt;a href="https://github.com/webstudio-is/immerhin/issues"&gt;issues&lt;/a&gt;. An ordered id on each transaction could potentially be used to throw an error if something is missing and the client has to refetch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immer patches contain a path, but a path is essentially relative to the original object. Should there be inconsistency, the path is going to be wrong. Potentially we could use IDs instead of paths. Also if the previous problem is solved, this is not a huge deal because as soon as the update is out of sync, it can be rejected.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Immer uses patches spec identical to &lt;a href="http://jsonpatch.com/"&gt;JSON patch&lt;/a&gt; with a difference on the &lt;code&gt;path&lt;/code&gt; being an array, not a string.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Future of Immerhin
&lt;/h2&gt;

&lt;p&gt;There is a bunch of missing features I really want to implement in the future, and Webstudio will need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Conflict resolution&lt;/li&gt;
&lt;li&gt;Sync mechanism between iframes and tabs&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you click on the "Try Webstudio" button on &lt;a href="https://webstudio.is/"&gt;webstudio.is&lt;/a&gt;, you will see the designer interface that uses Immerhin. While the UI is very alpha, I am curious if you will already find obvious bugs with undo/redo functionality.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Webstudio - next generation visual development</title>
      <dc:creator>Oleg Isonen</dc:creator>
      <pubDate>Sun, 20 Mar 2022 01:01:20 +0000</pubDate>
      <link>https://forem.com/oleg008/webstudio-next-generation-visual-development-4d0d</link>
      <guid>https://forem.com/oleg008/webstudio-next-generation-visual-development-4d0d</guid>
      <description>&lt;p&gt;Let me show you how an &lt;a href="https://github.com/webstudio-is/webstudio" rel="noopener noreferrer"&gt;open-source&lt;/a&gt; visual development tool will eliminate the friction between developers and designers, whether you are building a small landing page or a complex web application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goals
&lt;/h2&gt;

&lt;p&gt;It is critical to understand the goals before jumping into tech because goals are the main driver behind all technical decisions.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We need a visual development tool that can harness the full power of the web platform by embracing its foundation: CSS and HTML. &lt;/li&gt;
&lt;li&gt;We must give ownership over content back to the user.&lt;/li&gt;
&lt;li&gt;We want to empower designers to contribute directly to the software by manipulating production code visually, no matter how complex that software may be.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Embracing web foundation
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.&lt;br&gt;
-- &lt;cite&gt;Ew Dijkstra&lt;/cite&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We want a tool that lets you express your product visually more precisely than using text. Code in text form depends on many external factors: build tools, interpreters, compilers, etc., making writing code for many cases way harder than it should be.&lt;/p&gt;

&lt;p&gt;We want you to manipulate the result while staying true to web fundamentals. You are working with the same CSS properties and HTML elements as if you are writing code, and you will be learning the web fundamentals while building visually. &lt;/p&gt;

&lt;p&gt;Visual object manipulation is a higher-level abstraction that enables building for the web even if you have no coding skills.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yue7b7fcyncdwm7afwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0yue7b7fcyncdwm7afwi.png" alt="Image description"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing ownership
&lt;/h2&gt;

&lt;p&gt;We all have jumped on the train using services that make it easy to publish on the web, but slowly we have all realized that we are not in control over our content anymore.&lt;/p&gt;

&lt;p&gt;Several problems have emerged: expensive hosting, unwanted paywalls, government control, technical limitations.&lt;/p&gt;

&lt;p&gt;Today, we can easily publish while avoiding all those problems by decoupling content creation and publishing.&lt;/p&gt;

&lt;p&gt;Services like Vercel, Netlify, Fly, Cloudflare, and others make it easy to publish on the web without worrying about uptime or infrastructure maintenance and scalability. With Webstudio, you can publish to any infrastructure and stay in complete control. You could even publish on a blockchain. Thanks to Remix, this is already easy from the CLI and will be a one-click action directly from Webstudio later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open source is a requirement
&lt;/h2&gt;

&lt;p&gt;To truly fix the ownership and give creators control over the tool,  it is not enough to provide API and SDK. There is still a vendor lock-in in place because without the Designer or infrastructure, you can't continue development. &lt;/p&gt;

&lt;p&gt;Making it open-source provides the maximum guarantee that the tool won't suddenly become restrictive or expensive. It is how we say that you can trust us because we keep no leverage. Webstudio can be seen as an open-source alternative to Webflow, though it is much more than that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing collaboration
&lt;/h2&gt;

&lt;p&gt;Collaboration between design and business logic is still in bad shape. There are no great tools where a designer can build and maintain the design without a handover to a developer in complex projects. &lt;/p&gt;

&lt;p&gt;Today designers build mostly static pictures and then hand them over to developers to turn them into code. In some tools like Figma, you can even build interactions and animations, but at the end of the day, it is still a mockup, not a real product. It improves communication but doesn't remove design handoff entirely.&lt;/p&gt;

&lt;p&gt;Removing handoff is partially possible for marketing sites using tools like &lt;a href="https://webflow.com" rel="noopener noreferrer"&gt;Webflow&lt;/a&gt;, but it's very limited in its capabilities, has a complete vendor lock-in, and is quite expensive on top of that.&lt;/p&gt;

&lt;p&gt;We need a tool that decouples design from backend and business logic but still integrates with custom code easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webstudio landing site is built with Webstudio
&lt;/h2&gt;

&lt;p&gt;Webstudio is right now in the alpha stage, and it is so alpha that I don't recommend using it in production. It has lots of bugs and lots of missing essential features. That's why the landing site is also very basic, but it demonstrates very powerful architecture. The same architecture would allow me to build a site of virtually any complexity.&lt;/p&gt;

&lt;p&gt;I built this &lt;a href="https://github.com/webstudio-is/webstudio-landing" rel="noopener noreferrer"&gt;landing site&lt;/a&gt; visually in &lt;a href="https://github.com/webstudio-is/webstudio-designer" rel="noopener noreferrer"&gt;Webstudio Designer&lt;/a&gt;, then generated a standalone &lt;a href="https://remix.run/" rel="noopener noreferrer"&gt;Remix&lt;/a&gt; app, synchronized the data from Designer using &lt;a href="https://github.com/webstudio-is/webstudio-cli" rel="noopener noreferrer"&gt;Webstudio CLI&lt;/a&gt;, rendered the site inside Remix using &lt;a href="https://github.com/webstudio-is/webstudio-sdk" rel="noopener noreferrer"&gt;Webstudio SDK&lt;/a&gt; and published it on &lt;a href="https://vercel.com/docs/concepts/functions/introduction#serverless-functions" rel="noopener noreferrer"&gt;Vercel&lt;/a&gt; as a serverless function. 😅 There is a lot to unpack there!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building landing site visually
&lt;/h2&gt;

&lt;p&gt;Whenever I write CSS in text form and then run it in the browser until it looks the way I want is a serious waste of time. Most of the time, I try to work this around by writing CSS directly in the browser dev tools first and then copy-paste them into the code.&lt;/p&gt;

&lt;p&gt;Despite the Webstudio style panel is not ready by a long shot - it felt nice to build it that way, even as a developer who writes CSS as part of the job.&lt;/p&gt;

&lt;p&gt;Webstudio style panel is an alternative to browser dev tool for CSS that produces production-ready CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvmbtpc9rp1t7n89pnc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsvmbtpc9rp1t7n89pnc4.png" alt="Webstudio Designer Screenshot"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Integration with a custom codebase
&lt;/h2&gt;

&lt;p&gt;I needed to integrate with a custom codebase instead of just publishing the site straight from Designer because I have a signup form that sends data to &lt;a href="https://www.notion.so/" rel="noopener noreferrer"&gt;Notion&lt;/a&gt;. In the future, this will be possible using &lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; bindings, but there will be many other reasons to integrate with a custom codebase, so this is a good proof of concept.&lt;/p&gt;

&lt;p&gt;I generated a Remix app by running &lt;code&gt;npx create-remix@latest&lt;/code&gt; which guided me through all options. I was going to deploy to Vercel, so I selected Vercel as a deployment target.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npx create-remix@latest
💿 Welcome to Remix! Let's get you set up with a new project.

? Where would you like to create your app? ./
? What type of app do you want to create? Just the basics
? Where do you want to deploy? Choose Remix if you're unsure, it's easy to change deployment targets.
  Express Server
  Architect (AWS Lambda)
  Fly.io
  Netlify
❯ Vercel
  Cloudflare Pages
  Cloudflare Workers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sync the data
&lt;/h2&gt;

&lt;p&gt;The first step is to download the data from Designer API. This is where &lt;a href="https://github.com/webstudio-is/webstudio-cli" rel="noopener noreferrer"&gt;Webstudio CLI&lt;/a&gt; helps - a simple command &lt;code&gt;wstd sync &amp;lt;project id&amp;gt; --host https://alpha.webstudio.is&lt;/code&gt; downloads the data as JSON files and puts it in a local &lt;code&gt;.webstudio&lt;/code&gt; &lt;a href="https://github.com/webstudio-is/webstudio-landing/tree/main/.webstudio" rel="noopener noreferrer"&gt;folder&lt;/a&gt;. You don't have to worry about a particular data format because SDK components already know how to render it, so you only have to work with standard React components.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6235c46edd57c1fddb8ee7ed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"root"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Box"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6233a4152e5d952bb6fb2118"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fontFamily"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"keyword"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-apple-system, system-ui, Arial"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fontSize"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Box"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6230f40d9b138da42f55d3ed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"style"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"paddingBottom"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"unit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"px"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data is simply provided to Remix's loader by &lt;a href="https://github.com/webstudio-is/webstudio-landing/blob/main/app/routes/index.tsx#L25" rel="noopener noreferrer"&gt;importing&lt;/a&gt; JSON files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.webstudio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Render components
&lt;/h2&gt;

&lt;p&gt;The next step is to render that data. For this, I used &lt;a href="https://github.com/webstudio-is/webstudio-sdk" rel="noopener noreferrer"&gt;Webstudio SDK&lt;/a&gt; that provides all necessary APIs to render React components in any React application as well as components to render a &lt;a href="https://github.com/webstudio-is/webstudio-sdk/blob/main/src/remix/document.tsx" rel="noopener noreferrer"&gt;Remix Document&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I am not bound to this specific set of APIs, and I am free to render it with any other framework or just a different set of components. Over time Webstudio will provide adapters for different DOM and CSS rendering frameworks, from Sass and your favorite CSS-in-JS library to React or Vue. &lt;/p&gt;

&lt;p&gt;At the moment, SDK is using &lt;a href="https://reactjs.org/" rel="noopener noreferrer"&gt;React&lt;/a&gt; and &lt;a href="https://stitches.dev/" rel="noopener noreferrer"&gt;Stitches&lt;/a&gt; for rendering, but we look forward to a future where we can render without a framework at all and support all popular frameworks as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom logic
&lt;/h2&gt;

&lt;p&gt;Remember I mentioned I needed custom code to send email from signup form to notion? We pass our custom Component to the Root component, and we override the SignupForm and SignupSuccess components. Inside those override components, we handle all form submission states and show the success message.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;useUserProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;WrapperComponent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@webstudio-is/sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useLoaderData&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;remix&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignupForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SignupSuccess&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;~/signup/components&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUserProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SignupForm&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignupForm&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;override&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SignupSuccess&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;SignupSuccess&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;WrapperComponent&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useLoaderData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Root&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Publishing
&lt;/h2&gt;

&lt;p&gt;At this point, publishing is a no-brainer. You can push to git and &lt;a href="https://vercel.com/new" rel="noopener noreferrer"&gt;setup Vercel&lt;/a&gt; to import from it or publish directly from CLI &lt;code&gt;npm i -g vercel &amp;amp;&amp;amp; vercel&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Next time you change design in Webstudio you have to run the sync CLI locally and publish the changes from your computer. In the future we will have an optional automation that will let you either publish automatically directly from Webstudio or create a Pull Request with changes and a link to preview, so that developers can review it before publishing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Become part of this mission
&lt;/h2&gt;

&lt;p&gt;Please don't hesitate to share your thoughts and ideas on &lt;a href="https://github.com/webstudio-is/webstudio/discussions" rel="noopener noreferrer"&gt;github&lt;/a&gt;. Also don't forget to give it a star, because this is how we know you liked what you saw.&lt;/p&gt;

&lt;p&gt;Follow us on &lt;a href="https://twitter.com/webstudiois" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or signup &lt;a href="https://webstudio.is/" rel="noopener noreferrer"&gt;here&lt;/a&gt; for updates. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>react</category>
      <category>design</category>
      <category>serverless</category>
    </item>
    <item>
      <title>Is REST simpler than GraphQL?</title>
      <dc:creator>Oleg Isonen</dc:creator>
      <pubDate>Sat, 19 Mar 2022 12:01:43 +0000</pubDate>
      <link>https://forem.com/oleg008/is-rest-simpler-than-graphql-78c</link>
      <guid>https://forem.com/oleg008/is-rest-simpler-than-graphql-78c</guid>
      <description>&lt;p&gt;It came to my attention that many perceive GraphQL as a complex way to communicate with an API compared to REST. It seems there is a perception that REST is somehow simple and easy, and I think it's really not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let's compare apples to apples
&lt;/h3&gt;

&lt;p&gt;When we compare GraphQL to REST, we usually don't compare the same thing because we take a simple REST API setup and compare it with a complex GraphQL setup.&lt;/p&gt;

&lt;p&gt;To really compare adequately, we need to compare either a very simple setup in both cases or an equally complex setup. In this article, I am going to simplify the GraphQL setup to the level of a simple REST. &lt;/p&gt;

&lt;p&gt;First of all, what's the biggest thing that makes you think GraphQL is complex? My bet is the client-side code that most people use. Let's remove it.&lt;/p&gt;

&lt;p&gt;Let's take this GraphQL API &lt;a href="https://api.spacex.land/graphql/"&gt;https://api.spacex.land/graphql/&lt;/a&gt; and try to avoid using any abstractions to fetch some data. GraphiQL UI gives us a way to learn what is being sent and received very easily, so I just need to grab the query from network UI in the Browser DevTools.&lt;/p&gt;

&lt;p&gt;It sends an XHR request using POST to the endpoint. The GraphQL query of the request is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launchesPast(limit: 2) {
    mission_name
    launch_date_local    
    rocket {
        rocket_name
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This query is supposed to fetch a list of past SpaceX Rocket launches, limit the results to 2 items, and return mission name, launch date, and rocket name.&lt;/p&gt;

&lt;p&gt;To actually send this request using built-in browser API, I would need to do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.spacex.land/graphql/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`{
        launchesPast(limit: 2) {
            mission_name
            launch_date_local    
            rocket {
                rocket_name
            }
        }
    }`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The JSON result we get looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"launchesPast"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mission_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Starlink-15 (v1.0)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"launch_date_local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-10-24T11:31:00-04:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rocket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"rocket_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Falcon 9"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"mission_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sentinel-6 Michael Freilich"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"launch_date_local"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2020-11-21T09:17:00-08:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"rocket"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"rocket_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Falcon 9"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see what we have to do to get the same result from a REST API by using browser APIs only. Since I don't really have the same API, I will just imagine what it would be if we had it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.spacex.land/launches/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;content-type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// We use URLSearchParams here for the sake of encoding.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URLSearchParams&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;filter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mission_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;launch_date_local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rocket.rocket_name&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I am sure you would agree that the complexity between these two examples is very similar if we really compare the same thing from the client setup code perspective.&lt;/p&gt;

&lt;h3&gt;
  
  
  A hidden difference
&lt;/h3&gt;

&lt;p&gt;When we compare a primitive type of usage, they seem very similar. There aren't many benefits for one over another, though when you look closely, you will recognize that even here, GraphQL has solved a few big problems already:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;GraphQL dictates a single way of requesting the data. You don't have to agree with your team on when to use which HTTP method, query-string, or paths, how to structure the URL, how to specify additional parameters like the limit of results and fields to return. Even in this primitive example, you can see that GraphQL, as the name implies, is a language where REST is just a pattern that is flexible enough to allow your team to build inconsistent and messy APIs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The shape of the query is also the shape of data you will receive quite literally. This is another missing piece in the REST contract where you usually have no way of specifying the shape of the data, while in practice, a change in the shape will likely break your application.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The core reason why we need GraphQL
&lt;/h3&gt;

&lt;p&gt;We often talk of HTTP APIs as some simple data fetching mechanism. You send a request, you get some data - easy enough, sure. We really fail to mention that it's not just some data most of the time, it's a contract.&lt;/p&gt;

&lt;p&gt;API is a contract between two parties that is supposed to specify how exactly I can get and send the data, what data in which shape I am going to receive, and finally, what type it is gonna have. Potentially you can have all that with REST too, but that's where GraphQL helps you to do it consistently.&lt;/p&gt;

&lt;p&gt;It has become super relevant as we entered the age of distributed systems and microservices. A complex application consists of dozens or hundreds of APIs, some of which are often provided by a 3rd party. When all of them vary in the level of safety and precision, how robust is your application going to be in the end, really? How many bugs do you think are created because of weak contracts? 10%? 20%? 30%?&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced usage
&lt;/h3&gt;

&lt;p&gt;While you can use GraphQL for very simple use cases - you still get many benefits, but it doesn't stop there. It offers solutions for mutating data, describing types of data, conditional query logic, composing data from multiple sources, and much more. It really shows the difference between a language and a pattern.&lt;/p&gt;

&lt;p&gt;Despite that GraphQL is best for dynamic data, it's still possible to implement caching and avoid slow responses for complex schemas.&lt;/p&gt;

&lt;p&gt;For example, a startup &lt;a href="https://graphcdn.io/"&gt;GraphCDN&lt;/a&gt; created a caching layer on top of CDN that works with any GraphQL API implementation. It is only possible because GraphQL makes you specify everything that is needed by design to allow smart caching. &lt;br&gt;
Not only is GraphCDN able to avoid doing unnecessary computation on your application servers - it does so using edge computing. That means a client has a much shorter response time after the initial response was cached because the client will receive a response from a server geolocated nearby.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Last words on UI architecture before an AI takes over</title>
      <dc:creator>Oleg Isonen</dc:creator>
      <pubDate>Sun, 22 Aug 2021 10:36:27 +0000</pubDate>
      <link>https://forem.com/oleg008/last-words-on-ui-architecture-before-an-ai-takes-over-dd3</link>
      <guid>https://forem.com/oleg008/last-words-on-ui-architecture-before-an-ai-takes-over-dd3</guid>
      <description>&lt;p&gt;“Big Ball of Mud” is the current state of the UI architecture, despite component-based composition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components are not enough
&lt;/h3&gt;

&lt;p&gt;Components and composition are just one piece of architecture. You still need to know how to compose pieces without recreating the “Big Ball of Mud” using a new (not really) shiny components-based approach.&lt;/p&gt;

&lt;p&gt;Components are too generic to actually solve architectural problems. They are comparable to the bricks. You can for sure use bricks to build a house, but there is so much more to building a house that bricks aren’t even 10% of what you need to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Architecture?
&lt;/h3&gt;

&lt;p&gt;We spent decades defining methodologies and principles for great architecture and yet here we are — every new job will surprise you with new ways of creating an utterly broken architecture.&lt;/p&gt;

&lt;p&gt;Is everyone stupid? Did nobody read about SoC, SOLID, DRY, KISS, WTF, GoF, GRASP YAGNI, WTF, Atomic Design, DDD, Onion, Clean Architecture, etc?&lt;/p&gt;

&lt;p&gt;I am sure everyone heard at least one or two of those and maybe even understood them, so what’s the problem? Why can’t we have a better architecture in practice?&lt;/p&gt;

&lt;p&gt;Maybe all we need is another, “better” architecture that solves all the problems? (Not really)&lt;/p&gt;

&lt;h3&gt;
  
  
  Theory vs. practice
&lt;/h3&gt;

&lt;p&gt;These are the most popular reasons why your organization will inevitably fail at the architectural level:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Many well-known principles and methodologies are conflicting.&lt;/li&gt;
&lt;li&gt; Requirements change over time.&lt;/li&gt;
&lt;li&gt; Organizational restructuring.&lt;/li&gt;
&lt;li&gt; There are no generic good ways to enforce a particular architecture.&lt;/li&gt;
&lt;li&gt; Poor communication of the applied architecture.&lt;/li&gt;
&lt;li&gt; Practical implementation difficulties.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conflicting principles
&lt;/h3&gt;

&lt;p&gt;Let’s take one example: DRY — Don’t Repeat Yourself vs. High Cohesion — keep related code close.&lt;/p&gt;

&lt;p&gt;In theory, they both sound great, though both of them fall apart when you have 2 independent features that need the same function. I understand this is too theoretical and it never happened to YOU in the real world /s, but bear with me for a second.&lt;/p&gt;

&lt;p&gt;As soon as you have to share a function between 2 features you have 3 options:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Let feature A import that function from feature B directly and marry both features together in the sense one has to know about the existence of the other.&lt;/li&gt;
&lt;li&gt; Move that shared function into some “shared” directory and use it from both places.&lt;/li&gt;
&lt;li&gt; Copy that function into both features&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you are reusing that function, you are breaking “High Cohesion”. If you are copying — you are breaking DRY.&lt;/p&gt;

&lt;p&gt;And this is just one simple example, there are many more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Changing requirements
&lt;/h3&gt;

&lt;p&gt;In the modern world of startups, every company starts with some small prototype that can be manufactured quickly and then hopes to evolve it into something better.&lt;/p&gt;

&lt;p&gt;When building things quick and dirty — you don’t have time for good architecture, let alone you don’t know yet what “good” means for that particular product.&lt;/p&gt;

&lt;p&gt;Product is changing and so are the requirements. You either have decades of experience and can predict what architecture needs to be because you saw this very same thing happening already or you will start with some baseline architecture that minimizes your risks.&lt;/p&gt;

&lt;h3&gt;
  
  
  Organizational restructuring
&lt;/h3&gt;

&lt;p&gt;It’s not a secret that the architecture needs to not only be designed around the product requirements but also around the people working on it.&lt;/p&gt;

&lt;p&gt;If you have a team of 2 engineers, exchanging information, agreeing on architecture, and keeping things consistent is one million times easier than having a team of 20 people. (please teach me the math here /s)&lt;/p&gt;

&lt;p&gt;Every product starts with a small team and then adds the team members over time. People join, people leave and communication pathways change all the time.&lt;/p&gt;

&lt;p&gt;Can we have an architecture that is correct no matter how many people work on the product?&lt;/p&gt;

&lt;h3&gt;
  
  
  Enforcing an architecture
&lt;/h3&gt;

&lt;p&gt;If a magical tool existed that could enforce every principle and every architectural decision that is used on a product, we could have a consistent architecture and much better communication of it.&lt;/p&gt;

&lt;p&gt;People don’t read docs. They don’t read not because they don’t know how to, but because they don’t know what those words mean in their specific context, and learning it is hard work.&lt;/p&gt;

&lt;p&gt;Tools can tell a person to read something when they are about to make a mistake or create an inconsistency, but creating such tools is hard because many violations of principles are hard to detect in code.&lt;/p&gt;

&lt;p&gt;We definitely need tools to communicate architecture, and yet, do we know many tools that try to do that at least on some basic level?&lt;/p&gt;

&lt;h3&gt;
  
  
  Poor communication
&lt;/h3&gt;

&lt;p&gt;Documentation is hard. Documentation of principles and methodologies is harder. Documenting them well as a by-product of some product work — impossible.&lt;/p&gt;

&lt;p&gt;Your internal documentation around principles and architecture will always suck unless you have people who have a serious level of dedication for the topic — aka “architect”.&lt;/p&gt;

&lt;p&gt;How many companies have you worked for that have people with architectural roles?&lt;/p&gt;

&lt;h3&gt;
  
  
  Tactics vs. logistics
&lt;/h3&gt;

&lt;p&gt;One dude said once:&lt;/p&gt;

&lt;p&gt;“The amateurs discuss tactics: the professionals discuss logistics.”&lt;/p&gt;

&lt;p&gt;Tactics in our context are the same as abstract principles and methodologies. Those are just words that don’t tell you what to do in your context because they are too abstract and conflicting.&lt;/p&gt;

&lt;p&gt;The missing piece in the industry is a set of principles that can be well understood and applied in practice that go along with tools that help to enforce them.&lt;/p&gt;

&lt;p&gt;This is the logistics analogy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution
&lt;/h3&gt;

&lt;p&gt;Am I here to just criticize the current state or to move the needle? Well, without agreeing on critiques we can’t move on to something better. Can we come up with something better? I believe so.&lt;/p&gt;

&lt;p&gt;I expressed some ideas in my talk in 2018 with the title “Feature Driven Architecture” &lt;a href="https://www.youtube.com/watch?v=BWAeYuWFHhs"&gt;https://www.youtube.com/watch?v=BWAeYuWFHhs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is nothing fundamentally new in it, it is a collection of well-known ideas put together into a single practical architecture.&lt;/p&gt;

&lt;p&gt;If you like and retweet this article hard enough, I might work on defining those ideas and examples more clearly in this repository &lt;a href="https://github.com/kof/feature-driven-architecture"&gt;https://github.com/kof/feature-driven-architecture&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Features are everything
&lt;/h3&gt;

&lt;p&gt;The word "features" is the most common to describe application-level components. An application will have features added, removed, modified. It will have big features, small features, life-changing features. Features that make you proud of yourself, features that will embarrass the entire company. Features are the most important piece of every application.&lt;/p&gt;

&lt;p&gt;This is why it is so important to make it your life goal as an architect of an application to make sure a feature can be added, removed, or extended with the least amount of work.&lt;/p&gt;

&lt;p&gt;Being able to completely remove a feature without leaving pieces of logic left behind spread over the entire application is the key to a maintainable product.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;ultimate test&lt;/strong&gt; for any architecture should be to remove a feature directory from the file system and the application should stay fully functional without it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An ideal feature contains everything it needs.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Principles of a feature
&lt;/h3&gt;

&lt;p&gt;These are the most important principles that have to be followed in a Feature Driven Architecture:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. A feature can not know other features or its consumers.
&lt;/h4&gt;

&lt;p&gt;It’s simple — you will break one feature when you change the other otherwise. You also can’t remove one feature without affecting another, if you break this principle.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. A feature has to express its dependencies declaratively.
&lt;/h4&gt;

&lt;p&gt;If there is data or any other dependencies — it has to express them declaratively and a consumer should be able to provide them.&lt;/p&gt;

&lt;p&gt;Much like a component, features don’t live in a vacuum, they have dependencies. To name a few: data, static artifacts like images, libraries, functions shared between features, knowledge of app architecture, build system, and more.&lt;/p&gt;

&lt;p&gt;Dependencies are what makes it hard to build features that are fully encapsulated and integrate well with the rest of the application. That’s why on the architectural level we can only define basic rules of behavior, but not the specifics around implementation details because those will vary depending on your stack and particular business requirements.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;An ideal feature doesn’t have dependencies.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Be disposable
&lt;/h4&gt;

&lt;p&gt;Much like a component, a feature has to be disposable. You need to architect the feature so that you can remove it easily. &lt;strong&gt;That’s the whole point.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Screens
&lt;/h3&gt;

&lt;p&gt;If one feature doesn’t know any other feature, there has to be something else that knows all the features, otherwise, we will render a vacuum.&lt;/p&gt;

&lt;p&gt;Screens have to follow &lt;strong&gt;the same principles&lt;/strong&gt; we described for the feature for the exact same reasons. In addition, it acts as a features manager:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Doesn’t know about other screens
&lt;/h4&gt;

&lt;h4&gt;
  
  
  2. Renders the features
&lt;/h4&gt;

&lt;p&gt;A screen can import a feature and access its public API, including its rendering interface and its declaration of dependencies. The screen’s main goal is to render the features on the screen.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Provides dependencies
&lt;/h4&gt;

&lt;p&gt;In case a feature can not satisfy its dependencies itself, a screen will have to provide them. It could be static artifacts, data, or anything else. Important is that if a feature can provide those things itself — it should. I can only see the need to express dependencies when the dependency is outside of features scope or it is something 2+ features on the screen depend on, so it would potentially make sense to hoist the logic to the screen.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Connects the features
&lt;/h4&gt;

&lt;p&gt;Sometimes one feature depends on something that some other feature has or does, but since features can’t know about each other, this dependency has to be expressed via a simplified protocol. &lt;br&gt;&lt;br&gt;
If the dependency is data — one feature has to express what data it requires via an interface and the screen will pipe that data from one feature into another while trying to keep the least amount of knowledge about its contents.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The more screen knows about each feature — the harder will it be to remove a feature from the screen.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Clusters
&lt;/h3&gt;

&lt;p&gt;Sometimes a set of independent features is used on 2+ screens together as a set. You don’t need a cluster to achieve that if a feature is fully self-contained, but if a feature has dependencies provided by the screen — you would have to duplicate the setup logic that wires up those features 2+ times for each screen.&lt;/p&gt;

&lt;p&gt;If that setup code a feature needs is trivial — you may want to avoid having a cluster to avoid the need for this additional abstraction.&lt;/p&gt;

&lt;p&gt;But if you end up needing a cluster — the rules it follows are similar to a feature:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Doesn’t know about other clusters&lt;/li&gt;
&lt;li&gt; Doesn’t know about screens&lt;/li&gt;
&lt;li&gt; Knows all features from that cluster and exposes a declarative interface for the screen.&lt;/li&gt;
&lt;li&gt; Provides features with dependencies just like a screen.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Shared
&lt;/h3&gt;

&lt;p&gt;Sometimes you need to share a function between features, screens, or clusters that has business awareness. This is where a “shared” space comes in handy.&lt;/p&gt;

&lt;p&gt;Shared space has one rule to follow — it can’t directly depend on anything outside of “shared” except the libraries. It can’t depend on a feature, screen or a cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Libraries
&lt;/h3&gt;

&lt;p&gt;The final and the most low-level piece is a library. A library has no knowledge about business logic. It can be published to or installed from an Open Source repository and can be shared between businesses. You can have a lib directory inside the product or you can treat the installed package from an external source as a lib. One example is the node_modules package, if it doesn’t contain a business-aware logic — it's a library.&lt;/p&gt;




&lt;p&gt;To sum it up, my proposal is to split an application by a number of principled types that would let your application be maintainable, guided by the need to have built-in discoverability, disposability, and scalability. The proposed types are in the order from higher to a lower level of abstraction:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Screens
&lt;/li&gt;
&lt;li&gt;Clusters
&lt;/li&gt;
&lt;li&gt;Features
&lt;/li&gt;
&lt;li&gt;Shared
&lt;/li&gt;
&lt;li&gt;Libraries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am certain the constraints I expressed are basic and there is more to it. We need to work on a well-defined spec for them. I am also certain that enforcing those constraints should be automated one way or another and we need to build tools and interfaces to make a consistent architecture.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Why React’s Model is successful</title>
      <dc:creator>Oleg Isonen</dc:creator>
      <pubDate>Tue, 07 Apr 2020 10:06:49 +0000</pubDate>
      <link>https://forem.com/oleg008/why-react-s-model-is-successful-2mgp</link>
      <guid>https://forem.com/oleg008/why-react-s-model-is-successful-2mgp</guid>
      <description>&lt;p&gt;Throughout the history of React there have been many ways to describe what it does that makes its programming model successful and for sure there are multiple aspects to it, but let's have a look into its foundation — components.&lt;/p&gt;

&lt;p&gt;One of the most important characteristics for building large software is its maintainability and the most scalable mental framework for maintainability is the ability to delete and replace pieces of the system. So what makes React so special about code removal?&lt;/p&gt;

&lt;h2&gt;
  
  
  Props
&lt;/h2&gt;

&lt;p&gt;Props are the main way for a React component to receive information. It is its standard input interface. Props are pretty much the same to a React component as arguments are to a function, but with a small but important difference — components are automatically “subscribed” to the latest version of props and are getting executed automatically by React.&lt;/p&gt;

&lt;p&gt;Another interesting detail about props is that they can contain any data type, which can be used as a backchannel for communication. For example, by calling a function a child component received over props, it can communicate back to the parent component.&lt;/p&gt;

&lt;h2&gt;
  
  
  Children
&lt;/h2&gt;

&lt;p&gt;Children is a mechanism that gives React components 2 abilities: composition and nesting. What I mean is a particular kind of composition — the ability to render a component A inside of component B without component B knowing anything about component A. This can be achieved as well using props, in fact, “children” is a special key in props, but that’s an implementation detail. What’s important is that it enables nesting:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&amp;lt;ComponentA&amp;gt;&amp;lt;ComponentB /&amp;gt;&amp;lt;/ComponentA&amp;gt;&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Elements
&lt;/h2&gt;

&lt;p&gt;React elements is generally speaking a platform-agnostic description of renderable nodes. It’s a spec that component returns to React and describes components React needs to initialize and what props and children they will receive.&lt;br&gt;
The fact that we usually use JSX to describe elements or even that JSX is transpiled to a &lt;code&gt;React.createElement()&lt;/code&gt; function call is just another implementation detail.&lt;/p&gt;




&lt;p&gt;React has created a system that allows a component to receive data, express what needs to be rendered in return and allows it to get composed. This is the foundation and the main reason why React’s approach to building user interfaces scales — each component implements the same interface and can be replaced. In addition, the fact that React application is a tree, by replacing a single component you are able to replace an entire subtree it renders, giving you the ability to replace large building blocks at once.&lt;/p&gt;

&lt;p&gt;It’s not about VirtualDOM, JSX, hooks, state, context, performance or pure functions. Even though they are all important, they are just implementation details.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
    </item>
    <item>
      <title>How to kill creativity: The Definitive Guide</title>
      <dc:creator>Oleg Isonen</dc:creator>
      <pubDate>Thu, 02 Jan 2020 19:09:34 +0000</pubDate>
      <link>https://forem.com/oleg008/how-to-kill-creativity-the-definitive-guide-2fjb</link>
      <guid>https://forem.com/oleg008/how-to-kill-creativity-the-definitive-guide-2fjb</guid>
      <description>&lt;p&gt;I woke up today with a bunch of ideas about how a particular problem could be solved or at least what could be a better attempt than what I had so far because the problem is non-trivial and there is no standard well-known way to do it.&lt;/p&gt;

&lt;p&gt;At the same time, I realized why it took me so long.&lt;/p&gt;

&lt;h2&gt;
  
  
  I was blocked for over a year
&lt;/h2&gt;

&lt;p&gt;The problem I was thinking about was something I started to work on over a year ago. I did a prototype, settled on one particular implementation and then realized the problem is hard and doing it that way will result in too many problems later. This realization made me feel frustrated. I stopped working on it and thinking about it.&lt;/p&gt;

&lt;p&gt;A few days ago I decided to look at it again and I struggled to figure out what I was thinking as I did that initial prototype. I couldn't understand most of the code I wrote, it's been frustrating, I was staring at it for the entire day until I decided to throw it away. Luckily I had integration tests, so I could drop the implementation and rewrite it from scratch. Within a few hours, I rediscovered most of the problems I had over a year ago and why I stopped working on it. The difference now was though that I am determined to solve it, so I kept thinking about it and woke up with a bunch of ideas today.&lt;/p&gt;

&lt;p&gt;What I learned out of it blew my mind: &lt;em&gt;I was able to think about potential different solutions because I didn't have a definitive answer.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  I don't like reading books
&lt;/h2&gt;

&lt;p&gt;It might come surprising to you to hear that from someone from a tech industry since we are considered "intelligent" by the rest of the world. It's not like I don't like to read at all, in fact sometimes I enjoy doing so. I am reading stuff every day, but the only reason I do so is I am searching for a particular information or I read some short blog posts about a topic I care about. I never read a book from the beginning till the end just because it was recommended.&lt;/p&gt;

&lt;p&gt;Today I realized I don't like reading a particular type of content: &lt;em&gt;the one that gives you a definitive answer to everything it touches.&lt;/em&gt; It turns out I enjoy discovering things much more than I enjoy learning the facts.&lt;/p&gt;

&lt;p&gt;I realize not every book is a definitive guide, there are tons of books that spark your imagination and let you think creatively, I never realized that this is what makes me like one over another.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A book or a blog post that gives you a definitive answer to a problem diminishes your ability to think creatively. Ultimately it creates an internal conflict and builds up a wall between the solution and the reader.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  My biggest learning so far
&lt;/h2&gt;

&lt;p&gt;If you are writing a book, an article or even a tweet. If you are trying to propose a solution to a group of people. When people have different opinions about literally everything - the worst thing you can do is to provide a definitive solution.&lt;/p&gt;

&lt;p&gt;Instead, one could provide a clear problem-definition and eventually name a few potential ideas which could be thought of but shouldn't come over as a definitive solution. The last part is the most critical because the second your receiver realizes there is a solution already provided, you are taking their ability to think creatively.&lt;/p&gt;

&lt;p&gt;Gifting a joy of discovery to your receiver is the best thing you can do as someone who tries to convey a message or foster productivity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happens in the brain of a receiver-person
&lt;/h2&gt;

&lt;p&gt;When you announce a solution to a group of people that is supposed to solve that problem, this is what I feel is happening in their brains:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;They realize their help is not needed, their presence is a pure formality, the value of oneself is starting to fade.&lt;/li&gt;
&lt;li&gt;There is not much one can do now, especially if the author is the leader of the group, because it makes it even more clear that the solution is set in stones.&lt;/li&gt;
&lt;li&gt;Anything a receiver will say from now on that goes against that solution is going to be received as critique and potentially create conflict.&lt;/li&gt;
&lt;li&gt;It upsets the receiver and blocks their ability to process new information.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A friend &lt;a href="https://twitter.com/TejasKumar_/status/1212755087912771585"&gt;pointed out&lt;/a&gt; to me there is a scientific research about &lt;a href="https://www.ncbi.nlm.nih.gov/m/pubmed/15978553/"&gt;this topic&lt;/a&gt; in case you want to study this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The worst thing about it
&lt;/h2&gt;

&lt;p&gt;The short-term effect is going to be forgotten soon enough, but the long term effect of this solution-giving approach is truly harmful to any organization. &lt;br&gt;
A one-off event usually doesn't change much, but if it happens often enough, the result is - you are building a culture where people don't think on their own. &lt;br&gt;
Once this becomes a natural way of thinking within the same group of people, this group will neither think critically nor creatively anymore, they will just follow the orders. We know plenty of examples of such cultures: military, police, religions. People in all of those groups tend to show blindness in critical and creative thinking towards their leaders and foundations of that particular culture.&lt;/p&gt;

&lt;p&gt;There is an exception to every rule, but we are talking about a tendency here.&lt;/p&gt;




&lt;p&gt;I don't know why, but this realization today made me somehow feel free and happy once again, so I wrote it in the hope it will do the same for you.&lt;br&gt;
Today I wasn't lazy and wrote it as a blog post, but most of the time I share thoughts on twitter, so if you are interested in my low-quality award-losing content, follow me there &lt;a href="https://twitter.com/oleg008"&gt;@oleg008&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>career</category>
      <category>productivity</category>
      <category>writing</category>
      <category>creativity</category>
    </item>
  </channel>
</rss>
