<?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: FEConf</title>
    <description>The latest articles on DEV Community by FEConf (@feconf).</description>
    <link>https://dev.to/feconf</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%2F3149595%2F6c5a4eeb-507f-494f-81c2-a95d7ea4bc6d.png</url>
      <title>DEV Community: FEConf</title>
      <link>https://dev.to/feconf</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/feconf"/>
    <language>en</language>
    <item>
      <title>Wait, Why Is This Character Here? (2)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 29 Jun 2025 14:36:35 +0000</pubDate>
      <link>https://dev.to/feconf/wait-why-is-this-character-here-2-nke</link>
      <guid>https://dev.to/feconf/wait-why-is-this-character-here-2-nke</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a summary of the FEConf2024 presentation titled &amp;lt;&lt;a href="https://www.youtube.com/watch?v=RAOIUP4BJog" rel="noopener noreferrer"&gt;Wai�t, Why Is This Character Here? (Subtitle: A Complete, Sometimes-Useful Guide to Hangul Unicode)&lt;/a&gt;&amp;gt;. The content is divided into two parts. Part 1 covered Unicode and Hangul's representation within it. Part 2 will explore why the replacement character () appears and how to resolve the issue. All images in this article are from the presentation slides of the same name and are not separately cited.&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;'Wait, Why Is This Character Here? (Subtitle: A Complete, Sometimes-Useful Guide to Hangul Unicode)'&lt;/strong&gt;&lt;br&gt;
Jaehan Jae, CTO at Denier&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the last article, we discussed Unicode and how Hangul is represented within it. In this article, we'll dive into why the strange replacement character () appears and introduce methods for solving this problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Problem Analysis - Why Does Appear?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let's get to the bottom of why this mysterious character appears.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Is Windows the Problem?: 'euc-kr'&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Filga6bzpk2f5awi7n514.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Filga6bzpk2f5awi7n514.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, let's start with the assumption that the problem is caused by the encoding method on Windows. Windows primarily uses an encoding called &lt;code&gt;euc-kr&lt;/code&gt; to store characters, not UTF-8. Its standard name is KS X 1001 (formerly KSC 5601-1987), which defines an arrangement of Hangul and Hanja characters. The 1987 specification included only 2,350 Hangul characters. This set was compiled from the most commonly used Hangul characters, but considering the total number of possible syllables is 11,172, this only accounts for about 20%. With so many missing characters, various problems arise when representing Hangul.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Encoding vs. Character Set&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Before we dive into the various issues, let's briefly touch on the difference between encoding and a character set. A character set is a table that maps each character to a number. In other words, Unicode is a standard that defines which character corresponds to which number. On the other hand, encoding is the method of representing these characters when storing them in memory or transmitting them over a network.&lt;/p&gt;

&lt;p&gt;Sometimes, encoding and character sets are explained without distinction, as many character sets also function as encodings themselves. Since &lt;code&gt;euc-kr&lt;/code&gt; both maps Hangul characters to numbers and provides a method for storing those numbers directly, it functions as both a character set and an encoding.&lt;/p&gt;

&lt;p&gt;UTF-8 is a prime example of an encoding, and Unicode is a prime example of a character set.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjnf5i1fwq378s93ipg6w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjnf5i1fwq378s93ipg6w.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Issues with Hangul Code Standardization: KSC-5601 / euc-kr&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The 2,350 Hangul characters mentioned earlier were the first to be standardized, and most Hangul text on computer networks at the time was represented based on this standard. Soon, this led to various problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvb6xo9nd6tv1x7zuvz98.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvb6xo9nd6tv1x7zuvz98.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The story of a person named '서설믜' (Seo Seol-mui) is a classic example. Because the character '믜' (mui) was not included in the &lt;code&gt;euc-kr&lt;/code&gt; range, this person reportedly had trouble not only in online environments but also when opening a bank account or writing their name on their college entrance exam application.&lt;/p&gt;

&lt;p&gt;Another interesting case is the 'Hangul 815' editor tool. &lt;code&gt;euc-kr&lt;/code&gt; includes the character '쓩' (ssyung) but not '쓔' (ssyu). Because of this, other editors at the time had a bug where typing 'ㅆ' (ss) and 'ㅠ' (yu) consecutively would cause the input to freeze, as there was no way to represent the resulting character. The Hangul 815 editor even advertised that it could represent the character '쓩'.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;cp949&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;To solve these problems with &lt;code&gt;euc-kr&lt;/code&gt;, a character set called &lt;code&gt;cp949&lt;/code&gt; was introduced. It's a character set that includes the Hangul characters that were excluded from &lt;code&gt;euc-kr&lt;/code&gt;'s original 2,350. However, &lt;code&gt;cp949&lt;/code&gt; is not the official name. Although the HTML standard specifies using &lt;code&gt;euc-kr&lt;/code&gt;, most machines actually process it as &lt;code&gt;cp949&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fj1o9zjg06zfbfgs61ry5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fj1o9zjg06zfbfgs61ry5.png" alt="Image description" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;cp949&lt;/code&gt; works by keeping the positions of the original 2,350 characters and inserting the remaining characters in between. Therefore, the alphabetical (ganada) sorting order is incorrect. In the image below, the yellow section at the beginning represents symbols, numbers, or alphabets. The part after that, KS X 1001, contains the initial 2,350 characters. The remaining characters are inserted into the purple area. As a result, sorting &lt;code&gt;cp949&lt;/code&gt; strings alphabetically requires a separate process to achieve the correct order.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3gxiq4kihsl8qr8anbbc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3gxiq4kihsl8qr8anbbc.png" alt="Image description" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is why when an &lt;code&gt;euc-kr&lt;/code&gt; document's encoding is broken or mishandled, it results in question marks or strange, unreadable Hangul characters.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;euc-kr in HTML&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Pages built with &lt;code&gt;euc-kr&lt;/code&gt; are usually processed as &lt;code&gt;cp949&lt;/code&gt;. However, the actual meta tag is saved with &lt;code&gt;euc-kr&lt;/code&gt; as shown below. This might raise another question. If you read the &lt;code&gt;textContent&lt;/code&gt; of a DOM on an &lt;code&gt;euc-kr&lt;/code&gt; page, is it actually processed as &lt;code&gt;euc-kr&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fv63vt9hy0aqww1x4q65l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fv63vt9hy0aqww1x4q65l.png" alt="Image description" width="800" height="443"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before answering that, let's first look at UTF-8.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;UTF-8&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;UTF-8 was devised in 1992. Considering Unicode was introduced in '91 and the Hangul standard in '87, UTF-8 is a relatively new technology. UTF-8 uses a variable-length encoding to represent text, typically covering the 1-byte ASCII range up to 3 bytes for the BMP area. Therefore, Hangul characters in UTF-8 are typically represented using 3 bytes. Emojis are represented in 4 bytes.&lt;/p&gt;

&lt;p&gt;*Note: UTF-8 can physically represent up to 5 or 6 bytes, but a 2003 revision declared that encodings longer than 4 bytes would not be used.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Strings in JavaScript&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So how does JavaScript represent strings? JavaScript primarily uses UCS-2 and UTF-16 to process strings. UCS-2 means that one Unicode character is represented by 2 bytes. However, UCS-2 can only represent characters within the Basic Multilingual Plane (BMP). To represent a wider range, UTF-16 is used. UTF-16 maintains the UCS-2 format but extends it to represent characters beyond the BMP, like emojis, using 4 bytes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F55xu6bmih2jmvl0sp85a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F55xu6bmih2jmvl0sp85a.png" alt="Image description" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this understanding of UTF-8 and how JavaScript handles strings, let's return to our earlier question.&lt;/p&gt;

&lt;p&gt;What happens if you read an &lt;code&gt;euc-kr&lt;/code&gt; document and store it in a JavaScript variable? It gets converted and stored in JavaScript using the UCS-2 encoding. In other words, all browsers read documents encoded in various ways, but when processing them with JavaScript, they convert and store them as UTF-16.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6b8a3myfofyhrfwp1bw9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6b8a3myfofyhrfwp1bw9.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If so many web documents are encoded in UTF-8, why was JavaScript designed to use UTF-16? UTF-8 can represent alphabets, symbols, and ASCII codes in a single byte, making it a compact representation, while Hangul and many other characters can require 2 or 3 bytes. Therefore, UTF-8 is an efficient encoding for data transmission.&lt;/p&gt;

&lt;p&gt;However, despite this advantage, calculating the length of a variable-length string is computationally inefficient. To process strings of any length quickly, JavaScript represents them internally using UTF-16 or UCS-2.&lt;/p&gt;

&lt;p&gt;So how does JavaScript actually represent length? In the first example in the image below, the special character made of three horizontal bars is represented by two UTF-16 characters. A duck emoji, being in the &lt;code&gt;1F986&lt;/code&gt; range (beyond the BMP), has a length of 2. Finally, a family emoji, which includes four human emojis and a connector character, has a length of 11.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgk09nrvm1g6ohrb0z7sm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgk09nrvm1g6ohrb0z7sm.png" alt="Image description" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When explaining UCS-2 earlier, I mentioned that JavaScript processes all strings as 2-byte units. That's why &lt;code&gt;charAt&lt;/code&gt;, which retrieves a character at a specific index in a string, operates on 16-bit (2-byte) code units.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9ubys3lf4aw4zeafpgf4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9ubys3lf4aw4zeafpgf4.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Applying &lt;code&gt;charAt(0)&lt;/code&gt; to a duck emoji returns the high-surrogate character \uD83E. On the other hand, using &lt;code&gt;codePointAt(0)&lt;/code&gt; to get the Unicode code point returns the hexadecimal value 1F986.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;So, What Is?&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So far, we've looked at &lt;code&gt;euc-kr&lt;/code&gt;, UTF, and string representation in JavaScript. Actually, this problem isn't just caused by Windows using &lt;code&gt;euc-kr&lt;/code&gt;. The same issue occurs on macOS as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;U+FFFD&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We need to take another look at the replacement character () itself. Looking up this character in the Unicode standard, I found that it's assigned to the code point U+FFFD, located at the very end of the BMP. The Unicode standard describes this character as a substitute for a character that is unrecognizable or unrepresentable. Looking closer at Unicode spec 3.0, it gives the following explanation. It recommends three ways to handle a byte sequence that cannot be interpreted as a valid UTF character.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwdjqlmrol03d1il63lk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwdjqlmrol03d1il63lk2.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The recommended actions are: return an error, delete the invalid sequence, or insert a U+FFFD marker to indicate that a character was malformed.&lt;/p&gt;

&lt;p&gt;Finally, we know the cause of the replacement character: a character was broken during UTF-8 processing. Based on this hint, I checked the code I had written.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Solving the Problem&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I checked if the problem was in the editor, but the http response was fine. I also confirmed the issue wasn't the database itself, although I did find that U+FFFD characters were being stored in it. Finally, I looked at the backend code to see if there was a problem. Soon, I found code that was processing a UTF-8 data stream by splitting it into 1,000-byte chunks.&lt;/p&gt;

&lt;p&gt;When a 3-byte UTF-8 Hangul character was processed by this code, it would get split in the middle. Then, when &lt;code&gt;toString&lt;/code&gt; was executed on the chunk, U+FFFD was generated.&lt;/p&gt;

&lt;p&gt;The manager who reported the problem mentioned that the characters were only breaking in Hangul text. This gave me confidence that this was indeed the problem.&lt;/p&gt;

&lt;p&gt;After identifying this issue, I modified the backend logic to prevent multi-byte characters from being split during the chunking process, and the problem was resolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;In Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;To understand why the replacement character () appeared, we've explored Unicode, examined the structure of Hangul characters, and looked into &lt;code&gt;euc-kr&lt;/code&gt; and UTF. Through this process, we traced the issue to the U+FFFD specification in the Unicode standard and were able to resolve the problem.&lt;/p&gt;

&lt;p&gt;I hope this article helps you remember how Hangul is stored and how it's represented in the browser. I also hope that by following my problem-solving process and checking for issues in your own backend encoding logic, you'll be able to solve similar problems more easily.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Wait, Why Is This Character Here? (1)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 29 Jun 2025 14:29:59 +0000</pubDate>
      <link>https://dev.to/feconf/-wait-why-is-this-character-here-1-175</link>
      <guid>https://dev.to/feconf/-wait-why-is-this-character-here-1-175</guid>
      <description>&lt;p&gt;&lt;em&gt;This article summarizes the FEConf2024 presentation, &amp;lt;&lt;a href="https://www.youtube.com/watch?v=RAOIUP4BJog" rel="noopener noreferrer"&gt;Wait, Why Is This Character Here? (Subtitle: A Complete, Sometimes-Useful Guide to Hangul Unicode)&lt;/a&gt;&amp;gt;. The content is divided into two parts. In Part 1, we'll learn about Unicode and how Hangul is represented in it. In Part 2, we'll delve into why the replacement character () appears and how to fix it. All images in this article are from the presentation slides of the same name and are not separately cited.&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;'Wait, Why Is This Character Here? (Subtitle: A Complete, Sometimes-Useful Guide to Hangul Unicode)'&lt;/strong&gt;&lt;br&gt;
Jaehan Jae, CTO at Denier&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello. I'm Jaehan Jae, and I'll be presenting on the topic, "Wait, Why Is This Character Here?" I previously worked on the Word and Slide products at Naver Office, and I'm currently the CTO at Denier, where we run a community for dentists and build platforms for medical professionals.&lt;/p&gt;

&lt;p&gt;I've packed this article with valuable information so that you can easily solve this problem when you encounter it and give an outstanding answer if it comes up in a technical interview. I hope you find it useful.&lt;/p&gt;

&lt;p&gt;Here's what we'll cover in this article:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The structure of Hangul Syllables in Unicode&lt;/li&gt;
&lt;li&gt; The composition and limitations of Hangul characters in euc-kr/cp949&lt;/li&gt;
&lt;li&gt; UTF-8 and UCS-2 in everyday life&lt;/li&gt;
&lt;li&gt; Understanding composed and precomposed characters&lt;/li&gt;
&lt;li&gt; Troubleshooting Hangul and decoding broken UTF characters&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Unicode&lt;/strong&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Unicode Hell&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This presentation began with the image below. Let's take a look. A manager I worked with sent me this Slack message.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdcj63g528uax9pyvlecd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdcj63g528uax9pyvlecd.png" alt="Image description" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"When I'm writing a long piece of text in the editor, a strange question mark character keeps appearing."&lt;/p&gt;

&lt;p&gt;"When I edit the text to delete it and upload it again, it just shows up somewhere else. It's like playing whack-a-mole!"&lt;/p&gt;

&lt;p&gt;The manager who sent this message described this phenomenon as "Unicode hell." The bottom right of the image shows the actual broken text they experienced. Now, let's find out why this replacement character () appears and embark on a journey to solve this problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;What is Unicode?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;First, let's learn about Unicode. Unicode is a standard, specifically ISO 10646. In other words, Unicode is a consistent standard for representing all the world's characters, as well as the organization that manages them. Within Unicode, there are several large blocks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy1uk48vn04mazrlncehh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy1uk48vn04mazrlncehh.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first area is called the Basic Multilingual Plane (BMP). This area contains a character set called the basic language plane. In this character set, the notation U+0000 to U+FFFF is used. This is a common way to represent Unicode code points. The hexadecimal numbers following "U+" represent the code point in Unicode. The "U+" is likely used by convention to avoid confusion with plain numbers.&lt;/p&gt;

&lt;p&gt;The second area is the Supplementary Multilingual Plane (SMP), which contains supplementary languages. This area includes many of the emoji and symbols you use. Finally, the third area is the Ideographic Plane, which contains characters like Hanja (Chinese characters).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fk6hvtkxeulo6hmma1pj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fk6hvtkxeulo6hmma1pj9.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above shows the Basic Multilingual Plane. As you can see, it contains a vast number of characters. Each small square represents a block of 256 characters. Rows 00 to 02 contain Roman letters, and beyond that, past the blue European characters, you can see CJK characters starting from row 34. CJK is an acronym for Chinese, Japanese, and Korean, and this block contains the Hanja used in these three countries. The Hangul characters we use are in the red-marked East Asian script area. The red-marked East Asian scripts are in rows 11, 30, and 31, and from A0 to D7.&lt;/p&gt;

&lt;p&gt;The BMP is a 16-bit space, representing code points from U+0000 to U+FFFF, which allows for a total of 65,536 characters. Unicode states that most of the world's modern writing systems are encoded in this range.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hangul in Unicode&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So, where exactly does the Hangul we use fit in? As mentioned, it's included in the East Asian script block, starting from the section that begins with AC. The image below shows a partial list of Hangul in Unicode.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fms11luhe8javvdfmthnv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fms11luhe8javvdfmthnv.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Hangul we use is included in the range from U+AC00 to U+D7A3, starting with '가' (ga) and ending with '힣' (hih). This is the same range you use in regular expressions, from '가' to '힣', to check if a character is Hangul. This representation is how Hangul is defined in Unicode.&lt;/p&gt;

&lt;p&gt;So how many Hangul characters are in this range? The calculation is based on 19 initial consonants (choseong), 21 medial vowels (jungseong), and 27 final consonants (jongseong), plus the case for syllables with no final consonant. The formula 19 × 21 × (27 + 1) yields 11,172 possible syllables. A system's ability to render all 11,172 characters determines whether it fully supports modern Hangul.&lt;/p&gt;

&lt;p&gt;There are also Hangul characters not included here. As shown below, old Hangul characters like '아래 아' (arae-a), characters without an initial consonant, and characters without a medial vowel are not included in this precomposed Hangul syllables block.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fivq6m2ds5vcem1a9m9ed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fivq6m2ds5vcem1a9m9ed.png" alt="Image description" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Hangul Sorting Standard&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next, let's look at the Hangul sorting standard. The standard order, from '가나다라마바사' (ganadaramabasa) to '아자차카타파하' (ajachakatapaha), hasn't been established for very long. The order was finalized through a Ministry of Education notice in 1988, based on discussions about where to place double consonants and complex vowels during the computerization of Hangul. The Unicode standard reflects the order established by that notice.&lt;/p&gt;

&lt;p&gt;Further research revealed that this order had been proposed several times before, and prior to the 1980s, some dictionaries used a different order, such as placing double consonants at the end. The order determined through these various discussions is recorded in the standard KS X 1026-1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Foe2h7ozv1tdsh0pag4nb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Foe2h7ozv1tdsh0pag4nb.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, let's take a quick look at Hangul in North Korea. The sorting order used in North Korea is slightly different from ours. As shown in the image below, double consonants come at the end, and the consonant 'ㅇ' (ieung) is also placed last. Therefore, when North Korean developers represent Hangul and use it in development, they have to implement a custom sorting order that doesn't align with Unicode.&lt;/p&gt;

&lt;p&gt;In 1999, North Korea proposed a revised standard as shown below, but the Unicode Consortium did not accept it. This was because South Korea had already registered the sorting standard for Hangul in 1988.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcidiscdwemgmuxpk1veh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcidiscdwemgmuxpk1veh.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, how much of the Unicode space does Hangul occupy? Of the 65,536 characters in the BMP, Hangul accounts for 11,172 characters, a whopping one-sixth. This led to discussions within the Unicode Consortium about whether to accept such a large number of characters. As a result of these discussions, some characters were omitted.&lt;/p&gt;

&lt;p&gt;Because of this, the Unicode 1.0 spec included fewer Hangul characters than we have now. It wasn't until the Unicode 2.0 spec in 1996 that all 11,172 Hangul characters were included.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Combinable Unicode: Hangul Jamo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;There's another way to represent Hangul: using a combinable form of Unicode known as Hangul Jamo. This method allows you to display characters by combining initial, medial, and final consonants.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F62rcmqmetjj7gehxwihf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F62rcmqmetjj7gehxwihf.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cheot-ga-kkeut Unicode&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This method, known as '첫가끝' (Cheot-ga-kkeut), can create the character '한' (han) from my name by combining 'ㅎ' (h), 'ㅏ' (a), and 'ㄴ' (n). With this combination method, you can represent not only the 11,172 modern Hangul characters but also old Hangul characters as a single character (though to render old Hangul properly, you'd need a font that fully supports it).&lt;/p&gt;

&lt;p&gt;The image below shows an edge case involving composed text. We have a variable &lt;code&gt;t&lt;/code&gt; with the value '한재는 발표중!' ("Hanjae is presenting!"). If we use &lt;code&gt;substring&lt;/code&gt; to get the first two characters, we'd expect to get '한재'. However, the actual result is '하'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx9opnjf76sinbphibd1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx9opnjf76sinbphibd1x.png" alt="Image description" width="800" height="435"&gt;&lt;/a&gt;&lt;br&gt;
In its decomposed form (NFD), a JavaScript string represents a Hangul syllable by listing the initial, medial, and final consonants in sequence. Since we requested the first two characters from this sequence, we got '하' (ㅎ + ㅏ).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cheot-ga-kkeut Unicode in the Wild&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This type of composed text, Cheot-ga-kkeut Unicode, can often be seen in the wild, for example, in files created on a Mac.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frijdggtbfjj85kelnxp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frijdggtbfjj85kelnxp2.png" alt="Image description" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you open a Hangul file created on a Mac on a Windows machine, it looks like this. If you read the filenames together, it says '개발자_매뉴얼' (Developer_Manual). macOS uses this decomposed text format for filenames, but Windows generally does not. That's why it's not recognized as a single character and is displayed as broken-down components.&lt;/p&gt;

&lt;p&gt;So, how well do browsers support Cheot-ga-kkeut Unicode? Fortunately, most modern browsers do. However, this can lead to a problem where Hangul that displays correctly in the browser appears broken when downloaded and viewed on a Windows OS.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Typing Cheot-ga-kkeut Unicode: The Sebeolsik Keyboard&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A key characteristic of Hangul Jamo is that even when an initial and a final consonant look the same, they are distinct characters with different code points.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqppysmbr8xvyvl3pf4ny.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqppysmbr8xvyvl3pf4ny.png" alt="Image description" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Of course, on the keyboards we commonly use, you can't input initial and final consonants separately. However, there is a keyboard that allows for this: the Sebeolsik keyboard. As you can see in the image below, the Sebeolsik keyboard allows you to input initial, medial, and final consonants separately.&lt;/p&gt;

&lt;p&gt;When using the common Dubeolsik keyboard, you might often encounter typos like the one below. You intend to write '옷이 없어요' (I have no clothes), but in your haste, it comes out as '옷이 ㅇ벗어요'. The Dubeolsik keyboard doesn't distinguish between initial and final consonants; it determines a character's role based on input timing, which can lead to such errors.&lt;/p&gt;

&lt;p&gt;Even though they look the same to us, the Sebeolsik keyboard treats initial and final consonants as different characters. This provides a unique advantage and technique: Sebeolsik moa-chigi (gathering strokes). When typing the character '않' (anh), because the Sebeolsik keyboard knows the difference between initial and final consonants, you can complete the character by typing in the order of 'ㅏ' → 'ㄴ' → 'ㅎ' → 'ㅇ'. In other words, you can input the final consonant or the last sound first and still form the character correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Combination Methods: NFC / NFD&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Of course, the Cheot-ga-kkeut system we've discussed so far is not easy to understand, and figuring out how to combine and use them is even more difficult. To address this difficulty, Unicode introduces normalization forms: NFC (Normalization Form Canonical Composition) and NFD (Normalization Form Canonical Decomposition).&lt;/p&gt;

&lt;p&gt;The term "canonical" essentially means "the correct form." Unicode introduces these forms to provide a standard rule for correctly composing and decomposing characters.&lt;/p&gt;

&lt;p&gt;For example, the single character '(ㄱ)' can be canonically decomposed into three separate characters: '(', 'ㄱ', and ')'. Conversely, these can be canonically composed back into the single character '(ㄱ)'. Similarly, the character '한' can be decomposed and composed using canonical composition and decomposition.&lt;/p&gt;

&lt;p&gt;This rule is defined in the &lt;code&gt;String.prototype.normalize&lt;/code&gt; method in JavaScript. You can check it out at this &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Hangul Compatibility Jamo&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The last thing we'll look at is Hangul Compatibility Jamo. This is the third type of Hangul representation we've discussed.&lt;/p&gt;

&lt;p&gt;Hangul Compatibility Jamo are the jamo characters that correspond to the keys on the Dubeolsik keyboards we commonly use. The difference from the Cheot-ga-kkeut notation is that there's no distinction between initial and final consonants. When you type 'ㄱ' on a Dubeolsik keyboard, it corresponds to the character at U+3131 in the Hangul Compatibility Jamo block.&lt;/p&gt;

&lt;p&gt;So far, we've learned about Unicode and Hangul within Unicode.&lt;/p&gt;

&lt;p&gt;In the next article, we'll build on what we've covered to explore why the replacement character () appears and how to solve the problem.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Wait, Why Is This Character Here? (1)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;a href="https://yozm.wishket.com/magazine/detail/2837/" rel="noopener noreferrer"&gt;Wait, Why Is This Character Here? (2)&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>The Architecture of Web-Based Graphic Editors and 7 Design Patterns (Part 2)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 22 Jun 2025 10:31:08 +0000</pubDate>
      <link>https://dev.to/feconf/the-architecture-of-web-based-graphic-editors-and-7-design-patterns-part-2-4kfb</link>
      <guid>https://dev.to/feconf/the-architecture-of-web-based-graphic-editors-and-7-design-patterns-part-2-4kfb</guid>
      <description>&lt;p&gt;*This article is a summary of the talk &lt;a href="https://youtu.be/IaIFGYWDuuo?feature=shared" rel="noopener noreferrer"&gt;&lt;em&gt;The Architecture of Web-Based Graphic Editors and 7 Design Patterns&lt;/em&gt;&lt;/a&gt; presented at FEConf 2023. The content of the presentation will be published in a two-part series. &lt;a href="https://yozm.wishket.com/magazine/detail/2466/" rel="noopener noreferrer"&gt;Part 1&lt;/a&gt; covered the basic architecture of a web-based graphic editor and the design patterns embedded within it. Part 2 will take a deeper dive into design patterns by actually implementing a graphic editor and addressing its problems. All images in this article are from the presentation slides of the same name and are not individually cited. The presentation slides can be downloaded from the &lt;a href="https://2023.feconf.kr/" rel="noopener noreferrer"&gt;FEConf 2023 website&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;'The Architecture of Web-Based Graphic Editors and 7 Design Patterns' / Heungwoon Shim, Frontend Engineer at Naver, presented at FEConf 2023&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Implementing a Graphic Editor and Applying Design Patterns&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;In this section, we'll explore how to apply five design patterns by implementing a sample graphic editor. I recommend focusing on the overall flow and concepts rather than the specific implementation details.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Creating Controllers (Parts) from a Saved Model&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let's assume the sample graphic editor we're building supports a selection tool, a rectangle tool, and a pen tool. The domain model for this editor can be represented by the following JSON, consisting of rectangles, circles, and paths.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxbg4o5l12dguobyr9e6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxbg4o5l12dguobyr9e6j.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The domain model of the graphic editor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now, let's look at our first goal: "Creating controllers from the model." As I mentioned earlier, the internal structure is implemented as shown in the diagram below. The highlighted parts are the structures involved in creation. The graphic editor reads the model and displays it in the graphic viewer. So when the model is passed to the graphic editor, it appears in the graphic viewer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F08kw0chw2gjeqblauxvl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F08kw0chw2gjeqblauxvl.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Creating controllers (parts) from the model&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This process can be expressed in pseudocode as follows. The code on the left shows the graphic editor telling the graphic viewer to create parts, passing the model as a parameter. The graphic viewer then calls &lt;code&gt;createParts&lt;/code&gt;, receives the model as a parameter, iterates through the model array, and creates a specific part for each item based on its type. After creating the parts, it renders them and displays them on the screen using &lt;code&gt;addPart&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff3pneipk0h74m1bjf3ic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff3pneipk0h74m1bjf3ic.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Pseudocode for creating a controller from a model&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But doesn't the part-creation logic seem a bit off? The highlighted code below needs to be modified every time a new shape is added. We previously said that we can use design patterns to isolate the parts that change. Here, we'll use a Simple Factory to isolate this changing part.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9azl7ycpozxjlzieekfz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9azl7ycpozxjlzieekfz.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Inefficient code for creating parts&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Factory Trio&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Before we look at the solution, let's briefly review factories. There are three types of factories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Simple Factory&lt;/strong&gt;: A pattern that centralizes object creation in one class.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Factory Method Pattern&lt;/strong&gt;: A pattern where a superclass provides an interface for creating objects, but lets subclasses decide which class to instantiate.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Abstract Factory Pattern&lt;/strong&gt;: A pattern that implements an interface and creates a family of products using composition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since a Simple Factory is sufficient to solve this example, we'll use that.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Solution - Isolating the Changing Part&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We move the &lt;code&gt;if-else&lt;/code&gt; conditional statement from the highlighted code into a &lt;code&gt;PartFactory&lt;/code&gt;. Because the &lt;code&gt;PartFactory&lt;/code&gt; is now separate, the &lt;code&gt;GraphicViewer&lt;/code&gt; that calls it no longer needs to know about the specific part types. Therefore, the &lt;code&gt;createParts&lt;/code&gt; function can simply iterate through the model and call the &lt;code&gt;PartFactory&lt;/code&gt; to create the parts. This means that if new types are added or changed, the &lt;code&gt;GraphicViewer&lt;/code&gt; doesn't need to be modified; only the &lt;code&gt;PartFactory&lt;/code&gt; needs to be updated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft7a9qkjpzre02tgw9vk2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft7a9qkjpzre02tgw9vk2.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Isolating the changing part&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This method offers two main advantages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Open-Closed Principle (OCP)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dependency Inversion Principle (DIP)&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By using a Simple Factory, we adhere to the OCP, being open for extension but closed for modification. We also adhere to the DIP by delegating creation responsibility to the &lt;code&gt;PartFactory&lt;/code&gt; and depending on the &lt;code&gt;Part&lt;/code&gt; abstraction.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Creating Views (Figures) from a Controller&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now let's look at creating views. The model we looked at earlier was a one-dimensional model. However, we don't just edit one-dimensional models; we also need to handle nested structures, layers, and groups. How can we represent these nested structures? A nested model would look like the diagram below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3hxhkezgvlksm9z6fq9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3hxhkezgvlksm9z6fq9j.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Nested model&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's a pattern that makes it easy to handle nested models: the Composite pattern. The Composite pattern allows clients to treat individual objects and composite objects uniformly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx166m89vk8dyiywb9mty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx166m89vk8dyiywb9mty.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Composite pattern&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The concept of the Composite pattern originates from the idea of a part-whole hierarchy. It treats a tree of nodes as if it were a single object. In other words, the gray group area in the diagram below is treated the same as the light blue node object.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffuednouje2uzvl6v5vzf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffuednouje2uzvl6v5vzf.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Part-whole hierarchy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This is why this pattern is often used when implementing graphical user interfaces, and you can see that it's very similar to React's rendering process.&lt;/p&gt;

&lt;p&gt;Using this pattern, you can treat individual views and nested views as the same type for rendering. In the code on the left, the &lt;code&gt;addChild&lt;/code&gt; function takes a part as a parameter, which can be either a single object or a composite object. Either type can be added as a child. Next, in the &lt;code&gt;render()&lt;/code&gt; code, the part's render method is called when the model is first loaded or changed. It renders itself and then iterates through its children to render them.&lt;/p&gt;

&lt;p&gt;However, there's one problem here. For components that cannot have children, like a text part, you need code to throw an error when &lt;code&gt;addChild&lt;/code&gt; is called, as shown in the code on the right.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdi5pdo6peg48pnsh8emx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdi5pdo6peg48pnsh8emx.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Composite pattern&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To summarize, this pattern allows you to recursively apply operations like rendering to an entire structure with simple code.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Features of the Composite Pattern&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The previous example violated the SRP (Single Responsibility Principle). That's why the Composite pattern is known for trading SRP for transparency. Here, transparency means treating individual and composite objects as the same type. This reduces type safety. As seen with the text part in the previous example, logically inconsistent operations can occur, and you must prevent them. Therefore, this pattern is said to require a balance between transparency and safety during design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7onu5t9cfotfpe8rdcmd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7onu5t9cfotfpe8rdcmd.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Features of the Composite pattern&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Modifying the Model Using Tools&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Next, let's look at modifying the model using tools. The structures related to the editing process include the Event Dispatcher, Command Stack, Root Part, Tool, Request, and EditPolicy, as shown in the diagram below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsty94yscj4obbj3w1cy5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsty94yscj4obbj3w1cy5.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Structure related to editing&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;As mentioned before, an editing task is "the process of converting an event into a state change." There is a special component in this process: the "Tool". If we look at this process in detail with the structure introduced earlier, it looks like the diagram below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fian1sfjgdbjid24tegws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fian1sfjgdbjid24tegws.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Internal structure of the event flow&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Event Dispatcher is responsible for converting low-level browser events into high-level editor events. The Event Dispatcher receives an event through a masking layer, processes it, and passes it to the canvas.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Features of a Tool&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The tool described earlier has a distinct feature: even with the same event, the result must differ based on which tool is selected. For example, if the rectangle tool is selected, dragging should draw a rectangle. If the pen tool is selected, it should draw with a pen. In other words, the graphic editor can have only one tool active at a time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Implementing a Tool&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The code on the left is for the Event Dispatcher. The event listening part is represented in pseudocode. For example, it receives a &lt;code&gt;mousemove&lt;/code&gt; event and calls &lt;code&gt;transmitMouseMove&lt;/code&gt;. &lt;code&gt;transmitMouseMove&lt;/code&gt; then passes the event to the viewer. In the viewer code on the right, it receives the event and draws the appropriate element—a rectangle, circle, or pen stroke—on the screen based on the currently active tool.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fr21xad538g3soqypa0wa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fr21xad538g3soqypa0wa.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Tool implementation&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There's a problem with this code as well. The &lt;code&gt;if-else&lt;/code&gt; statement on the right has to grow every time a new tool is added. This means the &lt;code&gt;GraphicViewer&lt;/code&gt; code is not closed for modification. Also, similar code is repeated.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Solution - The State Pattern&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The way to solve this problem is with the State pattern. In the code on the right, the &lt;code&gt;if-else&lt;/code&gt; statement is removed, and the event is passed directly to the tool in the &lt;code&gt;receiveEvent&lt;/code&gt; method. The &lt;code&gt;Tool&lt;/code&gt; becomes an object that represents the current state, with an abstract base class delegating state-specific handling to concrete subclasses. In other words, representing the current state as a single class is the essence of the State pattern. This allows you to isolate the behavior for each state into its own class, making code management easier and eliminating repetitive conditional logic.&lt;/p&gt;

&lt;p&gt;With the State pattern, the &lt;code&gt;GraphicViewer&lt;/code&gt; is now closed for modification but open for extension, allowing us to adhere to the OCP.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F35uqp8xcgtl79ypairgu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F35uqp8xcgtl79ypairgu.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;State pattern&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To summarize, the rectangle, circle, and pen tools from the previous example are now implemented as concrete classes that handle their specific states. This way, you only need to inherit from a base &lt;code&gt;Tool&lt;/code&gt; class to implement the functionality for a new tool.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Completing the MVC Structure&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Now let's revisit the event flow so far and complete the MVC structure. We just learned about tools, but is it desirable for the tool to modify the model directly, as in the previous example? In an MVC structure, who should know how to modify the model?&lt;/p&gt;

&lt;p&gt;For example, let's say we have an eraser tool that erases a bitmap. Since this eraser is only for bitmaps, it should be able to erase the bitmap image on the left but not the vector image on the right.&lt;/p&gt;

&lt;p&gt;In this situation, if the eraser tool modifies the model directly, it needs to know about both erasable and non-erasable parts. Then, every time a new part is added, it needs to know whether that element is erasable or not. This means the code must be constantly modified as new elements are added, violating the Open-Closed Principle for the eraser tool.&lt;/p&gt;

&lt;p&gt;To solve this, the part itself should know how to interpret the event. When the eraser requests to delete an element, the part receives this event and handles it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkdbx0elnt8h9bs9gi85k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkdbx0elnt8h9bs9gi85k.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Event handling for the eraser tool&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's think about this more fundamentally. If we use MVC, who modifies the model? The Controller. In our example, the &lt;code&gt;Part&lt;/code&gt; can be considered the controller. Therefore, the &lt;code&gt;Part&lt;/code&gt; must know how to interpret events and be able to modify the model. And the tool doesn't pass the event directly to the &lt;code&gt;Part&lt;/code&gt;; instead, it processes the event into a &lt;code&gt;Request&lt;/code&gt; object and asks the &lt;code&gt;Part&lt;/code&gt; to modify the model. This allows various editing requests to be encapsulated in a &lt;code&gt;Request&lt;/code&gt; object. Here, the &lt;code&gt;Request&lt;/code&gt; can be considered a high-level event.&lt;/p&gt;

&lt;p&gt;So, should we put the model modification logic directly inside the &lt;code&gt;Part&lt;/code&gt;? That's also problematic. Because if the editing logic is similar, duplicate logic could occur across different &lt;code&gt;Part&lt;/code&gt;s. This would once again violate the Open-Closed Principle for the &lt;code&gt;Part&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy9ubhj40wsf0rd991403.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy9ubhj40wsf0rd991403.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The problem with parts knowing the modification logic&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Solution - The EditPolicy Pattern&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;A smart way to solve this problem is with the &lt;code&gt;EditPolicy&lt;/code&gt; pattern. An &lt;code&gt;EditPolicy&lt;/code&gt; is a micro-controller created by separating the model modification logic from the &lt;code&gt;Part&lt;/code&gt;, making this logic reusable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdx6l49ktgmz920eom6t5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdx6l49ktgmz920eom6t5.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;EditPolicy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The code below represents the drag behavior of a tool. When a &lt;code&gt;mousedown&lt;/code&gt; event occurs, dragging starts, and when a &lt;code&gt;mouseup&lt;/code&gt; event occurs, &lt;code&gt;dragEnd&lt;/code&gt; is called. In &lt;code&gt;dragEnd&lt;/code&gt;, the &lt;code&gt;changeModel&lt;/code&gt; function is called to change the model. And the &lt;code&gt;changeModel&lt;/code&gt; method of the &lt;code&gt;ResizeTool&lt;/code&gt; performs a specific task, as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbjc5oeamo9kt8emog5d8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbjc5oeamo9kt8emog5d8.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;EditPolicy Example - Resize Tool&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let's separate the Policy from the &lt;code&gt;ResizeTool&lt;/code&gt;. We move the model modification code from &lt;code&gt;ResizeTool&lt;/code&gt; to &lt;code&gt;ResizePolicy&lt;/code&gt;. This allows the &lt;code&gt;changeModel&lt;/code&gt; of &lt;code&gt;ResizeTool&lt;/code&gt; to be abstracted. And we add an &lt;code&gt;installPolicy&lt;/code&gt; function to the &lt;code&gt;Part&lt;/code&gt;, allowing it to manage multiple policies. In other words, the policies are stored in an array, allowing the &lt;code&gt;Part&lt;/code&gt; to apply various controller logics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4n3g0x9mttggkjva565w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4n3g0x9mttggkjva565w.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Extracting the EditPolicy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This means the &lt;code&gt;ResizeTool&lt;/code&gt; only needs to send the request, and the &lt;code&gt;changeModel&lt;/code&gt; logic that was in &lt;code&gt;ResizeTool&lt;/code&gt; is moved to its superclass, &lt;code&gt;Tool&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcjxj5p0v6he515dh26dw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcjxj5p0v6he515dh26dw.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Delegating to the superclass&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Strategy Pattern&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;EditPolicy&lt;/code&gt; we just looked at is an implementation of the Strategy pattern. This pattern works by separating the changing algorithm (the model modification logic) from the context (the &lt;code&gt;Tool&lt;/code&gt; and &lt;code&gt;Part&lt;/code&gt;) and delegating the task to a &lt;code&gt;Policy&lt;/code&gt; object. The &lt;code&gt;Part&lt;/code&gt; can choose a &lt;code&gt;Policy&lt;/code&gt; to change its behavior dynamically, even at runtime. It also makes algorithms easy to reuse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft70ujb1pn0p7c3ce8llb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft70ujb1pn0p7c3ce8llb.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Features of the Strategy pattern&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Managing Work History&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This section is related to undo and redo, which are used to manage the work history. In the event flow we've built so far, an event passes through the Event Dispatcher, Graphic Viewer, Tool, and Part, eventually causing the &lt;code&gt;EditPolicy&lt;/code&gt; to modify the model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fumin1uf3bpdgkdlg50qu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fumin1uf3bpdgkdlg50qu.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Command creation by EditPolicy&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If we improve the &lt;code&gt;EditPolicy&lt;/code&gt; process one more time, instead of the &lt;code&gt;EditPolicy&lt;/code&gt; modifying the model directly, it creates an object called a &lt;code&gt;Command&lt;/code&gt;. This command is pushed onto the &lt;code&gt;Command Stack&lt;/code&gt;, which in turn executes it to change the model. Inside the &lt;code&gt;Command Stack&lt;/code&gt;, &lt;code&gt;Command&lt;/code&gt; objects that encapsulate model modifications are stacked one by one, enabling operations like undo and redo.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdzajnzieu5r7adi3u9a7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdzajnzieu5r7adi3u9a7.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Command Stack&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A summary of the final flow is shown in the diagram below. The tool receives an event, requests the &lt;code&gt;Part&lt;/code&gt;'s &lt;code&gt;EditPolicy&lt;/code&gt; to create a command, and this command then modifies the model. When the model is modified, the &lt;code&gt;Part&lt;/code&gt; requests a refresh, and the view is updated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftdnszjkwqktbn68uu699.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftdnszjkwqktbn68uu699.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The final MVC flow&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;So far, we've explored seven design patterns through the architecture of a web-based graphic editor. We also explored how to apply design patterns by improving sample implementation code.&lt;/p&gt;

&lt;p&gt;What I've shared today is just one example of how design patterns can be applied. I hope this experience will be valuable as you build your own graphic editors and helpful for applying design patterns in your work. I encourage you to discover better patterns through your own experiences, share them with others, and collaborate on their application. Thank you.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Architecture of Web-Based Graphic Editors and 7 Design Patterns (Part 1)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 22 Jun 2025 10:26:46 +0000</pubDate>
      <link>https://dev.to/feconf/the-architecture-of-web-based-graphic-editors-and-7-design-patterns-part-1-2l43</link>
      <guid>https://dev.to/feconf/the-architecture-of-web-based-graphic-editors-and-7-design-patterns-part-1-2l43</guid>
      <description>&lt;p&gt;*This article is a summary of the talk &lt;a href="https://youtu.be/IaIFGYWDuuo?feature=shared" rel="noopener noreferrer"&gt;&lt;em&gt;The Architecture of Web-Based Graphic Editors and 7 Design Patterns&lt;/em&gt;&lt;/a&gt; presented at FEConf 2023. The content of the presentation will be published in a two-part series. Part 1 will cover the basic architecture of a web-based graphic editor and the design patterns embedded within it. Part 2 will take a deeper dive into design patterns by actually implementing a graphic editor and addressing its problems. All images in this article are from the presentation slides of the same name and are not individually cited. The presentation slides can be downloaded from the &lt;a href="https://2023.feconf.kr/" rel="noopener noreferrer"&gt;FEConf 2023 website&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;"The Architecture of Web-Based Graphic Editors and 7 Design Patterns" / Heungwoon Shim, Frontend Engineer at Naver, presented at FEConf 2023&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello, I'm Heungwoon Shim, a frontend engineer at Naver working on platform development. In this article, we'll explore web-based graphic editors and design patterns.&lt;/p&gt;

&lt;p&gt;At my previous and current companies, I've participated in five graphic editor development projects. These projects include a UI prototyping tool, a graphic editor framework, a bitmap image editor, and an annotation tool for generating machine learning training data. All five of these projects stem from a single root: GEF, a sub-project of Eclipse.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbxusv537uux5i6dw5obx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbxusv537uux5i6dw5obx.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Eclipse GEF&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;GEF is a long-standing project that began in the early 2000s. It's integrated into Eclipse and is used to edit various Eclipse models with a GUI. At the time, our team adapted the GEF architecture for our projects, and in the process, we discovered that graphic editors incorporate a wide variety of design patterns. Although we've adapted and improved it to keep up with the evolving web ecosystem, the design patterns within it remain just as valid.&lt;/p&gt;

&lt;p&gt;I wanted to share this to help others understand design patterns from a practical perspective. I hope this provides some ideas for web-based graphic editor projects and serves as a good starting point for those looking to apply design patterns in their work.&lt;/p&gt;

&lt;p&gt;Here's what we'll cover in this article:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;The basic architecture of a web-based graphic editor&lt;/li&gt;
&lt;li&gt;The design patterns involved and what they solve&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Revisiting Design Patterns&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;The word "pattern" can mean a template, a design, a sample, or a model. Derived from the French word &lt;em&gt;patron&lt;/em&gt;, it refers to a recurring event or the form of an object. A simple example is the geometric repetition found in beehives or architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Architecture and Patterns&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;From an engineering perspective, the concept of a pattern was first introduced in the book &lt;em&gt;"A Pattern Language: Towns, Buildings, Construction"&lt;/em&gt; by architect Christopher Alexander. This book introduces 253 patterns for correctly arranging the elements that make up our living environment. Concepts like grids, topology, and networks, shown in the image below, might seem familiar, right?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fal4s82lman36huu1bagt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fal4s82lman36huu1bagt.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The book "A Pattern Language"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Just as we find design patterns in code, the author observed recurring elements in spaces and buildings and contemplated their relationships with other elements. Did you know that the term 'portal,' which we often use, originates from architecture? If you look at the book &lt;em&gt;"100 Ideas that Changed Architecture"&lt;/em&gt;, you'll see many familiar terms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F08dhq28898fprj5qe3ey.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F08dhq28898fprj5qe3ey.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The book "100 Ideas that Changed Architecture"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Module, layer, composition, style, platform, context—even their abstract meanings are very similar to how we use them. As you can see, architecture and programming have a lot in common. In this sense, if an architect is someone who designs physical spaces, then programmers, especially frontend developers, could be seen as architects who design mental spaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Programming and Patterns&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The book below is the well-known &lt;em&gt;"Design Patterns"&lt;/em&gt;. It was published in 1994 by Erich Gamma, an architect of Eclipse and Visual Studio Code, along with Richard Helm, Ralph Johnson, and John Vlissides. This book widely popularized the concept of patterns in programming design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2re89nxospb9wu4qiwfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2re89nxospb9wu4qiwfm.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The book "Design Patterns"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It introduces 23 patterns for properly designing software components, and its popularity led to the discovery and creation of many more patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Design Patterns&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So, what exactly is a design pattern? According to its definition on Wikipedia, it is "an accumulation of design expertise, named and organized into a reusable format." To put it more simply, it's a solution to a problem that occurs repeatedly in a specific context.&lt;/p&gt;

&lt;p&gt;Design patterns are discovered through experience rather than invented. While DRY (Don't Repeat Yourself) is about reusing code, DP (Design Pattern) is about reusing experience.&lt;/p&gt;

&lt;p&gt;Software design patterns can be broadly classified as follows:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Architectural Patterns: MVC / Pub-Sub / DAO / DTO / Broker&lt;/li&gt;
&lt;li&gt;GoF (Gang of Four) Patterns: Creational / Structural / Behavioral&lt;/li&gt;
&lt;li&gt;Concurrency Patterns: Event-based / Scheduler / Reactor&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;There are many other design patterns besides these. In this article, we will introduce the seven patterns highlighted below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fucd4xk7i21uwvu95zk6v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fucd4xk7i21uwvu95zk6v.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The design patterns that will be covered in this article&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Advantages of Design Patterns&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Good design simplifies implementation.&lt;/strong&gt;
Implementing without a design might seem fast at first, but inefficiency and complexity quickly increase over time.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Enables effective communication.&lt;/strong&gt;
When explaining a system, using patterns allows for simple and precise communication. It reduces misunderstandings and helps everyone work more efficiently.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Helps adhere to object-oriented principles.&lt;/strong&gt; 
The goal of the SOLID principles, which are the foundation of software quality, is to increase reusability, scalability, and maintainability. Since design patterns are fundamentally based on SOLID principles, mastering them helps you write flexible and extensible code.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Fostering a design-first mindset.&lt;/strong&gt;
Design patterns become part of your mental model. When refactoring a large, problematic codebase, knowing design patterns helps you redesign the overall flow into a more effective structure.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;High-level view.&lt;/strong&gt;
A high-level view allows you to see the big picture. Design patterns are higher-level concepts than libraries or frameworks. Therefore, with an understanding of design patterns, you can more easily comprehend frameworks and code, and more clearly grasp the intent behind them.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How Design Patterns Solve Problems&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;We can think of how design patterns solve problems in three ways.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Provide a way to make changes easily.&lt;/strong&gt;
The principles of design patterns are deeply related to software changes. Therefore, their core function is to make it easy to change parts of a system.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Abstraction and delegation.&lt;/strong&gt;
To facilitate easy changes, they delegate the handling of variable parts to a specialized component. They isolate the changing parts of a system, abstract them, and delegate the work to specialized modules through an interface.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Composition over inheritance.&lt;/strong&gt;
And they solve problems using composition rather than inheritance. The reason is that inheritance is determined at compile-time, while composition is determined at runtime, making it more flexible. For this reason, as shown in the image below, most of the GoF patterns are based on composition.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvz9l8v373lu9keacy2ko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvz9l8v373lu9keacy2ko.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How design patterns solve problems&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;When is it good to use design patterns?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;It's good to use them when you anticipate that a specific part of the system will change continuously. This is why design patterns are frequently used in frameworks. Frameworks provide a foundation, and development proceeds by swapping out the changeable parts. Also, if you find yourself constantly modifying &lt;code&gt;if&lt;/code&gt; statements in your code, that's a good time to consider using one.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How should you use design patterns?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Simply follow the KISS principle. This principle was developed by the U.S. Navy: Keep It Small and Simple. In other words, it's best to use a design pattern when it simplifies the problem. However, there is no single correct answer. Since design patterns are "desirable experiences," you should apply them flexibly according to the situation.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Design Pattern of Drawing&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let's explore the design patterns involved in the act of drawing. First, let's think about the editing task of drawing on a piece of paper. The process of drawing on paper follows these steps:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;You move the pencil.&lt;/li&gt;
&lt;li&gt;Graphite is transferred to the paper.&lt;/li&gt;
&lt;li&gt;The state of the paper changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, what about the process of a program drawing a picture?&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;You move an input device.&lt;/li&gt;
&lt;li&gt;An event is delivered.&lt;/li&gt;
&lt;li&gt;The state of the screen changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;In essence, an editing task is the process of converting an event into a state change. Here, when the user provides an event, they expect the paper or monitor screen to change. This is the user's mental model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fuvc1vs4tan6zerupogx9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fuvc1vs4tan6zerupogx9.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Mental Model&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Therefore, to summarize this process again, an editing task can be described as the process of converting a user's intent—their mental model—into a computer model through events.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fo2y7lffs8svwcw1uzanb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fo2y7lffs8svwcw1uzanb.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The properties of an editing task&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The MVC Pattern&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;There is an architecture designed for the process we just described: the MVC pattern, created in 1976 by Norwegian computer scientist Trygve Reenskaug at Xerox PARC. In this model, the View is responsible for the mental model, the Controller for the editing device, and the Model for the computer model.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu2wxupxdjhjueve655o4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu2wxupxdjhjueve655o4.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MVC and the editing task&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Palo Alto Research Center is where the prototypes of many IT devices we enjoy today were developed. At the time, he was working on the team developing the Dynabook, which could be called a distant ancestor of the iPad. This team was led by Alan Kay, a pioneer in object-oriented programming and GUIs who also led the development of Smalltalk (the forerunner of object-oriented languages).&lt;/p&gt;

&lt;p&gt;The team focused on improving GUIs to make them easier for users to use. In this process, Trygve Reenskaug had a realization: the user's mental model is different from the computer's way of processing information.&lt;/p&gt;

&lt;p&gt;So, Trygve Reenskaug devised MVC as a structure for the process of converting the mental model into the computer model. The image below is a part of the paper Reenskaug wrote about MVC. The paper contains this passage: “This story is focused on the problem of bridging the gap between man and machine.”&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnci4zleqhnoz9txxaxck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnci4zleqhnoz9txxaxck.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The gap between man and machine - from the MVC paper&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Essence of MVC and What It Solves&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;He states, "The essential purpose of MVC is to bridge the gap between the user's mental model and the digital model that exists in the computer." "An ideal MVC solution supports the user's illusion of seeing and manipulating domain information directly." "This structure is also useful in situations where the user needs to see the same model from different perspectives simultaneously."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsxt55k4lcosffi89g14w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsxt55k4lcosffi89g14w.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;What MVC solves&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Separation of Concerns in the MVC Pattern&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The core of this pattern is to separate modules based on their roles—in other words, separation of concerns. By doing this, the MVC pattern can effectively translate the mental model into the computer model, which is why MVC and its derivatives are still used as the basis for various architectures even after 40 years.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5fs9xrzpq2slfcx7pia6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5fs9xrzpq2slfcx7pia6.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;MVC's separation of concerns&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Modern MVC is often configured in slightly modified forms to fit a given situation, but a standard structure is shown in the diagram below. When the Controller receives a user event through the View, it creates and sends a command to modify the Model. Once the Model is modified, the View is updated based on the change. This MVC pattern is actually a composite pattern, combining several other patterns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fc6muscbg0bkogpdbxr9s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fc6muscbg0bkogpdbxr9s.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The structure of the modern MVC pattern&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Model is a classic example of a subject in the Observer pattern. The View and Controller listen for changes in the Model's state, but the Model should be unaware of their existence. Also, when a View or Controller is destroyed during its lifecycle, its subscription must be terminated.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fphs7r79auol639j3u20w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fphs7r79auol639j3u20w.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Observer pattern in the Model&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The GUI components that make up the View are often composed of nested structures like windows, panels, labels, and buttons. The Composite pattern is a pattern that treats such nested structures as a single object, thus abstracting the nesting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff2144jh033eiiop19ay4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff2144jh033eiiop19ay4.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Composite pattern in the View&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The Strategy pattern is a typical pattern for the Controller. The View only passes events to the Controller and is not involved in changing the Model. How the Model is changed is determined by the Controller's logic. And this Controller, connected to the View, can be changed at runtime.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyapn8i87hytf3tryke3a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyapn8i87hytf3tryke3a.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Strategy pattern in the Controller&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We will take a closer look at these patterns in a later section.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Graphic Editor Architecture&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now, let's learn about the basic structure of a graphic editor. The diagram below shows the external structure of a graphic editor. There's a surrounding workbench, a menu and toolbar, and tools on the left. In the center is the canvas, and on the right, from top to bottom, are the Properties, Palette, History, and Layers panels. Finally, you can see the Status bar at the very bottom.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fosqtbmk1ri9p57h10gca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fosqtbmk1ri9p57h10gca.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;External structure of a graphic editor&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In this section, we will explore design patterns by implementing the Tool, Canvas, and History areas, which are highlighted in orange in the diagram.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Internal Components&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The internal structure varies by application, making it difficult to generalize, but I believe other applications likely have similar components. A graphic editor is internally composed of the following:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu1kwbawtzvi8z1fjed55.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu1kwbawtzvi8z1fjed55.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Internal components of a graphic editor&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Model&lt;/strong&gt;: The unique domain model that the editor can manipulate.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Graphic Viewer&lt;/strong&gt;: The space where editing occurs based on graphic events.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Event Dispatcher&lt;/strong&gt;: Converts low-level events from the web browser into high-level events for the editor.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Command Stack&lt;/strong&gt;: Manages history by supporting undo and redo.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Root Part&lt;/strong&gt;: The basic unit of editing is the "Part." Parts are responsible for performing editing actions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fq1ni5rrm2x0dw2eg3tje.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fq1ni5rrm2x0dw2eg3tje.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;RootPart of the internal components&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Tool&lt;/strong&gt;: Implements the tools from the external structure.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Action Registry&lt;/strong&gt;: Handles keyboard shortcuts.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Request&lt;/strong&gt;: An abstraction of an event.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;EditPolicy&lt;/strong&gt;: A micro-controller that executes the actual edits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next article, we will provide a more detailed introduction to these internal components and explore how to apply design patterns by implementing a virtual graphic editor.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Cross-Platform Design System: A 1.5-Year Journey (Part 2)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 15 Jun 2025 23:19:12 +0000</pubDate>
      <link>https://dev.to/feconf/cross-platform-design-system-a-15-year-journey-part-2-kpf</link>
      <guid>https://dev.to/feconf/cross-platform-design-system-a-15-year-journey-part-2-kpf</guid>
      <description>&lt;h1&gt;
  
  
  Cross-Platform Design System: A 1.5-Year Journey
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;This article is a written version of the "Cross-Platform Design System: A 1.5-Year Journey" presentation from FEConf2023. The content is divided into two parts. Part 1 covers design systems and design tokens, and explores component composition as a way to address communication issues between designers and developers. Part 2 builds on Part 1, focusing on implementing components and designing APIs. All images in this article are from the presentation of the same name, and their sources are not individually cited. The presentation slides can be downloaded from the &lt;a href="https://2023.feconf.kr/" rel="noopener noreferrer"&gt;FEConf2023 website&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;▶&lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSdcp-7HKQnHIg64QQ--dwe07djNzSgTOwvnth7JFLGzhoFc0A/closedform" rel="noopener noreferrer"&gt;2024 FEConf Session Speaker Applications Open&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;▶&lt;a href="https://docs.google.com/forms/d/e/1FAIpQLSc1lDk5UWU7aH88wT9bTm3gOkDprEYnguoXH8OEV5IJoH0Jtg/closedform" rel="noopener noreferrer"&gt;2024 FEConf Lightning Talk Speaker Applications Open&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Cross-Platform Design System: A 1.5-Year Journey" presented at FEConf2023 by Hayoung, Frontend Engineer at Daangn&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how to apply the concepts we covered in "Cross-Platform Design System: A 1.5-Year Journey (Part 1)" to actual component implementation. The goals of this part are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating APIs that balance consistency and flexibility&lt;/li&gt;
&lt;li&gt;Designing packages with cross-platform compatibility in mind&lt;/li&gt;
&lt;li&gt;Understanding patterns for reflecting component specifications in code&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Flexibility vs. Consistency
&lt;/h2&gt;

&lt;p&gt;Let's revisit the APIs of Chakra and Spectrum. From a product language perspective, it's natural to want to provide a concise API like Spectrum's (shown on the right), prioritizing consistency. However, even if a consistency-focused approach covers 90% of use cases, users might still face development delays due to the remaining 10% of edge cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fscf0f5rfp1pdd4qriaki.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fscf0f5rfp1pdd4qriaki.png" alt="Image description" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My approach to this problem is to separate packages into a core package that provides consistency and a composable package that offers flexibility.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fka63jn2jqks7etp7w8np.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fka63jn2jqks7etp7w8np.png" alt="Image description" width="800" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In our actual implementation, we separate packages into three layers: Core, Composable, and Pre-Composed. We provide consistency by default using pre-composed components. We also offer styling props similar to those in Styled Components, but limit them to layout properties like margin. When more flexibility is needed, users can opt for the Composable package, which is designed to be just as easy to use as the pre-composed components.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component Implementation
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fc93sn1q3f95shr7535mn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fc93sn1q3f95shr7535mn.png" alt="Image description" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the diagram above, the core logic of a component's functionality consists of state charts and DOM binding. For this article, we'll focus solely on DOM binding, leaving state charts for a future discussion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Functionality: DOM Binding
&lt;/h3&gt;

&lt;p&gt;Let's assume we need a component with the following requirements: it has 4 structural parts, maintains a 'checked' state, toggles when clicked, and prevents toggling when disabled.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; - Root, Control, Input, Label&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State&lt;/strong&gt; - isSelected: boolean&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interaction&lt;/strong&gt; - click&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context&lt;/strong&gt; - isDisabled: boolean&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To implement DOM binding, let's first express the structure. We can declare the four structures as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa6nm48sb7pi3ujjwhlzj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa6nm48sb7pi3ujjwhlzj.png" alt="Image description" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These props are ultimately spread into JSX to provide the desired functionality:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6c0lm3sniaf6t1jg4at0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6c0lm3sniaf6t1jg4at0.png" alt="Image description" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Applying state to the DOM involves determining element props based on the state received from the state chart. In this instance, we can bind isSelected to the checked prop of inputProps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9aqdjs55oom2cd3qo9l2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9aqdjs55oom2cd3qo9l2.png" alt="Image description" width="800" height="337"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Interactions are implemented by event handlers that pass events to the state chart. In this case, when an onChange event occurs in inputProps, it sends a 'TOGGLE' event to the state chart. The state chart then determines whether the current state should change based on the 'TOGGLE' event and the isDisabled context, and passes the updated state back to the DOM binding logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4ihqdgxqg8rxx48rsnkb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4ihqdgxqg8rxx48rsnkb.png" alt="Image description" width="800" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Applying context is implemented similarly to applying state: by determining element props. The key difference is that it uses props injected from outside rather than the state provided by the state chart. In this case, we bind isDisabled from the injected ctx to the disabled prop of inputProps:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fizb6sehrmyodcvpypue0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fizb6sehrmyodcvpypue0.png" alt="Image description" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This logic, being free of React dependencies, is integrated with React via a thin wrapper:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwuygf9t6ale03fznyxiz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwuygf9t6ale03fznyxiz.png" alt="Image description" width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can declare interfaces for state and context separately, then extend them to include all callbacks for state changes. This allows us to declare all props required by the headless checkbox component:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxm5qx390qf14xduk3txw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxm5qx390qf14xduk3txw.png" alt="Image description" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The React wrapper can be implemented by simply receiving these props, passing them to the state chart, and returning the results to the DOM binding logic. In actual code, additional techniques like useSyncExternalStore or useEffect are needed for state synchronization with React, but they're omitted here for brevity:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3rqnxcxahwowd2shan9v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3rqnxcxahwowd2shan9v.png" alt="Image description" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Form
&lt;/h3&gt;

&lt;p&gt;We've covered component implementation from a functional perspective. Now let's look at the presentational aspect (form). Since CSS and JavaScript are different languages, it's difficult to write wrappers analogous to hooks. We can't use CSS directly within JavaScript. Instead, we maintain consistency by generating both CSS and CSS-in-JS from a single schema.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdbiuihm6n0m3n5t1cbpa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdbiuihm6n0m3n5t1cbpa.png" alt="Image description" width="800" height="342"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's assume we have a component with these presentational requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; - Root, Control, Icon, Label&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual Options&lt;/strong&gt; - size = large, medium&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Options&lt;/strong&gt; - selected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design Decisions&lt;/strong&gt; - root height = 32px when large / 24px when medium / control background = primary when selected&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;First, let's define the structure. We start by defining the visual structure, similar to how we handled DOM binding for functionality:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdchdzrkd0q45wo92q364.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdchdzrkd0q45wo92q364.png" alt="Image description" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can define visual options using Variants expressions. In this case, we specify that the root's height is 32px when large and 24px when medium:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F475sptouq1xn3cs3pv7d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F475sptouq1xn3cs3pv7d.png" alt="Image description" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we can define state options using data attributes as selectors. For example, we can implement it so that when the data-selected attribute exists, the control's background color changes to the primary color. However, HTML doesn't automatically add this data-selected attribute. Therefore, we also need to add related logic to the DOM binding:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff1mpafm5o7mclhosrnvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff1mpafm5o7mclhosrnvb.png" alt="Image description" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The logic to pass the selected status as a data attribute to controlProps in the DOM binding is added as follows. This is necessary because state options were identified as a common concern between functionality and form:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnust4gzxrssbvhv53d4b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnust4gzxrssbvhv53d4b.png" alt="Image description" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on this schema, we can generate class names to be used as selectors, following rules based on structure and visual options:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6pswo4y1n0qxg2x1m8vn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6pswo4y1n0qxg2x1m8vn.png" alt="Image description" width="800" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also generate selectors for state options using CSS nesting syntax:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fri0cjwtmztivhr1xoc4m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fri0cjwtmztivhr1xoc4m.png" alt="Image description" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, we generate CSS-in-JS code that corresponds to the generated CSS. The code below has two parts: the checkboxVariantProps interface representing visual options, and a function that converts these visual options into class names for each slot. For example, when size is passed as large, it returns class names like checkbox__root--size_large, as defined in the visual options example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4k06vhsmw1besz7j4bhg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4k06vhsmw1besz7j4bhg.png" alt="Image description" width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ultimately, we complete the checkbox component interface by extending these functional and presentational interfaces and incorporating any necessary additional props:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7dl8hdijwykz6sll8j9u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7dl8hdijwykz6sll8j9u.png" alt="Image description" width="800" height="366"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can call the functional hooks and the form's class name generation functions respectively, using the combined props. Looking at the JSX below the return statement, we can see that we spread the APIs obtained from the functional hooks and the class names obtained from the form logic into JSX, binding them to compose functionality and presentation. This code is very simple and repetitive, making it easy for users to reimplement using the Composable package:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F66poxt3omifvsm4h7yu0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F66poxt3omifvsm4h7yu0.png" alt="Image description" width="800" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This way, by separating pre-composed components from composable packages, we can provide APIs that offer both consistency and flexibility, achieving our original goal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fg433yd9ew1vrtznsahhd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg433yd9ew1vrtznsahhd.png" alt="Image description" width="800" height="347"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since all React-dependent code is written as simple bindings, reusability across other frameworks has increased:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1ze29qvy6ym3timtxc4f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1ze29qvy6ym3timtxc4f.png" alt="Image description" width="800" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Furthermore, depending on how we define components, we can pre-calculate all possible rendering states of a component. This enables snapshot testing and QA automation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fj811dmn7k42z1il00akq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fj811dmn7k42z1il00akq.png" alt="Image description" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Daangn, we're also experimenting with automatically generating component specification documents for both Figma and the web using Figma Variables, and synchronizing them with the component code:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjsj1hqwhni1v6uwjqepq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjsj1hqwhni1v6uwjqepq.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The component approach and implementation I've explained are all built on the shoulders of giants. I've been particularly influenced by Adobe Spectrum, Zag.js, and Class Variance Authority. I recommend that teams building design systems, or anyone wanting to understand them deeply, explore these libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Let's summarize the topics we covered today:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Setting design system goals&lt;/li&gt;
&lt;li&gt;Defining and utilizing design tokens&lt;/li&gt;
&lt;li&gt;Why atomic design can be confusing&lt;/li&gt;
&lt;li&gt;Breaking down component composition&lt;/li&gt;
&lt;li&gt;Solving communication issues between designers and developers&lt;/li&gt;
&lt;li&gt;Component implementation and API design&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Based on this content, here are the key lessons I've learned. These are aspects I will definitely consider when creating a new design system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Clearly set design system goals and benchmarking targets&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid premature abstraction when encoding design intent&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Separate component functionality/form to define minimum units&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Improve communication through state compression and eliminate state explosion&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove circular references in communication through separation of concerns&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provide an environment where users can easily assemble components&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Ultimately, achieving a balance between consistency and flexibility, based on the principles discussed, is the core lesson I've learned while building design systems. I hope this article empowers more developers to create design systems with confidence and enjoyment. Thank you.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Cross-Platform Design System: A 1.5-Year Journey (Part 1)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 15 Jun 2025 23:14:23 +0000</pubDate>
      <link>https://dev.to/feconf/cross-platform-design-system-a-15-year-journey-part-1-4e4d</link>
      <guid>https://dev.to/feconf/cross-platform-design-system-a-15-year-journey-part-1-4e4d</guid>
      <description>&lt;h1&gt;
  
  
  Cross-Platform Design System: A 1.5-Year Journey
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This article summarizes the presentation "Cross-Platform Design System: A 1.5-Year Journey" from FEConf2023. The presentation material is divided into two parts. Part 1 explores design systems and design tokens, and examines component composition to solve communication issues between designers and developers. Part 2 builds upon Part 1 to implement components and design APIs. All images in this article are from the presentation with the same title, and no separate source attribution is provided. The presentation materials can be downloaded from the FEConf2023 website.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;▶FEConf 2024 Session Speaker Applications Open&lt;/p&gt;

&lt;p&gt;▶FEConf 2024 Lightning Talk Speaker Applications Open&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Cross-Platform Design System: A 1.5-Year Journey" presented at FEConf2023 by Ha Taeyoung, Frontend Engineer at Danggeun&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Hello, I'm Ha Taeyoung, working on design systems at Danggeun. In this article, I'll share various approaches I've tried while building design systems over the past year and a half, the pitfalls I've encountered, and the lessons learned from these experiences.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Design System
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Goals of a Design System
&lt;/h3&gt;

&lt;p&gt;When building a design system, you'll notice that the term "design system" has a very broad range of interpretations, which can lead to miscommunication among team members or confusion about the team's direction. In this section, we'll explore how to clearly set the direction of a design system, accurately understand its design goals, and prepare for potential pitfalls when setting these goals.&lt;/p&gt;

&lt;p&gt;The image below shows well-known open-source design systems: Chakra and Adobe's Spectrum. Even for implementing the same range slider functionality, there are significant differences. Chakra requires you to explicitly specify all sub-components, while Spectrum only needs a single line for the range slider.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2lpyqeajaol851chpjdf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2lpyqeajaol851chpjdf.png" alt="Image description" width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Why do these differences exist? I believe it's because the two design systems are built on different core values. Chakra was created with the goal of being "a library that developers can flexibly use through open source," while Spectrum was made for use in Adobe's product suite. Chakra values flexibility more, while Spectrum prioritizes consistency, leading to these differences.&lt;/p&gt;

&lt;p&gt;For this reason, I view these two design systems from different perspectives, calling Chakra-like systems "general-purpose design systems" and Spectrum-like systems "product languages." Product languages include not only Spectrum but also many others like Material, Carbon, and Fluent. But why do many companies create separate design system organizations and build product languages instead of using general-purpose design systems?&lt;/p&gt;

&lt;p&gt;The reason is that UI needs to reflect company-specific decisions like branding and app characteristics. Product languages also encompass "managing a collection of decisions," going beyond the general functionality of design systems. To emphasize further, the essence of a product language is a collection of design decisions, and the UI library is just a means to implement them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff97j33awn57mer1e7r35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff97j33awn57mer1e7r35.png" alt="Image description" width="800" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Companies can use external libraries when the library is either unrelated to decision-making or can fully reflect their decisions. However, UI libraries often cannot reflect these decisions, which is why many companies create design system organizations and build product languages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall: Setting Design System Goals
&lt;/h3&gt;

&lt;p&gt;I encountered several pitfalls while building the design system. First, I failed to set clear goals, which led to poor judgment in choosing reference materials. Danggeun's app needed a product language as its design system, with mobile as the priority and implementations needed for web, iOS, and Android. In the early stages of team formation, I referenced design systems designed for responsive web, which was appropriate for that context but led to incorrect approaches for our product.&lt;/p&gt;

&lt;p&gt;Another pitfall was the desire to cover multiple environments and products with a single design system. As the service grew, we needed to support not only mobile apps but also websites, admin panels, and more. Since branding is generally similar across all products, I wanted to cover everything with one well-made general-purpose design system. However, this attempt either proved impossible or created situations requiring more cost than building a product language.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fb1l2x3nr5jbgpynbxxts.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fb1l2x3nr5jbgpynbxxts.png" alt="Image description" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Through this experience, I learned that rather than creating a perfect general-purpose design system, it's more important to utilize recurring methodologies that exist regardless of what product language you create. The most important of these is "a system that effectively synchronizes design decisions across all product design-related areas." I jokingly call this the "design system-system." In the next section, we'll explore "design tokens" as a means to effectively manage and synchronize these design decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Design Tokens
&lt;/h2&gt;

&lt;p&gt;In this section, we'll explore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understanding the meaning of design tokens&lt;/li&gt;
&lt;li&gt;Examining design token usage cases&lt;/li&gt;
&lt;li&gt;Preparing for potential pitfalls in design token implementation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Building a design system is ultimately a series of design decisions. Statements like "The button component's height will be 40px" or "The brand color will be #ff6f0f" are all design decisions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff6h32qccb714zv9zpsbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff6h32qccb714zv9zpsbw.png" alt="Image description" width="800" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And as with most products, decisions change over time. As the product grows, we learn what constitutes better UI. So what's the fastest way to reflect these decision changes across all products?&lt;/p&gt;

&lt;p&gt;It's the approach of encoding decisions in a machine-readable format and having products read and reflect them. "Design tokens," the title of this section, refers to encoding design decisions in a way that both humans and machines can read.&lt;/p&gt;

&lt;p&gt;Let's explore design tokens in more detail.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encoding Values
&lt;/h3&gt;

&lt;p&gt;First, design tokens can encode values. What meaning does the design token in the image below have?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzuez8lmaur41qny9m7di.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzuez8lmaur41qny9m7di.png" alt="Image description" width="800" height="163"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You could say "This color's name is $carrot-500." This isn't wrong, but I think it contains a more important meaning. The precise meaning I think is "We will use this color in our product, and its name is $carrot-500."&lt;/p&gt;

&lt;p&gt;These two expressions might seem similar or like wordplay, but there's an important difference. The meaning of "we will use this color in our product" implies that we won't use colors that haven't been declared. Through this, the design system team can maintain design decisions as a flexible set and effectively manage design decisions. However, this might reduce design flexibility and freedom.&lt;/p&gt;

&lt;p&gt;While there are infinite concepts in the world, we can communicate effectively with a finite set of words. I believe that similarly, while there are infinite design values, we can communicate with a finite set of design tokens.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8is77x299405bi5o0ah3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8is77x299405bi5o0ah3.png" alt="Image description" width="800" height="198"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Encoding Intent
&lt;/h3&gt;

&lt;p&gt;To use design tokens more flexibly, we can add layers. As shown below, we can add layers that encode intent to the finite set of design tokens that encode values. This design token expression encodes the intent: "We will use $carrot-500 in areas intended for primary brand."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1nhfwjti96i13bqsf1m8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1nhfwjti96i13bqsf1m8.png" alt="Image description" width="800" height="185"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By adding these layers, we can express the decision that while it's currently represented by the same color, it might be represented by a different color in the future. Also, by pursuing a way of thinking that designs based on intent rather than values, we improve decision-making and communication in the product design process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyonx6i0nfvaaywwlkfoo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyonx6i0nfvaaywwlkfoo.png" alt="Image description" width="800" height="159"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Context
&lt;/h3&gt;

&lt;p&gt;Finally, design tokens can assign different values based on context. For example, we can add branches for light mode and dark mode to the same $carrot-500 value.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fms7jmejzfbaqfrmlvw71.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fms7jmejzfbaqfrmlvw71.png" alt="Image description" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Summarizing the three aspects above, design tokens are structured through context and layers as shown below. By utilizing context and layers, design tokens gain sufficient expressive power for use in product design.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0mvyv8tmzykxi63q39fg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0mvyv8tmzykxi63q39fg.png" alt="Image description" width="800" height="357"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At Danggeun, we manage design tokens by creating a simple DSL (Domain-Specific Language) based on this definition. This expression semantically matches Figma Variables added this year, so if you're a team newly building design tokens, it might be worth actively considering this approach. At Danggeun, we put this DSL inside Figma component frames and use these components as the source of truth. Through this approach, we've automated documentation synchronization in Figma and implemented features like dark mode previews.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxxzca3llnbpepm6zv73x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxxzca3llnbpepm6zv73x.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Also, when deploying components, we add webhooks to trigger actions, through which we use Figma as the source of truth and generate platform-specific code to synchronize design decisions across platforms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fx1nk5durs2097aaaunt5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fx1nk5durs2097aaaunt5.png" alt="Image description" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Design Tokens to Code
&lt;/h3&gt;

&lt;p&gt;Let's look more closely at how to apply this in frontend development. In the stylesheet below, we implement context branching through data attributes and design tokens and layers through CSS Variables.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fogsg9umnp2b7da4gnw4p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fogsg9umnp2b7da4gnw4p.png" alt="Image description" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And in CSS-in-JS, we create a package that aliases CSS Variables for easier use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa90upo683ggbyej116oh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa90upo683ggbyej116oh.png" alt="Image description" width="800" height="372"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This alias package can be used in Emotion as shown in the first image below, and in vanilla-extract as shown in the second image. Given Danggeun's diverse technology stack, we can't apply only specific CSS-in-JS technologies, so we've implemented various approaches suitable for each technology.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkyv2emgwkpvzjiq6rif7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkyv2emgwkpvzjiq6rif7.png" alt="Image description" width="800" height="746"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Pitfall: Premature Abstraction of Semantic Tokens
&lt;/h3&gt;

&lt;p&gt;The difficulties I faced while creating design tokens were more operational than technical. One particularly memorable misjudgment was prematurely defining semantic tokens. If we have a FAB component like the one below, can we say that this component's background color has the meaning of floating?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffmww4zxfosstp4np1g8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffmww4zxfosstp4np1g8y.png" alt="Image description" width="800" height="166"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on my experience, such premature semantic token definitions often create more design confusion rather than being useful.&lt;/p&gt;

&lt;p&gt;So what's the method for defining useful semantic tokens? It's to examine what concerns are shared by considering various use cases. When various input fields like Input, Text area, and Number Input exist and they all need to share the same background color, defining their background color as Field-bg can be a useful semantic token.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F79ujkiy03lh5bhb6qza9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F79ujkiy03lh5bhb6qza9.png" alt="Image description" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Semantic tokens can be powerful tools when well-defined. However, poorly defined semantic tokens can cause greater confusion. Therefore, it's dangerous to hastily assign intent to semantic tokens. In other words, since abstraction comes through observation, it's safer to examine common concerns shared by multiple cases and extract intent through this process.&lt;/p&gt;

&lt;p&gt;We've now explored design tokens. While design tokens are a powerful means for operating a design system organization, what users expect from a design system is components. In the next section, we'll explore components in design systems.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Components
&lt;/h2&gt;

&lt;p&gt;In this section, we'll explore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Why Atomic Design is confusing&lt;/li&gt;
&lt;li&gt;Accurately understanding component composition&lt;/li&gt;
&lt;li&gt;Solving communication problems between designers and developers&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Atomic Design
&lt;/h3&gt;

&lt;p&gt;Atomic Design was a frequently mentioned keyword until a few years ago, but it's rarely heard now. Are you successfully applying Atomic Design to your products without significant modifications? At least I find it difficult to apply Atomic Design as is.&lt;/p&gt;

&lt;p&gt;In the radio group component shown below, which part is the atomic unit?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbfop7l7gm71a9g7bwiah.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbfop7l7gm71a9g7bwiah.png" alt="Image description" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Actually, there's no single correct answer. The answer changes depending on the criteria. In terms of form units, the yellow box is the atom, but in terms of functionality, a single radio button has no meaning, so the blue box's functional unit is the atom. And in terms of accessibility, since we have an obligation to provide form element levels, the red box's accessibility unit is the atom.&lt;/p&gt;

&lt;p&gt;Let's briefly explore this approach of separating functionality and form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components: Form
&lt;/h3&gt;

&lt;p&gt;The checkbox below has the form of an empty box when not selected, and a box with a check mark when selected. This rendering of styles corresponding to current states is the formal aspect of components.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmndrf4wuw8obedh8a1qo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmndrf4wuw8obedh8a1qo.png" alt="Image description" width="800" height="207"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Components: Functionality
&lt;/h3&gt;

&lt;p&gt;As shown in the image below, a checkbox can toggle selection through clicks. In this case, how the checkbox looks is not important. The order of checkboxes might change, colors might differ, or it might take a completely different form like a toggle switch instead of a checkbox.&lt;/p&gt;

&lt;p&gt;So if we remove rendering and leave only state, the functional aspect of components becomes clearer. Therefore, the functional aspect of components is defined by how the component's state transitions in response to user input, excluding rendering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fv9vj9xuuuqj56nzhjk5p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fv9vj9xuuuqj56nzhjk5p.png" alt="Image description" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Atomic Design is Confusing
&lt;/h3&gt;

&lt;p&gt;So why is Atomic Design confusing? In conclusion, atomic functionality and atomic form can exist. However, atomic components cannot be determined as a single unit. Therefore, trying to classify only components and define them as minimum units is likely to fail. We need to think separately about style sheets that provide atomic form and headless components that provide atomic functionality to eliminate confusion about what the minimum unit is and allow the team to move forward. Now, let's move beyond Atomic Design and dissect the composition elements of components for both functionality and form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dissecting Components: Functionality
&lt;/h3&gt;

&lt;p&gt;First, component functionality consists of structure, state, interaction, and context.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft5ddx2gn1uajgn9kvlzx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft5ddx2gn1uajgn9kvlzx.png" alt="Image description" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, structure defines what parts the component consists of and what role each part plays in user interaction. For example, the component above consists of a checkbox with the role of control and an Item Label text with the role of label, wrapped in a Root.&lt;/p&gt;

&lt;p&gt;Second, state defines the component's states that can change through user input. Representative examples include Pressed (active), Hover, Focused, Selected(Checked), etc.&lt;/p&gt;

&lt;p&gt;Third, interaction is the unit that causes state changes. For example, in a checkbox, clicking changes Checked, and tabbing changes Focused.&lt;/p&gt;

&lt;p&gt;Finally, context is an option injected in code that affects behavior. For example, injecting a Disabled option deactivates the checkbox, and no matter how much you click, Checked won't change. Of course, state can also affect behavior, but there's an important difference: state changes through user input, while context changes only through code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dissecting Components: Form
&lt;/h3&gt;

&lt;p&gt;Next, let's explore component form. Component form consists of structure, visual options, state options, and design decisions.&lt;/p&gt;

&lt;p&gt;First, let's look at structure. The structure in form has much overlap with structure in functionality but doesn't necessarily match. Here, structure defines what parts the component consists of and what layout each part has. For example, an icon doesn't exist functionally but exists formally. In Figma, it's good to structure component frames as close as possible to this.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxbu6x8ayexatgqkokr8b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxbu6x8ayexatgqkokr8b.png" alt="Image description" width="800" height="387"&gt;&lt;/a&gt;&lt;br&gt;
Second, visual options define how form changes according to set options. Size, Variant, etc., are representative examples.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8bmsfs6t8vq6v4gf4ti6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8bmsfs6t8vq6v4gf4ti6.png" alt="Image description" width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Third, state options define how form changes based on states and context derived from the functionality explained earlier. Hovered, Focused, Checked, etc., are representative examples.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fww8rrnx1c6itwpmtcxw1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fww8rrnx1c6itwpmtcxw1.png" alt="Image description" width="800" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, design decisions are a complete organization of what design values are assigned to which properties for each structure for all combinations of these state options and visual options. And design tokens can be used as a means to more effectively express these design decisions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fonr9f2rgc2lkovq4sutr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fonr9f2rgc2lkovq4sutr.png" alt="Image description" width="800" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've now explored what composition elements exist for both functionality and form. To summarize, while structure in functionality and structure in form have significant overlap, they can differ, and while states and context in functionality correspond almost exactly to state options in form, there's still a problem here.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F916u0mn3u43dn7xcu4kf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F916u0mn3u43dn7xcu4kf.png" alt="Image description" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problems with Functionality and Form Structure
&lt;/h3&gt;

&lt;p&gt;From a designer's perspective, state options have only 5 possible cases as shown on the left. However, from a developer's perspective, the combination of states and context can have 2^4, or 16 possible cases. I once argued that we should draw all these cases in Figma as well. Of course, this wasn't welcomed, and it was indeed unnecessary work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1zqxokmu8e4olpuzm9yd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1zqxokmu8e4olpuzm9yd.png" alt="Image description" width="800" height="322"&gt;&lt;/a&gt;&lt;br&gt;
When states increase more severely, combinatorial state explosion occurs as shown below, creating an unmanageable number of cases.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgpfo9ytdkxomw15lq08v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgpfo9ytdkxomw15lq08v.png" alt="Image description" width="800" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, we know from experience that there are priorities between states. For example, when Disabled, we don't need to consider hover. The state combinations that actually affect rendering are limited, and these limited states will align with the number of cases from the designer's visual perspective. Therefore, by organizing the combination of states and context into a one-dimensional enum based on specific conditions, we can compress these states and effectively grasp and communicate the relevant context and state options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fass84cobambhh51oe6e1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fass84cobambhh51oe6e1.png" alt="Image description" width="800" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Separation of Concerns
&lt;/h3&gt;

&lt;p&gt;After completing state compression, we can identify common and unique concerns between functionality and form. Structure, state, and context exist as common concerns on both sides, while interaction exists only in functionality, and visual options and design decisions exist only in form. What benefits can we gain from this separation of concerns?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F08dri380yrq8d3b194sr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F08dri380yrq8d3b194sr.png" alt="Image description" width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Communication Circular Reference
&lt;/h3&gt;

&lt;p&gt;Separation of concerns is the key to solving communication circular references. Let's consider a situation like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: I can't implement the component yet because there's no design.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Designer&lt;/strong&gt;: What variants are needed to design the component?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Designer&lt;/strong&gt;: I've designed it, but the developer says it can't be made this way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: But it's already published in Figma and designers are using it.&lt;/p&gt;

&lt;p&gt;This kind of situation is called a communication circular reference problem. These are problems that can actually occur in organizations developing at the component level or with design systems. I think these problems are issues with the "design first, then implement components" approach. When designing, it's difficult to know in advance what states will exist in development, creating a dependency on development. Meanwhile, when developing, there's a dependency on design because developers think they need the design to write code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solving Communication Circular References
&lt;/h3&gt;

&lt;p&gt;So how can we solve these mutually dependent communication circular references? In development, we often use interfaces to reverse the direction of dependencies. This can be applied to our way of working. Based on the separation of concerns mentioned earlier, we know what the common interface between design and development is. Based on this, I'll outline what I believe is an effective workflow for a design system team.&lt;/p&gt;

&lt;p&gt;First, designers and developers together sketch the component they want to create and define what structure and state options it will have. The image below shows the common interface that will be used in this component.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvkxyk8sns16j9u4hw4br.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvkxyk8sns16j9u4hw4br.png" alt="Image description" width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Developers consider user accessibility within the given structure, retain only the functionally meaningful aspects of that structure, and understand state transitions in response to user interaction. With this, they either implement headless components directly or bring in existing libraries.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu81mvzqes74fop006m5m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu81mvzqes74fop006m5m.png" alt="Image description" width="800" height="303"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Designers design components according to the structure based on state options.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyiavyquygqni14vuxikh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyiavyquygqni14vuxikh.png" alt="Image description" width="800" height="257"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, developers write styles based on the designed component and combine them with the previously written headless component to implement both functionality and form. And they complete the component, ensuring it's synchronized with the Figma designs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ft18fmmotaxmcpgtu0shd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ft18fmmotaxmcpgtu0shd.png" alt="Image description" width="800" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To summarize, rather than working in the way of "design first, then implement components" where design from Figma leads to development, we can solve communication circular references by first defining common interfaces like structure and state, and then having this content lead to both Figma and development.&lt;/p&gt;

&lt;p&gt;Through this process, we can learn something. Ultimately, it's more aligned with our actual workflow to view Figma as an environment akin to development, treating it as an implementation target, much like our development platforms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fao9w8jig2l4729rmwojo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fao9w8jig2l4729rmwojo.png" alt="Image description" width="800" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've now explored how to define components and how design and development can work together. In the next article, we'll explore how to apply this content to actual component implementation.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Dreaming of Easy and Convenient E2E Test Automation - pt 2.</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 08 Jun 2025 16:51:15 +0000</pubDate>
      <link>https://dev.to/feconf/a-guide-to-debugging-memory-in-ssr-environment-pt-1-18fg</link>
      <guid>https://dev.to/feconf/a-guide-to-debugging-memory-in-ssr-environment-pt-1-18fg</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a summary of the presentation  delivered at FEConf2024. The presentation is published in two parts. Part 1 explores E2E testing, the tools that assist with it, and methods for building efficient test code by reducing maintenance costs. Part 2 focuses on test code reuse, modularization, and improvements made to Playwright. All images included in this article are sourced from the presentation materials of the same title and are not cited individually.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Dreaming of Easy and Convenient E2E Test Automation
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;By Buseok Baek, CTO at Stibee&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dreaming of Easy and Convenient E2E Test Automation - pt 1.&lt;/li&gt;
&lt;li&gt;Dreaming of Easy and Convenient E2E Test Automation - pt 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the previous article, we explored E2E testing and the tools that facilitate it. We also examined how to write efficient test code while navigating authentication workflows. In this second part, we'll cover the final stages of E2E testing: modularization, reusability, and enhancements to Playwright.&lt;/p&gt;




&lt;h2&gt;
  
  
  Modularization and Reusability
&lt;/h2&gt;

&lt;p&gt;What does modularization mean in test automation?&lt;/p&gt;

&lt;p&gt;For instance, login sequences are frequently reused across test cases. Ideally, we could reuse them as-is, but in practice, developers must often refactor and abstract such code into reusable modules. While common, this approach falls short of complete automation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frwlxbrm8gtx0jtp6y6jj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frwlxbrm8gtx0jtp6y6jj.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A Custom Test Automation Tool
&lt;/h3&gt;

&lt;p&gt;To achieve complete automation, I evaluated various tools—but none satisfied my requirements. Therefore, I built a custom solution using Electron.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8xt8ljgfdwbmhh52esqs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8xt8ljgfdwbmhh52esqs.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the tool, test cases appear on the left and are linked to Playwright. By clicking a record button, user actions are recorded, and test code is automatically generated.&lt;/p&gt;

&lt;p&gt;This code is then processed to extract variables—user inputs such as email or password—which are displayed on the right panel.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F86qjhqxo971y7shgyi0f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F86qjhqxo971y7shgyi0f.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These inputs are parameterized, eliminating the need for additional developer work when reusing the module elsewhere.&lt;/p&gt;

&lt;p&gt;This process leverages AST (Abstract Syntax Tree), which represents code as a hierarchical tree structure. In this tool, AST manipulation is employed to identify and extract values for modularization.&lt;/p&gt;

&lt;p&gt;To use a modularized login flow, you can simply select it from a list and prepend it to any new test case. The entire login sequence becomes reusable with minimal effort.&lt;/p&gt;

&lt;p&gt;By visually chaining modules together and recording interactions, developers can generate comprehensive test scenarios without code duplication or rewriting.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F66oagsird3f1yxt9p599.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F66oagsird3f1yxt9p599.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Sustainable Test Code
&lt;/h2&gt;

&lt;p&gt;As mentioned in Part 1, creating a sustainable test environment is crucial. Static input values can cause failures in subsequent test executions. For example, if a test consistently creates an address book named "FEConf 2024 Address Book," it will pass initially but fail thereafter due to duplicate key constraints.&lt;/p&gt;

&lt;p&gt;To address this issue, I implemented Faker to randomize input values—ensuring each test execution uses unique titles or identifiers, thereby preventing conflicts and reducing maintenance overhead.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5afeqihxdkgwb88rft03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5afeqihxdkgwb88rft03.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, traditional test runners discourage using external variables for the sake of test independence. But I re-evaluated this constraint: as long as test outcomes remain stable, why not reuse result data?&lt;/p&gt;

&lt;p&gt;For example, after creating an address book, its unique ID can be stored locally and used in subsequent tests. This enables seamless test chaining—passing data from Test A to Test B without friction.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbqk1drnjg9x2s2ynk4l0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbqk1drnjg9x2s2ynk4l0.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Improving Playwright
&lt;/h2&gt;

&lt;p&gt;Even with this modular tool, about 10% of the test code still required manual edits. Having modified Selenium and Puppeteer in the past, I decided to enhance Playwright as well.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvexg2qr0q8wt72hmalv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvexg2qr0q8wt72hmalv1.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Improved Result Reports
&lt;/h3&gt;

&lt;p&gt;Conventional test reports are typically stored locally, making them difficult to share. I enhanced this by capturing comprehensive metadata about test failures—including which user action triggered the issue—and making reports accessible through shareable URLs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Custom Selector Strategy
&lt;/h3&gt;

&lt;p&gt;Playwright’s selectors are powerful, but I customized the logic that determines selector priority. For instance, if an input field includes a &lt;code&gt;name&lt;/code&gt; attribute, that should take precedence—making tests more stable against text or placeholder changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fce3fz65ev43la61o1x85.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fce3fz65ev43la61o1x85.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By analyzing Playwright's internal architecture, I modified the selector engine to prioritize the name attribute when available, thereby enhancing resilience against UI copy modifications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dictionary-Based UI Copy
&lt;/h3&gt;

&lt;p&gt;In our service, UI copy is stored in Google Sheets as key-value pairs. Developers bind keys to the frontend; planners and designers can update text values directly in the spreadsheet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnsnz09p52fzgv53ku2bs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnsnz09p52fzgv53ku2bs.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This eliminates the need for designers to revise Figma files or for developers to redeploy just to change a label. It also ensures test selectors based on keys remain consistent across UI changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checkpoints for Test Recovery
&lt;/h3&gt;

&lt;p&gt;If a test fails midway, restarting from the beginning is tedious. Using Playwright’s Session Storage API, I implemented checkpoints that allow the test to resume from the last stable state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9azzwgd46g3y7vu76q94.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9azzwgd46g3y7vu76q94.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When a failure occurs, the tool automatically loads the saved session and resumes execution—saving time and debugging effort.&lt;/p&gt;

&lt;h3&gt;
  
  
  Screenshot Comparison Enhancements
&lt;/h3&gt;

&lt;p&gt;Animations and dynamic content can interfere with screenshot-based visual testing. For example, background effects or dynamic ads cause screenshots to differ on every run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftb7d1n3hpp532dhyzhp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftb7d1n3hpp532dhyzhp7.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To address this, I added logic to pause CSS animations and hide unstable elements before capturing screenshots—reducing false positives in visual comparisons.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-Generated User Flows
&lt;/h3&gt;

&lt;p&gt;Since the test reports already include step-by-step screenshots and interaction logs, I added a feature to auto-generate user flow diagrams. These show how users navigate through the app—e.g., from login to dashboard—based on recorded test activity.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8razonvpspb3a14uav72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8razonvpspb3a14uav72.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Regrets and Improvements
&lt;/h2&gt;

&lt;p&gt;Looking back, I wish I had built the tool as a VS Code extension rather than a standalone app—it would have made integration much smoother.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbis4cbghdprbfvmh33h9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbis4cbghdprbfvmh33h9.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There’s also more potential in the test reports. For example, using Kubernetes tools like &lt;a href="https://aerokube.com/moon/" rel="noopener noreferrer"&gt;MOON&lt;/a&gt; could allow test cases to run remotely at scale.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsd768g4mzi2ep8wyaapc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsd768g4mzi2ep8wyaapc.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the future, I plan to enhance this further by linking frontend screens to backend APIs—understanding which APIs are called on each screen, and how changes affect the system.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flsdjiswchdj3q3unoes3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flsdjiswchdj3q3unoes3.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The tool I created is still far from perfect. I aimed for usability, but it remains difficult for others to adopt without guidance. That said, I’m continuing to refine it based on lessons learned.&lt;/p&gt;

&lt;p&gt;Even if you don't adopt this specific tool, I hope the concepts shared here help you enhance your own E2E testing workflows—making them more efficient, maintainable, and developer-centric.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>node</category>
    </item>
    <item>
      <title>Dreaming of Easy and Convenient E2E Test Automation - pt 1.</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 08 Jun 2025 16:48:13 +0000</pubDate>
      <link>https://dev.to/feconf/dreaming-of-easy-and-convenient-e2e-test-automation-pt-1-346g</link>
      <guid>https://dev.to/feconf/dreaming-of-easy-and-convenient-e2e-test-automation-pt-1-346g</guid>
      <description>&lt;p&gt;*This article summarizes the FEConf2024 presentation titled 'Dreaming of Easy and Convenient E2E Test Automation'. The presentation is divided into two parts. Part 1 explores E2E testing, tools that support it, and how to build efficient test code by reducing maintenance costs. Part 2 will cover test code reuse, modularization, and enhancements to Playwright. All images included in the article are taken from the presentation slides of the same title, and separate citations are not provided.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;'Dreaming of Easy and Convenient E2E Test Automation'&lt;br&gt;&lt;br&gt;
Buseok Baek, CTO at Stibee&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Dreaming of Easy and Convenient E2E Test Automation - pt 1&lt;/li&gt;
&lt;li&gt;Dreaming of Easy and Convenient E2E Test Automation - pt 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hello, I'm Buseok Baek, and today I'll be discussing "Dreaming of Easy and Convenient E2E Test Automation." I consider myself a pragmatic developer. With experience in large-scale SI projects and e-commerce platforms like home shopping, I’ve focused on automating repetitive tasks and minimizing manual labor. Currently, I serve as the CTO of Stibee, a service that helps users create and send marketing emails. Leveraging our experience of sending over 2.8 billion emails, I'll share insights on email compatibility optimization, which will serve as the foundation for our deep dive into E2E testing.&lt;/p&gt;

&lt;p&gt;The topics covered in this article include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The evolution of E2E test automation tools&lt;/li&gt;
&lt;li&gt;Improving efficiency through E2E test automation&lt;/li&gt;
&lt;li&gt;Frontend project setup strategies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, the following topics will not be covered in depth:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TDD (Test-Driven Development)&lt;/li&gt;
&lt;li&gt;Mobile-specific E2E testing&lt;/li&gt;
&lt;li&gt;Test CI/CD pipeline automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  E2E
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is E2E?
&lt;/h3&gt;

&lt;p&gt;When operating a service, it’s best practice to write unit tests for both the backend and frontend. In reality, though, unit testing is often skipped or only partially implemented. That’s where E2E testing becomes essential—validating a complete user journey to ensure the system works as expected from the user’s perspective.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8rjxvvljz23gk93em7fx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8rjxvvljz23gk93em7fx.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That said, E2E testing is notoriously challenging. As shown in the diagram below, unit tests are relatively fast and inexpensive to run, and developers can typically manage them independently. In contrast, E2E tests combine integration and UI testing, making them slower to execute and more time-consuming to write.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0mvawusaopmtqo9s15hr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0mvawusaopmtqo9s15hr.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Despite these drawbacks, their reliability makes E2E tests indispensable, so we decided to maintain a baseline level of test coverage.&lt;/p&gt;

&lt;h3&gt;
  
  
  E2E Test Process
&lt;/h3&gt;

&lt;p&gt;The E2E testing process involves test design, implementation, execution, and result validation. Since test design typically falls outside a developer’s responsibilities, we’ll skip that part and instead focus on how to write better tests and streamline the process.&lt;/p&gt;

&lt;p&gt;Similarly, while test execution optimizations are important, they won’t be the main focus here either. When it comes to reviewing test results, some teams rely on video recordings, but I’ve found that comparing screenshots is often more effective—and that's the approach I use.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpia1tlx3v6j2rvgxtxfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpia1tlx3v6j2rvgxtxfv.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Even though E2E test scripts cover the full user journey, they almost always begin on the frontend. So I started thinking about ways to automate that process and make debugging, updates, and reuse through modularization easier.&lt;/p&gt;

&lt;h2&gt;
  
  
  My E2E Journey: The Beginning
&lt;/h2&gt;

&lt;p&gt;My interest in improving E2E testing began during an SI project. At the time, different browsers rendered HTML, CSS, and JavaScript very differently. This was before WebKit became widespread, so even minor code changes would cause issues across multiple browsers. Debugging these problems was time-consuming and frustrating, so I decided to find a better way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fppaour4vvsrpn16ea4q0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fppaour4vvsrpn16ea4q0.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Selenium
&lt;/h3&gt;

&lt;p&gt;My first approach involved using Selenium, a popular tool for web application testing and automation. Selenium can automatically generate test scripts by recording user interactions. These scripts are stored in a database and replayed to verify behavior.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ftpmk0lj3p6onltt3oyig.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftpmk0lj3p6onltt3oyig.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To make Selenium testing more efficient, I explored screenshot-based visual comparison. During a hackathon, I implemented a feature that captured screenshots at various points during test execution. Comparing screenshots from different browsers helped pinpoint exactly where layout or rendering issues occurred.&lt;/p&gt;

&lt;p&gt;However, this method also had limitations, as shown in the presentation slide.&lt;/p&gt;

&lt;h3&gt;
  
  
  Puppeteer
&lt;/h3&gt;

&lt;p&gt;While working with Selenium, I discovered Puppeteer—a Chrome-based headless testing tool. Right away, I found it much more powerful than Selenium. Puppeteer’s superior network control and speed made it easier to write efficient tests.&lt;/p&gt;

&lt;p&gt;But Puppeteer had a major downside: it didn’t support native code generation. I even tried building a browser extension to automate test creation, but it wasn’t stable. Eventually, I switched to using a third-party code generation library.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fixcvj198rosg2oadl54a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fixcvj198rosg2oadl54a.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another challenge was test replay. If one step failed—say, during checkout after login and cart actions—you had to restart from the login step and repeat every subsequent action.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fk6ozar1t46bef0t323wz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fk6ozar1t46bef0t323wz.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To fix this, I implemented a way to store test progress in sessionStorage or localStorage and resume from specific checkpoints. Even so, Puppeteer had enough pain points that I wouldn't recommend it today.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations of Selenium and Puppeteer
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3y1ku78t3byh078eo29y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3y1ku78t3byh078eo29y.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Both tools had serious drawbacks for scalable test automation. One major issue was selector reliability. Frameworks like React and Vue dynamically generate class names, making traditional CSS or XPath selectors unreliable in many cases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright
&lt;/h2&gt;

&lt;p&gt;Then came Playwright—the tool that solved many of these problems. Developed by the original Puppeteer team at Microsoft, Playwright feels like Puppeteer 2.0, but better in almost every way.&lt;/p&gt;

&lt;p&gt;Playwright supports multiple browsers and executes tests in parallel, which dramatically reduces test execution time. It also supports multiple programming languages and provides robust network control for intercepting or modifying requests.ple programming languages and offers powerful network control for intercepting or modifying requests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fdr1x1arshx2l3smfu5r7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdr1x1arshx2l3smfu5r7.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For me, the most important feature is built-in code generation.&lt;/p&gt;

&lt;p&gt;Even better, Playwright supports advanced selectors that go beyond CSS or XPath, making it easier to work with modern frontend frameworks. It also includes native support for features like automatic screenshots and persistent browser storage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0znovo277avck5cp9iht.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0znovo277avck5cp9iht.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s take a closer look at how this works.&lt;/p&gt;

&lt;p&gt;Once you install Playwright in VS Code and click the record button, it begins generating test scripts automatically within the editor. While Puppeteer offers a similar feature, Playwright makes it far easier to get a working script on the first try.&lt;/p&gt;

&lt;p&gt;Playwright’s reporting tools are also a standout feature. It saves screenshots at every test step, lets you inspect the DOM, and provides console and network logs—making debugging faster and more effective.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F2zsiis7o1ddgpxcqwulu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2zsiis7o1ddgpxcqwulu.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  E2E Example
&lt;/h2&gt;

&lt;h3&gt;
  
  
  E2E Test Environment
&lt;/h3&gt;

&lt;p&gt;Now, let’s look at a real-world example. We’ll explore how E2E testing works using common flows like user registration and login, as well as workflows specific to Stibee, like creating an address book, sending emails, and confirming receipt.&lt;/p&gt;

&lt;p&gt;There are generally two types of test environments: temporary and persistent.&lt;/p&gt;

&lt;p&gt;A temporary environment spins up Docker containers for databases and servers just to run tests. This setup minimizes test flakiness but is hard to integrate into most real-world scenarios.&lt;/p&gt;

&lt;p&gt;Instead, I opted for a persistent testing environment—something that runs against an already active staging or test server. This allowed me to run tests even on accounts created a month or a year ago. It also made it possible to run automated tests on live data periodically, ensuring continuous quality assurance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Test Code
&lt;/h3&gt;

&lt;p&gt;As you know, maintaining test code can be costly. When writing E2E tests, I focused on minimizing areas with high maintenance overhead. That meant avoiding extra API endpoints or manipulating data just for test purposes.&lt;/p&gt;

&lt;p&gt;If test code isn’t written with maintainability in mind, it can quickly become unmanageable—and teams may abandon testing altogether.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fr9zxa0leox02duhkp1yn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fr9zxa0leox02duhkp1yn.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Authentication Test Examples
&lt;/h3&gt;

&lt;p&gt;Let’s look at some authentication examples. In E2E tests, you’ll often encounter login flows like Gmail, Daum, or Naver. These platforms tend to block automated bots, so I created an email service that verifies email links in real-time using WebSockets—without adding new APIs.&lt;/p&gt;

&lt;p&gt;Next comes reCAPTCHA. While there’s a Puppeteer plugin for solving reCAPTCHA, it’s unreliable. Instead, I used an external service that runs in-browser for about a minute and successfully solves reCAPTCHA, though it requires proper timeout handling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fp3ld9x90dkr27gur3onl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fp3ld9x90dkr27gur3onl.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then there’s SMS verification. Although I generally avoid building custom APIs, I couldn’t find an off-the-shelf SMS solution. So I built a lightweight API that checks if a 6-digit code exists in the DB.&lt;/p&gt;

&lt;p&gt;To make this easier to manage, I used a no-code tool called &lt;a href="https://n8n.io" rel="noopener noreferrer"&gt;n8n&lt;/a&gt;, similar to Zapier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F3c6dxqt62imaa4qx8sqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F3c6dxqt62imaa4qx8sqf.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With n8n, you can automate workflows like converting a Slack emoji into a Notion task—without writing a line of code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fukc5nc8qux518mcjmsk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fukc5nc8qux518mcjmsk3.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For SMS verification, I configured n8n to detect a phone number in the DB, retrieve the matching verification code, and return it for validation. Maintenance is simple, thanks to the visual drag-and-drop editor.&lt;/p&gt;

&lt;p&gt;By integrating tools like n8n with Playwright's code generation capabilities, you can build highly maintainable and efficient E2E test suites.&lt;/p&gt;




&lt;p&gt;So far, we've explored the evolution of E2E testing—from Selenium to Puppeteer to Playwright—and discussed how to handle complex authentication scenarios. We've also seen how to write efficient test code that reduces maintenance costs.&lt;/p&gt;

&lt;p&gt;In Part 2, we'll dive into modularizing test code and improving maintainability even further with Playwright’s advanced features.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dreaming of Easy and Convenient E2E Test Automation (Part 1)&lt;/li&gt;
&lt;li&gt;Dreaming of Easy and Convenient E2E Test Automation (Part 2)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>node</category>
    </item>
    <item>
      <title>React Native and Web Coexistence - Another Approach (Part 2)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 01 Jun 2025 12:53:14 +0000</pubDate>
      <link>https://dev.to/feconf/react-native-and-web-coexistence-another-approach-part-2-ao9</link>
      <guid>https://dev.to/feconf/react-native-and-web-coexistence-another-approach-part-2-ao9</guid>
      <description>&lt;h1&gt;
  
  
  React Native and Web Coexistence - Another Approach (Part 2)
&lt;/h1&gt;

&lt;p&gt;This article summarizes the presentation &amp;lt;&lt;a href="https://www.youtube.com/watch?v=GyU9-pE0dAg&amp;amp;t=667s&amp;amp;ab_channel=FEConfKorea" rel="noopener noreferrer"&gt;React Native and Web Coexistence - Another Approach&lt;/a&gt;&amp;gt; delivered at FEConf2024. The presentation content will be published in two parts. Part 1 explores the problems encountered when web and React Native communicate and the methods to solve them. Part 2 will cover the actual case studies and the process of achieving Type-Safety and synchronization between web and app. All images inserted in this article are from the presentation materials with the same title, so individual sources are not indicated separately.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;'React Native and Web Coexistence - Another Approach'&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Seonkyu Kang, In-Edit Developer&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the previous article, we explored the problems encountered when web and React Native communicate and the methods to solve them. In this article, I'll introduce the process of solving actual cases using the content from the previous article, and the Type-Safety and web-app synchronization encountered during this process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solving Real Cases
&lt;/h2&gt;

&lt;p&gt;we are now able to more effectively address the problems encountered in real-world cases. When we received the requirement that "clicking a product in WebView should show either a native screen or an existing web screen," we previously had to write code assuming successful execution and manually handle all edge cases, but now we have various tools prepared. Therefore, I thought we could deliberately cause failures and perform failover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9zoqy87t0adob3i8eia3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9zoqy87t0adob3i8eia3.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image below shows the code for the previous real case. In this bridge, navigate that calls React Native screens from the web is placed in throwOnError. When navigate fails, the web will also throw an error, enabling unified error handling via catch blocks. So when moving to the ProductDetail screen through this bridge navigate, if it's an existing screen, it will navigate well, and if not, it can navigate to the legacy page of the old version through catch.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F35yu4eqn108cwr7ei495.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F35yu4eqn108cwr7ei495.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let me summarize how we solved the problems with the existing approach.&lt;/p&gt;

&lt;p&gt;The phenomenon where complex logic is concentrated in onMessage can be solved through method-by-method management. Previously, when adding features, we had to write repetitive communication functions on both sides, but by automatically generating communication functions, we only need to maintain them in React Native. Also, the important problem of unidirectional communication was solved by changing to a Promise structure, and finally, regarding backward compatibility issues, while we couldn't solve everything, we were able to mitigate the issue by leveraging utilities that support graceful failover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0wwaaxv5cuvl25jh921m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0wwaaxv5cuvl25jh921m.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Safety
&lt;/h2&gt;

&lt;p&gt;What I'll introduce now is about 'Type Safety', which I considered important while creating a library through this process. Let's briefly look at why Type Safety, often mentioned in the TypeScript ecosystem, is necessary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Type Safety is Needed: Type Mismatch
&lt;/h3&gt;

&lt;p&gt;One reason Type Safety is needed is type mismatch. When frontend and backend projects exist independently, they generally cannot know about each other. When the frontend calls APIs to the backend, it cannot know the types for the API responses, so types must be defined separately.&lt;/p&gt;

&lt;p&gt;However, when defining types separately, mistakes occur and naturally type mismatches happen. Likewise, if we consider React Native as the backend and WebView as the frontend, type mismatches are bound to happen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbeipqo7yegb5phqns0si.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbeipqo7yegb5phqns0si.png" alt="Image description" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's look at this type mismatch in code. As shown below, edges has an array of objects composed of node and the corresponding node's id. Through this response, types for responses are defined as shown in the sample code on the right. Since types exist normally, we expect rendering to work without problems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyj2u4cx5c0p7a7x5o024.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyj2u4cx5c0p7a7x5o024.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what happens if null comes in the id value? Probably rendering won't work properly and runtime errors will occur as shown in the left code below. When such type errors occur, we first need to handle null exceptions through optional chaining and solve the problem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fod5tjehjjc283006bxuu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fod5tjehjjc283006bxuu.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Efforts to Solve Type Mismatch
&lt;/h3&gt;

&lt;p&gt;Going through this process, I thought that types written by humans cannot be 100% trusted. If such type mismatch situations occur repeatedly, TypeScript can lose its purpose. I think the biggest purpose of TypeScript is to discover bugs in advance at the compile stage. When we frequently encounter type mismatches, situations where we ignore types and develop often occur, which makes TypeScript lose its purpose.&lt;/p&gt;

&lt;p&gt;However, the TypeScript ecosystem is very large, so there are many efforts to solve these type mismatches. In REST APIs, we can convert Swagger to TypeScript through openapi-generator. In GraphQL, we can convert schemas to TypeScript through graphql-codegen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fls3v0ns7l95iqy39j0gj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fls3v0ns7l95iqy39j0gj.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These tools are also sufficiently excellent, but Swagger also involves human touch, which can eventually lead to mistakes like type mismatches. If incorrectly written types in Swagger are converted to TypeScript, this can also lead to type mismatches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not Defining Types Directly: Type Inference
&lt;/h3&gt;

&lt;p&gt;To solve this problem, I decided not to define types directly. More precisely, I decided to actively utilize type inference. Let me look at the type inference concept I applied for Type Safety while creating the library through simple examples.&lt;/p&gt;

&lt;h4&gt;
  
  
  typeof
&lt;/h4&gt;

&lt;p&gt;When declaring native methods in the bridge, Instead of manually defining interfaces, I allowed the bridge to infer types using typeof. Like this, with the compiler's help, we can infer all types, and if we send this well to the web, the types received on the web can be reflected as normal code intended by the native side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fg7rtha0hy4t2x95p3woa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fg7rtha0hy4t2x95p3woa.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  keyof
&lt;/h4&gt;

&lt;p&gt;Next, we can improve developer experience by utilizing simple keywords like keyof. As shown in hasMethod in the image below, types can be inferred through keyof. With keyof's help, we can determine and use whether it's an available type.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjgp3m7racdg298l878uh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjgp3m7racdg298l878uh.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  generic
&lt;/h4&gt;

&lt;p&gt;What I think is most important in type inference is generic. The bridge function below returns functions called subscribe and getState. And I declared a generic object as the bridge type. Therefore, if a value like 1234 is passed to this bridge, it will cause a type error because it's not an object. On the other hand, if an object is entered here, all types can be completed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsgdixud01kfts3psnbua.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsgdixud01kfts3psnbua.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most important part of the previous content is completing types through input. As shown below, when using Tanstack Query's useQuery, if you receive a return from the query function, you can complete data based on the return value. It looks like simple code, but internally it's a situation where all types are completed through input via type inference processes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fb08j7eeee4l65ouemfn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fb08j7eeee4l65ouemfn6.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The sentence in the image below is what Tanstack Query's maintainer said. Looking at this, if you utilize type inference well, it looks like you're using JavaScript when just looking at the code, but actually you can use it with all types being safe. This is because although useQuery doesn't have a separate interface, all types are completed based on input.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmqaqnlublqonihrwbdam.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmqaqnlublqonihrwbdam.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Diving deep into type inference, type definitions also look very complex and difficult to maintain. However, I greatly empathized with his words that these things are the library's responsibility, not the user's responsibility.&lt;/p&gt;

&lt;p&gt;The final result of type inference is as follows. Native methods are declared in the bridge, and corresponding types are exported through typeof. Then, when this type is put as a generic in linkBridge, all types are completed based on this in the bridge.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgib3045570lvip1o8fnz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgib3045570lvip1o8fnz.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Therefore, this bridge can infer available methods like openInAppBrowser, and you can see that available methods are recommended.&lt;/p&gt;

&lt;p&gt;If we apply this a bit more, integration with React Navigation is also possible. As shown in the image below, all navigable screens are defined in React Native's StackRootParams. When using the bridge's navigate on the web, all previously defined names and parameters can be inferred.&lt;/p&gt;

&lt;p&gt;In other words, the screen list defined in React Native can be used on the web without additional type definitions. This experience greatly improves developer experience and can eliminate situations where developers make typos. This leads to the effect of reducing mistakes about screens to navigate to on the web.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fgh31oz1k3she01ghge2o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fgh31oz1k3she01ghge2o.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Synchronization Between React Native and Web
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting Authentication Information from Native App in WebView
&lt;/h3&gt;

&lt;p&gt;Having implemented type inference well in the library, I deployed the first version. I thought everything would be perfect, but I encountered another problem. it turned out to be a state synchronization issue related to authentication tokens.&lt;/p&gt;

&lt;p&gt;One of the reasons why communication between web and app is necessary is to transmit and use authentication information. In the current structure, we first declare getToken in the bridge to return tokens, and on the web, we can extract and use values with this getToken.&lt;/p&gt;

&lt;p&gt;However, what happens if this token expires in the native app and the token changes? React Native would have the latest token, but the web would have an expired token, creating a bad situation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwdxj4jjg7jsc530lfxx8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwdxj4jjg7jsc530lfxx8.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Separating Web Core Logic for Integration: Shared State
&lt;/h3&gt;

&lt;p&gt;To solve this situation, I thought state synchronization between React Native and web was necessary. The concept I created based on this thinking is Shared State.&lt;/p&gt;

&lt;p&gt;Since this concept revolves around state management, it needed to be easily integrable with modern frameworks. Therefore, I started by separating from the web core and began making state-related libraries starting from web core logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5yexcxeyc08k7slzms1f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5yexcxeyc08k7slzms1f.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Shared State was also designed with a usage-centered approach. This bridge was originally in a state where only native methods could be declared. Therefore, it could only receive Promise functions, but to store values like tokens, I made it possible to input Primitive types like null or strings.&lt;/p&gt;

&lt;p&gt;Looking at the React Native declaration part in the image above, get and set are exposed so current state and values can also be set, which resembles Zustand a lot. Since it's a library that manages state, I developed it with much inspiration from Zustand.&lt;/p&gt;

&lt;p&gt;On the web, in addition to exposing existing React Native methods, the store is also exposed from the bridge. This store supports subscriptions, allowing the web to react to state changes from React Native. I implemented it so synchronization is possible on the web this way.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integration with React
&lt;/h3&gt;

&lt;p&gt;The previous example is in vanilla JavaScript. Thanks to this, integration with React is easily possible. React 18 introduced the useSyncExternalStore hook, which enables seamless integration of external stores into React's rendering flow. I was able to extend this into a React state library called webview-bridge/react by wrapping this hook.&lt;/p&gt;

&lt;p&gt;If you put the store in useBridge, you can get and use tokens from state. This token exists on the web but stays synchronized with React Native’s state in real time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1cr5ynbajfx4ml2ajptn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1cr5ynbajfx4ml2ajptn.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Usage: Shared State
&lt;/h3&gt;

&lt;p&gt;Now let's look at the final usage. In React Native below, count is declared as 0 and increased by 1 through increase. Since React Native is also React, if you put appBridge in useBridge, you can use the state called count and the method called increase.&lt;/p&gt;

&lt;p&gt;On the web, the bridge is declared through linkBridge and AppBridge, and through this bridge's store and useBridge, you can extract and use count and increase from native core logic. This allows the web to consume and update shared state in real-time, fully synchronized with React Native.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fc2hnhwis1zd2eaqs41je.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fc2hnhwis1zd2eaqs41je.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Final Usage: Native Method
&lt;/h3&gt;

&lt;p&gt;Also, as shown in the image below, the bridge puts input into greeting and returns a return value called msg. Since the bridge uses generics to enforce type safety, calling undefined methods will immediately raise type errors, even when invoked externally. Also, when input is wrong, type errors occur, and when input is received normally, you can see that types for response values are displayed correctly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fpb3qlzg4h5wxwjthinxk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpb3qlzg4h5wxwjthinxk.png" alt="Image description" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: Lessons Learned from Creating the Library
&lt;/h2&gt;

&lt;p&gt;Eventually, I successfully completed developing a type-safe WebView communication library, reducing manual type definitions and utilizing inference as much as possible so all types are completed based on input. Building this library offered valuable insights and lessons across both design and implementation.&lt;/p&gt;

&lt;p&gt;Believing that great developer experience stems from usage patterns, I prioritized designing intuitive usage scenarios before implementing features. As a result, This approach led to a clean abstraction and outcomes that aligned well with practical development needs. Also, my library resembles tRPC and Zustand a lot. Through development, I could use various libraries, which itself provided much learning. Furthermore, I could have the valuable experience of directly applying learned concepts to development.&lt;/p&gt;

&lt;p&gt;Finally, since I developed starting from web core logic from the beginning, integration with other modern React frameworks was easily possible. Had I built the state library using Vue.js, cross-framework integration would have been significantly more difficult.&lt;br&gt;
Through this, I could also learn what scalable architecture is.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ff5qq39omnqjg1hls6x1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ff5qq39omnqjg1hls6x1n.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The library introduced today is open-source and published under the name webview-bridge.&lt;br&gt;
It includes many additional features not covered in this article. so if you're interested, I recommend visiting the address below to learn more details and check related documentation. If you like it, please consider giving it a star on GitHub. Thank you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Famkycynkvbjv7thvyyuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Famkycynkvbjv7thvyyuw.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>React Native and Web Coexistence - Another Approach (Part 1)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Sun, 01 Jun 2025 12:47:39 +0000</pubDate>
      <link>https://dev.to/feconf/react-native-and-web-coexistence-another-approach-part-1-533e</link>
      <guid>https://dev.to/feconf/react-native-and-web-coexistence-another-approach-part-1-533e</guid>
      <description>&lt;h1&gt;
  
  
  React Native and Web Coexistence - Another Approach (Part 1)
&lt;/h1&gt;

&lt;p&gt;This article summarizes the presentation &amp;lt;&lt;a href="https://www.youtube.com/watch?v=GyU9-pE0dAg&amp;amp;t=667s&amp;amp;ab_channel=FEConfKorea" rel="noopener noreferrer"&gt;React Native and Web Coexistence - Another Approach&lt;/a&gt;&amp;gt; delivered at FEConf2024. The presentation content will be published in two parts. Part 1 explores the problems encountered when web and React Native communicate and the methods to solve them. Part 2 will cover the actual case studies and the process of achieving Type-Safety and synchronization between web and app. All images inserted in this article are from the presentation materials with the same title, so individual sources are not indicated separately.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;'React Native and Web Coexistence - Another Approach'&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;em&gt;Seonkyu Kang, In-Edit Developer&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello. I'm Seonkyu Kang, and I'll be presenting "React Native and Web Coexistence - Another Approach." I'm developing a platform called 'Brandergen' at In-Edit, which connects brands and creators. I'm passionate about open source and constantly think about ways to improve developer experience.&lt;/p&gt;

&lt;p&gt;I've developed a library that creates communication interfaces between React Native WebView and web. In this presentation, I'll introduce communication concepts necessary for WebView development based on this library.&lt;/p&gt;

&lt;p&gt;You can find documentation for this library at the address below, and I think it will be easier to understand if you follow along while reading it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fqe4db1ghpydzkx59jj33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fqe4db1ghpydzkx59jj33.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Transition from Cordova to React Native
&lt;/h2&gt;

&lt;p&gt;Before diving into the main content, let me first introduce what happened while building our service. The existing Brandergen service was developed with Cordova, and we decided to transition it to React Native. The Cordova-written code contained a lot of legacy code, and we wanted to gradually transition to React Native while solving various problems arising from the legacy code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cordova
&lt;/h3&gt;

&lt;p&gt;First, let me briefly explain Cordova. Cordova is a tool that packages web app-developed services into apps for distribution. Since our company's existing service was built as a 100% web app based on Vue2, we needed a tool to wrap the web app as an app to release it on app stores. We developed this wrapping part using Cordova and released it on app stores.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fiirrj9y7b4rbpgkum6vy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fiirrj9y7b4rbpgkum6vy.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Legacy Code
&lt;/h3&gt;

&lt;p&gt;The image below shows the Lighthouse score of our existing service. It shows quite poor scores, and the reason can be found in the existing legacy code. As mentioned earlier, managing legacy code based on Vue2 led to a situation where we could only write more legacy code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F7mkedtznfzmkslig51m7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F7mkedtznfzmkslig51m7.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This led us to decide to eliminate legacy code and gradually transition to React Native. Eventually, newly created screens would be developed with React Native, while existing Vue.js parts would be wrapped in WebView, resulting in an app where React Native and WebView coexist.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebView Communication
&lt;/h2&gt;

&lt;p&gt;Through the transition to React Native, we began developing WebView in earnest. When developing with WebView, you encounter communication problems.&lt;/p&gt;

&lt;p&gt;Why is WebView communication necessary, and how did I solve the communication problems I experienced? In this article, we'll explore the following three situations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In-app browser&lt;/li&gt;
&lt;li&gt;Native navigation&lt;/li&gt;
&lt;li&gt;Shared data&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnwp0zkonu17wzm4bhm0h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnwp0zkonu17wzm4bhm0h.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An in-app browser is a feature that launches a browser in-app from a native app. This feature cannot be used on the web, so we need to borrow the power of the native app. Also, since we were making a gradual transition to React Native, the web needed to be able to navigate to React Native screens. Finally, authentication information is mostly stored in native apps, so the web needs to be able to fetch and use shared data from the native app.&lt;/p&gt;

&lt;p&gt;It's also necessary to understand WebView communication. The code below shows the basic communication method provided by React Native. When communicating from web to React Native, you can send strings through the web's ReactNativeWebView's postMessage. The WebView processes the received strings through a prop called onMessage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbwd6vtp7ukktic4i6zu3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbwd6vtp7ukktic4i6zu3.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When communicating from React Native to web, you can inject JavaScript through a prop called injectJavascript. There's also a method to extract a reference (ref) and inject JavaScript in the same way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1ujlcg8tb0ryj56tifes.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1ujlcg8tb0ryj56tifes.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8i0e1zvik0xfsj1l4lye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8i0e1zvik0xfsj1l4lye.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Initially, I proceeded with development using these methods and created communication interfaces.&lt;/p&gt;

&lt;p&gt;If I had continued this way, could I have successfully completed the development? This approach brought several problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problems Encountered with WebView Communication
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. onMessage Branching
&lt;/h3&gt;

&lt;p&gt;The first problem is the countless branches in onMessage. As shown in the code below, all event-based logic needs to be handled with branching in onMessage. The code below only handles 3 cases, but in real situations, countless communication scenarios are required, making the code increasingly complex and heavy. This creates maintenance difficulties.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fa3ya8psjrajntjhaxvd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fa3ya8psjrajntjhaxvd9.png" alt="Image description" width="800" height="442"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Inefficient Function Redefinition
&lt;/h3&gt;

&lt;p&gt;The second problem is having to declare communication functions on both web and React Native sides as shown below. For example, when an in-app browser feature is added, the web needs to send a message saying "please open in-app browser," and React Native needs to write code to handle this. Therefore, every time a feature is added, leads to development inefficiencies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1mllmwyy5fj6o8kczrpj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1mllmwyy5fj6o8kczrpj.png" alt="Image description" width="800" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Unidirectional Problem
&lt;/h3&gt;

&lt;p&gt;The third problem is the unidirectional issue. When the web sends PostMessage to React Native, React Native cannot return a result value. The biggest problem with this structure is not knowing success or failure. Therefore, the web faces the challenge of writing foolproof code. I considered this unidirectional problem the biggest issue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fyobooo33cchjdglzcdbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fyobooo33cchjdglzcdbq.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Backward Compatibility
&lt;/h3&gt;

&lt;p&gt;The last problem is backward compatibility. Unlike the web, applications don't immediately maintain the latest version even after deployment. The app store needs to review, approve the review, and then actually update to the app store. On the other hand, the web maintains the latest version immediately upon deployment.&lt;/p&gt;

&lt;p&gt;What happens when users have past versions of the application and the web calls new native features? Past versions of the application cannot handle code with the latest features, so there will be no response or errors will occur.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fszps6lvhao60g35myqet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fszps6lvhao60g35myqet.png" alt="Image description" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Case Study
&lt;/h2&gt;

&lt;p&gt;Let me look at real cases of the problems introduced earlier. The service I was building was making a gradual transition to React Native, and I started improving the product detail screen. However, this created situations where clicking a product on a WebView screen needed to either navigate to a new native screen or to an existing legacy web screen.&lt;/p&gt;

&lt;p&gt;In other words, I needed to check the user's app version and implement different page navigations according to the situation. Therefore, I had to write code that guaranteed success, checking the user's app version through user agent and calling events that could definitely succeed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fse5117xq62n1spxi307o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fse5117xq62n1spxi307o.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To briefly summarize the problems with the existing approach described so far: First, there's a maintenance problem because all events are handled through onMessage. Also, it's somewhat inefficient to write communication functions on both web and app sides whenever features are added. Next, due to the limitations of unidirectional communication, you can't know the success or failure of features, so you must write code that guarantees success. Finally, there are difficulties in determining backward compatibility based on app versions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F07j92zw9txy31si1zfjl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F07j92zw9txy31si1zfjl.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing Perspective to Solve Problems
&lt;/h2&gt;

&lt;p&gt;I decided to rethink these problems from the beginning. Looking at the client-server structure that's very familiar to web developers, the client sends requests to the server, and the server receives these requests, processes them, and sends responses back to the client.&lt;/p&gt;

&lt;p&gt;In this structure, the client can know appropriate success and failure. I want to introduce a simple interface that borrows this structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4kg3v79506nfz8jsg4lv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4kg3v79506nfz8jsg4lv.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is tRPC code. First, tRPC is a framework that ensures type safety between server and client and allows building APIs without separate schema definitions.&lt;/p&gt;

&lt;p&gt;The left code in the image below is the server, and the right is the client code. The server declares procedures and passes input and result values. The client can write code in a form that can directly use those procedures. You can see that tRPC doesn't have separate communication code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvtoe5cjkdnrtoota2qzy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvtoe5cjkdnrtoota2qzy.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inspired by tRPC, I thought we could create a similar structure. By slightly changing the perspective from the server-client structure, you can see it's not much different. For example, what if we think of React Native as the server and WebView as the client?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fhd1eddbeesr48nxew7c4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fhd1eddbeesr48nxew7c4.png" alt="Image description" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If WebView sends requests to React Native and React Native delivers appropriate responses, we could use tRPC's structure without much difference from the frontend-backend structure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage-Centered Design: Usage
&lt;/h2&gt;

&lt;p&gt;Let me introduce the usage patterns decided after various considerations. In the React Native shown in the image below, native methods are declared in the bridge and return values are sent in getMessage. This structure is called a bridge, and this bridge is injected into createWebView.&lt;/p&gt;

&lt;p&gt;On the web, executing the linkBridge function should make the openInAppBrowser contained in this bridge directly usable, and to solve the unidirectional problem, it should return as a Promise structure so you can infer success and failure through then and catch. Also, it should be able to receive and output return values sent from React Native, and executing strange functions like asd that aren't declared in the bridge should generate errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5r6jzrwc846vqt7ffa5t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5r6jzrwc846vqt7ffa5t.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage-Centered Design: Initialization
&lt;/h2&gt;

&lt;p&gt;Now that we've designed the usage, it's time to develop the actual functionality. Several concepts were established while developing the functionality.&lt;/p&gt;

&lt;p&gt;The first concept is Initialization. When native methods are initially declared, the names of these native methods are injected into the web. Therefore, the web receives the list of native method names.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd17r8zbizcc0my960hkr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd17r8zbizcc0my960hkr.png" alt="Image description" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage-Centered Design: Hydration
&lt;/h2&gt;

&lt;p&gt;The second concept is Hydration. If you've used Next.js or Remix, you'll know the concept of Hydration, and I established this concept inspired by that. Since native method names are exposed to the web during initialization, I enabled automatic generation of communication code based on those names.&lt;/p&gt;

&lt;p&gt;In other words, when something like openInAppBrowser is injected, I implemented functionality that automatically creates communication code at runtime like the postMessage below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fowh9c6d1wqzjly9tl23r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fowh9c6d1wqzjly9tl23r.png" alt="Image description" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage-Centered Design: Event to Promise
&lt;/h2&gt;

&lt;p&gt;The third concept is converting event structure to Promise structure. When the automatically created openInAppBrowser is executed on the web, it sends an event to React Native. openInAppBrowser simultaneously installs an EventEmitter while React Native sends a response event. When openInAppBrowser receives that response event, it resolves the Promise and sends the result value to the web. This structure helps mitigate the limitations of unidirectional communication between the web and React Native.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Ffnco0qw3lh72vk2chwzo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffnco0qw3lh72vk2chwzo.png" alt="Image description" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage-Centered Design: Exception Handling for Non-existent Methods
&lt;/h2&gt;

&lt;p&gt;As mentioned earlier, since communication code is automatically generated, mistakes of using non-existent methods can occur. For example, if a bridge is declared as shown below and ready to use, but you execute a method that doesn't exist in this bridge, runtime errors can occur as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fxycxfgn0g38rr81oz6g4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxycxfgn0g38rr81oz6g4.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Such runtime errors can be gracefully handled using a Proxy. As shown below, I designed it to wrap the original object: returning the actual method if it exists, and returning a no-op function for any non-existent method. Therefore, executing methods that don't exist in the bridge will execute but allow error handling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F0mtv0g1ndhlzpurwuzm6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0mtv0g1ndhlzpurwuzm6.png" alt="Image description" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Based on the previous design and feature development, you can see that implementation is possible as shown below.&lt;/p&gt;

&lt;p&gt;Native methods are declared in the bridge, and native methods are injected through createWebView. During this process, method names are injected into the web through the initialization process.&lt;/p&gt;

&lt;p&gt;When linkBridge is executed on the web, communication code is automatically generated through the Hydration process, and functions that can be used directly like bridge's openInAppBrowser are created. Since it's changed to a Promise structure, you can know success and failure through then and catch, and even executing strange functions like asd can be error-handled through Proxy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fr6vk7pscmiatemu7cua3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fr6vk7pscmiatemu7cua3.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Backward Compatibility: Checking Available Methods
&lt;/h2&gt;

&lt;p&gt;Next, let me introduce how I solved backward compatibility related problems. Since the web always guarantees the latest version, I approached backward compatibility by checking method availability at runtime on the web, which helps prevent compatibility issues when native methods evolve.&lt;/p&gt;

&lt;p&gt;When openInAppBrowser and getMessage are injected through the Initialization process, you can easily create utility functions like below. Therefore, you can determine if it's a currently usable method and execute it if available, or execute alternative code if not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbq1z6yg1pc3lz1uth0gw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbq1z6yg1pc3lz1uth0gw.png" alt="Image description" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Backward Compatibility: throwOnError
&lt;/h2&gt;

&lt;p&gt;Second, I also introduced an option called throwOnError. If you put openInAppBrowser in throwOnError, it's a mechanism that causes the web to fail together if this openInAppBrowser method doesn't exist or fails. By propagating errors to the web, this mechanism allows you to catch and handle them seamlessly using standard Promise error handling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F69hulv53rzxa09qeg7z2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F69hulv53rzxa09qeg7z2.png" alt="Image description" width="800" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Backward Compatibility: onFallback
&lt;/h2&gt;

&lt;p&gt;The last is an option called onFallback. This option is a tool that enables batch processing of bridge errors. I think it can be used more effectively when combined with error tracking tools like Sentry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmy74tit2l9feqz4b5j0y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmy74tit2l9feqz4b5j0y.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So far, we've explored the problems that arose from web and React Native communication and methods to solve them. In the next article, I'll introduce the processes of solving problems in real situations using these concepts.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>reactnative</category>
    </item>
    <item>
      <title>A Guide to Debugging Memory Leaks in SSR Environments (Part 2)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Mon, 26 May 2025 13:41:06 +0000</pubDate>
      <link>https://dev.to/feconf/aguidetodebuggingmemoryleaksinssrenvironmentspart2-32ia</link>
      <guid>https://dev.to/feconf/aguidetodebuggingmemoryleaksinssrenvironmentspart2-32ia</guid>
      <description>&lt;p&gt;This article summarizes the talk &lt;em&gt;&lt;a href="https://www.youtube.com/watch?v=P3C7fzMqIYg&amp;amp;ab_channel=FEConfKorea" rel="noopener noreferrer"&gt;A Guide to Debugging Memory Leaks in SSR Environments (Node.js)&lt;/a&gt;&lt;/em&gt; presented at FEConf 2023. The content will be published in two parts.&lt;br&gt;&lt;br&gt;
In Part 1, we explored what memory leaks are and how to detect them using monitoring tools.&lt;br&gt;&lt;br&gt;
In Part 2, we’ll walk through the process of debugging an actual memory leak and discuss how to resolve it.&lt;br&gt;&lt;br&gt;
All images in this article are from the presentation slides of the same title; therefore, separate attributions are not provided.&lt;br&gt;
You can download the presentation materials from the &lt;a href="https://2023.feconf.kr/" rel="noopener noreferrer"&gt;FEConf 2023 website&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;‘Debugging Memory Leaks in SSR Environments (Node.js)’ presented at FEConf2023 / Jihye Park, Frontend Engineer at Toss Place&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In this article, we'll get hands-on with debugging and fixing the memory leak issues we covered in Part 1.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Solving Memory Leaks&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In our previous elevator analogy, we looked at two ways to solve memory leaks.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Increase heap memory, or&lt;/li&gt;
&lt;li&gt; Debug to find the culprit of the memory leak.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's take a closer look at these two methods.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Increasing Heap Memory&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;First, let's explore the method of increasing memory. The thought process of 'We're running out of memory, so let's just add more' is pretty natural. However, will simply increasing heap memory resolve the memory leak?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fkhq9kby8euhyi82ojp0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkhq9kby8euhyi82ojp0e.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, no. Even if you bump up the heap memory, this code will keep leaking memory and eventually bring down your server. Why is that? You can understand it if you know how Node.js's V8 engine manages memory. The V8 engine uses an algorithm called 'Mark and Sweep' to manage memory effectively. This means it marks what's in use and sweeps away (cleans up) what's not.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fo72fbsktabwqg2gijvxd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fo72fbsktabwqg2gijvxd.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Data types like arrays, objects, and functions all get their memory allocated from the heap. For simplicity, let's refer to all of these as 'objects'. The garbage collector recursively checks from the root whether these objects are being used. It then collects unnecessary objects that are no longer in use to free up memory space. This is the 'Mark and Sweep' algorithm.&lt;/p&gt;

&lt;p&gt;But if an object is being referenced from somewhere—basically if something is still 'holding onto' it—it stays in heap memory. What happens if objects persist like this? To understand this, we need to know about heap memory.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Heap Memory&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Node.js's V8 engine divides memory into zones for better management. Below is a simplified structure of the garbage collector to explain the V8 engine's lifecycle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Frxx2am02orltiyy9sj41.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Frxx2am02orltiyy9sj41.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's primarily divided into the Young Generation and the Old Generation, and the garbage collectors are divided into Minor GC (Scavenger) and Major GC (Mark-Sweep &amp;amp; Mark-Compact). If an object is newly declared, it's typically allocated memory in an area called the 'nursery' (within the Young Generation). If the object survives one garbage collection cycle, it moves to an 'intermediate' or 'survivor' space (still often within Young Generation). If it survives another garbage collection cycle, the object eventually moves to the Old Generation area. According to V8's documentation, very few objects actually make it this far.&lt;/p&gt;

&lt;p&gt;So, what happens if more objects survive and accumulate in this Old Generation area? The V8 engine operates the application by managing these two generations. However, since heap memory has a limited capacity, it will eventually fill up, causing the server to crash.&lt;/p&gt;

&lt;p&gt;Let's revisit our previous example code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fzmbje432vr1fv08cjqbw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fzmbje432vr1fv08cjqbw.png" alt="Image description" width="800" height="732"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;listItems&lt;/code&gt; array, being declared as a global variable, isn't collected by the garbage collector and ends up residing in the Old Generation. Initially, it might occupy a small area, like in a conceptual first diagram. Then, when the server receives requests, the loop runs a million times, increasing the length of &lt;code&gt;listItems&lt;/code&gt;. If this repeats, it will occupy a large amount of space, as might be shown in a second conceptual diagram. Eventually, a moment comes when the heap memory is full. Then, the server crashes.&lt;/p&gt;

&lt;p&gt;The example above is simple, but when you encounter such problems while writing real code, will merely increasing heap memory solve the issue? When you face such problems and search online, you'll easily find answers suggesting you increase &lt;code&gt;max-old-space-size&lt;/code&gt;, like the one shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4212v691oqpgohwxl68s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4212v691oqpgohwxl68s.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, &lt;code&gt;max-old-space-size&lt;/code&gt; is an option to adjust the capacity of Node.js's Old Generation. In other words, since most objects causing memory leaks reside in the Old Generation, you'll often find advice to increase the Old Generation's capacity, based on the garbage collector structure explained earlier.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fcp76kzjcf39rsnl2miic.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcp76kzjcf39rsnl2miic.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are various other causes of memory leaks besides the global variables mentioned. Common examples include not clearing &lt;code&gt;setTimeout&lt;/code&gt; or &lt;code&gt;setInterval&lt;/code&gt; timers, and closures. In the case of closures, situations where objects or variables are declared and referenced within an execution context, leading to further references, can also result in significant heap memory allocation.&lt;/p&gt;

&lt;p&gt;In these various scenarios, situations arise where a very large amount of heap memory is needed, so indiscriminately increasing heap memory might not always be the solution.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Debugging Memory Leaks&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Debugging Method&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Now, let's explore solutions through debugging. If you run Node.js using the &lt;code&gt;inspect&lt;/code&gt; option, like &lt;code&gt;node --inspect index.js&lt;/code&gt;, and open your browser's developer tools, you'll see a green Node.js icon. I primarily use Chrome's inspect menu (accessible via &lt;code&gt;chrome://inspect&lt;/code&gt;). This menu lists currently running local servers, and you can select the desired server to open its inspect window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fbhofufd3o63jdprkqlbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fbhofufd3o63jdprkqlbk.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you open the inspect window for debugging, you'll see a window like the one below. In the left panel (typically the "Memory" tab), there's a profiling record button (often a circle). To check memory usage for a specific period, you need to start and stop recording, and this button allows you to do just that.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F8tjkhigihkswe3trstbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8tjkhigihkswe3trstbk.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next to it is a 'clear all profiles' button (often a circle with a line through it, or individual trash can icons for profiles), and below that is the list of completed profiling result files. The 'clear all profiles' button deletes all profiling recording result files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fh8fjo5rojyux6snrexc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh8fjo5rojyux6snrexc4.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, there's a trash can icon button, which manually triggers the garbage collector. Typically, before starting memory profiling, you trigger the garbage collector to stabilize the memory state and then begin profiling.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F04ws2hl2nk39gim9liqg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F04ws2hl2nk39gim9liqg.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, let's look at the most important area. Chrome supports three profiling types in the Memory tab:&lt;/p&gt;

&lt;p&gt;First, &lt;strong&gt;Heap snapshot&lt;/strong&gt;. This type records the heap memory usage at a specific moment. If you select this type, the &lt;code&gt;Take snapshot&lt;/code&gt; button below becomes active. Clicking it captures the heap memory state at that instant. If you have code that you've significantly improved in terms of memory or performance, you can record snapshots before and after the change to compare the two. This comes in handy when you have a pretty good idea where the leak might be happening and want to zero in on that specific area.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fy8mn50wlt8h28n2hy9nk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fy8mn50wlt8h28n2hy9nk.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second is &lt;strong&gt;Allocation instrumentation on timeline&lt;/strong&gt;. This is a very useful and frequently used feature. It periodically records heap memory and shows how much heap memory is being used over time as a graph during the recording. When you suspect a memory leak and start debugging, you can use this timeline to observe how memory usage changes over time. It's often difficult to pinpoint where a memory leak is occurring, and this feature is very helpful in such cases.&lt;/p&gt;

&lt;p&gt;The final type is &lt;strong&gt;Allocation sampling&lt;/strong&gt;. This is similar to the second one but is mainly used when you need to record for a much longer duration. Recording every moment can cause overhead, so this method debugs using sampled information over a longer period. When you press the record button, it might not look like much is happening, but it is recording. When you stop recording, it shows the sampled information.&lt;/p&gt;

&lt;p&gt;We've briefly looked at these three debugging methods. You can choose the type that best suits your situation, but the second type (Allocation instrumentation on timeline) is generally used most often. When you encounter a memory leak error, starting your debugging with the second type will likely help you identify the problem area quickly.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Finding the Memory Leak Culprit&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;When you start debugging using the second method described earlier (Allocation instrumentation on timeline), a graph appears at the top. This graph shows how much heap memory Node.js is using while requests are being processed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fn4i41kweqyqv2mhjxgu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fn4i41kweqyqv2mhjxgu5.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The height of the graph at any point represents the total amount of heap memory allocated at that moment. Grey areas indicate memory reclaimed by the garbage collector, while blue areas represent memory currently occupied on the heap. So, if there's a lot of persistent blue, it could indicate a significant leak. If there's a lot of grey activity, memory is being managed.&lt;/p&gt;

&lt;p&gt;You can also drag to select a specific section of the graph to view only that interval. It's a good idea to first examine the entire range and then select areas with a persistent blue graph. You might find common objects appearing consistently, like an intersection. Focusing your debugging on these areas can save time.&lt;/p&gt;

&lt;p&gt;The graph makes it easy to see &lt;em&gt;when&lt;/em&gt; a memory leak is occurring, but it doesn't tell you &lt;em&gt;who&lt;/em&gt; the culprit is. There's a lot to know to find the culprit, but the following concepts are essential:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F9krh8e2w0qqv998wdksm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9krh8e2w0qqv998wdksm.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These are &lt;strong&gt;Shallow Size&lt;/strong&gt; and &lt;strong&gt;Retained Size&lt;/strong&gt;. Shallow Size is the size of the object itself in bytes. Retained Size is the total size of memory that would be freed if the object itself were deleted, including all objects it exclusively references (and so on, recursively). Additionally, there's a metric called &lt;strong&gt;Distance&lt;/strong&gt;, which indicates how far an object is from the garbage collector's root. A larger distance value can suggest a higher likelihood of being part of a memory leak. It's more of a supplementary indicator rather than a precise debugging metric, but it's useful for quick reference.&lt;/p&gt;

&lt;p&gt;In our previous example code, the globally declared &lt;code&gt;listItems&lt;/code&gt; array was referenced within a function. The garbage collector couldn't reclaim this variable, so it continued to occupy heap memory. The function itself is simple, but within the actual execution context, this variable can grow to a very large size, requiring a significant amount of heap memory. In other words, its Retained Size can be much larger than its Shallow Size. You should focus your debugging on objects or variables where the Retained Size is significantly larger than the Shallow Size.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fnz82dc9v5vmxu9xevada.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fnz82dc9v5vmxu9xevada.png" alt="Image description" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the inspect menu for the code we wrote earlier, if you sort by Retained Size in descending order, you can see the &lt;code&gt;memoryLeakFunction&lt;/code&gt; I created. Select this object, and in the &lt;strong&gt;Retainers&lt;/strong&gt; tab below, you can see the chain of references that keeps this object allocated in heap memory. You can click on filenames to see which file and what code is involved. You can also identify paths to specific libraries you're using.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwguxzo3exvt0wzohh5qz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwguxzo3exvt0wzohh5qz.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scrolling down (in the Retainers view or object list), you'll see &lt;code&gt;listItems&lt;/code&gt;. If you were to look at its details (conceptually, as in the image the presenter might show), you'd see that &lt;code&gt;listItems&lt;/code&gt;' Retained Size is much larger than its Shallow Size. It can be a tedious process, but by finding and modifying these objects one by one to reduce these numbers, you can find the culprit of the memory leak and resolve it.&lt;/p&gt;

&lt;p&gt;The image below is an inspect screen from my actual experience. After filtering out unnecessary parts, I found the section shown. Usually, you'd see a path like &lt;code&gt;node_modules&lt;/code&gt;, but in my case, since I manage Node packages with Yarn Berry, you can see a &lt;code&gt;.yarn&lt;/code&gt; path. I was able to identify which file in this path was causing the issue, confirmed the memory leak originated there, fixed it, and deployed the changes, which resolved the memory leak.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Flnh672ioypivr27recko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flnh672ioypivr27recko.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before the fix, we had that sawtooth pattern I mentioned earlier, but after deployment, you can see it flattened out into a nice, stable line. It has maintained a state without memory leaks since then.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F6pe4y75qo1qsny75ppyy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6pe4y75qo1qsny75ppyy.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;code&gt;using&lt;/code&gt; - A Keyword to Ease the Pain&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Lastly, there's a keyword I'd like to introduce: &lt;code&gt;using&lt;/code&gt;. If you've worked with C#, this will look familiar. Python has something similar too (the with statement). Many of you might have seen it with the recent announcement of TypeScript 5.2, but it's not actually a new concept.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F4xz6p4bmt70nb40y1pex.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4xz6p4bmt70nb40y1pex.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's an existing concept in C#, and it has already reached Stage 3 in JavaScript's TC39 (the group that manages JavaScript standards), so we might see it in native JavaScript soon.&lt;/p&gt;

&lt;p&gt;Simply put, if you declare a variable with &lt;code&gt;using&lt;/code&gt; instead of &lt;code&gt;var&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, or &lt;code&gt;const&lt;/code&gt;, an object's &lt;code&gt;[Symbol.dispose]()&lt;/code&gt; or &lt;code&gt;[Symbol.asyncDispose]()&lt;/code&gt; method (if it implements one) will be called at the end of the variable's scope, allowing for cleanup. You can remove declared event listeners, release DB connections if one was made, or manage the lifecycle of resources like streams that need to be connected and then disconnected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fo2socgeh2x114li7c2yi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fo2socgeh2x114li7c2yi.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we modify the previous example code to use &lt;code&gt;using&lt;/code&gt;, it looks like this. We declared &lt;code&gt;using&lt;/code&gt; instead of &lt;code&gt;const&lt;/code&gt;, and to work with &lt;code&gt;using&lt;/code&gt;, the object would need to implement the &lt;code&gt;Disposable&lt;/code&gt; interface (i.e., have a &lt;code&gt;[Symbol.dispose]()&lt;/code&gt; method). The function's content is the same, and a cleanup part has been added via the &lt;code&gt;dispose&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fmpn1vzokiuym88dt1gi1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmpn1vzokiuym88dt1gi1.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you run this (conceptual) code, unlike before, you'll see the heap memory usage remain stable. By utilizing &lt;code&gt;using&lt;/code&gt; in this manner, it seems we can write code that avoids memory leaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Wrapping Up&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Let me conclude by summarizing what we've discussed. We've looked at debugging methods for server and client environments, how to use the &lt;code&gt;inspect&lt;/code&gt; option in server environments and profile heap memory usage with the timeline, how to find objects causing memory leaks by comparing Shallow Size and Retained Size, and finally, how to potentially prevent memory leaks using the upcoming &lt;code&gt;using&lt;/code&gt; keyword or similar explicit resource management patterns.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fvjuycgz8en7v6u0krnln.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fvjuycgz8en7v6u0krnln.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope that when you run into memory leaks in your day-to-day work, these techniques will help you track them down, figure out what's causing them, and make your apps run better.&lt;/p&gt;

&lt;p&gt;Thank you.&lt;/p&gt;

</description>
      <category>frontend</category>
      <category>webdev</category>
      <category>node</category>
    </item>
    <item>
      <title>A Guide to Debugging Memory in SSR Environment (Part 1)</title>
      <dc:creator>FEConf</dc:creator>
      <pubDate>Mon, 26 May 2025 13:30:23 +0000</pubDate>
      <link>https://dev.to/feconf/a-guide-to-debugging-memory-in-ssr-environment-pt-1-9h9</link>
      <guid>https://dev.to/feconf/a-guide-to-debugging-memory-in-ssr-environment-pt-1-9h9</guid>
      <description>&lt;p&gt;This article summarizes the talk &lt;a href="https://www.youtube.com/watch?v=P3C7fzMqIYg&amp;amp;ab_channel=FEConfKorea" rel="noopener noreferrer"&gt;A Guide to Debugging Memory Leaks in SSR Environments (Node.js)&lt;/a&gt; presented at FEConf 2023. The content will be published in two parts.&lt;br&gt;&lt;br&gt;
In Part 1, we’ll explore what memory leaks are and how to detect them using monitoring tools.&lt;br&gt;&lt;br&gt;
In Part 2, we’ll walk through the process of debugging an actual memory leak and discuss how to resolve it.&lt;br&gt;&lt;br&gt;
All images included in this article are taken from the presentation slides of the same title, so separate attributions are not provided.&lt;br&gt;&lt;br&gt;
You can download the presentation materials from the &lt;a href="https://2023.feconf.kr/" rel="noopener noreferrer"&gt;FEConf 2023 website&lt;/a&gt;.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;“A Guide to Debugging Memory Leaks in SSR Environments (Node.js)” – Presented at FEConf 2023 by Jihye Park, Frontend Engineer at Toss Place&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello, I’m Jihye Park, a frontend engineer at Toss Place.&lt;br&gt;&lt;br&gt;
In this article, I’ll introduce how to debug memory leaks that can occur in server-side rendering (SSR) environments built with Node.js.&lt;/p&gt;

&lt;p&gt;When you see the title, “A Guide to Debugging Memory Leaks in SSR Environments (Node.js),” which keywords stand out the most to you?&lt;br&gt;&lt;br&gt;
I'd say the key terms here are "Node.js" and "memory leaks". Among these, I’d like to focus on the concept of memory leaks, drawing from my personal experience.&lt;/p&gt;

&lt;p&gt;One day, a DevOps engineer on my team came up to me and said, “There’s an OOM (Out Of Memory) issue happening with a particular service. Could you check it out?”&lt;br&gt;&lt;br&gt;
I initially thought I could resolve it quickly by reviewing the code and making some simple adjustments. However, when I opened the code, nothing seemed obviously wrong—yet the memory leak continued.&lt;br&gt;&lt;br&gt;
That’s when I decided to study more deeply and try to fix the issue through thorough debugging.&lt;/p&gt;

&lt;p&gt;Through this article, I hope to deliver two key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Confidence&lt;/strong&gt; that you, too, can debug memory leaks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practical know-how&lt;/strong&gt; on identifying the cause of memory leaks using the browser’s Memory tab, even in complex environments&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you're facing similar issues, I hope this article will be helpful to you—just as I once searched for guidance during my own debugging process.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;What Is a Memory Leak, and Why Is It a Problem?&lt;/strong&gt;
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Memory Leak&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;A memory leak refers to a situation where memory that is no longer needed continues to be occupied.&lt;br&gt;&lt;br&gt;
Let’s use an elevator analogy to better understand memory leaks.&lt;br&gt;
s&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fu6sqqceawwjqztf6p009.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu6sqqceawwjqztf6p009.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: "Memory that should be freed stays in use – like people who never get off an elevator."&lt;/p&gt;

&lt;p&gt;Think of an elevator that can hold 10 people. If 4 people get on and never get off, even as others enter and leave, the elevator effectively only has room for 6 more people. &lt;br&gt;
As a result, it often reaches full capacity more quickly and operates inefficiently.&lt;br&gt;&lt;br&gt;
In other words, memory that should have been freed remains in use, reducing the overall efficiency—this is what we call a memory leak.&lt;/p&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Why Memory Leaks Are Problematic&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;So why is an inefficient elevator (i.e., memory leak) such a problem?&lt;br&gt;&lt;br&gt;
JavaScript applications need memory to function properly. When memory runs low, performance takes a hit.&lt;/p&gt;

&lt;p&gt;JavaScript handles memory management through a &lt;strong&gt;garbage collector (GC)&lt;/strong&gt;. When memory leaks occur, the GC has to work harder, which increases &lt;strong&gt;CPU usage&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
As the CPU becomes more strained, &lt;strong&gt;event loops&lt;/strong&gt; can get blocked. This is critical because the event loop is at the heart of JavaScript's operation, if it's delayed, the application slows down significantly.&lt;/p&gt;

&lt;p&gt;In severe cases, the server may even crash.&lt;br&gt;&lt;br&gt;
Even if you have a system in place to automatically restart crashed servers, there will still be a moment when the server is down and unable to serve users—this affects &lt;strong&gt;availability&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
In short, memory leaks can lead to degraded performance and unstable applications that crash frequently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F1mkhgklxahxk7qhwyb59.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1mkhgklxahxk7qhwyb59.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: "Memory leaks reduce available memory, increase GC work, block the event loop, and can crash the server."&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;How to Detect Memory Leaks&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So, how can we detect memory leaks in a Node.js environment?&lt;br&gt;&lt;br&gt;
One of the most common signs is when you see an error like &lt;code&gt;heap out of memory&lt;/code&gt; printed in the terminal where Node.js is running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fo3q8qa9bpd1ji15jkx8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fo3q8qa9bpd1ji15jkx8l.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “Node.js process crashes with 'heap out of memory' – a classic sign of a memory leak.”&lt;/p&gt;

&lt;p&gt;However, developers aren’t always watching the terminal in real time.&lt;br&gt;&lt;br&gt;
More often, servers are connected to monitoring tools that help observe their behavior over time. These tools typically visualize CPU usage, memory status, and other metrics as graphs.&lt;/p&gt;

&lt;p&gt;It's relatively straightforward to integrate monitoring tools in server environments. &lt;br&gt;
However, it's much more challenging on the client-side (like browsers) due to the wide variety of browser types and hardware specifications.&lt;br&gt;&lt;br&gt;
That said, the actual &lt;strong&gt;debugging method&lt;/strong&gt; is the same in both environments, so I’ll explain them using a shared approach.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Detecting Memory Leaks with Monitoring Tools&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;In this section, we’ll walk through an example using real source code to observe a memory leak via monitoring tools.&lt;/p&gt;

&lt;p&gt;We’ll intentionally introduce a memory leak and then debug it.&lt;br&gt;&lt;br&gt;
Pay close attention to this example code—we'll be coming back to it throughout our debugging process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fwcyu207tr76s417zdfss.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwcyu207tr76s417zdfss.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “Basic Node.js HTTP server code — returning simple HTML for every request.”&lt;/p&gt;

&lt;p&gt;The above is a basic Node.js HTTP server that returns an HTML page with a &lt;code&gt;200&lt;/code&gt; status in response to every request.&lt;/p&gt;

&lt;p&gt;Now, let’s compare &lt;strong&gt;two versions&lt;/strong&gt; of this server: one with a memory leak and one without.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fjbn4c51mwnp60b27csdr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjbn4c51mwnp60b27csdr.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “Only one line differs between the two versions — that one line causes a memory leak.”&lt;/p&gt;

&lt;p&gt;We used a conditional statement to switch between the two versions.&lt;br&gt;&lt;br&gt;
Other than the highlighted part, the code is identical.&lt;/p&gt;

&lt;p&gt;To simulate user traffic, we created a simple shell script that sends repeated requests:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fekq7rorbg1fk3p8gdab8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fekq7rorbg1fk3p8gdab8.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “We created a simple shell script to simulate user traffic by sending repeated requests..”&lt;/p&gt;

&lt;p&gt;Let’s first look at the &lt;strong&gt;non-leaking&lt;/strong&gt; version, which calls &lt;code&gt;nonMemoryLeakFunction&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This function creates a &lt;code&gt;listItems&lt;/code&gt; array &lt;strong&gt;inside the function scope&lt;/strong&gt; and fills it with one million items using a loop.&lt;br&gt;&lt;br&gt;
It also prints the amount of heap memory currently in use.&lt;/p&gt;

&lt;p&gt;Pay attention to where the &lt;code&gt;listItems&lt;/code&gt; array is declared — this will be important.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fd0po1x8332qqn5brhxq0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd0po1x8332qqn5brhxq0.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “Declaring the list inside the function allows it to be garbage-collected after use.”&lt;/p&gt;

&lt;p&gt;When this function is executed, memory usage stays stable — around 25MB per second — without significant changes.&lt;/p&gt;

&lt;p&gt;📝 Slide summary: “Stable memory usage confirms no memory leak in this version.”&lt;/p&gt;

&lt;p&gt;If you observe this in a monitoring tool, you’ll likely see a flat memory usage graph.&lt;br&gt;&lt;br&gt;
It might briefly dip during a deployment, but overall, the memory usage remains consistent.&lt;br&gt;&lt;br&gt;
If your monitoring tool shows a graph like this, it's a good indication that your service is free of memory leaks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fieu8eg6hgloq1nfh0m4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fieu8eg6hgloq1nfh0m4k.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “Flat graph = good. Indicates stable memory without leaks.”&lt;/p&gt;




&lt;p&gt;Now let’s examine the &lt;strong&gt;leaking&lt;/strong&gt; version.&lt;br&gt;&lt;br&gt;
This time, &lt;code&gt;listItems&lt;/code&gt; is declared &lt;strong&gt;outside&lt;/strong&gt; the function — as a global variable.&lt;/p&gt;

&lt;p&gt;Yes, this is an intentional memory leak.&lt;br&gt;&lt;br&gt;
The same one-million-item loop is used, and memory usage is printed again.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Foneb4bwe19770d9i8u4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Foneb4bwe19770d9i8u4a.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “Declaring the list outside the function keeps data alive — causes memory leak.”&lt;/p&gt;

&lt;p&gt;When you run this version, memory usage starts at 33MB and climbs to 193MB.&lt;br&gt;&lt;br&gt;
Eventually, the process crashes with a heap out of memory error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fweqzp3v8qqecsiespvau.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fweqzp3v8qqecsiespvau.jpeg" alt="Image description" width="640" height="354"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “Memory grows over time until process crashes – a textbook memory leak.”&lt;/p&gt;

&lt;p&gt;What does this look like in a monitoring tool?&lt;/p&gt;

&lt;p&gt;You’ll see a steadily increasing line — an &lt;strong&gt;upward slope&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
When the server crashes and restarts, the graph will &lt;strong&gt;drop sharply&lt;/strong&gt;, only to begin rising again as the memory leak continues.&lt;/p&gt;

&lt;p&gt;This recurring sawtooth pattern is a dead giveaway that you have a memory leak.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fw5fyiy9xyp17m0uo5xxe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fw5fyiy9xyp17m0uo5xxe.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;br&gt;
📝 Slide summary: “‘Mountain-shaped’ memory graph = sign of recurring memory leak and server restarts.”&lt;/p&gt;




&lt;p&gt;In the next article, we'll take a hands-on approach to debug this memory leak and explore effective ways to resolve it.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>frontend</category>
      <category>node</category>
    </item>
  </channel>
</rss>
