<?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>DEV Community: Percy Hanna</title>
    <description>The latest articles on DEV Community by Percy Hanna (@percyhanna).</description>
    <link>https://dev.to/percyhanna</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%2F852009%2F4201f341-7228-47b8-98c0-c28ff40a2bc2.jpeg</url>
      <title>DEV Community: Percy Hanna</title>
      <link>https://dev.to/percyhanna</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/percyhanna"/>
    <language>en</language>
    <item>
      <title>Building a dynamic Canvas rendering engine using JSX</title>
      <dc:creator>Percy Hanna</dc:creator>
      <pubDate>Tue, 02 Apr 2024 15:59:38 +0000</pubDate>
      <link>https://dev.to/aha/building-a-dynamic-canvas-rendering-engine-using-jsx-3237</link>
      <guid>https://dev.to/aha/building-a-dynamic-canvas-rendering-engine-using-jsx-3237</guid>
      <description>&lt;p&gt;Our product team is busy adding many great new features to &lt;a href="https://www.aha.io/whiteboards/overview"&gt;Aha! Whiteboards&lt;/a&gt; and &lt;a href="https://www.aha.io/knowledge/overview"&gt;Aha! Knowledge&lt;/a&gt; — including wireframes, voting, and improvements to viewing &lt;a href="https://www.aha.io/roadmaps/overview"&gt;Aha! Roadmaps&lt;/a&gt; data within a whiteboard. We added all of this functionality in just the last few months, and we are busy building even more features that will deliver product value to our users.&lt;/p&gt;

&lt;p&gt;As the engineering lead for &lt;a href="https://www.aha.io/blog/aha-expands-product-development-suite-with-new-whiteboarding-and-knowledge-base-tools"&gt;these products&lt;/a&gt;, I saw all of the above features (and others) on our product roadmap and had a few thoughts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How can we easily build features that rely on dynamic content and real-time user collaboration?&lt;/li&gt;
&lt;li&gt;How can we do that on Canvas? Rendering dynamic content on Canvas is complicated — or at least, it's much more difficult than using HTML and React, which is how we have built most interactive features at Aha!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The challenge
&lt;/h2&gt;

&lt;p&gt;Anyone who has used Canvas in the past understands the challenge. You cannot simply render content to the screen — you must manually draw content using functions such as &lt;code&gt;lineTo&lt;/code&gt;, &lt;code&gt;fillRect&lt;/code&gt;, &lt;code&gt;fillText&lt;/code&gt;, and so on. Consider the really simple example of rendering "Hello, world!" with basic styling and a quick layout. Let's try and reproduce this HTML in Canvas:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;span&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"border: solid 1px black; padding: 5px; font: 16px Times;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  Hello, world!
&lt;span class="nt"&gt;&amp;lt;/span&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above will produce this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdat1cyv1qbykc2waew1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdat1cyv1qbykc2waew1g.png" alt="Hello world!" width="113" height="46"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But let's see what we would need to generate this in Canvas:&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;// Rendering context&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanvasRenderingContext2D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&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;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Measure the text&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;16px Times&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textBaseline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top&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;textMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Calculate the box size&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;contentWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;contentHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxDescent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Draw the border&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fillStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;black&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strokeRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentHeight&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Render the text&lt;/span&gt;
&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello, world!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Almost 20 lines of code just to render the most basic of content.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic content
&lt;/h2&gt;

&lt;p&gt;This becomes even more complex when you start adding dynamic content. For example, one of the first features in our Aha! Whiteboards roadmap was to add emoji reactions to our sticky note shape. This is a simple way to gather feedback from everyone viewing the whiteboard. The feature seems simple enough. In React/HTML, you might have something that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"emoji-button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  👍 1
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here is what the user would see:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh67hefac5aadf0irpjs4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh67hefac5aadf0irpjs4.png" alt="Thumbs up" width="86" height="54"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In Canvas, this becomes much more difficult. We have to render the button, add the padding and border radius, and then render the emoji and reaction number.&lt;/p&gt;

&lt;p&gt;But it doesn't stop there: What if 10 people react with a thumbs-up? Or 100? Now, that button must be wider to adapt to the content inside of it. In HTML, you don't even have to think about this. It just happens. You would just write your code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"emoji-button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  👍 &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Chances are that's how your component was written in the first place. You didn't even think about how to resize the &lt;code&gt;button&lt;/code&gt; element — the browser's rendering engine just did that for you. Emoji reactions was just the first feature I saw in our roadmap. And there were many others. But the big, daunting one was wireframes.&lt;/p&gt;

&lt;p&gt;Not only did we need a way to render a button that was entirely in our control, but we also needed to allow users to create their own buttons and add their own content to it. And a button was one of the simpler components we needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive solution
&lt;/h2&gt;

&lt;p&gt;The very naive solution would be to write each button and wireframe shape separately. You could perhaps create a &lt;code&gt;renderButton&lt;/code&gt; function as a helper to render a button to the Canvas. Generalize the above code into a function, and you come up with something like 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="c1"&gt;// Rendering context&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanvasRenderingContext2D&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;canvas&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2d&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;renderButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Calculate the box size&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&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;contentWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&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;contentHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxDescent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Draw the button background&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;beginPath&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;roundRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;contentHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Render the text&lt;/span&gt;
  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&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;p&gt;But this solution is limited. It only solves a specific problem and doesn't help fill in the functionality gap that HTML addresses out of the box. Imagine how this would scale as you add more complex shapes like these:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Navigation menu:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5cqikmt03rxocdcqc8j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv5cqikmt03rxocdcqc8j.png" alt="Navigation menu" width="579" height="79"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dropdown picker:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu180urwvs9qvllhwqxm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqu180urwvs9qvllhwqxm.png" alt="Dropdown picker" width="480" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the complexity of rendering all of these wireframes grows quickly, even with relatively simple shapes. How do you calculate the width and height of each navigation item? How do you render the borders, backgrounds, and dividers between each item?&lt;/p&gt;

&lt;h2&gt;
  
  
  Buttons
&lt;/h2&gt;

&lt;p&gt;Let's take a deeper look at a very simple shape we have in our Wireframes: the button. As I demonstrated above, implementing a basic button renderer would not be difficult in and of itself. But there is already a bit of added complexity here. Our button needed to support multiple styles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Style&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlorcym15tv9qen35lq1.png" alt="Basic button" width="304" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rounded&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdhge31xz1o1e7z73lg23.png" alt="Rounded button" width="344" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outline&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbwepeuf19dpezr6dn3d.png" alt="Outline button" width="304" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rounded outline&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7cxtztg09xm0pllt1b10.png" alt="Rounded outline button" width="344" height="106"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Link&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rlg8h046vcu7fz6ovlb.png" alt="Link button" width="268" height="84"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each of these variations adds a bit more code and complexity to our &lt;code&gt;renderButton&lt;/code&gt; function. It would definitely be possible to support all of these designs with a single function, but would it be elegant? Reusable? Dynamic?&lt;/p&gt;

&lt;p&gt;Did you notice the icons? Well, users can choose to place the icon on the right side instead. And they can change the color of the button and the text, too. By themselves, each of these are minor things to add. But eventually, that function would become overloaded with all the variations, customizations, and edge cases that need to be handled.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSX
&lt;/h2&gt;

&lt;p&gt;In order for our team to be able to easily implement all of these features, we needed a readable, elegant, and powerful way to render dynamic content to Canvas in a familiar way. I thought to myself, wouldn't it be great if we could just render content to Canvas the same way we do with React?&lt;/p&gt;

&lt;p&gt;And then I had my &lt;a href="https://www.aha.io/company/history"&gt;Aha! moment&lt;/a&gt;. WE COULD! To do this, we would use one of the really powerful and innovative features that React introduced to the world — something called JSX. It's really just syntactic sugar to write HTML syntax into declarative and well-structured element objects. For those of you who are unfamiliar with JSX, what it does for you under the hood is convert code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"emoji-button"&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;clickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  👍 &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Into something like 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="nf"&gt;_jsx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emoji-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clickHandler&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👍 &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;JSX, in my opinion, was the &lt;a href="https://react.dev/learn/writing-markup-with-jsx"&gt;biggest innovation&lt;/a&gt; that came out of React given that you could write HTML directly in your JavaScript. I had used other frameworks such as &lt;a href="https://mootools.net/"&gt;MooTools&lt;/a&gt; in the past. You could use them to dynamically construct HTML in JavaScript, but with a very verbose and tedious syntax. It looked very similar to the converted code above. And it worked, but it was not satisfying or pleasant.&lt;/p&gt;

&lt;p&gt;The beauty of JSX is that you can override how elements are converted to JavaScript code using something called a &lt;a href="https://en.wikipedia.org/wiki/Directive_(programming)"&gt;pragma&lt;/a&gt;. A pragma, or directive, is something that tells the JSX compiler how to process the code. In your JSX/TSX code, you can add a pragma to the top of your file like 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="cm"&gt;/** @jsx CanvaSX */&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;CanvaSX&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;canvasx&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, instead of converting your JSX to React code, the above code would be converted to something called CanvaSX code (more on this later):&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="nc"&gt;CanvaSX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;emoji-button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;clickHandler&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;👍 &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;My goal was simple and clear: I wanted to be able to write JSX code that could render a simple navigation/tab menu. The system should generate the layout and render that content for me dynamically, and I shouldn't have to think about the complexities of measuring text and padding and layouts. It would need to handle very common layout patterns such as margin, padding, (rounded) borders, and so on.&lt;/p&gt;

&lt;p&gt;The really powerful feature would be to have all the layout complexity completely hidden from the developer. When rendering the navigation items, the width of each item would need to be based on the text and/or icon inside it, and then the borders and background should be rendered based on the width of that content. We would then need to render dividers between each item.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing CanvaSX
&lt;/h2&gt;

&lt;p&gt;I spent some time prototyping a rough implementation of a rendering engine that I've come to call CanvaSX (Canvas+JSX). The early versions were quite rough, but the benefits were obvious. I could write simple shapes like a button or navigation menu using very familiar syntax, and I didn't even have to think about calculating coordinates or layouts.&lt;/p&gt;

&lt;p&gt;Eventually, I added support for basic interaction properties (such as onClick and tooltip handlers) and some very rudimentary flexbox-like components, too. Now, we could have something like a real button completely rendered in Canvas. The end result made the emoji button as simple as this code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;
  &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emojiClickHandler&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;tooltip&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emojiTooltipContent&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;FlexRow&lt;/span&gt; &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;verticalAlignment&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'center'&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Emoji&lt;/span&gt; &lt;span class="na"&gt;shortCode&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;shortCode&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;votes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;FlexRow&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which made sure we could offer reactions like these:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0op03pjuwel8ly6f5hds.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0op03pjuwel8ly6f5hds.png" alt="Emoji reaction" width="434" height="437"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The wireframe button is very similar, but it simply passes along user input to the properties. For example, to support rounded vs. square buttons, we just need to write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;
  &lt;span class="na"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;backgroundColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;round&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;textColor&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Rect&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is very easy to read for anyone familiar with React, and CanvaSX addresses all of the layout complexities.&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;The magic behind CanvaSX is that every component can measure itself. With this information, CanvaSX can generate the layouts automatically. This is what the &lt;code&gt;Text&lt;/code&gt; component's measuring function might look like:&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="nx"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;calculateDimensions&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="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CanvasRenderingContext2D&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;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&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;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;font&lt;/span&gt; &lt;span class="o"&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;font&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;textMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;measureText&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;children&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;restore&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;textMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontBoundingBoxAscent&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A parting note
&lt;/h2&gt;

&lt;p&gt;Building CanvaSX is one of the highlights of my career. I have had the opportunity to work on many features in my more than five years of working at Aha! These include capacity planning for teams, redesigned drawers, &lt;a href="https://www.aha.io/develop/overview"&gt;Aha! Develop&lt;/a&gt;, and our custom card layout editor. I've recently worked on many improvements to Aha! Whiteboards, which is where CanvaSX came in. It has proven itself to be extremely powerful and flexible — and it is now used as the foundation for &lt;a href="https://www.aha.io/blog/introducing-emoji-reactions-on-whiteboards"&gt;emoji reactions&lt;/a&gt;, &lt;a href="https://www.aha.io/whiteboards/wireframes"&gt;wireframes&lt;/a&gt;, updated &lt;a href="https://www.aha.io/whiteboards/roadmaps-integration"&gt;record cards&lt;/a&gt;, and our brand-new &lt;a href="https://www.aha.io/whiteboards/product-prioritization"&gt;voting tool&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;I work with a team of extremely talented engineers at Aha! We collaborate to solve hard problems and deliver value to our users. If this sounds like something you would enjoy doing, you should check out our &lt;a href="https://www.aha.io/company/careers/current-openings"&gt;open positions&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>react</category>
      <category>canvas</category>
      <category>jsx</category>
      <category>whiteboards</category>
    </item>
    <item>
      <title>Embrace the monolith: Adding a new product to Aha!</title>
      <dc:creator>Percy Hanna</dc:creator>
      <pubDate>Fri, 29 Apr 2022 20:52:29 +0000</pubDate>
      <link>https://dev.to/aha/embrace-the-monolith-adding-a-new-product-to-aha-hp3</link>
      <guid>https://dev.to/aha/embrace-the-monolith-adding-a-new-product-to-aha-hp3</guid>
      <description>&lt;p&gt;When our engineering team first began conceptualizing Aha! Develop, we were faced with a monumental question. How should we implement the architecture of adding a brand new product? We could start fresh with a brand new codebase, utilizing the latest technologies and frameworks. Or we could build on top of our existing monolithic codebase that powers Aha! Roadmaps and Aha! Ideas. We examined the pros and cons of greenfield, microservices, or continuing to use our existing monolith — all in pursuit of the most lovable solution for our users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: Greenfield
&lt;/h2&gt;

&lt;p&gt;Starting with a brand new codebase is really appealing. We could keep it simple without any extra baggage like complex dependencies, legacy code, technical debt, or old build pipelines. Using the latest versions of all of our favorite frameworks like Rails 7 and React 18 would mean not having to worry about testing for regressions. And we could start fresh with a new design system or CSS framework.&lt;/p&gt;

&lt;p&gt;Developing something new out of nothing is one of the things that excites me the most about product development work. But eventually you will start building the same features you’ve built before. Maybe some are already in a Ruby gem or JavaScript npm package, so you could easily incorporate them into a new product. But in most situations, it’s specific to your business and not neatly packaged inside a gem. You could extract some from other codebases, but you'll end up reinventing the wheel or duplicating a lot of old code.&lt;/p&gt;

&lt;p&gt;Even if the new greenfield product does not have a lot of overlap with previous products, you might find yourself rewriting common systems like authentication, permissions, notifications, or other things you’ve implemented before. Most applications share a lot of this core functionality regardless of the product itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: Microservices
&lt;/h2&gt;

&lt;p&gt;Microservices have been popular in the past several years. Building Aha! Develop on top of microservices would make our application more modular and scalable. We could transition core functionality of authentication, permissions, notifications, and sharing/publishing to their own microservices. These would lead to smaller pieces of isolated code that are only responsible for a concise set of tasks. They can be scaled independently in order to maintain service level objectives (SLOs) or other key metrics. Then Aha! Develop could reuse these core services with a UI built separately from the other products in our suite. All of our core business logic would be nicely encapsulated, tested, and deployed separately. Any number of interfaces or APIs could be built on top of that foundation.&lt;/p&gt;

&lt;p&gt;That would all come with some fairly significant tradeoffs. As the dependency hierarchy between each microservice or application grows, changes require complex coordination between various teams to prevent regressions. When new features are needed, meetings and thorough documentation ensure the necessary APIs behave as expected to support the desired functionality. Teams must coordinate back and forth over weeks or months to manage the testing, development, and deployment of the new features. As the product requirements evolve, additional meetings and work are needed to support the new behaviors.&lt;/p&gt;

&lt;p&gt;The benefits of having simpler code in microservices get eroded by increased process and operational complexities. Sometimes those tradeoffs are worth the effort, though.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: Monolith
&lt;/h2&gt;

&lt;p&gt;Our third option was to continue building on top of our existing monolithic application. We had already added Aha! Ideas to our suite of products in late 2020. This gave us the confidence that we could build another new product using a similar approach, even though it was targeting an entirely new demographic — developers. This approach also allows for better integration and consistency for customers using multiple Aha! products together.&lt;/p&gt;

&lt;p&gt;Monolithic applications are sometimes frowned upon due to their other costs of operation. For example, some frontend JavaScript developers might prefer more isolation from our Rails backend. If a developer is using a platform or framework they're less familiar with, this could affect productivity or developer happiness — something that cannot be ignored. We've found that providing documentation and onboarding processes helps alleviate these issues.&lt;/p&gt;

&lt;p&gt;A monolithic application can also quickly run into performance limits, often from an underlying service. For example, due to the large volume of job queuing, we have run into CPU and memory bottlenecks with our Redis-backed Resque service. We were able to make improvements to our job queuing by implementing a Kafka-based queuing system. This improved the performance and reduced the latency of our jobs. The jobs are still executed as part of our monolithic application — meaning the operational costs are fairly isolated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extending the monolith
&lt;/h2&gt;

&lt;p&gt;Extending the monolith ended up being the clear winner for Aha! Develop. We already had a solid foundation of core functionality. Adding new features or APIs would not be any more difficult than if we had chosen greenfield or microservices.&lt;/p&gt;

&lt;p&gt;By embracing our monolithic codebase, we were able to launch Aha! Develop on top of the foundation we had already built. For example, instead of reimplementing authentication and permissions from scratch, we focused on building compelling new features like extensions, the workflow board, and our new GraphQL API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security and permissions
&lt;/h3&gt;

&lt;p&gt;Aha! takes &lt;a href="https://www.aha.io/legal/security" rel="noopener noreferrer"&gt;security and permissions&lt;/a&gt; very seriously. Building the same level of security and compliance into a brand new codebase would take a significant amount of effort. It would also increase the surface area for attacks by having more infrastructure that needs monitoring and safeguarding, as well as additional codebases that must have their dependencies reviewed, approved, and updated to protect us against security threats.&lt;/p&gt;

&lt;h3&gt;
  
  
  GraphQL API
&lt;/h3&gt;

&lt;p&gt;To support Aha! Develop, we built a new GraphQL API on top of our existing, rich Aha! data schema. This API supports many features in Aha! Develop, such as the workflow board, backlog management, and sprint planning views. &lt;a href="https://www.aha.io/support/develop/develop/extensions/extensions-introduction" rel="noopener noreferrer"&gt;Extension authors&lt;/a&gt; can also interact with the data or &lt;a href="https://www.aha.io/support/develop/develop/extensions/extension-fields" rel="noopener noreferrer"&gt;create custom extension fields&lt;/a&gt; to customize the tool to suit their needs.&lt;/p&gt;

&lt;p&gt;Since we built this into our monolith, the API takes full advantage of our existing data model and Aha! Ideas and Roadmaps take advantage of the new GraphQL API. We are also able to implement performance improvements that benefit all of our customers, such as our solution to &lt;a href="https://www.aha.io/engineering/articles/automatically-avoiding-graphql-n-1s" rel="noopener noreferrer"&gt;automatically avoid N+1 queries in GraphQL&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common features
&lt;/h3&gt;

&lt;p&gt;One of the major benefits of using a monolithic codebase is the ability to reuse code. Our Rails monolith uses many common patterns for writing reusable and modular code. We've used patterns like concerns, service objects, and decorators to help us avoid repetitive code, simplify testing, and provide consistent behavior across the application.&lt;/p&gt;

&lt;p&gt;We reused code to add components like comments, to-dos, record links, and audit history to Aha! Develop with very little effort. For example, we added a new model for Aha! Develop — the iteration (or "sprint") model. We use Rails concerns so our models are composable. Here are a few of the concerns we included in the iteration model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;HasDescription&lt;/span&gt;
&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;HasWatchlist&lt;/span&gt;
&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Terminology&lt;/span&gt;
&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActsAsTabbedRecord&lt;/span&gt;
&lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;ActsAsCommentable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these few lines of code, our iteration models included many advanced behaviors we had already implemented elsewhere in Aha!&lt;/p&gt;

&lt;p&gt;Not only is reusing the models helpful, but we could also reuse our existing views. This gives us access to our drawer and details design that lets Aha! Develop manage custom fields and layouts with a simple and intuitive interface. We have now added a completely new model to our application that looks and behaves just like the others.&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%2Fsjvd4vv4n8z9ocikj0lx.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%2Fsjvd4vv4n8z9ocikj0lx.png" alt="Sprint detail view"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Collaborative text editor
&lt;/h3&gt;

&lt;p&gt;Our &lt;a href="https://www.aha.io/support/suite/suite/collaboration/aha-text-editor" rel="noopener noreferrer"&gt;rich collaborative text editor&lt;/a&gt; is one of the many compelling features in the Aha! product suite. Including it as part of Aha! Develop was a critical feature. We even added syntax highlighting to the editor — an improvement built for Aha! Develop — but now all Aha! customers can take advantage of this great feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a new product to Aha!
&lt;/h2&gt;

&lt;p&gt;Since all of this core functionality was already available to us, adding a new product to the Aha! suite was relatively simple. The only difficult part was focusing on Aha! Develop’s differentiating features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Develop teams
&lt;/h3&gt;

&lt;p&gt;Develop "teams" were constructed as a natural extension to our existing workspace hierarchy. This allows team configuration and settings to behave similarly to workspaces in Aha! Roadmaps. Each team can configure their own statuses, terminology, and workflows. This was yet another way to build on top of existing patterns used in Aha! Roadmaps without writing a lot of new code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Billing and permissions
&lt;/h3&gt;

&lt;p&gt;Adding a new product to our billing system was not without its challenges. The system was originally built to support only a single product with a few different pricing tiers. Our new billing system would need to support several different products, with a number of pricing tiers each. We introduced additional subscription plans and can now support accounts that use multiple products. Customers can have quite a few different billing configurations but there are some we need to prevent.&lt;/p&gt;

&lt;p&gt;To implement this, we introduced a new "flavor" model that is used to determine the billing plan, pricing tier, and number of seats for the various products in our suite. Previously this information was stored at the account level. This flavor model also dictates available functionality from within the application. We can now use a single model to manage the billing and available functionality for accounts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Launching Aha! Develop
&lt;/h2&gt;

&lt;p&gt;In the end, we didn't need to reinvent any wheels. We didn't reimplement permissions, authentication, comments, custom fields, or layouts. All of these features have evolved and matured over many years, representing countless iterations and improvements to the Aha! product suite. It would be wasteful to throw out all of that effort. Embracing the monolith allowed us to build and launch Aha! Develop with a rich and mature interface — while saving our developers months of effort and providing a &lt;a href="https://www.aha.io/roadmapping/guide/product-strategy/complete-product-experience" rel="noopener noreferrer"&gt;Complete Product Experience&lt;/a&gt; for our users.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sign up for a free trial of Aha! Develop&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aha! Develop is a fully extendable agile development tool. Prioritize the backlog, estimate work, and plan sprints. If you are interested in an integrated &lt;a href="https://www.aha.io/suite-overview" rel="noopener noreferrer"&gt;product development&lt;/a&gt; approach, use &lt;a href="https://www.aha.io/product/overviewhttps://www.aha.io/product/integrations/develop" rel="noopener noreferrer"&gt;Aha! Roadmaps and Aha! Develop&lt;/a&gt; together. Sign up for a &lt;a href="https://www.aha.io/trial" rel="noopener noreferrer"&gt;free 30-day trial&lt;/a&gt; or &lt;a href="https://www.aha.io/live-demo" rel="noopener noreferrer"&gt;join a live demo&lt;/a&gt; to see why more than 5,000 companies trust our software to build lovable products and be happy doing it.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
