<?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: kohii</title>
    <description>The latest articles on DEV Community by kohii (@kohii).</description>
    <link>https://dev.to/kohii</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%2F930961%2F5ae6318a-100d-473d-899c-bfff31923018.jpg</url>
      <title>DEV Community: kohii</title>
      <link>https://dev.to/kohii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kohii"/>
    <language>en</language>
    <item>
      <title>Writing Brain-Friendly Code: Principles of Extraction and Abstraction</title>
      <dc:creator>kohii</dc:creator>
      <pubDate>Tue, 17 Feb 2026 13:00:00 +0000</pubDate>
      <link>https://dev.to/kohii/writing-brain-friendly-code-principles-of-extraction-and-abstraction-4p29</link>
      <guid>https://dev.to/kohii/writing-brain-friendly-code-principles-of-extraction-and-abstraction-4p29</guid>
      <description>&lt;p&gt;AI-powered agentic coding has become the norm. It's not much of an exaggeration to say that development productivity now depends on how fully you can put AI to work.&lt;/p&gt;

&lt;p&gt;It's easy to get caught up in AI coding techniques and tooling. But whether your codebase itself is ready for AI is a more fundamental question. What does an AI-ready codebase look like? Low cognitive load. Easy to change. Easy to test. Well-documented. In short, just good software design.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How do you structure code so that treating each piece as a black box doesn't cause things to break?&lt;/li&gt;
&lt;li&gt;How do you get AI to write code that's easy for reviewers to understand at a glance?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Building a codebase like this requires you to make sound design decisions yourself and to give AI clear direction. I wrote about these design principles in Japanese two years ago. I think they might apply even more now, so I translated the article into English.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://zenn.dev/kohii/articles/2c1126389d914a" rel="noopener noreferrer"&gt;Original article (Japanese)&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;My code has a decent reputation for readability, or so I like to think. Here are the personal principles I follow to write code that's easy for the reader's brain to absorb.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Software engineer at a healthcare startup. I also build side projects.&lt;/li&gt;
&lt;li&gt;Social accounts:

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/kohii" rel="noopener noreferrer"&gt;kohii on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/kohii00" rel="noopener noreferrer"&gt;@kohii00 on X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Side project:

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://smoothcsv.com/" rel="noopener noreferrer"&gt;SmoothCSV&lt;/a&gt;: The ultimate CSV editor you've been waiting for. Think "VS Code for CSV".&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Act of Extracting Functions and Classes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why do we split code?
&lt;/h3&gt;

&lt;p&gt;We split code because the overall scale and complexity of a codebase far exceeds the limits of human cognitive capacity. The fundamental strategy for handling something massive and complex is divide and conquer.&lt;/p&gt;

&lt;p&gt;There are various ways to split code: functions, classes, components, packages, modules, microservices, etc. These are all essentially the same: mechanisms to hide details and procedures, presenting them in a different way.&lt;/p&gt;

&lt;p&gt;Extracting a function or class shouldn't just be about deduplication or moving code around. It should be an exercise in creating understandable abstractions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code design is much like UI design
&lt;/h3&gt;

&lt;p&gt;In UI design, we hide technical complexities that the user doesn't need to know. We present them as well-thought-out concepts that can be understood and operated intuitively.&lt;/p&gt;

&lt;p&gt;Designing functions, classes, components, and modules is the same. It's about deciding what details to push behind which interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fractal structure of abstraction
&lt;/h3&gt;

&lt;p&gt;A car provides interfaces like the gas pedal and the steering wheel, which hide the internal machinery and give the driver a way to move the vehicle. From the driver's perspective, operating these controls feels like the objective itself. But from a higher-level perspective ("getting from point A to point B"), those operations are merely the means.&lt;/p&gt;

&lt;p&gt;The systems we develop are also a means to the greater goal of providing value to users. Building a system means creating multiple layers of this Goal (Interface) → Means (Implementation) structure within 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%2F30384xwqupr65xqi2rz5.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%2F30384xwqupr65xqi2rz5.png" alt="Layers of abstraction in action" width="800" height="425"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How to Extract Functions and Classes in a Brain-Friendly Way
&lt;/h2&gt;

&lt;p&gt;In this section, I'll use "functions and classes" as a general term for all units of abstraction, including components, modules, and so on.&lt;/p&gt;

&lt;h3&gt;
  
  
  If they need to read the implementation, you've lost
&lt;/h3&gt;

&lt;p&gt;A well-designed API or library lets you understand what it does and how to use it just from its interface. This should be the ideal when you create your own functions and classes.&lt;/p&gt;

&lt;p&gt;By extracting code and pushing it behind an interface, you are creating parts where the reader can say, "I don't need to read any further" or "I don't need to think about what happens past this point." This reduces cognitive load.&lt;/p&gt;

&lt;p&gt;If a reader can't understand the code without diving into the implementation, then that extraction has failed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NG: You have to look inside to see what kind of filtering is happening&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;filterUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="c1"&gt;// OK: The behavior can be inferred from the function name and signature&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;filterActiveUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  If you abstract, hide the details completely
&lt;/h3&gt;

&lt;p&gt;In nominal typing languages like Kotlin or C#, we often use value objects to wrap primitive values instead of using them directly.&lt;/p&gt;

&lt;p&gt;For example, imagine an &lt;code&gt;Email&lt;/code&gt; value object. Suppose we want to extract the domain name from this email.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substringAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Extracting the domain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might look fine at first glance, but it directly uses the internal value that the value object is supposed to hide. It's like a driver reaching into the car to manually manipulate the engine instead of using the pedals.&lt;/p&gt;

&lt;p&gt;When you abstract something, the consumer should interact with the abstraction itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;substringAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"@"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's another example. Two different interfaces for a React component that displays a notification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// A&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt; &lt;span class="na"&gt;onClickDismissButton&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Some notification&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// B&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt; &lt;span class="na"&gt;onDismiss&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Some notification&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Alert&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the property name &lt;code&gt;onClickDismissButton&lt;/code&gt;, Option A reveals that the &lt;code&gt;Alert&lt;/code&gt; component has a button to close the notification and that clicking it triggers the callback. These are internal details the consumer doesn't need to know.&lt;/p&gt;

&lt;p&gt;Option B hides these details. It leaves it up to the &lt;code&gt;Alert&lt;/code&gt; component to decide how to handle the dismissal or whether to include a button at all. You could change it to trigger &lt;code&gt;onDismiss&lt;/code&gt; via a keyboard shortcut without affecting the consumer.&lt;/p&gt;

&lt;p&gt;Exposing details makes your code fragile to changes and undermines the stability of the interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  Let the function or class define its own interface
&lt;/h3&gt;

&lt;p&gt;There are two ways to think about the relationship between a function and its caller:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A.&lt;/strong&gt; The signature is determined by the caller's needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;B.&lt;/strong&gt; The function defines its own appropriate signature, and the caller adapts to it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Option A, the function must fulfill its own responsibility while also catering to the caller's circumstances. This makes the Single Responsibility Principle hard to achieve. Option B is generally simpler and easier to understand. (For private functions, I often choose A for convenience. The further the distance from the caller, the more you should lean toward B.)&lt;/p&gt;

&lt;h4&gt;
  
  
  The bad example
&lt;/h4&gt;

&lt;p&gt;Below is a use case for searching Pokémon in your possession, written in TypeScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SearchMyPokemonRequest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// Search string&lt;/span&gt;
  &lt;span class="nl"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;             &lt;span class="c1"&gt;// Number of items to fetch&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PokemonType&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// Pokémon type&lt;/span&gt;
  &lt;span class="nl"&gt;outputFormat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;xml&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;csv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Output format&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Use Case: Search my Pokémon (excluding those obtained via trade)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchMyPokemons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchMyPokemonRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Auth info (framework-dependent)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pokemons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findPokemons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;outputPokemons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Data Access Layer&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findPokemons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchMyPokemonRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pokemon&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;ownerUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// Owned by me&lt;/span&gt;
      &lt;span class="na"&gt;capturedUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Caught by me (= excludes trades)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;take&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are several issues with this &lt;code&gt;findPokemons&lt;/code&gt; function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It knows too much:&lt;/strong&gt; It receives the entire &lt;code&gt;SearchMyPokemonRequest&lt;/code&gt; and the full &lt;code&gt;AuthContext&lt;/code&gt; (even though it only needs &lt;code&gt;userId&lt;/code&gt;). This stamp coupling makes the function's responsibility ambiguous.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It requires use-case knowledge to understand:&lt;/strong&gt; The logic for "excluding traded Pokémon" is use-case-specific. The fact that &lt;code&gt;query&lt;/code&gt; means a partial name match is also use-case-specific (or even UI-form-specific). These behaviors are hard to infer from the signature alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  The improved example
&lt;/h4&gt;

&lt;p&gt;The function defines the parameters it needs, regardless of the caller's convenience:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Use Case: Search my Pokémon (excluding those obtained via trade)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;searchMyPokemons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SearchMyPokemonsRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AuthContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Call findPokemons according to its own signature.&lt;/span&gt;
  &lt;span class="c1"&gt;// The behavior is obvious from the signature, so no need to look inside.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pokemons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;findPokemons&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;nameContains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ownerUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;capturedUserId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authContext&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;outputPokemons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pokemons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Data Access Layer&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;findPokemons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;// Define parameters based on the responsibility of "fetching Pokémon matching criteria"&lt;/span&gt;
  &lt;span class="nx"&gt;criteria&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;nameContains&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;PokemonType&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;ownerUserId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;capturedUserId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;UserId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Pokemon&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you only think about the caller's convenience, it's tempting to pass existing variables directly to child functions. But this causes deeper layers of code to depend on unnecessary context, making the code harder to understand.&lt;/p&gt;

&lt;h3&gt;
  
  
  Minimize the context being pulled in
&lt;/h3&gt;

&lt;p&gt;Functions and classes exist within a certain context:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The execution environment, infrastructure, or framework.&lt;/li&gt;
&lt;li&gt;The caller, the caller's caller, and so on.&lt;/li&gt;
&lt;li&gt;A specific business flow or use case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having a function define its own signature, as discussed above, is essentially stripping away the caller's context.&lt;/p&gt;

&lt;p&gt;Functions and classes with minimal context have many advantages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Self-contained understanding:&lt;/strong&gt; Fewer context dependencies mean less extra information to hold in your head while reading.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High reuse value:&lt;/strong&gt; Less context makes code more generic and easier to reuse or relocate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stability:&lt;/strong&gt; Minimal context means fewer reasons for the code to change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ease of testing:&lt;/strong&gt; Stable interfaces and minimal inputs make unit tests straightforward and test data easy to prepare.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-friendly:&lt;/strong&gt; Less context means AI agents can generate, review, and refactor each piece more accurately. As agentic coding becomes the norm, this is becoming increasingly important.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Mind the overhead of extraction
&lt;/h3&gt;

&lt;p&gt;Look at these two TypeScript snippets. Which is easier to read?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;dumpActiveUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;filterActiveUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeUsers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;filterActiveUsers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// This function hides almost nothing&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;dumpActiveUsers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findUsers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;activeUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isActive&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Declarative enough. Directly expresses intent.&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;activeUsers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I find the latter more readable. Extracting functions (or classes, or components) comes with overhead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increase in code volume (boilerplate to define the function).&lt;/li&gt;
&lt;li&gt;Management burden for the extracted function.&lt;/li&gt;
&lt;li&gt;The cognitive cost of jumping to the definition.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the amount of information hidden by a function is negligible, it's often better not to extract it at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write slightly generous comments
&lt;/h3&gt;

&lt;p&gt;Some argue that good design doesn't need comments. I recommend erring on the side of "slightly generous" when in doubt, especially with documentation comments like JSDoc or JavaDoc.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interfaces have limits.&lt;/strong&gt; Sometimes a signature simply can't express everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill and mental models vary.&lt;/strong&gt; People have different ideas of what "good design" looks like. The metaphors or vocabulary that click for one person might not work for another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save the reader's time.&lt;/strong&gt; Good design makes meaning guessable, but guessing still takes effort. If you can reduce the reader's burden, do it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Record the "why."&lt;/strong&gt; Sometimes you write imperfect or tricky logic for specific reasons. Comments convey your design decisions to others and your future self.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Discover flaws.&lt;/strong&gt; If you find a function hard to explain in a comment, it may be a sign that the design itself needs work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Think twice
&lt;/h3&gt;

&lt;p&gt;To judge whether an interface is appropriate, toggle between two perspectives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;From the function's perspective:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Is the context minimized?&lt;/li&gt;
&lt;li&gt;What should it know, and what should it not know, to fulfill its responsibility?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;From the caller's perspective:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Can I guess the meaning just from the interface?&lt;/li&gt;
&lt;li&gt;Would I still think so if someone else wrote this?&lt;/li&gt;
&lt;li&gt;Is there room for misinterpretation or misuse?&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Good design is often discovered gradually as you go. When unsure, start with a simple, naive approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where to invest effort
&lt;/h3&gt;

&lt;p&gt;Designing everything to perfection is exhausting. Pick your battles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invest effort when:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The code is used in many places (including tests).&lt;/li&gt;
&lt;li&gt;The code is called across different services, teams, layers, or features.&lt;/li&gt;
&lt;li&gt;The code is expected to live long or belongs to the core domain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;There is no single correct method for every situation. The best way to learn is to try, fail, and iterate. I hope these ideas help you in that process.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Please try &lt;a href="https://smoothcsv.com/" rel="noopener noreferrer"&gt;SmoothCSV&lt;/a&gt; if you ever need to work with CSV files)&lt;/em&gt;&lt;br&gt;
&lt;em&gt;(Cover art by my wife, a painter. It is unrelated to the text.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>codequality</category>
      <category>cognitiveload</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The Technology Behind SmoothCSV - The Ultimate CSV Editor</title>
      <dc:creator>kohii</dc:creator>
      <pubDate>Mon, 30 Jun 2025 22:07:04 +0000</pubDate>
      <link>https://dev.to/kohii/the-technology-behind-smoothcsv-the-ultimate-csv-editor-3lg0</link>
      <guid>https://dev.to/kohii/the-technology-behind-smoothcsv-the-ultimate-csv-editor-3lg0</guid>
      <description>&lt;p&gt;My self-developed CSV editor SmoothCSV (v3) has become Generally Available, so I'd like to share some technical insights and innovations behind it.&lt;/p&gt;

&lt;p&gt;Also launching on Product Hunt on July 1st (PT) 🚀&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.producthunt.com/products/smoothcsv" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;producthunt.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  About Me
&lt;/h2&gt;

&lt;p&gt;I work as a software engineer at a healthcare startup while doing personal development projects.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://x.com/kohii00" rel="noopener noreferrer"&gt;@kohii00&lt;/a&gt; on X (en: &lt;a href="https://x.com/kohiidev" rel="noopener noreferrer"&gt;@kohiidev&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/kohii" rel="noopener noreferrer"&gt;kohii&lt;/a&gt; on GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  About SmoothCSV
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://smoothcsv.com" rel="noopener noreferrer"&gt;SmoothCSV&lt;/a&gt; is a CSV editor for macOS and Windows. (Linux coming soon)&lt;br&gt;
I've been developing the original SmoothCSV for 15 years, and started working on v3 last year.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://smoothcsv.com/" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsmoothcsv.com%2Fog.png" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://smoothcsv.com/" rel="noopener noreferrer" class="c-link"&gt;
            SmoothCSV - The ultimate CSV editor for macOS &amp;amp; Windows
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            SmoothCSV is the ultimate CSV editor for macOS &amp;amp; Windows. It's fast, easy to use, and packed with powerful features.
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fsmoothcsv.com%2Ffavicon-48x48.png"&gt;
          smoothcsv.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Excel-like operation for intuitive use&lt;/li&gt;
&lt;li&gt;Equipped with basic to advanced tools necessary for handling CSV&lt;/li&gt;
&lt;li&gt;Supports various formats and character encodings. Can handle CSVs with different column counts&lt;/li&gt;
&lt;li&gt;Fast! Handles large files smoothly&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%2F89gcf2kgjzecr47a93ne.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%2F89gcf2kgjzecr47a93ne.png" alt="Slide1"&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%2F2y5h2gqvmjbgqademnro.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%2F2y5h2gqvmjbgqademnro.png" alt="Slide2"&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%2Fiv23megw0tojgrofz8z7.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%2Fiv23megw0tojgrofz8z7.png" alt="Slide3"&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%2Fec6vt4rgxe7n2hmftav1.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%2Fec6vt4rgxe7n2hmftav1.png" alt="Slide4"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Main Technology Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Desktop App

&lt;ul&gt;
&lt;li&gt;Framework: &lt;a href="https://tauri.app/" rel="noopener noreferrer"&gt;Tauri&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Backend: &lt;a href="https://www.rust-lang.org/" rel="noopener noreferrer"&gt;Rust&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Frontend: &lt;a href="https://www.typescriptlang.org/" rel="noopener noreferrer"&gt;TypeScript&lt;/a&gt; + &lt;a href="https://react.dev/" rel="noopener noreferrer"&gt;React&lt;/a&gt; + &lt;a href="https://vitejs.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt; + &lt;a href="https://pnpm.io/" rel="noopener noreferrer"&gt;pnpm&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Styling: &lt;a href="https://tailwindcss.com/" rel="noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;State Management: &lt;a href="https://mobx.js.org/" rel="noopener noreferrer"&gt;MobX&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Testing: &lt;a href="https://nodejs.org/api/test.html" rel="noopener noreferrer"&gt;Node.js built-in test runner&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Formatter, Linter: &lt;a href="https://biomejs.dev/" rel="noopener noreferrer"&gt;Biome&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Website (&lt;a href="https://github.com/kohii/smoothcsv-website" rel="noopener noreferrer"&gt;kohii/smoothcsv-website&lt;/a&gt;)

&lt;ul&gt;
&lt;li&gt;Static Site Generator: &lt;a href="https://astro.build/" rel="noopener noreferrer"&gt;Astro&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hosting: &lt;a href="https://www.cloudflare.com/" rel="noopener noreferrer"&gt;Cloudflare&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Distribution, Communication (&lt;a href="https://github.com/kohii/smoothcsv3" rel="noopener noreferrer"&gt;kohii/smoothcsv3&lt;/a&gt;)

&lt;ul&gt;
&lt;li&gt;GitHub Issues, Releases&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Technical Architecture
&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%2F96stl6t0pnsqa99rn28d.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%2F96stl6t0pnsqa99rn28d.png" alt="Technical Architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Being a desktop application, the technical architecture is quite simple. There's no external communication except for checking and downloading updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Technology for Building Complex Desktop Applications
&lt;/h2&gt;

&lt;p&gt;SmoothCSV aims to be a feature-rich yet simple and highly extensible application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tauri is Great
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tauri.app/" rel="noopener noreferrer"&gt;Tauri&lt;/a&gt; is a desktop application framework that serves as an alternative to Electron. (From Tauri v2, it also supports mobile apps.)&lt;/p&gt;

&lt;p&gt;While Electron uses Node.js for the backend and Chromium for the frontend, Tauri uses Rust for the backend and the OS's bundled web renderer for the frontend. This results in smaller distribution size and lower memory usage.&lt;/p&gt;

&lt;p&gt;Tauri comes with comprehensive APIs and &lt;a href="https://v2.tauri.app/plugin/" rel="noopener noreferrer"&gt;plugins&lt;/a&gt; that make it easy to implement desired features.&lt;/p&gt;

&lt;h3&gt;
  
  
  Separation of Concerns through Package Extraction
&lt;/h3&gt;

&lt;p&gt;Managing as a monorepo with pnpm workspaces + Turborepo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(root)
├─ apps
│  └─ desktop             # SmoothCSV desktop app main body (depends on packages/*)
└─ pakages
   ├─ csv                 # CSV parsing and stringification
   ├─ grid-editor         # React component library for tabular editor
   ├─ result              # Result type TypeScript implementation
   ├─ utils               # General utilities
   ├─ js-sql              # TypeScript SQL parser and evaluation engine
   ├─ framework           # General desktop app foundation. Command system, key bindings, settings, etc.
   ├─ context-expression  # VSCode-compatible conditional expression parser
   └─ l10n                # VSCode-compatible localization API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Under &lt;code&gt;packages&lt;/code&gt;, independent concerns that don't depend on SmoothCSV-specific context are extracted as libraries. This makes individual concerns easier to handle while reducing the complexity of the &lt;code&gt;desktop&lt;/code&gt; main body.&lt;br&gt;
Within desktop, code is also structured by independent concerns as much as possible. (package by feature, etc.)&lt;/p&gt;
&lt;h3&gt;
  
  
  Command-Centric System
&lt;/h3&gt;

&lt;p&gt;In SmoothCSV, operations that users can execute on the application are defined as &lt;strong&gt;commands&lt;/strong&gt;. While this was similar in the previous version of SmoothCSV, this time it's built as an API similar to VS Code.&lt;/p&gt;

&lt;p&gt;Commands consist of handler functions and metadata (ID, title, availability conditions, etc.).&lt;/p&gt;

&lt;p&gt;Image of a command to close a tab:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;aCommand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;view.closeEditor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Close Editor&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;View&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Processing when command is invoked&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Commands are mapped to UI elements such as keyboard shortcuts, context menus, and buttons, and are triggered. You can also search for and execute any command from the "Command Palette".&lt;/p&gt;

&lt;p&gt;Example of assigning a keyboard shortcut to the close tab command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"keybindings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"view.closeEditor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ctrl+w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"mac"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cmd+w"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Example of executing from the Command Palette:&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%2Fobzspfzzd2lm03os5ik8.jpg" 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%2Fobzspfzzd2lm03os5ik8.jpg" alt="Command palette"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For applications with a certain level of complexity, having command-centric extension points makes it easier to add features.&lt;/p&gt;
&lt;h3&gt;
  
  
  System for Declaring Command and Menu Enabling Conditions
&lt;/h3&gt;

&lt;p&gt;This is also a system mimicking &lt;a href="https://code.visualstudio.com/api/references/when-clause-contexts" rel="noopener noreferrer"&gt;VS Code's when clause contexts&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each command, menu, and key binding has situations where it cannot be used.&lt;br&gt;
For example, the command to start cell editing requires the Grid editor to have focus and not be in cell editing mode.&lt;br&gt;
SmoothCSV provides a notation to declaratively express such conditions.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cellEditor.startEditingCell&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Command to start cell editing&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;enablement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gridEditorCellFocus &amp;amp;&amp;amp; inReadyMode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Command is available when Grid editor is focused and not in cell editing mode&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;While implementing parsing is somewhat difficult, having such a system allows requirements to be configured and facilitates future extensions and customizations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Providing Extension APIs from the Initial Stage
&lt;/h3&gt;

&lt;p&gt;As with commands, extension APIs are created first rather than immediately building individual features.&lt;/p&gt;

&lt;p&gt;While this is partly for users to create extensions in the future, the main purpose is to make standard features easier to build. Having stable APIs makes development easier, and several features are actually built as system extensions.&lt;/p&gt;

&lt;p&gt;Things built as extensions within SmoothCSV:&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%2Ftcng20deblo08ug2qzqj.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%2Ftcng20deblo08ug2qzqj.png" alt="System extensions"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Advantages
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Separation of concerns

&lt;ul&gt;
&lt;li&gt;Concerns are contained within each extension's directory&lt;/li&gt;
&lt;li&gt;Organized and easy to read&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Easy to have AI create

&lt;ul&gt;
&lt;li&gt;Even if design and code structure are somewhat rough, it doesn't matter much as it's contained within the extension&lt;/li&gt;
&lt;li&gt;If you don't like it anymore, you can just throw away the entire extension and rebuild&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Lazy loading

&lt;ul&gt;
&lt;li&gt;Code loading is deferred until extension features are actually used → Performance improvement&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, this approach may work well only in limited situations. It's likely to waste time as it's difficult to see what APIs are needed at the initial stage. (SmoothCSV was relatively predictable as it was a replacement development)&lt;/p&gt;
&lt;h3&gt;
  
  
  Adopting MobX for State Management
&lt;/h3&gt;

&lt;p&gt;For frontend state management, after trying various options like a self-made simple Store → Zustand → self-made again... I settled on &lt;strong&gt;MobX&lt;/strong&gt;.&lt;br&gt;
In early development, I was particular about handling state as plain objects, but this resulted in more work to implement each use case and difficulty managing computed values.&lt;/p&gt;

&lt;p&gt;These problems were well resolved by using MobX.&lt;br&gt;
MobX manages state with class-based objects and reactively reflects them in React components.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// MobX object holding state&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FindWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Model governing the widget for search/replace&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;observable&lt;/span&gt; &lt;span class="nx"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;replace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt;　&lt;/span&gt;&lt;span class="c1"&gt;// @observable makes property changes observable&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="nx"&gt;findParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FindParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="err"&gt;　&lt;/span&gt;&lt;span class="c1"&gt;// @observable.ref observes only reference changes. (FindParams is readonly object, so it's replaced as a whole when updated)&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;observable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ref&lt;/span&gt; &lt;span class="nx"&gt;replaceParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReplaceParams&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;computed&lt;/span&gt; &lt;span class="c1"&gt;// Derived property (value is cached)&lt;/span&gt;
  &lt;span class="kd"&gt;get&lt;/span&gt; &lt;span class="nf"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bound&lt;/span&gt; &lt;span class="c1"&gt;// Add @action to change state&lt;/span&gt;
  &lt;span class="nf"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Using from React component (wrapped with observer to render reactively to state changes)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FindWidgetComponent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FindWidget&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isVisible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;FindWidgetForm&lt;/span&gt; &lt;span class="nx"&gt;findParams&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;findParams&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;onHide&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;hide&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;; /&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;Call&lt;/span&gt; &lt;span class="nx"&gt;pure&lt;/span&gt; &lt;span class="nx"&gt;component&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;However, I try to minimize the scope of MobX dependencies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Container/Presentational Pattern-like

&lt;ul&gt;
&lt;li&gt;Create core parts as pure and controllable function components (Presenter)&lt;/li&gt;
&lt;li&gt;Only top-level components (Container) depend on MobX and map from MobX objects to Presenter component props&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Important/complex logic is also created as independent pure functions

&lt;ul&gt;
&lt;li&gt;Creating as methods of MobX objects creates unnecessary property dependencies (most functions don't need the entire object)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Components and functions without unnecessary dependencies are highly stable and become &lt;strong&gt;assets&lt;/strong&gt;.&lt;br&gt;
SmoothCSV is particular about building up these assets.&lt;/p&gt;
&lt;h3&gt;
  
  
  Auto-update
&lt;/h3&gt;

&lt;p&gt;SmoothCSV has a mechanism to automatically detect when a new version is released, notify users, and allow them to download and install it directly.&lt;br&gt;
This mechanism is crucial for rapidly adding features and fixing bugs.&lt;/p&gt;

&lt;p&gt;Tauri has an &lt;a href="https://v2.tauri.app/plugin/updater/" rel="noopener noreferrer"&gt;updater&lt;/a&gt; plugin that makes it easy to implement these processes.&lt;br&gt;
SmoothCSV uploads JSON with update information, distribution files, and signatures to GitHub Releases, and checks for new versions at application startup.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Technology for Building Tabular Editors
&lt;/h2&gt;

&lt;p&gt;SmoothCSV has an Excel-like tabular editor.&lt;br&gt;
The core React component is built as a library independent from SmoothCSV.&lt;/p&gt;
&lt;h3&gt;
  
  
  Cell Rendering
&lt;/h3&gt;

&lt;p&gt;CSV files can contain large amounts of data, such as millions of rows.&lt;br&gt;
Rendering all cells directly is impractical, so SmoothCSV uses &lt;strong&gt;virtual scrolling&lt;/strong&gt;. However, there are actually limitations to regular virtual scrolling, so I've built the scrolling mechanism from scratch.&lt;br&gt;
Details here:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/kohii/how-to-implement-virtual-scrolling-beyond-the-browsers-limit-16ol" class="crayons-story__hidden-navigation-link"&gt;How to Implement Virtual Scrolling Beyond the Browser's Limit&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/kohii" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F930961%2F5ae6318a-100d-473d-899c-bfff31923018.jpg" alt="kohii profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/kohii" class="crayons-story__secondary fw-medium m:hidden"&gt;
              kohii
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                kohii
                
              
              &lt;div id="story-author-preview-content-2027697" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/kohii" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F930961%2F5ae6318a-100d-473d-899c-bfff31923018.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;kohii&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/kohii/how-to-implement-virtual-scrolling-beyond-the-browsers-limit-16ol" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 8 '24&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/kohii/how-to-implement-virtual-scrolling-beyond-the-browsers-limit-16ol" id="article-link-2027697"&gt;
          How to Implement Virtual Scrolling Beyond the Browser's Limit
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/kohii/how-to-implement-virtual-scrolling-beyond-the-browsers-limit-16ol" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;4&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/kohii/how-to-implement-virtual-scrolling-beyond-the-browsers-limit-16ol#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;


&lt;h3&gt;
  
  
  Cell Editing
&lt;/h3&gt;

&lt;p&gt;In Excel and Google Sheets, typing any character immediately enters cell editing mode.&lt;/p&gt;

&lt;p&gt;While this behavior is common in many tabular editors, few work correctly with Japanese input.&lt;/p&gt;

&lt;h4&gt;
  
  
  Common Implementation
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Monitor &lt;code&gt;keydown&lt;/code&gt; to start cell editing&lt;/li&gt;
&lt;li&gt;Set the entered character as the initial value of the cell editor and display it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This causes "a" to be entered when trying to input "あ" in Japanese.&lt;/p&gt;

&lt;h4&gt;
  
  
  SmoothCSV's Implementation
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Place an invisible &lt;code&gt;textarea&lt;/code&gt; and keep it focused&lt;/li&gt;
&lt;li&gt;When any character is entered, display this &lt;code&gt;textarea&lt;/code&gt; as the cell editor&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%2F0uuqf82rerxkq9dm33f7.gif" 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%2F0uuqf82rerxkq9dm33f7.gif" alt="start editing cell"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dynamically Expanding/Contracting Cell Editor Based on Input
&lt;/h3&gt;

&lt;p&gt;When text is entered into the cell editor, its width and height change according to the content.&lt;br&gt;
The mechanism involves placing an invisible &lt;code&gt;span&lt;/code&gt; with exactly the same style as the &lt;code&gt;textarea&lt;/code&gt;, copying the content entered in the &lt;code&gt;textarea&lt;/code&gt; into it. The width and height of this &lt;code&gt;span&lt;/code&gt; are obtained and set as the &lt;code&gt;style&lt;/code&gt; of the &lt;code&gt;textarea&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%2F6pmiqxn9u6fe5gfjzb2x.gif" 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%2F6pmiqxn9u6fe5gfjzb2x.gif" alt="auto resize cell editor"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  3. Technology for Building SQL Integration Features
&lt;/h2&gt;

&lt;p&gt;SmoothCSV has two features that utilize SQL.&lt;/p&gt;
&lt;h3&gt;
  
  
  Filter
&lt;/h3&gt;

&lt;p&gt;A feature that displays only rows matching specified conditions and hides others. While Excel and Google Sheets also have filter features, SmoothCSV allows specifying conditions with SQL WHERE clauses for more flexible filtering.&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%2Fk21oh371emuqe4qeatwk.jpg" 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%2Fk21oh371emuqe4qeatwk.jpg" alt="Filter"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The main purpose is to express filter conditions as strings, borrowing the widely familiar SQL notation for this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Filter conditions being strings makes them easy to save and reuse with copy &amp;amp; paste&lt;/li&gt;
&lt;li&gt;Easy to have AI generate&lt;/li&gt;
&lt;li&gt;Can be handled as structured data when parsed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, there's a feature to edit filter conditions with a GUI, making it easy for non-technical users to 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%2Fza9tk88syxy4m1d6m802.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%2Fza9tk88syxy4m1d6m802.png" alt="Filter Condition Builder"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside, it converts SQL ↔ AST ↔ GUI data model.&lt;/p&gt;
&lt;h3&gt;
  
  
  SQL Console
&lt;/h3&gt;

&lt;p&gt;The SQL Console is a feature that issues SQL SELECT statements against files opened in SmoothCSV or local CSV files.&lt;br&gt;
Using notation like &lt;code&gt;"@file:/Path/To/File.csv"&lt;/code&gt;, you can treat CSV files as tables.&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%2Fodb61bg4vs30sj1uqa68.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%2Fodb61bg4vs30sj1uqa68.png" alt="SQL Console"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Mechanism
&lt;/h4&gt;

&lt;p&gt;Uses embedded SQLite. When SQL is entered and executed, table notation is extracted from the SQL, corresponding temporary tables are created, data is loaded, and then the actual SQL is executed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TEMPORARY&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;"@file:/Path/To/File.csv"&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="c1"&gt;-- SQLite allows any table name when enclosed in ""&lt;/span&gt;
  &lt;span class="cm"&gt;/* columns */&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

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

&lt;p&gt;Thank you for reading to the end!&lt;br&gt;
There's so much I wanted to write about that the explanations became brief, so if you have questions about how something works, please feel free to ask.&lt;/p&gt;

&lt;p&gt;If you have questions, requests, or feedback, please let me know through comments or &lt;a href="https://github.com/kohii/smoothcsv3/issues" rel="noopener noreferrer"&gt;GitHub Issues&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Upvotes and comments on Product Hunt would also be encouraging 🙏&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
      &lt;div class="c-embed__body flex items-center justify-between"&gt;
        &lt;a href="https://www.producthunt.com/products/smoothcsv" rel="noopener noreferrer" class="c-link fw-bold flex items-center"&gt;
          &lt;span class="mr-2"&gt;producthunt.com&lt;/span&gt;
          

        &lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;h3&gt;
  
  
  Bonus
&lt;/h3&gt;

&lt;p&gt;Your support for development would be appreciated.&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://buymeacoffee.com/kohii" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fopengraph%2Fimages%2F6372243%2F1%2Fog_9157868_1762848612.jpg" height="auto" class="m-0"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://buymeacoffee.com/kohii" rel="noopener noreferrer" class="c-link"&gt;
            kohii is SmoothCSV, a powerful and intuitive tool for editing CSV files - Buymeacoffee
          &lt;/a&gt;
        &lt;/h2&gt;
          &lt;p class="truncate-at-3"&gt;
            Hi there👋I'm kohii — full-stack developer, architect, designer, and indie hacker. I work at a medical startup while developing my own products in my spare time.Right now, I’m building SmoothCSV (v3), 
          &lt;/p&gt;
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.buymeacoffee.com%2Fuploads%2Fprofile_pictures%2F2024%2F09%2FMstbu2pIFXhEJIeR.jpg%401f.png"&gt;
          buymeacoffee.com
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


</description>
    </item>
    <item>
      <title>How to Implement Virtual Scrolling Beyond the Browser's Limit</title>
      <dc:creator>kohii</dc:creator>
      <pubDate>Tue, 08 Oct 2024 09:19:10 +0000</pubDate>
      <link>https://dev.to/kohii/how-to-implement-virtual-scrolling-beyond-the-browsers-limit-16ol</link>
      <guid>https://dev.to/kohii/how-to-implement-virtual-scrolling-beyond-the-browsers-limit-16ol</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I am currently developing a CSV editor called &lt;a href="https://smoothcsv.com/" rel="noopener noreferrer"&gt;SmoothCSV&lt;/a&gt;. It uses the framework &lt;a href="https://v2.tauri.app/" rel="noopener noreferrer"&gt;Tauri&lt;/a&gt;, and for the renderer, I employ web technologies (React + TypeScript).&lt;/p&gt;

&lt;p&gt;CSV editors need to display a large number of rows and cells. In web technologies, &lt;strong&gt;virtual scrolling&lt;/strong&gt; is the standard approach for such scenarios. SmoothCSV also utilized virtual scrolling, but I encountered a problem where extremely large datasets, such as those with millions of rows, could not be fully displayed due to the limitations of virtual scrolling.&lt;/p&gt;

&lt;p&gt;In this article, I will introduce the basics of virtual scrolling and explain how I overcame its limitations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Do We Need Virtual Scrolling?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Ordinary Scrolling
&lt;/h3&gt;

&lt;p&gt;When displaying lists or tables with a very large number of items in a browser, a naive implementation will suffer performance degradation as the number of items increases, eventually causing the browser to freeze. This is because the browser constructs and renders the DOM tree for all items, including those outside the viewport.&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%2Fd0wpoew5arq2fbex7gv7.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%2Fd0wpoew5arq2fbex7gv7.png" alt="Ordinary Scrolling" width="768" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Virtual Scrolling as a Solution
&lt;/h3&gt;

&lt;p&gt;To solve this problem, virtual scrolling is often used. Virtual scrolling is a technique where only the items visible based on the scroll position are dynamically generated using JavaScript, thereby reducing the load on the browser.&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%2Fc3ly3rkqx9qqrp6qnl7g.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%2Fc3ly3rkqx9qqrp6qnl7g.png" alt="Virtual Scrolling" width="768" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The visible area is called the &lt;strong&gt;viewport&lt;/strong&gt;, and the content outside the viewport is called the &lt;strong&gt;buffer&lt;/strong&gt;. To enable proper scrolling in the browser, dummy elements (spacers) are placed in the buffer areas to maintain the height of the content.&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%2F73iuqpauraghnrke2lik.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%2F73iuqpauraghnrke2lik.png" alt="Spacer" width="800" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Alternatively, you can specify the height of the content container and position the displayed items using &lt;code&gt;position: absolute&lt;/code&gt;.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How to Implement Virtual Scrolling
&lt;/h3&gt;

&lt;p&gt;There are many libraries available to easily implement virtual scrolling, and usually, you would use these. In React, the following are well-known:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://react-window.vercel.app/" rel="noopener noreferrer"&gt;react-window&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://virtuoso.dev/" rel="noopener noreferrer"&gt;React Virtuoso&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tanstack.com/virtual" rel="noopener noreferrer"&gt;TanStack Virtual&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I personally prefer using &lt;a href="https://tanstack.com/virtual" rel="noopener noreferrer"&gt;TanStack Virtual&lt;/a&gt;, which also supports Vue, Svelte, Solid, Lit, and Angular besides React.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Limits of Virtual Scrolling
&lt;/h2&gt;

&lt;p&gt;It's not widely known, but &lt;strong&gt;there is an upper limit to the width and height of elements that can be displayed in a browser&lt;/strong&gt;. The values I measured are as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Safari: 33,554,428px&lt;/li&gt;
&lt;li&gt;Chromium: 16,777,216px&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Elements with &lt;code&gt;height&lt;/code&gt; or &lt;code&gt;width&lt;/code&gt; larger than this will be truncated to the maximum value. &lt;em&gt;(Properties like &lt;code&gt;top&lt;/code&gt;, &lt;code&gt;left&lt;/code&gt;, &lt;code&gt;margin&lt;/code&gt;, and &lt;code&gt;padding&lt;/code&gt; also have such upper limits.)&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Example in Safari --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Truncated to 33,554,428px --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"height: 9999999999999px;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;&amp;lt;!-- Same even without directly specifying the size --&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Should total 40,000,000px, but truncated to 33,554,428px --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"height: 20000000px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"height: 20000000px"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Virtual scrolling is also affected by this limitation. If the height of the scrollable content (i.e., the total height of all items) exceeds this upper limit, you cannot scroll to the end even with virtual scrolling.&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%2Fwkjpjnby1jhh2gypync2.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%2Fwkjpjnby1jhh2gypync2.png" alt="Limitation" width="768" height="552"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my CSV editor, since the height per row is 22px, the limit is reached at around 1,525,200 rows on macOS (Safari), resulting in the inability to display rows beyond that point.&lt;/p&gt;

&lt;p&gt;While this number of rows might not be a common consideration, CSV files can have a very large number of rows. Accepting this limitation would diminish the value of a CSV editor.&lt;/p&gt;

&lt;h2&gt;
  
  
  Strategies to Overcome the Limits
&lt;/h2&gt;

&lt;p&gt;Virtual scrolling relies on the browser for the scrolling mechanism itself, so the height of the scrollable content, including the buffer, must be the same as the actual total height of all rows. In other words, as long as we depend on the browser's scrolling functionality, we are bound by this limitation.&lt;/p&gt;

&lt;p&gt;So, let's create our own scrolling mechanism!&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%2F4tino6xlkcvdkerj4put.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%2F4tino6xlkcvdkerj4put.png" alt="Solution" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(it might even be better to render using canvas.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing Beyond the Limits
&lt;/h2&gt;

&lt;p&gt;Let's try implementing this in React.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Creating a Scrollbar
&lt;/h3&gt;

&lt;p&gt;First, we'll create a scrollbar by combining &lt;code&gt;div&lt;/code&gt; elements. Generally, the rail of the scrollbar is called the &lt;code&gt;Track&lt;/code&gt;, and the thumb is called the &lt;code&gt;Thumb&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%2F2ijhg7yxe6hikattkpy7.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%2F2ijhg7yxe6hikattkpy7.png" alt="ScrollBar" width="768" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ScrollBar&lt;/code&gt; component receives properties such as the viewport height (&lt;code&gt;viewportSize&lt;/code&gt;), the height of the content to be scrolled (&lt;code&gt;contentSize&lt;/code&gt;), and the current scroll position (&lt;code&gt;scrollPosition&lt;/code&gt;), and renders a custom scrollbar. It notifies the result of user interactions via a callback (&lt;code&gt;onScroll&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%2Fhhersmp0l9j8wz62tavp.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%2Fhhersmp0l9j8wz62tavp.png" alt="Sizes" width="800" height="195"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The implementation is roughly as follows. It is a stateless, controlled component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ScrollBarProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Size of the viewport&lt;/span&gt;
  &lt;span class="nl"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Total size of the content&lt;/span&gt;
  &lt;span class="nl"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Scroll position&lt;/span&gt;
  &lt;span class="nl"&gt;onScroll&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Callback when scroll position changes&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ScrollBar&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;onScroll&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ScrollBarProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Calculate the size and position of the scrollbar from contentSize, viewportSize, and scrollPosition&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scrollRatio&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thumbSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Minimum size of the thumb (to prevent it from becoming too small)&lt;/span&gt;
    &lt;span class="nx"&gt;scrollRatio&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maxScrollPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contentSize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thumbPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;maxScrollPosition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;thumbSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Do not display the scrollbar if the content fits within the viewport&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scrollBarVisible&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;contentSize&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="c1"&gt;// Convert the thumb position back to the actual scroll position&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translateToScrollPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thumbPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thumbPosition&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;thumbSize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;maxScrollPosition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxScrollPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newPosition&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Handler for when the thumb is grabbed and dragged&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseDownOnThumb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;scrollBarVisible&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;preventDefault&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopPropagation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startMousePosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startThumbPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;thumbPosition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Handler for mouse movement while holding down the button&lt;/span&gt;
    &lt;span class="c1"&gt;// (Register the event on the document to capture movement anywhere)&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseMove&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;clientY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startMousePosition&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;onScroll&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="nf"&gt;translateToScrollPosition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startThumbPosition&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseUp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleMouseMove&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;removeEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleMouseUp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mousemove&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleMouseMove&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mouseup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;handleMouseUp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleMouseDownOnTrack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MouseEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handler for when the track outside the thumb is clicked&lt;/span&gt;
    &lt;span class="c1"&gt;// Option 1: Move the thumb to the clicked position&lt;/span&gt;
    &lt;span class="c1"&gt;// Option 2: Move one page in the clicked direction&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;relative&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onMouseDown&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleMouseDownOnTrack&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;scrollBarVisible&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
          &lt;span class="na"&gt;onMouseDown&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleMouseDownOnThumb&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;thumbPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;thumbSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0 3px&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;rgba(0, 0, 0, 0.5)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you need to handle more aspects to consider accessibility and behavior on mobile devices.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Creating a Scroll Pane
&lt;/h3&gt;

&lt;p&gt;Next, we'll create a component that provides state management for the scrollbar and a viewport that displays content synchronized with it. &lt;br&gt;
&lt;em&gt;(This component provides only the scrolling mechanism, and the content to be rendered based on the scroll position is received via &lt;code&gt;children&lt;/code&gt;.)&lt;/em&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%2Fyb5qulx6c8147n5kxz1y.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%2Fyb5qulx6c8147n5kxz1y.png" alt="ScrollPane" width="768" height="324"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ScrollBar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./ScrollBar&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ScrollPaneProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="c1"&gt;// Receives a function as a child that returns content to display based on the scroll position&lt;/span&gt;
  &lt;span class="c1"&gt;// (Function as a Child (FaCC) pattern)&lt;/span&gt;
  &lt;span class="nl"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ScrollPane&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;ScrollPaneProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setScrollPosition&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handleWheel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WheelEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Handler for scrolling with mouse wheel&lt;/span&gt;
    &lt;span class="nf"&gt;setScrollPosition&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newScrollPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deltaY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;contentSize&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newScrollPosition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;onWheel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;handleWheel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* Viewport */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;relative&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;children&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollBar&lt;/span&gt;
        &lt;span class="na"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;contentSize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onScroll&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;setScrollPosition&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using this component looks like this; you pass the content you want to display in the &lt;code&gt;ScrollPane&lt;/code&gt; as a function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Here, we're simply displaying the scroll position&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollPane&lt;/span&gt;
      &lt;span class="na"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;1px solid #ddd&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;scrollPosition: &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollPane&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;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%2Fzux31ig32lzxvjlbnz5g.gif" 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%2Fzux31ig32lzxvjlbnz5g.gif" alt="Test ScrollPane" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Rendering the Content
&lt;/h3&gt;

&lt;p&gt;Finally, we render the content we want to display based on the scroll position. Here, we'll show an example of displaying a list of items with uniform height.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ITEM_HEIGHT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Generate a list of 3 million items&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000000&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`Item &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3 million * 30px = 90,000,000px (&amp;gt; browser limit)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;totalHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ITEM_HEIGHT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollPane&lt;/span&gt; &lt;span class="na"&gt;contentSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;totalHeight&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Calculate the items to display within the viewport&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;ITEM_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endIndex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;scrollPosition&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;viewportSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;ITEM_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;visibleItems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;startIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endIndex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Logical position of the first visible item&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startPosition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startIndex&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;ITEM_HEIGHT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;
            &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;startPosition&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;scrollPosition&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Distance from the top of the viewport&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;visibleItems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ITEM_HEIGHT&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollPane&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with that, we're done! 🎉&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%2F2rsczgsp0undpp0ysuxr.gif" 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%2F2rsczgsp0undpp0ysuxr.gif" alt="Done" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We adjust the display position of the items by specifying the distance from the top of the viewport using &lt;code&gt;top&lt;/code&gt;. For items towards the end, &lt;code&gt;startPosition&lt;/code&gt; becomes very large, but since &lt;code&gt;scrollPosition&lt;/code&gt; is also large, the value passed to &lt;code&gt;top&lt;/code&gt; remains within a practical range.&lt;/p&gt;

&lt;p&gt;Here's a cleaner version of the completed code (using TailwindCSS):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/kohii/react-custom-scroll-example" rel="noopener noreferrer"&gt;https://github.com/kohii/react-custom-scroll-example&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Thank you for reading to the end 🙇‍♂️&lt;/p&gt;

&lt;p&gt;Please give SmoothCSV a try if you're interested.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://smoothcsv.com/" rel="noopener noreferrer"&gt;https://smoothcsv.com/&lt;/a&gt;&lt;br&gt;
&lt;a href="https://github.com/kohii/smoothcsv3" rel="noopener noreferrer"&gt;https://github.com/kohii/smoothcsv3&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>The Technology Behind “Moyuk”: Create, Run and Share Tools with TypeScript on Your Browser</title>
      <dc:creator>kohii</dc:creator>
      <pubDate>Mon, 10 Apr 2023 11:38:27 +0000</pubDate>
      <link>https://dev.to/kohii/the-technology-behind-moyuk-create-run-and-share-tools-with-typescript-on-your-browser-4k16</link>
      <guid>https://dev.to/kohii/the-technology-behind-moyuk-create-run-and-share-tools-with-typescript-on-your-browser-4k16</guid>
      <description>&lt;p&gt;After over a year of development, I launched my personal project, &lt;a href="https://www.producthunt.com/posts/moyuk-beta"&gt;Moyuk, on Product Hunt&lt;/a&gt;. In this post, I'll share some technical insights and knowledges about the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Created
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Moyuk
&lt;/h3&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;a href="https://moyukapp.com" rel="noopener noreferrer"&gt;
      moyukapp.com
    &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;You can check out the Product Hunt launch &lt;a href="https://www.producthunt.com/posts/moyuk-beta"&gt;here&lt;/a&gt; 🚀:&lt;/p&gt;

&lt;h4&gt;
  
  
  Brief Overview
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Simplest way to create and share your custom tools&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In short, Moyuk is a platform that turns functions written in TypeScript into web applications (or "Apps") that can be executed, managed, and shared directly from your browser.&lt;/p&gt;

&lt;p&gt;Please check out the link below for more information:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="https://medium.com/@kohii/introducing-moyuk-a-service-for-creating-running-and-sharing-tools-in-seconds-on-your-browser-7067ca513936" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wIIY6TZW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/v2/resize:fill:96:96/1%2AZb1gavbD37Oy60mzoEJS9Q.jpeg" alt="Kohei Ishikawa"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://medium.com/@kohii/introducing-moyuk-a-service-for-creating-running-and-sharing-tools-in-seconds-on-your-browser-7067ca513936" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Moyuk: Create, Run, and Share Tools on Your Browser | Medium&lt;/h2&gt;
      &lt;h3&gt;Kohei Ishikawa ・ &lt;time&gt;Apr 10, 2023&lt;/time&gt; ・ 
      &lt;div class="ltag__link__servicename"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YjpYcCMa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/medium-f709f79cf29704f9f4c2a83f950b2964e95007a3e311b77f686915c71574fef2.svg" alt="Medium Logo"&gt;
        Medium
      &lt;/div&gt;
    &lt;/h3&gt;
&lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Overview of Technical Elements
&lt;/h2&gt;

&lt;p&gt;In addition to the common application components (such as data input/output and UI rendering), Moyuk has several distinctive features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Components that analyze and transpile user-written TypeScript&lt;/li&gt;
&lt;li&gt;A sandboxed JavaScript execution environment for running transpiled code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These elements are all written in TypeScript and organized as a monorepo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Main Technology Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Application

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.typescriptlang.org/"&gt;TypeScript&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://react.dev/"&gt;React&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://nextjs.org/"&gt;Next.js&lt;/a&gt; (Application framework)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mui.com/"&gt;MUI&lt;/a&gt; (UI library)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://trpc.io/"&gt;tRPC&lt;/a&gt; (Frontend-backend communication)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.prisma.io/"&gt;Prisma&lt;/a&gt; (ORM)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Infrastructure

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://vercel.com/"&gt;Vercel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://supabase.com/"&gt;Supabase&lt;/a&gt; (DB, authentication, storage)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.cloudflare.com/"&gt;Cloudflare&lt;/a&gt; (Workers, Pages)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Other

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docusaurus.io/"&gt;Docusaurus&lt;/a&gt; (Documentation)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://turbo.build/"&gt;Turborepo&lt;/a&gt; (Monorepo)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iJ2Tffyj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3113atvzyjagnj2ucm7o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iJ2Tffyj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3113atvzyjagnj2ucm7o.png" alt="Configuration" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The core application is built on Next.js

&lt;ul&gt;
&lt;li&gt;Deployed on Vercel, with Supabase used for DB, authentication, and storage&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;A sandboxed JavaScript execution environment runs within the browser to execute user-written code

&lt;ul&gt;
&lt;li&gt;Content for the sandbox is served from Cloudflare&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Moyuk as a General Application
&lt;/h2&gt;

&lt;p&gt;Moyuk is built on Next.js and deployed on Vercel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Features Directory
&lt;/h3&gt;

&lt;p&gt;One of the distinctive aspects of the project is the adoption of the &lt;a href="https://github.com/alan2207/bulletproof-react/blob/master/docs/project-structure.md"&gt;features directory&lt;/a&gt;, inspired by &lt;a href="https://github.com/alan2207/bulletproof-react"&gt;Bulletproof React&lt;/a&gt;. I personally prefer organizing components with high functional cohesion close together, which is why I chose this structure. I try not to write too much logic inside the &lt;code&gt;pages&lt;/code&gt; directory of Next.js and instead just combine and use components exported from &lt;code&gt;features&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Management
&lt;/h3&gt;

&lt;p&gt;After considering various options, I decided not to use a library for global state management.&lt;/p&gt;

&lt;p&gt;Initially, I used &lt;a href="https://github.com/pmndrs/zustand"&gt;zustand&lt;/a&gt; as a lightweight state management library, but eventually replaced it with React's Context, which was deemed sufficient. This decision was based on a policy of minimizing global state, which has not caused much pain.&lt;/p&gt;

&lt;p&gt;Generally, the &lt;a href="https://react-query-v3.tanstack.com/"&gt;React Query&lt;/a&gt; client manages data fetched from the server.&lt;br&gt;
Other data states are handled within appropriate components or hooks.&lt;/p&gt;
&lt;h3&gt;
  
  
  Backend
&lt;/h3&gt;

&lt;p&gt;The database is &lt;a href="https://supabase.com/"&gt;Supabase&lt;/a&gt;, with &lt;a href="https://www.prisma.io/"&gt;Prisma&lt;/a&gt; as the ORM, and &lt;a href="https://trpc.io/"&gt;tRPC&lt;/a&gt; for frontend-backend data exchange. tRPC endpoints are built on top of Next.js API Routes and deployed as Vercel Serverless Functions. Supabase is used as a managed PostgreSQL service.&lt;/p&gt;

&lt;p&gt;Supabase is a BaaS "Firebase alternative," allowing direct data reads and writes from the frontend using a client library. Initially, I used this system but switched to tRPC + Prisma as it became difficult to handle complex configurations and store domain logic in stored procedures.&lt;/p&gt;

&lt;p&gt;The combination of tRPC + Prisma offers a great developer experience.&lt;/p&gt;
&lt;h3&gt;
  
  
  UI
&lt;/h3&gt;

&lt;p&gt;I'm using &lt;a href="https://mui.com/"&gt;MUI&lt;/a&gt; as the component library. The richness of MUI components greatly helped in quickly building the UI.&lt;/p&gt;

&lt;p&gt;Forms are built with the popular &lt;a href="https://react-hook-form.com/"&gt;React Hook Form&lt;/a&gt; + &lt;a href="https://github.com/colinhacks/zod"&gt;Zod&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Page Rendering
&lt;/h3&gt;

&lt;p&gt;Next.js offers various rendering methods, which are crucial for performance and Web Vitals scores.&lt;/p&gt;
&lt;h4&gt;
  
  
  Basic Policy
&lt;/h4&gt;

&lt;p&gt;The policy for Moyuk is as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Static pages (e.g., landing pages) → SG

&lt;ul&gt;
&lt;li&gt;Generated statically at build time and cached on the CDN for speed.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Authenticated dynamic pages (e.g., dashboard) → CSR

&lt;ul&gt;
&lt;li&gt;Data is fetched client-side.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Public dynamic pages (e.g., App or profile) → ISR

&lt;ul&gt;
&lt;li&gt;Generated server-side (&amp;amp; cached on CDN), with revalidation on background requests when content becomes outdated.&lt;/li&gt;
&lt;li&gt;When page data is updated, cache is explicitly purged using &lt;a href="https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration#on-demand-revalidation"&gt;On-demand revalidation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  tRPC and Rendering
&lt;/h4&gt;

&lt;p&gt;tRPC offers &lt;a href="https://trpc.io/docs/nextjs/ssr"&gt;SSR features&lt;/a&gt;, which enables automatic SSR for all pages. Initially, I used it without thinking but later stopped due to performance issues. Instead, I began explicitly prefetching the required data using &lt;a href="https://trpc.io/docs/nextjs/server-side-helpers"&gt;Server-Side Helpers&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  Rendering Pages with User-Selectable Private/Public Settings
&lt;/h4&gt;

&lt;p&gt;Users can choose to make their created App public or private. If all App pages are rendered with ISR without consideration, CDN caches for private App pages will be created. Simply marking private App pages as 404 (&lt;code&gt;notFound: true&lt;/code&gt;) will return a 404 to the App owner.&lt;/p&gt;

&lt;p&gt;To resolve this, in Moyuk, &lt;code&gt;getStaticProps&lt;/code&gt; fetches the App data. If the App is public, it renders normally with the data. For private (or non-existent) Apps, it renders a page with empty content. When users access the page, the App data is fetched again client-side. If the viewer is the App owner, the page continues to render. If not, a Not Found page is displayed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Generating an App from User-Written TypeScript
&lt;/h2&gt;

&lt;p&gt;Below is the UI for the App editing screen. When you write code in TypeScript, a preview of the App is displayed on the right side.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NBikYz_9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vn7o2nsa8lfug5t639v4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NBikYz_9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vn7o2nsa8lfug5t639v4.png" alt="App editor" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are mainly two routes through which the user-written TypeScript is processed, and the App preview is generated.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Extracting Type Information from TypeScript Functions
&lt;/h3&gt;

&lt;p&gt;In Moyuk, a form is automatically generated from the type information of the &lt;code&gt;export default&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;The extraction of type information is done using the &lt;a href="https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API"&gt;TypeScript Compiler API&lt;/a&gt;. It was quite challenging due to the lack of documentation and articles.&lt;/p&gt;
&lt;h4&gt;
  
  
  Helpful Resources
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://basarat.gitbook.io/typescript/overview"&gt;TypeScript Deep Dive&lt;/a&gt; - This resource helped in understanding internal concepts like the Scanner, Checker, and Binder.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/microsoft/TypeScript/wiki/Using-the-Compiler-API"&gt;Using the Compiler API&lt;/a&gt; - Sample code for specific use cases is provided, which helped in guessing the purpose of each API.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ts-ast-viewer.com/"&gt;TypeScript AST Viewer&lt;/a&gt; - This viewer was useful in guessing the internal concepts and behavior of the Compiler API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;
  
  
  Support for Deno-compatible import statements
&lt;/h4&gt;

&lt;p&gt;In Moyuk, you can use Deno-compatible import statements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;format&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://deno.land/std@0.181.0/fmt/duration.ts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;encode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;npm:js-base64@^3.7&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The TypeScript Compiler itself does not have a mechanism for automatically resolving remote modules, so compiling them as it will result in errors. I will explain how this is resolved.&lt;/p&gt;
&lt;h4&gt;
  
  
  https
&lt;/h4&gt;

&lt;p&gt;First, to resolve imports starting with &lt;code&gt;https://&lt;/code&gt;, all import URLs in the code are extracted before calling the TS Compiler API, and the files they reference are downloaded in advance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files referenced by the downloaded files are also downloaded in a cascading manner.&lt;/li&gt;
&lt;li&gt;If the reference destination supports the &lt;a href="https://deno.land/manual@v1.32.0/advanced/typescript/types#using-x-typescript-types-header"&gt;X-TypeScript-Type header&lt;/a&gt; on a CDN (e.g., esm.sh, Skypack), the corresponding &lt;code&gt;.d.ts&lt;/code&gt; file is downloaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Next, the actual compiler is called, but with some modifications. The TS Compiler API has a component called &lt;code&gt;CompilerHost&lt;/code&gt; that resolves the reference file for a module name. This is customized to resolve downloaded files for URL-formatted module names.&lt;/p&gt;
&lt;h4&gt;
  
  
  npm
&lt;/h4&gt;

&lt;p&gt;By using the &lt;code&gt;npm:&lt;/code&gt; prefix (&lt;a href="https://deno.land/manual/node/npm_specifiers"&gt;npm: specifiers&lt;/a&gt;), you can import npm packages.&lt;/p&gt;

&lt;p&gt;Moyuk uses a CDN called &lt;a href="http://esm.sh/"&gt;esm.sh&lt;/a&gt; to download npm packages. esm.sh builds and distributes npm packages in ES Module format, so you can use npm packages in web-standard JS runtimes like browsers.&lt;/p&gt;

&lt;p&gt;In Moyuk, if there is an import statement starting with &lt;code&gt;npm:&lt;/code&gt;, it is converted to an esm.sh URL, and then treated in the same way as the aforementioned https case.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;npm:js-base64@^3.7&lt;/code&gt; → &lt;code&gt;https://esm.sh/js-base64@^3.7&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Transpiling TypeScript to JavaScript
&lt;/h3&gt;

&lt;p&gt;Since the TypeScript written by users is actually executed in the browser, it is transpiled to executable JavaScript. If an import statement references an external module, that module is bundled into a single JavaScript file along with the user-written TypeScript.&lt;/p&gt;

&lt;p&gt;Internally, &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt; is used. Esbuild has a plugin system, and I created custom plugins to resolve import statements like &lt;code&gt;npm:&lt;/code&gt; and &lt;code&gt;https://&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;By the way, during app editing and preview, these processes are executed within a Web worker. When you publish an app, the build is performed on the backend.&lt;/p&gt;
&lt;h2&gt;
  
  
  Executing User-Written Code Safely
&lt;/h2&gt;

&lt;p&gt;Moyuk needs to actually execute the code written by users and obtain the results.&lt;br&gt;
The famous method for dynamically executing code in JavaScript is &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval"&gt;eval&lt;/a&gt;, but it &lt;a href="https://www.notion.so/PH-First-comment-7e9cbbf8848b4db4af0ecb367da7e435"&gt;is not safe&lt;/a&gt;.&lt;br&gt;
Considering the possibility that app creators may write malicious code, it is necessary to execute code in an isolated, safe environment.&lt;/p&gt;
&lt;h3&gt;
  
  
  Selecting a Sandbox Technology for the JS Runtime
&lt;/h3&gt;

&lt;p&gt;After extensive research, it was difficult to decide because many options were not secure. The table below shows some promising technologies that were investigated.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Client / Server&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://bellard.org/quickjs/"&gt;QuickJS&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;A lightweight JS engine developed by Fabrice Bellard. Can be run as WASM when combined with &lt;a href="https://github.com/justjake/quickjs-emscripten"&gt;quickjs-emscripten&lt;/a&gt;. Figma plugins run on this.&lt;/td&gt;
&lt;td&gt;Initially considered, but eventually abandoned because it was imperfect.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://webcontainers.io/"&gt;WebContainers&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;A browser-based Node.js developed by StackBlitz. More faithful reproduction of Node.js compared to Sandpack.&lt;/td&gt;
&lt;td&gt;Not considered because it was discovered late in development.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://sandpack.codesandbox.io/"&gt;Sandpack&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;A browser-based Node.js developed by CodeSandbox. Runs on more browsers compared to WebContainers.&lt;/td&gt;
&lt;td&gt;Not considered because it was discovered late in development.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://deno.com/subhosting"&gt;Deno Deploy Subhosting&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;A new Deno Deploy-related service. An environment prepared for safely executing user-input JS/TS on the Edge.&lt;/td&gt;
&lt;td&gt;Discovered midway through development but not noticed until recently. Still in Private Beta, but a top candidate depending on pricing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/tc39/proposal-shadowrealm"&gt;ShadowRealm&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;An API for creating separate Realms (similar to global scope).&lt;/td&gt;
&lt;td&gt;Rejected due to many limitations and being in the proposal stage (Stage 3).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom runtime&lt;/td&gt;
&lt;td&gt;Server&lt;/td&gt;
&lt;td&gt;Setting up a custom server and creating a sandbox using Deno / Node.js or similar.&lt;/td&gt;
&lt;td&gt;Rejected due to cost and maintenance concerns.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.w3schools.com/tags/att_iframe_sandbox.asp"&gt;iframe sandbox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;An iframe with the sandbox attribute becomes sandboxed.&lt;/td&gt;
&lt;td&gt;See below.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In the end, a combination of iframe sandbox, web worker, and Content Security Policy (CSP) was adopted.&lt;br&gt;
The iframe sandbox isolates the environment, CSP restricts contact with the outside world, and the web worker executes the code.&lt;/p&gt;
&lt;h4&gt;
  
  
  iframe
&lt;/h4&gt;

&lt;p&gt;First, create the iframe dynamically. Add the &lt;a href="https://www.w3schools.com/tags/att_iframe_sandbox.asp"&gt;sandbox&lt;/a&gt; attribute to the iframe and serve the content loaded into the iframe from a separate domain (different origin) from &lt;a href="http://moyukapp.com/"&gt;moyukapp.com&lt;/a&gt;. This prevents any access from inside the iframe to the outside. Note that &lt;a href="https://codesandbox.io/"&gt;CodeSandbox&lt;/a&gt; also uses an iframe, but Moyuk's settings are much stricter.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;iframe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iframe&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sandbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;allow-scripts allow-same-origin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;none&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sandboxUrl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iframe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Please note that it is dangerous to add &lt;code&gt;allow-scripts&lt;/code&gt; and &lt;code&gt;allow-same-origin&lt;/code&gt; to the &lt;code&gt;sandbox&lt;/code&gt; attribute and fetch iframe content from the same origin, as this will break the sandboxing.&lt;/p&gt;
&lt;h4&gt;
  
  
  Web worker and CSP
&lt;/h4&gt;

&lt;p&gt;Next, start a Web worker inside the iframe. The JS loaded into the Web worker has the following &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;Content-Security-Policy (CSP)&lt;/a&gt; specified in the response header:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; script-src blob:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This prevents scripts loaded into the Web worker from executing network access, &lt;code&gt;eval&lt;/code&gt;, and other operations. Loading scripts from &lt;code&gt;blob:&lt;/code&gt; URLs to execute user-written code is allowed (explained later).&lt;/p&gt;

&lt;p&gt;In Moyuk, network access can be explicitly allowed by the user (Cloudflare Workers are used to dynamically rewrite the CSP).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yeLNMSXK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4ffl1hv8vkc0b0qh9k4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yeLNMSXK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4ffl1hv8vkc0b0qh9k4.png" alt="You can explicitly allow network access" width="800" height="331"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;
  
  
  Loading user-written code
&lt;/h4&gt;

&lt;p&gt;Load the user-written code (transpiled to JS) into the aforementioned Web worker.&lt;/p&gt;

&lt;p&gt;Moyuk's main application passes the code to the iframe, which in turn passes it to the Web worker. The Web worker converts the received code into a &lt;code&gt;blob:&lt;/code&gt; URL and performs a dynamic import. The return value of the dynamic import allows you to obtain members exported from the user-written script.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;importSourceCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createObjectURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Blob&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;code&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text/javascript&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exportedMembers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Scripts imported via &lt;code&gt;blob:&lt;/code&gt; URLs inherit the parent (Web worker)'s CSP, so they cannot perform external network access or execute &lt;code&gt;eval&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Note that in Firefox, dynamic import within a Web worker is not yet supported, making it an unsupported browser for Moyuk.&lt;/p&gt;
&lt;h4&gt;
  
  
  Executing user-written functions
&lt;/h4&gt;

&lt;p&gt;Execute the target function with the values entered on the App's UI as arguments. The process looks something like this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ExecutionContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;callFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exportedMembers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;functionName&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;returnValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;returnValue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FUNCTION_NOT_FOUND&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Search for the function to be executed from the members &lt;code&gt;export&lt;/code&gt;ed from the code dynamically imported in the previous step, provide the arguments, and execute the function. If the result is a &lt;code&gt;Promise&lt;/code&gt;, &lt;code&gt;await&lt;/code&gt; and return the result.&lt;/p&gt;
&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Thank you for reading until the end!&lt;br&gt;
As the content is quite partial, if you have any questions, please feel free to ask.&lt;/p&gt;

&lt;p&gt;I'm also planning to write about personal development retrospectives and lessons learned at some point.&lt;/p&gt;

&lt;p&gt;And please give Moyuk a try!&lt;br&gt;
&lt;a href="https://moyukapp.com/"&gt;https://moyukapp.com/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I appreciate your feedback and am happy to answer any questions you may have. The following channels are available:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Send a DM/reply to &lt;a href="https://twitter.com/moyukapp"&gt;@moyukapp&lt;/a&gt; on Twitter&lt;/li&gt;
&lt;li&gt;Create an Issue in Moyuk's &lt;a href="https://github.com/moyukapp/moyuk"&gt;GitHub repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Send an email to &lt;a href="//mailto:hello@moyukapp.com"&gt;hello@moyukapp.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Upvotes and comments on Product Hunt are also greatly appreciated and motivating 🙏&lt;/p&gt;
&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Moyuk onProduct Hunt:&lt;br&gt;
&lt;/p&gt;
&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
      &lt;div class="c-embed__cover"&gt;
        &lt;a href="https://www.producthunt.com/posts/moyuk-beta" class="c-link s:max-w-50 align-middle" rel="noopener noreferrer"&gt;
          &lt;img alt="" src="https://res.cloudinary.com/practicaldev/image/fetch/s--WIHwvhUL--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ph-files.imgix.net/6210ca52-765c-457a-a6ec-1e06aacb3872.png%3Fauto%3Dformat%26fit%3Dcrop%26frame%3D1%26h%3D512%26w%3D1024" height="400" class="m-0" width="800"&gt;
        &lt;/a&gt;
      &lt;/div&gt;
    &lt;div class="c-embed__body"&gt;
      &lt;h2 class="fs-xl lh-tight"&gt;
        &lt;a href="https://www.producthunt.com/posts/moyuk-beta" rel="noopener noreferrer" class="c-link"&gt;
           Moyuk Beta - Create, run &amp;amp; share your custom tools effortlessly | Product Hunt
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;p class="truncate-at-3"&gt;
          Moyuk makes building, running and sharing custom tools a breeze. Simply write a TypeScript function, and Moyuk turns it into a web app – ready to execute, manage, and share in the browser. No Moyuk-specific code is required.
        &lt;/p&gt;
      &lt;div class="color-secondary fs-s flex items-center"&gt;
          &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://res.cloudinary.com/practicaldev/image/fetch/s--cmeqGyAO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://ph-static.imgix.net/ph-favicon-coral.ico" width="240" height="240"&gt;
        producthunt.com
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;



&lt;p&gt;Introduction to Moyuk:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="https://medium.com/@kohii/introducing-moyuk-a-service-for-creating-running-and-sharing-tools-in-seconds-on-your-browser-7067ca513936" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--wIIY6TZW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://miro.medium.com/v2/resize:fill:96:96/1%2AZb1gavbD37Oy60mzoEJS9Q.jpeg" alt="Kohei Ishikawa"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://medium.com/@kohii/introducing-moyuk-a-service-for-creating-running-and-sharing-tools-in-seconds-on-your-browser-7067ca513936" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Moyuk: Create, Run, and Share Tools on Your Browser | Medium&lt;/h2&gt;
      &lt;h3&gt;Kohei Ishikawa ・ &lt;time&gt;Apr 10, 2023&lt;/time&gt; ・ 
      &lt;div class="ltag__link__servicename"&gt;
        &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YjpYcCMa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev.to/assets/medium-f709f79cf29704f9f4c2a83f950b2964e95007a3e311b77f686915c71574fef2.svg" alt="Medium Logo"&gt;
        Medium
      &lt;/div&gt;
    &lt;/h3&gt;
&lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;Docs:&lt;br&gt;
&lt;a href="https://docs.moyukapp.com/"&gt;https://docs.moyukapp.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nextjs</category>
      <category>supabase</category>
      <category>sandbox</category>
    </item>
  </channel>
</rss>
