<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Soojin Jung on Medium]]></title>
        <description><![CDATA[Stories by Soojin Jung on Medium]]></description>
        <link>https://medium.com/@sojjung3?source=rss-387cb432e7c8------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*sBR31cc1w0g1Vpv1lDdzmQ.jpeg</url>
            <title>Stories by Soojin Jung on Medium</title>
            <link>https://medium.com/@sojjung3?source=rss-387cb432e7c8------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 17 Oct 2026 12:39:18 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@sojjung3/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Building a PWA App in a Day with Claude Code]]></title>
            <link>https://medium.com/@sojjung3/claude-code%EB%A1%9C-%ED%95%98%EB%A3%A8-%EB%A7%8C%EC%97%90-pwa-%EC%95%B1-%EB%A7%8C%EB%93%A4%EA%B8%B0-b82f0e218ea1?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/b82f0e218ea1</guid>
            <category><![CDATA[pwa]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[claude]]></category>
            <category><![CDATA[claude-code]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Sat, 24 Jan 2026 07:56:54 GMT</pubDate>
            <atom:updated>2026-03-04T17:31:04.254Z</atom:updated>
            <content:encoded><![CDATA[<p>Today I want to share how I use AI to build services quickly and track user responses after launch.</p><h3>My AI Coding Tool: Claude Code</h3><p>The AI tool I use is Claude Code.</p><p>I’ve tried Cursor, ChatGPT, and Gemini, but ultimately settled on Claude Code. I’m currently on the Max plan ($100/month).</p><p>At this point, a paid plan feels pretty much essential if you’re going to have AI handle your development work. I was originally on the Plus plan, but I kept running out of usage, so I upgraded to Max. $100 has been more than enough for me.</p><h3>Documenting with CLAUDE.md and the .ai Folder</h3><p>This is something I use not just for personal projects, but for team projects as well.</p><p>On our team, everyone’s AI-generated code style was different, so we needed a way to unify it. My solution was to specify in CLAUDE.md that the AI should read the markdown files in the .ai folder before writing any code.</p><p>Here’s what we keep in the .ai folder and share across the team:</p><ul><li>CODING_STANDARDS.md (coding conventions)</li><li>ARCHITECTURE.md (project structure)</li><li>SEO.md (SEO guidelines)</li></ul><p>We also established a rule: if the documentation and code are out of sync, update the documentation first, then reflect the changes in the code.</p><p>This approach gave us three benefits:</p><ol><li><strong>Code consistency</strong> — Same style regardless of who’s working on it</li><li><strong>Better reusability</strong> — Shared components and utility functions are actually used</li><li><strong>Maintained project understanding</strong> — Even when AI writes the code, developers don’t lose track of the overall flow</li></ol><h3>Fast Development + Learning in One Go: The /wrap Command</h3><p>This is something I picked up from a post on LinkedIn.</p><p>I created a custom command called /wrap that summarizes the tasks I had Claude work on that day and generates technical quizzes based on them.</p><p>As a junior developer, it’s been great for keeping up with learning while shipping at the same time.</p><figure><img alt="wrap 명령어 예시" src="https://cdn-images-1.medium.com/max/1024/1*AMAvBUld4g26z-_4ciNcTA.png" /></figure><h3>Deploying to Vercel</h3><p>I use the Vercel platform for fast deployment.</p><p>Vercel deployment is well-documented elsewhere, so I’ll skip the details here.</p><h3>Using PWA to Test Like a Native App</h3><p>On iPhone Safari, you can go to Share → Add to Home Screen for your deployed service, and an app icon will appear on your home screen.</p><p>This lets you test the experience as if it were a native app before actually publishing to the App Store.</p><h3>Tracking Service Usage</h3><p>Checking user responses after launch is crucial. No matter how fast you build something, you can’t improve it if you don’t know whether people are actually using it or where they’re dropping off.</p><h4>1. Google Analytics (GA4)</h4><p>A tool for tracking “what users are doing in my service.”</p><p>Key metrics I look at:</p><ul><li><strong>Real-time users</strong> — How many people are on the site right now</li><li><strong>Traffic acquisition</strong> — Where they came from (Google search, direct, social media, etc.)</li><li><strong>Popular pages</strong> — Which pages get the most views</li><li><strong>Average session duration</strong> — How long users stay</li><li><strong>Bounce rate</strong> — The percentage of users who leave after viewing only one page</li></ul><p>Setup is straightforward — get your Measurement ID (G-XXXXXX) from GA4 and add the script to your &lt;head&gt;. If you&#39;re using Next.js, the @next/third-parties package makes integration even easier.</p><h4>2. Google Search Console</h4><p>A tool for managing “how my site appears in Google search results.”</p><p>Key metrics I look at:</p><ul><li><strong>Search keywords</strong> — What search terms are bringing people in</li><li><strong>Impressions vs. clicks</strong> — Whether people see my site in results but don’t click</li><li><strong>Average position</strong> — Where my site ranks in search results</li><li><strong>Indexing status</strong> — Whether Google has properly crawled my pages</li></ul><p>Submitting a sitemap.xml here helps Google understand your site structure faster. With Next.js, you can auto-generate it using the next-sitemap package. Keep in mind that submitting doesn&#39;t mean instant indexing — expect to wait at least 1–2 days.</p><h4>Key Metrics to Focus on Early</h4><p>For an early-stage service, you don’t need to look at every metric. These three matter most:</p><ul><li><strong>Daily Active Users (DAU)</strong> — How many people use it each day</li><li><strong>Return rate</strong> — Whether users come back after their first visit</li><li><strong>Core feature usage</strong> — Whether people are actually using the main functionality</li></ul><h3>Wrapping Up</h3><p>Here’s the overall flow:</p><p><strong>Idea → Build with Claude Code → Deploy on Vercel → Test as PWA → Improve with analytics</strong></p><p>It used to take weeks to build a single app, but now it feels like we’re in an era where you can ship an MVP in a day and put it in front of real users.</p><p>I put this post together while thinking about <strong>how to make better use of AI in this new landscape, and how to set up documentation and conventions within a team.</strong></p><p>I’m still learning as I go, so this is by no means the definitive answer — but I hope it helps anyone going through similar thought processes.</p><p>If you have questions or want to know more, feel free to leave a comment!</p><p>Thanks for reading 😊</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b82f0e218ea1" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Frontend Developer’s Guide to Web Accessibility]]></title>
            <link>https://medium.com/@sojjung3/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%9B%B9-%EC%A0%91%EA%B7%BC%EC%84%B1-%EA%B0%80%EC%9D%B4%EB%93%9C-81d81d6bd479?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/81d81d6bd479</guid>
            <category><![CDATA[web-accessibility]]></category>
            <category><![CDATA[semanticweb]]></category>
            <category><![CDATA[web]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Mon, 20 Oct 2025 10:34:12 GMT</pubDate>
            <atom:updated>2026-02-27T01:15:34.373Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HOkHXd4Rl6Ua_8uC2MfiAA.png" /><figcaption>web accessibility</figcaption></figure><p>With the rapid advancement of AI, it seems like the skills expected of developers no longer stay confined to a single domain.</p><p>Frontend developers need to understand the backend, backend developers need to know AI — the scope of what a single developer has to cover is constantly expanding.</p><p>That said, I believe there’s one area that frontend developers are uniquely positioned to champion.</p><p>That area is <strong>Web Accessibility</strong>.</p><p>Web accessibility means ensuring that all users — including those with disabilities — can use websites equally and without barriers.</p><p>Many frontend developers tend to overlook this when racing against deadlines, but especially in today’s world where global services have become the norm, I believe it’s an essential value we must prioritize.</p><p>In reality, each individual accessibility practice isn’t that difficult.</p><p>However, there are so many things to keep track of, and the details are so granular, that it’s easy to miss things in practice.</p><p>That’s why I decided to put together this document — to organize everything systematically.</p><h3>1. Semantic HTML: Building Meaningful Structure</h3><h4>Choosing the Right Tags</h4><p>Semantic HTML is the foundation of web accessibility. Using the appropriate HTML tags allows screen readers to understand the page structure and helps users navigate to the content they need quickly.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*h21fT0dNSy7IKd5CLEk39g.png" /><figcaption>semantic html</figcaption></figure><p><strong>Bad example:</strong></p><pre>&lt;div class=&quot;header&quot;&gt;<br>  &lt;div class=&quot;nav&quot;&gt;<br>    &lt;div class=&quot;nav-item&quot;&gt;Home&lt;/div&gt;<br>    &lt;div class=&quot;nav-item&quot;&gt;About&lt;/div&gt;<br>  &lt;/div&gt;<br>&lt;/div&gt;<br>&lt;div class=&quot;content&quot;&gt;<br>  &lt;div class=&quot;title&quot;&gt;Title&lt;/div&gt;<br>  &lt;div class=&quot;text&quot;&gt;Body content...&lt;/div&gt;<br>&lt;/div&gt;</pre><p><strong>Good example:</strong></p><pre>&lt;header&gt;<br>  &lt;nav&gt;<br>    &lt;ul&gt;<br>      &lt;li&gt;&lt;a href=&quot;/&quot;&gt;Home&lt;/a&gt;&lt;/li&gt;<br>      &lt;li&gt;&lt;a href=&quot;/about&quot;&gt;About&lt;/a&gt;&lt;/li&gt;<br>    &lt;/ul&gt;<br>  &lt;/nav&gt;<br>&lt;/header&gt;<br>&lt;main&gt;<br>  &lt;article&gt;<br>    &lt;h1&gt;Title&lt;/h1&gt;<br>    &lt;p&gt;Body content...&lt;/p&gt;<br>  &lt;/article&gt;<br>&lt;/main&gt;</pre><h4>Heading Hierarchy</h4><p>Heading tags should be used sequentially. You should never skip from h1 to h3, or go from h2 to h4.</p><pre>&lt;h1&gt;Main Title&lt;/h1&gt;<br>  &lt;h2&gt;First Section&lt;/h2&gt;<br>    &lt;h3&gt;Subtopic 1&lt;/h3&gt;<br>    &lt;h3&gt;Subtopic 2&lt;/h3&gt;<br>  &lt;h2&gt;Second Section&lt;/h2&gt;<br>    &lt;h3&gt;Subtopic 3&lt;/h3&gt;</pre><h3>2. Keyboard Accessibility: Fully Usable Without a Mouse</h3><h4>Focus Management</h4><p>All interactive elements (buttons, links, input fields) must be accessible via keyboard. Users should be able to navigate with the Tab key and activate elements with Enter or Space.</p><pre>/* Never do this! */<br>*:focus {<br>  outline: none;<br>}<br><br>/* Instead, provide clear focus styles */<br>button:focus,<br>a:focus,<br>input:focus {<br>  outline: 2px solid #0066cc;<br>  outline-offset: 2px;<br>}<br><br>/* Or a more polished style */<br>button:focus-visible {<br>  box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.5);<br>}</pre><h4>Logical Tab Order</h4><p>The HTML structure order is the tab order. Avoid forcing the order by setting tabindex to positive values.</p><pre>&lt;!-- Good example: Natural order --&gt;<br>&lt;form&gt;<br>  &lt;input type=&quot;text&quot; placeholder=&quot;Name&quot;&gt;<br>  &lt;input type=&quot;email&quot; placeholder=&quot;Email&quot;&gt;<br>  &lt;button type=&quot;submit&quot;&gt;Submit&lt;/button&gt;<br>&lt;/form&gt;<br><br>&lt;!-- Bad example: Forcing order with tabindex --&gt;<br>&lt;button tabindex=&quot;3&quot;&gt;Third&lt;/button&gt;<br>&lt;button tabindex=&quot;1&quot;&gt;First&lt;/button&gt;<br>&lt;button tabindex=&quot;2&quot;&gt;Second&lt;/button&gt;</pre><h4>Preventing Keyboard Traps</h4><p>When a modal or dropdown is opened, focus must not become trapped inside it. Users should be able to close it with the ESC key, and focus should cycle within the modal.</p><pre>// Modal focus trap implementation example<br>const modal = document.querySelector(&#39;.modal&#39;);<br>const focusableElements = modal.querySelectorAll(<br>  &#39;button, [href], input, select, textarea, [tabindex]:not([tabindex=&quot;-1&quot;])&#39;<br>);<br>const firstElement = focusableElements[0];<br>const lastElement = focusableElements[focusableElements.length - 1];<br><br>modal.addEventListener(&#39;keydown&#39;, (e) =&gt; {<br>  if (e.key === &#39;Escape&#39;) {<br>    closeModal();<br>  }<br>  if (e.key === &#39;Tab&#39;) {<br>    if (e.shiftKey &amp;&amp; document.activeElement === firstElement) {<br>      e.preventDefault();<br>      lastElement.focus();<br>    } else if (!e.shiftKey &amp;&amp; document.activeElement === lastElement) {<br>      e.preventDefault();<br>      firstElement.focus();<br>    }<br>  }<br>});</pre><h3>3. ARIA: Communicating with Screen Readers</h3><h4>ARIA Usage Principles</h4><p>ARIA (Accessible Rich Internet Applications) conveys information to assistive technologies that can’t be expressed through HTML alone. But remember the principle: <strong>“No ARIA is better than Bad ARIA.”</strong></p><p><strong>Ground rules:</strong></p><ul><li>If a native HTML element exists, don’t use ARIA.</li><li>ARIA does not change the visual appearance of an element.</li><li>All interactive ARIA controls must be keyboard-accessible.</li></ul><h4>Key ARIA Attributes</h4><p><strong>role</strong>: Defines an element&#39;s role</p><pre>&lt;!-- Custom button --&gt;<br>&lt;div role=&quot;button&quot; tabindex=&quot;0&quot;&gt;Click me&lt;/div&gt;<br><br>&lt;!-- But this is better --&gt;<br>&lt;button&gt;Click me&lt;/button&gt;</pre><p><strong>aria-label</strong>: Provides a brief description</p><pre>&lt;button aria-label=&quot;Search&quot;&gt;<br>  &lt;svg&gt;&lt;!-- Magnifying glass icon --&gt;&lt;/svg&gt;<br>&lt;/button&gt;<br><br>&lt;button aria-label=&quot;Close menu&quot;&gt;<br>  &lt;span aria-hidden=&quot;true&quot;&gt;×&lt;/span&gt;<br>&lt;/button&gt;</pre><p><strong>aria-labelledby / </strong><strong>aria-describedby</strong>: References other elements</p><pre>&lt;div role=&quot;dialog&quot; aria-labelledby=&quot;dialog-title&quot; aria-describedby=&quot;dialog-desc&quot;&gt;<br>  &lt;h2 id=&quot;dialog-title&quot;&gt;Sign Up&lt;/h2&gt;<br>  &lt;p id=&quot;dialog-desc&quot;&gt;Please fill in the information below&lt;/p&gt;<br>  &lt;!-- Form content --&gt;<br>&lt;/div&gt;</pre><p><strong>State attributes:</strong></p><pre>&lt;!-- Toggle button --&gt;<br>&lt;button aria-pressed=&quot;false&quot; onclick=&quot;this.setAttribute(&#39;aria-pressed&#39;, &#39;true&#39;)&quot;&gt;<br>  Enable Notifications<br>&lt;/button&gt;<br><br>&lt;!-- Accordion --&gt;<br>&lt;button aria-expanded=&quot;false&quot; aria-controls=&quot;content-1&quot;&gt;<br>  FAQ 1<br>&lt;/button&gt;<br>&lt;div id=&quot;content-1&quot; hidden&gt;Answer content...&lt;/div&gt;<br>&lt;!-- Tab panel --&gt;<br>&lt;div role=&quot;tablist&quot;&gt;<br>  &lt;button role=&quot;tab&quot; aria-selected=&quot;true&quot; aria-controls=&quot;panel-1&quot;&gt;Tab 1&lt;/button&gt;<br>  &lt;button role=&quot;tab&quot; aria-selected=&quot;false&quot; aria-controls=&quot;panel-2&quot;&gt;Tab 2&lt;/button&gt;<br>&lt;/div&gt;<br>&lt;div role=&quot;tabpanel&quot; id=&quot;panel-1&quot;&gt;Panel 1 content&lt;/div&gt;<br>&lt;div role=&quot;tabpanel&quot; id=&quot;panel-2&quot; hidden&gt;Panel 2 content&lt;/div&gt;</pre><p><strong>aria-live</strong>: Announces dynamic content changes</p><pre>&lt;!-- Notification message --&gt;<br>&lt;div aria-live=&quot;polite&quot; aria-atomic=&quot;true&quot; class=&quot;notification&quot;&gt;<br>  &lt;!-- Messages are dynamically added here --&gt;<br>&lt;/div&gt;<br><br>&lt;!-- Live chat --&gt;<br>&lt;div aria-live=&quot;assertive&quot; role=&quot;log&quot;&gt;<br>  &lt;!-- New messages are read immediately --&gt;<br>&lt;/div&gt;</pre><h3>4. Alternative Text: Giving Meaning to All Content</h3><h4>Image Alt Text</h4><pre>&lt;!-- Meaningful image --&gt;<br>&lt;img src=&quot;chart.png&quot; alt=&quot;2024 revenue trend chart: increased from $10,000 in Q1 to $50,000 in Q4&quot;&gt;<br><br>&lt;!-- Decorative image --&gt;<br>&lt;img src=&quot;decoration.png&quot; alt=&quot;&quot;&gt;<br>&lt;!-- Image inside a link --&gt;<br>&lt;a href=&quot;/profile&quot;&gt;<br>  &lt;img src=&quot;avatar.jpg&quot; alt=&quot;View John&#39;s profile&quot;&gt;<br>&lt;/a&gt;<br>&lt;!-- Complex images: provide longdesc or detailed description --&gt;<br>&lt;figure&gt;<br>  &lt;img src=&quot;complex-diagram.png&quot; alt=&quot;System architecture diagram&quot;&gt;<br>  &lt;figcaption&gt;<br>    &lt;details&gt;<br>      &lt;summary&gt;View detailed description&lt;/summary&gt;<br>      &lt;p&gt;The frontend is built with React...&lt;/p&gt;<br>    &lt;/details&gt;<br>  &lt;/figcaption&gt;<br>&lt;/figure&gt;</pre><h4>Icons and SVGs</h4><pre>&lt;!-- Font Awesome icon --&gt;<br>&lt;button&gt;<br>  &lt;i class=&quot;fas fa-trash&quot; aria-hidden=&quot;true&quot;&gt;&lt;/i&gt;<br>  &lt;span class=&quot;sr-only&quot;&gt;Delete&lt;/span&gt;<br>&lt;/button&gt;<br><br>&lt;!-- SVG icon --&gt;<br>&lt;button aria-label=&quot;Go to home&quot;&gt;<br>  &lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot;&gt;<br>    &lt;use xlink:href=&quot;#icon-home&quot;&gt;&lt;/use&gt;<br>  &lt;/svg&gt;<br>&lt;/button&gt;<br>&lt;!-- Meaningful SVG --&gt;<br>&lt;svg role=&quot;img&quot; aria-labelledby=&quot;chart-title chart-desc&quot;&gt;<br>  &lt;title id=&quot;chart-title&quot;&gt;Monthly Visitors&lt;/title&gt;<br>  &lt;desc id=&quot;chart-desc&quot;&gt;Increased from 1,000 in January to 5,000 in December&lt;/desc&gt;<br>  &lt;!-- SVG content --&gt;<br>&lt;/svg&gt;</pre><h4>Visually Hidden but Exposed to Screen Readers</h4><pre>.sr-only {<br>  position: absolute;<br>  width: 1px;<br>  height: 1px;<br>  padding: 0;<br>  margin: -1px;<br>  overflow: hidden;<br>  clip: rect(0, 0, 0, 0);<br>  white-space: nowrap;<br>  border: 0;<br>}</pre><h3>5. Color and Contrast: Making Everything Readable</h3><h4>WCAG Contrast Standards</h4><ul><li><strong>Level AA (minimum):</strong> Normal text 4.5:1, large text (18pt+ or bold 14pt+) 3:1</li><li><strong>Level AAA (enhanced):</strong> Normal text 7:1, large text 4.5:1</li></ul><pre>/* Good example: Sufficient contrast */<br>.text-good {<br>  color: #333333; /* Near-black gray */<br>  background-color: #ffffff; /* White */<br>  /* Contrast ratio: 12.63:1 */<br>}<br><br>/* Bad example: Insufficient contrast */<br>.text-bad {<br>  color: #cccccc; /* Light gray */<br>  background-color: #ffffff; /* White */<br>  /* Contrast ratio: 1.61:1 - Hard to read */<br>}</pre><h4>Don’t Convey Information Through Color Alone</h4><pre>&lt;!-- Bad example: Status indicated only by color --&gt;<br>&lt;span style=&quot;color: red;&quot;&gt;Error&lt;/span&gt;<br>&lt;span style=&quot;color: green;&quot;&gt;Success&lt;/span&gt;<br><br>&lt;!-- Good example: Icons and text used together --&gt;<br>&lt;span class=&quot;status-error&quot;&gt;<br>  &lt;svg aria-hidden=&quot;true&quot;&gt;&lt;!-- X icon --&gt;&lt;/svg&gt;<br>  Error<br>&lt;/span&gt;<br>&lt;span class=&quot;status-success&quot;&gt;<br>  &lt;svg aria-hidden=&quot;true&quot;&gt;&lt;!-- Checkmark icon --&gt;&lt;/svg&gt;<br>  Success<br>&lt;/span&gt;</pre><h4>Link Differentiation</h4><pre>/* Distinguish links in body text with underlines */<br>p a {<br>  text-decoration: underline;<br>  color: #0066cc;<br>}<br><br>/* Don&#39;t rely on color alone */<br>p a {<br>  color: #0066cc;<br>  text-decoration: underline;<br>}<br><br>p a:hover {<br>  text-decoration: none;<br>  background-color: #e6f2ff;<br>}</pre><h3>6. Form Accessibility: The Heart of User Input</h3><h4>Connecting Labels</h4><p>Associate related label and input elements using the for and id attributes.</p><pre>&lt;!-- Properly connected label --&gt;<br>&lt;label for=&quot;username&quot;&gt;Username&lt;/label&gt;<br>&lt;input type=&quot;text&quot; id=&quot;username&quot; name=&quot;username&quot;&gt;<br><br>&lt;!-- Or wrap with label --&gt;<br>&lt;label&gt;<br>  Email<br>  &lt;input type=&quot;email&quot; name=&quot;email&quot;&gt;<br>&lt;/label&gt;<br><br>&lt;!-- Don&#39;t use only placeholder --&gt;<br>&lt;!-- Bad example --&gt;<br>&lt;input type=&quot;text&quot; placeholder=&quot;Enter your name&quot;&gt;<br><br>&lt;!-- Good example --&gt;<br>&lt;label for=&quot;name&quot;&gt;Name&lt;/label&gt;<br>&lt;input type=&quot;text&quot; id=&quot;name&quot; placeholder=&quot;e.g. John Doe&quot;&gt;</pre><h4>Indicating Required Fields</h4><p>These days, many libraries handle this well, but required is still an attribute you should add for accessibility.</p><pre>&lt;label for=&quot;email&quot;&gt;<br>  Email &lt;span aria-label=&quot;required&quot;&gt;*&lt;/span&gt;<br>&lt;/label&gt;<br>&lt;input<br>  type=&quot;email&quot;<br>  id=&quot;email&quot;<br>  required<br>  aria-required=&quot;true&quot;<br>&gt;<br><br>&lt;!-- Or more explicitly --&gt;<br>&lt;label for=&quot;email&quot;&gt;<br>  Email &lt;span class=&quot;required&quot;&gt;(required)&lt;/span&gt;<br>&lt;/label&gt;<br>&lt;input type=&quot;email&quot; id=&quot;email&quot; required&gt;</pre><h4>Error Message Handling</h4><pre>&lt;label for=&quot;password&quot;&gt;Password&lt;/label&gt;<br>&lt;input<br>  type=&quot;password&quot;<br>  id=&quot;password&quot;<br>  aria-invalid=&quot;true&quot;<br>  aria-describedby=&quot;password-error&quot;<br>&gt;<br>&lt;span id=&quot;password-error&quot; class=&quot;error&quot; role=&quot;alert&quot;&gt;<br>  Password must be at least 8 characters<br>&lt;/span&gt;</pre><pre>// Real-time validation<br>const passwordInput = document.getElementById(&#39;password&#39;);<br>const errorMessage = document.getElementById(&#39;password-error&#39;);<br><br>passwordInput.addEventListener(&#39;blur&#39;, () =&gt; {<br>  if (passwordInput.value.length &lt; 8) {<br>    passwordInput.setAttribute(&#39;aria-invalid&#39;, &#39;true&#39;);<br>    errorMessage.textContent = &#39;Password must be at least 8 characters&#39;;<br>  } else {<br>    passwordInput.setAttribute(&#39;aria-invalid&#39;, &#39;false&#39;);<br>    errorMessage.textContent = &#39;&#39;;<br>  }<br>});</pre><h4>Grouping with Fieldset</h4><pre>&lt;fieldset&gt;<br>  &lt;legend&gt;Shipping Information&lt;/legend&gt;<br><br>  &lt;label for=&quot;address&quot;&gt;Address&lt;/label&gt;<br>  &lt;input type=&quot;text&quot; id=&quot;address&quot;&gt;<br><br>  &lt;label for=&quot;postal&quot;&gt;Postal Code&lt;/label&gt;<br>  &lt;input type=&quot;text&quot; id=&quot;postal&quot;&gt;<br>&lt;/fieldset&gt;<br><br>&lt;fieldset&gt;<br>  &lt;legend&gt;Notification Settings&lt;/legend&gt;<br><br>  &lt;label&gt;<br>    &lt;input type=&quot;checkbox&quot; name=&quot;notification&quot; value=&quot;email&quot;&gt;<br>    Email notifications<br>  &lt;/label&gt;<br><br>  &lt;label&gt;<br>    &lt;input type=&quot;checkbox&quot; name=&quot;notification&quot; value=&quot;sms&quot;&gt;<br>    SMS notifications<br>  &lt;/label&gt;<br>&lt;/fieldset&gt;</pre><h3>7. Responsive Design and Zoom</h3><h4>Handling Text Enlargement</h4><pre>/* Use relative units */<br>html {<br>  font-size: 16px; /* Base size */<br>}<br><br>body {<br>  font-size: 1rem; /* 16px */<br>}<br><br>h1 {<br>  font-size: 2rem; /* 32px */<br>}<br><br>.small-text {<br>  font-size: 0.875rem; /* 14px */<br>}<br><br>/* Avoid fixed px values */<br>.bad-example {<br>  font-size: 14px; /* Size stays fixed even when user zooms */<br>  width: 300px;<br>  height: 200px;<br>}<br><br>/* Flexible layout */<br>.good-example {<br>  font-size: 0.875rem; /* Responds to zoom */<br>  max-width: 90%;<br>  padding: 1em;<br>}</pre><h4>Touch Target Size</h4><p>Ensure adequate touch areas on mobile devices.</p><pre>/* Minimum 44x44px touch target */<br>button,<br>a {<br>  min-height: 44px;<br>  min-width: 44px;<br>  padding: 0.5em 1em;<br>}<br><br>/* Even small icon buttons need sufficient touch area */<br>.icon-button {<br>  width: 20px; /* Icon size */<br>  height: 20px;<br>  padding: 12px; /* Total: 44x44px */<br>}</pre><h4>Viewport Settings</h4><pre>&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0, maximum-scale=5.0&quot;&gt;</pre><p>Setting maximum-scale to 1.0 prevents zooming and should be avoided.</p><h3>8. Multimedia Accessibility</h3><h4>Video and Captions</h4><pre>&lt;video controls&gt;<br>  &lt;source src=&quot;video.mp4&quot; type=&quot;video/mp4&quot;&gt;<br>  &lt;track<br>    kind=&quot;subtitles&quot;<br>    src=&quot;subtitles-ko.vtt&quot;<br>    srclang=&quot;ko&quot;<br>    label=&quot;Korean&quot;<br>    default<br>  &gt;<br>  &lt;track<br>    kind=&quot;subtitles&quot;<br>    src=&quot;subtitles-en.vtt&quot;<br>    srclang=&quot;en&quot;<br>    label=&quot;English&quot;<br>  &gt;<br>  &lt;track<br>    kind=&quot;descriptions&quot;<br>    src=&quot;descriptions.vtt&quot;<br>    srclang=&quot;ko&quot;<br>    label=&quot;Audio Descriptions&quot;<br>  &gt;<br>&lt;/video&gt;</pre><h4>Limiting Autoplay</h4><pre>&lt;!-- Provide controls when autoplaying --&gt;<br>&lt;video autoplay muted loop controls&gt;<br>  &lt;source src=&quot;background.mp4&quot;&gt;<br>&lt;/video&gt;<br><br>&lt;!-- Or provide a pause button --&gt;<br>&lt;button id=&quot;pause-animation&quot;&gt;Pause Animation&lt;/button&gt;</pre><h4>Reduced Motion</h4><pre>/* For users sensitive to motion */<br>@media (prefers-reduced-motion: reduce) {<br>  *,<br>  *::before,<br>  *::after {<br>    animation-duration: 0.01ms !important;<br>    animation-iteration-count: 1 !important;<br>    transition-duration: 0.01ms !important;<br>  }<br>}<br><br>/* Original animation */<br>.fade-in {<br>  animation: fadeIn 0.5s ease-in;<br>}<br><br>/* In reduced motion mode, show immediately */<br>@media (prefers-reduced-motion: reduce) {<br>  .fade-in {<br>    animation: none;<br>    opacity: 1;<br>  }<br>}</pre><h3>9. Practical Component Examples</h3><h4>Accessible Modal</h4><pre>&lt;button id=&quot;open-modal&quot;&gt;Open Modal&lt;/button&gt;<br><br>&lt;div<br>  id=&quot;modal&quot;<br>  class=&quot;modal&quot;<br>  role=&quot;dialog&quot;<br>  aria-modal=&quot;true&quot;<br>  aria-labelledby=&quot;modal-title&quot;<br>  hidden<br>&gt;<br>  &lt;div class=&quot;modal-content&quot;&gt;<br>    &lt;h2 id=&quot;modal-title&quot;&gt;Notification&lt;/h2&gt;<br>    &lt;p&gt;This is the modal content.&lt;/p&gt;<br>    &lt;button id=&quot;close-modal&quot;&gt;Close&lt;/button&gt;<br>  &lt;/div&gt;<br>&lt;/div&gt;</pre><pre>const openBtn = document.getElementById(&#39;open-modal&#39;);<br>const closeBtn = document.getElementById(&#39;close-modal&#39;);<br>const modal = document.getElementById(&#39;modal&#39;);<br>let previousFocus;<br><br>openBtn.addEventListener(&#39;click&#39;, () =&gt; {<br>  previousFocus = document.activeElement;<br>  modal.hidden = false;<br>  closeBtn.focus();<br>  document.body.style.overflow = &#39;hidden&#39;;<br>});<br><br>closeBtn.addEventListener(&#39;click&#39;, () =&gt; {<br>  modal.hidden = true;<br>  document.body.style.overflow = &#39;&#39;;<br>  previousFocus.focus();<br>});<br><br>// Close with ESC key<br>modal.addEventListener(&#39;keydown&#39;, (e) =&gt; {<br>  if (e.key === &#39;Escape&#39;) {<br>    closeBtn.click();<br>  }<br>});</pre><h4>Accessible Dropdown</h4><pre>&lt;div class=&quot;dropdown&quot;&gt;<br>  &lt;button<br>    id=&quot;menu-button&quot;<br>    aria-expanded=&quot;false&quot;<br>    aria-haspopup=&quot;true&quot;<br>    aria-controls=&quot;menu-list&quot;<br>  &gt;<br>    Menu<br>  &lt;/button&gt;<br><br>  &lt;ul<br>    id=&quot;menu-list&quot;<br>    role=&quot;menu&quot;<br>    hidden<br>  &gt;<br>    &lt;li role=&quot;none&quot;&gt;<br>      &lt;a href=&quot;/profile&quot; role=&quot;menuitem&quot;&gt;Profile&lt;/a&gt;<br>    &lt;/li&gt;<br>    &lt;li role=&quot;none&quot;&gt;<br>      &lt;a href=&quot;/settings&quot; role=&quot;menuitem&quot;&gt;Settings&lt;/a&gt;<br>    &lt;/li&gt;<br>    &lt;li role=&quot;none&quot;&gt;<br>      &lt;a href=&quot;/logout&quot; role=&quot;menuitem&quot;&gt;Logout&lt;/a&gt;<br>    &lt;/li&gt;<br>  &lt;/ul&gt;<br>&lt;/div&gt;</pre><pre>const menuButton = document.getElementById(&#39;menu-button&#39;);<br>const menuList = document.getElementById(&#39;menu-list&#39;);<br>const menuItems = menuList.querySelectorAll(&#39;[role=&quot;menuitem&quot;]&#39;);<br><br>menuButton.addEventListener(&#39;click&#39;, () =&gt; {<br>  const isExpanded = menuButton.getAttribute(&#39;aria-expanded&#39;) === &#39;true&#39;;<br>  menuButton.setAttribute(&#39;aria-expanded&#39;, !isExpanded);<br>  menuList.hidden = isExpanded;<br><br>  if (!isExpanded) {<br>    menuItems[0].focus();<br>  }<br>});<br><br>// Arrow key navigation<br>menuList.addEventListener(&#39;keydown&#39;, (e) =&gt; {<br>  const currentIndex = Array.from(menuItems).indexOf(document.activeElement);<br>  <br>  if (e.key === &#39;ArrowDown&#39;) {<br>    e.preventDefault();<br>    const nextIndex = (currentIndex + 1) % menuItems.length;<br>    menuItems[nextIndex].focus();<br>  } else if (e.key === &#39;ArrowUp&#39;) {<br>    e.preventDefault();<br>    const prevIndex = (currentIndex - 1 + menuItems.length) % menuItems.length;<br>    menuItems[prevIndex].focus();<br>  } else if (e.key === &#39;Escape&#39;) {<br>    menuButton.click();<br>    menuButton.focus();<br>  }<br>});</pre><h3>10. Testing Methods</h3><h4>Screen Reader Testing</h4><ul><li><strong>Windows:</strong> NVDA (free) or JAWS</li><li><strong>Mac:</strong> VoiceOver (built-in)</li><li><strong>Mobile:</strong> iOS VoiceOver, Android TalkBack</li></ul><h4>Color Blindness Simulation</h4><ul><li>Chrome DevTools → Rendering → Emulate vision deficiencies</li></ul><h3>Closing Thoughts</h3><p>Web accessibility isn’t for a special group of users — it’s for everyone.</p><p>It might feel like extra work at first, but once you get used to it, writing accessible code becomes second nature.</p><p>I hope these efforts, however small, contribute to a web that everyone can use without barriers — and that more developers continue to make accessibility a priority.</p><h3>References</h3><ul><li><a href="https://www.w3.org/WAI/WCAG21/quickref/">WCAG 2.1 Guidelines</a></li><li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility">MDN Web Accessibility</a></li><li><a href="https://www.a11yproject.com/checklist/">A11y Project Checklist</a></li><li><a href="https://webaim.org/">WebAIM</a></li><li><a href="https://www.wa.or.kr/">Korean Web Accessibility Certification</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=81d81d6bd479" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[프로젝트에 shadcn/ui 도입하기 ‍♂️]]></title>
            <link>https://medium.com/@sojjung3/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8%EC%97%90-shadcn-ui-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0-%EF%B8%8F-86f36094ef06?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/86f36094ef06</guid>
            <category><![CDATA[shadcn]]></category>
            <category><![CDATA[react]]></category>
            <category><![CDATA[shadcn-ui]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Sun, 16 Feb 2025 18:25:50 GMT</pubDate>
            <atom:updated>2025-05-26T05:01:38.783Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="shadcn/ui 공식 홈페이지 https://ui.shadcn.com/" src="https://cdn-images-1.medium.com/max/1024/1*9eukBz2_oXviw1zntVb8Lg.png" /><figcaption>shadcn/ui 공식홈페이지 <a href="https://ui.shadcn.com/">https://ui.shadcn.com/</a></figcaption></figure><p>회사에서 어드민 페이지 개발을 혼자 맡게 되면서</p><p>스택을 홀로 정할 수 있는 기회가 주어졌다.</p><p>(이참에 하고싶었던 거 다 써봐야지)</p><p>그래서 전부터 써보고 싶었던 요즘 핫한 shadcn/ui를 우선으로 생각했다.</p><p>그렇게 최종적으로 결정된 스택들은 React + TS, Vite, Tailwind css, Shadcn/ui, React Query !</p><p>But.. 현실은 버전 충돌의 연속</p><p>설치하면서 오류를 몇번이나 마주했는지 모르겠다.</p><p>그 중 하나의 예시는 tailwind를 설치하면서 발생했다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*6t6_f8FwZ4oYdgftvHfNSg.png" /></figure><p>tailwind 버전을 3.x대로 낮춰서 다시 설치하였는데, 다른데서 계속해서 버전문제가 발생했다. 그래서 그냥 지피티한테 각 스택들이 호환되는 버전을 알려달라고 했다. ㅋㅋ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tlobKdPp3Tg4xs3v0PQG5w.png" /></figure><p>이것들로 다시 초기세팅 진행</p><p>우선 노드 버전 체크부터</p><pre>node -v<br><br>// v20.14.0</pre><h3>(1) Vite React + TypeScript 프로젝트 생성</h3><pre>npm create vite@5 . # 현재 디렉토리에 Vite 프로젝트 생성</pre><p>그럼 프레임워크와 언어를 선택하라고 뜨는데 React와 TypeScript를 선택해준다.</p><p>React는 v18.3.1, TypeScript는 v5.6.2 가 설치되었다.</p><h3>(2) Tailwind CSS 설정</h3><pre># 1) Tailwind CSS 3.x 버전 지정해서 설치<br>npm install -D tailwindcss@^3.3.0 postcss autoprefixer<br><br># 2) Tailwind config 파일 생성<br>npx tailwindcss init -p</pre><ul><li>^3.3.0으로 설치해두면 3.x 범위의 마이너/패치 업데이트가 허용되므로, 3.4.x, 3.5.x 등이 나오면 자동으로 업데이트된다.</li></ul><p>tailwind.config.js에서 content 부분을 Vite 구조에 맞게 설정:</p><pre>module.exports = {<br>  content: [<br>    &quot;./index.html&quot;,<br>    &quot;./src/**/*.{ts,tsx}&quot;,<br>    // shadcn/ui 컴포넌트를 직접 src/components/ui 아래에 두는 경우<br>    &quot;./src/components/ui/**/*.{ts,tsx}&quot;,<br>  ],<br>  theme: {<br>    extend: {},<br>  },<br>  plugins: [],<br>};</pre><p>그리고 src/main.tsx 혹은 src/index.css 등에 Tailwind 지시문 추가:</p><pre>@tailwind base;<br>@tailwind components;<br>@tailwind utilities;</pre><p>현재 Vite의 버전에서는 TypeScript 구성이 세 개의 파일로 분리되어 있으며, 그 중 두 파일을 수정해야 한다.<br>tsconfig.json과 tsconfig.app.json 파일의 compilerOptions 섹션에 baseUrl과 paths 속성을 추가:</p><p>(a) tsconfig.json</p><pre>{<br>  &quot;files&quot;: [],<br>  &quot;references&quot;: [<br>    {<br>      &quot;path&quot;: &quot;./tsconfig.app.json&quot;<br>    },<br>    {<br>      &quot;path&quot;: &quot;./tsconfig.node.json&quot;<br>    }<br>  ],<br>  &quot;compilerOptions&quot;: {<br>    &quot;baseUrl&quot;: &quot;.&quot;,<br>    &quot;paths&quot;: {<br>      &quot;@/*&quot;: [&quot;./src/*&quot;]<br>    }<br>  }<br>}</pre><p>(b) tsconfig.app.json</p><pre>{<br>  &quot;compilerOptions&quot;: {<br>    // ...<br>    &quot;baseUrl&quot;: &quot;.&quot;,<br>    &quot;paths&quot;: {<br>      &quot;@/*&quot;: [<br>        &quot;./src/*&quot;<br>      ]<br>    }<br>    // ...<br>  }<br>}</pre><p>마지막으로, 다음 코드를 vite.config.ts 파일에 추가해서 앱에서 경로를 문제없이 해석할 수 있게 한다.</p><pre>npm install -D @types/node</pre><pre>import path from &quot;path&quot;<br>import react from &quot;@vitejs/plugin-react&quot;<br>import { defineConfig } from &quot;vite&quot;<br><br>export default defineConfig({<br>  plugins: [react()],<br>  resolve: {<br>    alias: {<br>      &quot;@&quot;: path.resolve(__dirname, &quot;./src&quot;),<br>    },<br>  },<br>})</pre><h3>(3) shadcn/ui 가져오기</h3><p>참고로 shadcn/ui를 Vite에서 쓸 경우, 공식 CLI(npx @shadcn/ui init)가 Next.js 전용 설정을 만들어버리므로, 일부 설정은 수동으로 가져와야 한다. 헷갈리면 공식문서 ㄱㄱ</p><pre>npx shadcn init</pre><p>그러면 추가로 선택할 것들이 뜨는데, 나는 아래처럼 선택해주었다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*fqyuIpfkYsCBpf3wX-jbWA.png" /></figure><p>이러면 shadcn까지 설치 끝!</p><p>npm run dev로 정상 작동되는지 확인하기</p><h3>(4) 이제 진짜 shadcn 컴포넌트 사용</h3><p>나는 사이드바 부터 사용해보기로 하였다.</p><pre>npx shadcn add sidebar</pre><p>이렇게 했을 때 프로젝트의 src &gt; components &gt; ui 에 sidebar.tsx가 생긴다.</p><p>그리고 나는 아래 문서를 참고해서 임시 사이드바 컴포넌트(AppSidebar.tsx)를 생성해주었다.</p><p><a href="https://ui.shadcn.com/docs/components/sidebar#your-first-sidebar">https://ui.shadcn.com/docs/components/sidebar#your-first-sidebar</a></p><p>App.tsx에서 사이드바 불러오기</p><pre>import { SidebarProvider, SidebarTrigger } from &quot;@/components/ui/sidebar&quot;;<br>import AppSidebar from &quot;@/components/AppSidebar&quot;;<br><br>function App() {<br>  return (<br>    &lt;div&gt;<br>      &lt;SidebarProvider&gt;<br>        &lt;AppSidebar /&gt;<br>        &lt;main&gt;<br>          &lt;SidebarTrigger /&gt;<br>        &lt;/main&gt;<br>      &lt;/SidebarProvider&gt;<br>    &lt;/div&gt;<br>  );<br>}<br><br>export default App;</pre><p>그렇게 만들어진 사이드바! 짜잔</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*TRuUsXhaW6zFuhDpKvY4fw.png" /></figure><p>좀 더 shadcn스럽게(?) 다크테마 적용</p><p>index.html :</p><pre>&lt;html lang=&quot;en&quot; class=&quot;dark&quot;&gt;</pre><p>혹은, React 컴포넌트 레벨에서:</p><pre>&lt;div className=&quot;dark&quot;&gt;</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MTcBsdey9fBFDgdzL5eyIg.png" /></figure><p>완성!!!</p><p>이제 본격적으로 커스텀해서 사용해봐야겠다 ㅎㅎ</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*LiqzYKzZfLZRTAyQVzytyA.png" /></figure><h4>참고자료</h4><ul><li>[shadcn 설치] <a href="https://ui.shadcn.com/docs/installation/vite">https://ui.shadcn.com/docs/installation/vite</a></li><li>[shadcn sidebar 컴포넌트] <a href="https://ui.shadcn.com/docs/components/sidebar">https://ui.shadcn.com/docs/components/sidebar</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=86f36094ef06" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Sorting Import Order with ESLint & Setting Up Absolute Paths]]></title>
            <link>https://medium.com/@sojjung3/eslint-import-%EC%88%9C%EC%84%9C-%EC%A0%95%EB%A0%AC%ED%95%98%EA%B8%B0-%ED%8C%8C%EC%9D%BC-%EC%A0%88%EB%8C%80%EA%B2%BD%EB%A1%9C-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0-530fe58d94e2?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/530fe58d94e2</guid>
            <category><![CDATA[eslint]]></category>
            <category><![CDATA[project-management]]></category>
            <category><![CDATA[react]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Mon, 10 Feb 2025 11:51:31 GMT</pubDate>
            <atom:updated>2026-03-04T17:15:02.249Z</atom:updated>
            <content:encoded><![CDATA[<p>Without any established conventions for module import ordering in our internal project, things ended up looking like this…</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*9ZYBg565UP3ipy2i8Rz7NA.png" /></figure><p>Of course, import order isn’t the most critical part of a project — but it was hard to see at a glance which modules were being used, so I thought it was worth improving.</p><p>So I set up ESLint rules to sort import order automatically, and also cleaned up relative paths by converting them to absolute paths using @/* based on the src directory.</p><h3>1. Setting Up Absolute Paths</h3><h4>If You’re Using Create React App (CRA)</h4><p>Create a jsconfig.json file at the project root and add the following:</p><pre>// jsconfig.json<br><br>{<br>  &quot;compilerOptions&quot;: {<br>    &quot;baseUrl&quot;: &quot;src&quot;,<br>    &quot;paths&quot;: {<br>      &quot;@/*&quot;: [&quot;./*&quot;]<br>    }<br>  }<br>}</pre><h4>📌 Before (relative paths):</h4><pre>import MyComponent from &quot;../../component/MyComponent&quot;;<br>import useCustomHook from &quot;../../hook/useCustomHook&quot;;<br>import apiClient from &quot;../../api/client&quot;;</pre><h4>📌 After (absolute paths with @):</h4><pre>import MyComponent from &quot;@/component/MyComponent&quot;;<br>import useCustomHook from &quot;@/hook/useCustomHook&quot;;<br>import apiClient from &quot;@/api/client&quot;;</pre><ul><li>@/ is automatically resolved to src/, <strong>keeping the code clean!</strong></li><li>Much better for <strong>maintainability and readability</strong></li></ul><h3>2. Sorting Imports with ESLint</h3><h4>Step 1: Install eslint-plugin-import</h4><p>First, install the eslint-plugin-import package in your project.</p><pre>npm install --save-dev eslint-plugin-import<br># or with yarn<br>yarn add -D eslint-plugin-import</pre><p>✔️ Use the --save-dev flag to add it as a dev dependency.</p><h4>Step 2: Add the import/order Rule to Your .eslintrc</h4><p>Update your .eslintrc file to automatically enforce import sorting.</p><p><strong>Add the following to your </strong><strong>.eslintrc:</strong></p><pre>&quot;import/order&quot;: [<br>  &quot;warn&quot;,<br>  {<br>    &quot;groups&quot;: [<br>      &quot;builtin&quot;,<br>      &quot;external&quot;,<br>      &quot;internal&quot;,<br>      [&quot;sibling&quot;, &quot;parent&quot;, &quot;index&quot;]<br>    ],<br>    &quot;pathGroups&quot;: [<br>      {<br>        &quot;pattern&quot;: &quot;react&quot;,<br>        &quot;group&quot;: &quot;builtin&quot;,<br>        &quot;position&quot;: &quot;before&quot;<br>      },<br>      {<br>        &quot;pattern&quot;: &quot;react-dom&quot;,<br>        &quot;group&quot;: &quot;builtin&quot;,<br>        &quot;position&quot;: &quot;before&quot;<br>      },<br>      {<br>        &quot;pattern&quot;: &quot;react-dom/*&quot;,<br>        &quot;group&quot;: &quot;builtin&quot;,<br>        &quot;position&quot;: &quot;before&quot;<br>      },<br>      {<br>        &quot;pattern&quot;: &quot;recoil&quot;,<br>        &quot;group&quot;: &quot;external&quot;,<br>        &quot;position&quot;: &quot;before&quot;<br>      }<br>    ],<br>    &quot;pathGroupsExcludedImportTypes&quot;: [&quot;builtin&quot;],<br>    &quot;alphabetize&quot;: { &quot;order&quot;: &quot;asc&quot;, &quot;caseInsensitive&quot;: true }<br>  }<br>]</pre><h4><strong>If you’re using </strong><strong>eslint.config.js instead:</strong></h4><p>Add &quot;import/order&quot; to rules the same way, and also add import: importPlugin to plugins:</p><pre>// eslint.config.js<br>import importPlugin from &quot;eslint-plugin-import&quot;;<br>    <br>plugins: {<br>  &quot;react-hooks&quot;: reactHooks,<br>  &quot;react-refresh&quot;: reactRefresh,<br>  import: importPlugin,<br>},</pre><h4>Step 3: Understanding the import/order Groups</h4><ul><li><strong>builtin</strong> → Node.js built-in modules (react, react-dom, fs, path, http, etc.)</li><li><strong>external</strong> → npm libraries like axios, lodash, moment</li><li><strong>internal</strong> → Internal project modules (e.g., @/component/)</li><li><strong>parent</strong> → Relative path modules starting with ../</li><li><strong>sibling</strong> → Modules in the same folder starting with ./</li><li><strong>index</strong> → Files like index.js</li></ul><p>✔️ Auto alphabetical sorting is enabled with: &quot;alphabetize&quot;: { &quot;order&quot;: &quot;asc&quot;, &quot;caseInsensitive&quot;: true }</p><h4>Step 4: Result After Applying the Rules</h4><h4>❌ Before (React modules mixed in the middle):</h4><pre>import html2canvas from &quot;html2canvas&quot;;<br>import jsPDF from &quot;jspdf&quot;;<br>import { useCallback, useRef } from &quot;react&quot;;<br>import { createRoot } from &quot;react-dom/client&quot;;<br>import { RecoilRoot } from &quot;recoil&quot;;</pre><h4>✅ After (React modules sorted to the top):</h4><pre>import { useCallback, useRef } from &quot;react&quot;;<br>import { createRoot } from &quot;react-dom/client&quot;;<br>import { RecoilRoot } from &quot;recoil&quot;;<br>import html2canvas from &quot;html2canvas&quot;;<br>import jsPDF from &quot;jspdf&quot;;</pre><p>Typically, people add newlines between groups, but I personally prefer no newlines — so I didn’t add &quot;newlines-between&quot;: &quot;always&quot;.</p><h4>Step 5: Enable Auto-Fix on Save in VS Code</h4><p>To automatically sort imports when you save (⌘ + S), add the following to your VS Code settings.</p><h4>📌 VS Code settings (settings.json):</h4><pre>{<br>  &quot;editor.codeActionsOnSave&quot;: {<br>    &quot;source.fixAll.eslint&quot;: true<br>  }<br>}</pre><p>Setting &quot;source.fixAll.eslint&quot;: true ensures all ESLint rules are auto-fixed on save.</p><h4>References</h4><p><a href="https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/order.md">eslint-plugin-import/docs/rules/order.md at main · import-js/eslint-plugin-import</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=530fe58d94e2" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[React + TypeScript + Vite: My Project Setup Checklist]]></title>
            <link>https://medium.com/@sojjung3/%EC%A0%80%EC%9E%A5%EC%9A%A9-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%B4%88%EA%B8%B0%EC%84%B8%ED%8C%85-react-vite-11c8e4e1cf03?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/11c8e4e1cf03</guid>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Wed, 09 Oct 2024 07:44:27 GMT</pubDate>
            <atom:updated>2026-03-17T10:35:24.386Z</atom:updated>
            <content:encoded><![CDATA[<h3>1. Create a Repository on GitHub</h3><p>Click the green <strong>New</strong> button on the right.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZA_rISW0T1fUMd6zquTXBA.png" /></figure><h3>2. Clone the Empty Repo Locally</h3><pre>git clone &lt;your-repo-url&gt;<br>cd &lt;cloned-repo-folder&gt;</pre><h3>3. Create a Project with Vite</h3><pre>npm create vite@latest . # Create a Vite project in the current directory<br><br>git init<br>git add .<br>git commit -m &quot;init&quot;</pre><pre>npm install<br>npm run dev</pre><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*xnyjzt7l4m0SHUY-PCQcrg.png" /></figure><h4>3–1. Push to the Remote Repo</h4><pre>git branch -M main<br>git remote add origin &lt;your-repo-url&gt; # Can be skipped in most cases<br>git push -u origin main</pre><p>At this point, your repository should look something like this:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Nb5uoDn2QT6nw2JLyoT8cg.png" /></figure><h3>4. ESLint Configuration</h3><pre>module.exports = {<br>  root: true,<br>  env: { browser: true, es2020: true }, // Browser and ES2020 environment<br><br>  extends: [<br>    &#39;eslint:recommended&#39;, // ESLint default recommended rules<br>    &#39;plugin:@typescript-eslint/recommended&#39;, // TypeScript recommended rules<br>    &#39;plugin:react-hooks/recommended&#39;, // React Hooks rules<br>  ],<br><br>  ignorePatterns: [&#39;dist&#39;, &#39;.eslintrc.cjs&#39;], // Files/directories to ignore<br><br>  parser: &#39;@typescript-eslint/parser&#39;, // Use TypeScript parser<br><br>  plugins: [&#39;react-refresh&#39;], // React Refresh plugin<br><br>  rules: {<br>    &#39;react-refresh/only-export-components&#39;: [<br>      &#39;warn&#39;,<br>      { allowConstantExport: true },<br>    ], // Components should only export components<br><br>    indent: [&#39;error&#39;, 2], // 2-space indentation<br><br>    &#39;no-unused-vars&#39;: &#39;off&#39;, // Disable base unused vars rule<br><br>    &#39;@typescript-eslint/no-unused-vars&#39;: [<br>      &#39;error&#39;,<br>      { vars: &#39;all&#39;, args: &#39;after-used&#39;, ignoreRestSiblings: false },<br>    ], // Detect unused variables in TypeScript<br><br>    &#39;@typescript-eslint/explicit-function-return-type&#39;: &#39;warn&#39;,<br>    // Warn when function return types are not explicitly declared<br><br>    &#39;no-empty&#39;: &#39;warn&#39;, // Warn on empty block statements<br><br>    semi: [&#39;error&#39;, &#39;always&#39;], // Enforce semicolons<br>  },<br>};</pre><p>.eslintrc</p><pre>{<br>  &quot;extends&quot;: [&quot;react-app&quot;, &quot;plugin:prettier/recommended&quot;],<br>  &quot;rules&quot;: {<br>    &quot;no-var&quot;: &quot;warn&quot;, // Disallow var<br>    &quot;no-multiple-empty-lines&quot;: &quot;warn&quot;, // Disallow multiple blank lines<br>    &quot;no-console&quot;: [&quot;warn&quot;, { &quot;allow&quot;: [&quot;warn&quot;, &quot;error&quot;] }], // Disallow console.log()<br>    &quot;eqeqeq&quot;: &quot;warn&quot;, // Require strict equality operators<br>    &quot;dot-notation&quot;: &quot;warn&quot;, // Use dot notation when possible<br>    &quot;no-unused-vars&quot;: &quot;warn&quot;, // Disallow unused variables<br>    &quot;react/destructuring-assignment&quot;: &quot;warn&quot;, // Enforce destructuring for state/props<br>    &quot;react/jsx-pascal-case&quot;: &quot;warn&quot;, // Component names must be PascalCase<br>    &quot;react/no-direct-mutation-state&quot;: &quot;warn&quot;, // Disallow direct state mutation<br>    &quot;react/jsx-no-useless-fragment&quot;: &quot;warn&quot;, // Disallow unnecessary fragments<br>    &quot;react/no-unused-state&quot;: &quot;warn&quot;, // Disallow unused state<br>    &quot;react/jsx-key&quot;: &quot;warn&quot;, // Require key on iterated elements<br>    &quot;react/self-closing-comp&quot;: &quot;warn&quot;, // Use self-closing tags when possible<br>    &quot;react/jsx-curly-brace-presence&quot;: &quot;warn&quot;, // Disallow unnecessary curly braces in JSX<br>    &quot;prettier/prettier&quot;: [&quot;error&quot;, { &quot;endOfLine&quot;: &quot;auto&quot; }]<br>  },<br>  &quot;settings&quot;: {<br>    &quot;import/resolver&quot;: {<br>      &quot;node&quot;: {<br>        &quot;moduleDirectory&quot;: [&quot;node_modules&quot;, &quot;src&quot;]<br>      }<br>    }<br>  }<br>}</pre><h3>5. Prettier Configuration</h3><pre>npm install -D prettier</pre><pre>// .prettierrc<br>{<br>   &quot;trailingComma&quot;: &quot;none&quot;,<br>   &quot;endOfLine&quot;: &quot;auto&quot;,<br>   &quot;tabWidth&quot;: 2,<br>   &quot;semi&quot;: true,<br>   &quot;singleQuote&quot;: true<br>}</pre><pre>// .prettierignore<br>node_modules/<br>build/<br>dist/<br>*.min.js</pre><h3>6. .vscode/settings.json</h3><pre>{<br>  &quot;editor.defaultFormatter&quot;: &quot;esbenp.prettier-vscode&quot;,<br>  &quot;editor.formatOnSave&quot;: true<br>}</pre><h3>7. Create an .nvmrc File</h3><p>Set the Node version for the project.</p><pre>// .nvmrc<br><br>20.14.0</pre><h3>8. Install a Styling Library</h3><pre>npm install styled-components</pre><h3>9. Router</h3><pre>npm install react-router-dom</pre><h3>10. Write the README.md</h3><pre># &lt;Project Name&gt;<br><br>## 🎧 About Project<br>Project description<br><br>## ✨ Installation<br>```bash<br>git clone &lt;repo-url&gt;<br>nvm use<br>npm install<br>npm run dev<br>```</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=11c8e4e1cf03" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[1주일에서 1년이 되어버린.. 사이드 프로젝트 회고]]></title>
            <link>https://medium.com/@sojjung3/1%EC%A3%BC%EC%9D%BC%EC%97%90%EC%84%9C-1%EB%85%84%EC%9D%B4-%EB%90%98%EC%96%B4%EB%B2%84%EB%A6%B0-%EC%82%AC%EC%9D%B4%EB%93%9C-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-a8535093b5e5?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/a8535093b5e5</guid>
            <category><![CDATA[personal-color-analysis]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[side-project]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Sun, 06 Oct 2024 20:43:07 GMT</pubDate>
            <atom:updated>2026-10-15T17:28:49.043Z</atom:updated>
            <content:encoded><![CDATA[<p>안녕하세요, 수야입니다 🙂</p><p>저에게는 개발을 시작하고 꾸준히 진행하고 있는 사이드 프로젝트가 하나 있는데요,</p><p>이번에 major 버전을 업그레이드 하면서 돌이켜보니 1년이 넘는 시간이 훌쩍 지났더라구요!?</p><p>이참에 사이드 프로젝트 관련하여 회고록을 남겨보면 좋겠다는 생각에 이렇게 회고록을 작성하게 되었습니다 :)</p><h3>🎧 시작하게 된 계기</h3><p>시작하게 된 계기는 작년 여름 당시 취준생이던 친구가 “테오의 스프린트 14기”를 신청하고</p><p>“당장 내일 아이디어 발표인데 나 뭘 할지 못 정했어” 라고 말을 했고</p><p>그 말을 들은 저는 핸드폰을 꺼내어 메모장에 있던 저의 아이디어를.. 방출했습니다.</p><p>한 6가지 정도 말했던걸로 기억하는데</p><p>그 중 친구는 “퍼스널컬러 자가진단”이 괜찮다며 사용하겠다고 했습니다.</p><p>그러고 발표를 얼마나 잘한건지 다음날 친구는 50명 정도의 아이디어 중에서 Top10으로 선정 되어 본인이 팀장이 되었다고 전해줬습니다. 😂</p><p>그렇게 친구는 일주일간 진행된 스프린트를 무사히 마쳤고,</p><p>“몇몇 팀원들과 이 프로젝트를 더 보완해나가고 싶은데, 너도 같이 할래?” 라고 제안해주어 그렇게 이 팀에 합류하게 되었습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*QakGPXjkzvsKW70LW9-Ocw.png" /><figcaption>테오의 스프린트 당시 아이디어 회의 일부</figcaption></figure><p>합류해서 맡았던 첫 작업은 봄, 여름, 가을, 겨울 이렇게 4가지였던 퍼스널컬러 타입을 12유형으로 확장하는 작업이었습니다.</p><p>12타입에 쓰일 <strong>색상, 타입별 설명, 연예인 이미지</strong> 등등 꽤나 많은 정보가 필요했고 “<strong>결과 타입 도출 로직</strong>”을 구현하는데에도 많은 고민이 들어갔습니다.</p><p>온라인 자가진단이지만 그래도 결과가 정확하게 나오면 좋겠고.. 그렇다고 또 너무 복잡한 과정을 거치면 안되고.. 결과적으로는 9번의 단계에서 모든 유형의 컬러가 3번씩 등장하고, 필요시 보너스단계를 진행하는 것으로 결론 내렸습니다.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*IhdZG_KTiRYm3s8BjL5lsA.png" /></figure><p>그렇게 만들어진 결과 페이지..!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*1HZOw2P56EDV8JW57gijAw.png" /><figcaption>TMI) 나는 여름 라이트 타입이다</figcaption></figure><h3>🏃🏻‍♀️ 사이드 프로젝트에서 진행한 것</h3><p>사실 일년내내 계속 이 프로젝트만 진행했던 건 아니고, 중간에 다른 프로젝트도 시도하고 방학?도 가졌지만</p><p>어찌됐든 유지보수를 진행하며 이 사이트는 초기 모습과는 꽤나 많이 달라졌습니다.</p><p>그동안 진행되었던 작업들로는</p><ul><li>JavaScript → TypeScript 마이그레이션</li><li>Next.js 도입</li><li>SEO 개선</li><li>다국어 서비스</li><li>퍼스널컬러 타인 대리 테스트 기능</li><li>랜딩 페이지에 3D 이미지 추가</li><li>컬러칩 선택 인터랙션</li><li>CI/CD</li><li>코딩 컨벤션</li></ul><p>(사실 더 많지만 여기까지만..)</p><p>매주 일요일 아침10시 일어나는 것도 힘든데 주간회의를 하며 회의록도 열심히 남기고</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*uOdd2Yj4X36TvxzGKmAeaw.png" /></figure><p>마일스톤 설정하여 자체 스프린트 진행</p><p>: 작업할 내용 리스트업 후, 가치와 비용 기준으로 우선순위 정해서 진행하는 방식</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hGH17iw1IwNPm9TNGlEY4w.png" /></figure><p>서로 코드리뷰 해주는 팀원들..</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*YpxBmdl5zZb0czQZR6W5XQ.png" /></figure><p>각자 작업한 내용도 문서화 해보고</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*PpKwBPKp_JY41bmt2dVt8w.png" /></figure><p>열심히 꾸민 팀 노션 페이지</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WMCKYeztwrtaZyI65V8DHw.png" /></figure><p>그렇게 다가온 마지막 배포!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WNYK5U61Mq7x6JlmGHpjbA.png" /></figure><h3>🩰 사이드 프로젝트를 하며 느낀점 (반모주의)</h3><p>어느날 문득 들었던 생각이</p><p>어떻게 보면 나의 메모장에 들어있던 작은 아이디어 하나였는데,</p><p>그게 세상 밖으로 나오고</p><p>친구들의 도움으로 완성도가 높아져가고</p><p>그러다 결국 누군가에게 사용되는 모습을 보니 기분이 되게 묘했다.</p><p>이제 더이상 이 아이디어를 설명할 때 긴 말 필요없이 사이트를 보여주면 되고, 그렇게 생각하니 그동안 함께 달려와준 팀원들에게 너무 고마웠다.</p><p>잘하는데 열심히까지 하는 친구들 덕분에 옆에서 많이 배우고<br>재밌는 경험, 기억에 남을 추억이 만들어졌다.</p><p>퇴근하고 따로 시간을 내어 사이드 프로젝트를 한다는게 쉽지 않지만</p><p>혼자였다면 절대 해내지 못했을 것들이 함께였기에 가능했고</p><p>처음에는 별 기대 없이 시작한 프로젝트에서 많은 것을 얻은 프로젝트가 되어</p><p>누군가가 사이드 프로젝트 하는 것을 망설이고 있다면 하는 것을 적극 권장합니다 ㅎㅎ</p><p>마무리는 첫 오프라인 만남때 방탈출 사진으로 ㅎㅎ..</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*-amuomaJogzA5nssuQUeKg.png" /></figure><p>긴 글 읽어주셔서 감사합니다!!</p><p>샤라웃 to 설탕, 준, 코코 ✨</p><p>지금 바로 내 퍼스널컬러 확인하러 가기 =&gt; <a href="https://omct.web.app/ko">https://omct.web.app/ko</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a8535093b5e5" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript Modules: CJS vs. ESM]]></title>
            <link>https://medium.com/@sojjung3/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EB%AA%A8%EB%93%88-cjs%EC%99%80-esm%EC%9D%98-%EC%B0%A8%EC%9D%B4-d133660d01a8?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/d133660d01a8</guid>
            <category><![CDATA[javascript-modules]]></category>
            <category><![CDATA[import]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[es6]]></category>
            <category><![CDATA[commonjs]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Tue, 01 Oct 2024 14:13:45 GMT</pubDate>
            <atom:updated>2026-03-17T10:45:42.409Z</atom:updated>
            <content:encoded><![CDATA[<p>While working on a few projects recently, I ran into two errors.</p><p>The first was about needing to use the .cjs extension for the ESLint config file.</p><p>The second was a library failing to find a .config.js file due to an incorrect file extension.</p><p>Both of these errors are related to JavaScript’s module system.</p><p>JavaScript has two module systems: <strong>CommonJS (CJS)</strong> and <strong>ECMAScript Modules (ESM)</strong>.</p><p>In this post, I’ll cover the differences between the two, and why JavaScript ended up with two separate ways of loading modules in the first place.</p><h3>The JavaScript Standard</h3><p>To understand the difference, it helps to first note that JavaScript is used on both the backend and the frontend.</p><p>From its introduction in 1995 through 2009, JavaScript was originally used only on the frontend.</p><p>Then in 2009, Node.js came along — an environment that made it possible to write backend code in JavaScript. However, the base JavaScript standard didn’t have APIs for things like file system access or networking. To address this, <strong>CommonJS</strong> was introduced as a new standard for backend development.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*Xe7nnF8xLz89P4Z6O-3zlQ.png" /><figcaption>Source : <a href="https://www.youtube.com/@%ED%94%84%EB%A1%A0%ED%8A%B8%EB%A7%A8-e2b">YouTube 프론트맨</a></figcaption></figure><h3>The Emergence of CommonJS</h3><p>Along with the APIs mentioned above, CommonJS also introduced a <strong>module management system</strong> — and that’s where the require syntax first appeared.</p><p>This is exactly why you get an error when you try to use require in a JavaScript environment that doesn&#39;t have CommonJS applied.</p><p>So the backend now had a proper module system with CommonJS, but the frontend still had no module management system of its own.</p><p>(There were workarounds like global variables and &lt;script&gt; tags, but these came with namespace collision issues and performance problems.)</p><p>Some developers got around this by using bundlers like webpack to emulate a CommonJS environment and use require in the browser.</p><p>So two problems remained:</p><ol><li>The frontend still lacked a proper module management system</li><li>The JavaScript standards for backend and frontend were out of sync</li></ol><h3>The Emergence of ECMAScript Modules</h3><p>Then in 2015, ES6 arrived and finally introduced a new syntax for managing modules on the frontend: import.</p><p>import has two key advantages over require: it&#39;s better suited for <strong>static analysis</strong> and <strong>code optimization</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*UWgdlQukin3vcUZ3uHqc5w.png" /></figure><p>First, unlike require, which can be placed anywhere in the code, import must be placed at the top of the file. This addresses the readability issues caused by module loading logic scattered throughout the code.</p><p>Second, import explicitly declares dependency modules, making it easy to see which parts of a module are dependencies — and it&#39;s also favorable for <strong>tree shaking</strong>.</p><blockquote>“Tree shaking” is an optimization technique that removes unused code at build time to reduce bundle size.</blockquote><p>Today, ES6 is supported not only on the frontend but also in Node.js LTS versions, so import can be used in both backend and frontend environments.</p><p>If you’re on an older version of Node.js that only supports CommonJS, you’ll need to use require. Similarly, if your frontend targets legacy browsers that don&#39;t support ES6, you&#39;ll need to use a bundler or manage modules through global variables.</p><h3>The type Field in package.json</h3><p>.cjs files are always interpreted as CJS, and .mjs files are always interpreted as ESM.</p><p>So what determines how a .js file is interpreted?</p><p>It’s determined by the <strong>type field in </strong><strong>package.json</strong>.</p><ul><li>The default value is &quot;commonjs&quot;, in which case .js files are interpreted as CJS.</li><li>If set to &quot;module&quot;, .js files are interpreted as ESM.</li></ul><h3>CJS vs. ESM Syntax</h3><h4>CommonJS (CJS)</h4><pre>// add.js<br>module.exports.add = (x, y) =&gt; x + y;<br><br>// main.js<br>const { add } = require(&#39;./add&#39;);<br><br>add(1, 2);</pre><h4><strong>ECMAScript Modules (ESM)</strong></h4><pre>// add.js<br>export function add(x, y) {<br> return x + y<br>}<br><br>// main.js<br>import { add } from &#39;./add.js&#39;;<br><br>add(1, 2);</pre><p>CJS uses require / module.exports.</p><p>ESM uses import / export statements.</p><h4>References</h4><ul><li><a href="https://www.youtube.com/watch?v=KlpPYjCo0Do">https://www.youtube.com/watch?v=KlpPYjCo0Do</a></li><li><a href="https://toss.tech/article/commonjs-esm-exports-field">https://toss.tech/article/commonjs-esm-exports-field</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d133660d01a8" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why Use TypeScript?]]></title>
            <link>https://medium.com/@sojjung3/%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EB%A5%BC-%EC%93%B0%EB%8A%94-%EC%9D%B4%EC%9C%A0-9bdbf2ee6acb?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/9bdbf2ee6acb</guid>
            <category><![CDATA[interpreters]]></category>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[typescript]]></category>
            <category><![CDATA[compilers]]></category>
            <category><![CDATA[transpilers]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Sun, 22 Sep 2024 02:19:25 GMT</pubDate>
            <atom:updated>2026-03-18T05:21:19.092Z</atom:updated>
            <content:encoded><![CDATA[<p>These days, many frontend developers are using TypeScript instead of JavaScript. In this post, I’ll explore why TypeScript has become such a popular choice.</p><h3>The Weaknesses of JavaScript</h3><p>JavaScript is a “dynamically typed language.”</p><p>In a dynamically typed language, developers don’t specify the types of variables when writing code. But this makes it very likely that unexpected bugs will surface.</p><p>The weaknesses of JavaScript:</p><h4>☑️ Variable types are determined at runtime.</h4><ul><li>Because JavaScript resolves variable types at runtime, mistakes are easy to make and hard to track down.</li><li>Errors caused by these mistakes only reveal themselves when the program actually runs.</li></ul><h4>☑️ <strong>Weak type checking.</strong></h4><ul><li>JavaScript only provides keywords like let and const to distinguish between variables and constants.</li><li>For example, let a = 1; a = &quot;hello&quot;; is perfectly valid (changing from number to string), which can lead to unexpected behavior.</li><li>It doesn’t throw an error, but the type change can easily cause unintended side effects.</li></ul><h3>Enter TypeScript!</h3><p>TypeScript is an open-source programming language developed by Microsoft. It includes all of JavaScript’s features while providing additional capabilities — namely type definitions and type checking.</p><p>TypeScript requires you to specify variable types when writing code, enabling static analysis to catch errors that would otherwise only appear at runtime.</p><p>Without this, errors would only surface after deployment to production, meaning users would be affected before you could fix anything.</p><p>Of course, TypeScript only catches type-related errors — you still have to find other bugs on your own. But catching type-related errors alone covers a significant portion of all errors.</p><p>Here’s how it differs from JavaScript:</p><h4>☑️ <strong>Runtime type resolution → Compile-time type checking</strong></h4><p><em>The problem with JavaScript code:</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*8iNOlelR1LSJakQQawWmlA.png" /><figcaption>You create an addition function expecting numeric results, but get a string instead. When code like this accumulates, debugging a live service becomes extremely painful.</figcaption></figure><p>What changed after introducing TypeScript:</p><ul><li>Errors caught in VS Code</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*U0o_2DcFvUg6yOvYQCa8lQ.png" /><figcaption>VS Code raises error messages the moment you type the code.</figcaption></figure><ul><li>Errors caught at compile time</li></ul><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*psVkSCDc17Th5J9mTuedHA.png" /><figcaption>Since types are checked at compile time, the code fails to execute and throws an error instead.</figcaption></figure><h4>☑️ Weak type checking → Error messages the moment you type in VS Code</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SL4sUmjh5ae_aB7HNtpO2g.png" /></figure><p>This early error detection through type checking is arguably the biggest benefit.</p><p>Now that we’ve covered the basic differences, let’s take a closer look at the terms “compile” and “static typing” mentioned above.</p><h3>Compiled Languages vs. Interpreted Languages</h3><p>TypeScript is a <strong>compiled language</strong>; JavaScript is an <strong>interpreted language</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*hPZrbGmrI1l0rLlykB3kiQ.png" /></figure><p>With a <strong>compiled language</strong>, once you finish writing your code, it goes through a process called compilation that transforms it into a different form. The output could be machine code, bytecode for a virtual machine (like Java’s .class files), or even JavaScript — as in the unique case of TypeScript. The key characteristic of compiled languages is that this compilation step happens before execution.</p><p>An <strong>interpreted language</strong>, on the other hand, doesn’t go through a separate build step. The code you write is sent directly to an interpreter, which translates it into machine code on the fly as it runs.</p><p>Strictly speaking, languages like JavaScript do go through a compilation phase at the execution stage. But the important distinction is this: during the development phase (before the code runs), compiled languages include a compilation step, while interpreted languages do not.</p><h3>Data Types: Static vs. Dynamic Typing</h3><p>Generally, compiled languages use static typing, while interpreted languages use dynamic typing.</p><p><strong>Dynamic typing</strong> is convenient for writing simple code, but as a project grows, the risk of runtime errors increases — like passing a string into a function that expects a number.</p><p>Since type consistency isn’t enforced at the time of writing, problems only explode at runtime.</p><p>This is why you need to be extra careful about type-related errors when using dynamically typed languages.</p><p>TypeScript was created to address exactly this. You write code safely with static types, then compile (transpile) it to JavaScript that runs in the browser or Node.js — preventing type-related errors along the way.</p><p>With a <strong>statically typed language</strong>, you must declare data types upfront when defining variables. The variable’s type can’t be changed, only values matching the declared type can be assigned, and type checking happens at compile time — failing to pass means an error.</p><p>In JavaScript, a variable’s type is determined not by declaration but by assignment (type inference). And through reassignment, a variable’s type can change dynamically at any time.</p><p>Fundamentally, “variables” don’t have types — “values” do. It’s more accurate to say that a variable’s type is dynamically determined (inferred) by the value assigned to it.</p><pre>let a = 1;   // The type of 1 (number) becomes the type of variable a<br>a = &#39;1&#39;;     // The type of &#39;1&#39; (string) changes the type of variable a</pre><h3>Is TypeScript Really Necessary?</h3><p>Now that we’ve looked at the differences, is TypeScript truly essential?</p><p>First, let’s understand how browsers read TypeScript.</p><p>Browsers like Chrome, Whale, and Safari don’t understand TypeScript — they only have a JavaScript engine built in, not a TypeScript engine.</p><p>So if they can only understand JavaScript, how can we use TypeScript?</p><p>The answer is <strong>transpilation</strong>.</p><p><strong>Compilation</strong> refers to converting a higher-level (more abstracted) language into a lower-level language. <strong>Transpilation</strong>, on the other hand, converts between languages at a similar or equal level of abstraction.</p><p>Since JavaScript isn’t a lower-level language like machine code, the TS → JS process is called transpilation.</p><p>In short: browsers can’t understand TypeScript, so it gets converted to JavaScript before running in the browser.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N7bm7CCxPhdbnH7IFajsdQ.png" /></figure><p>This means TypeScript code goes through a transpilation step, which adds to build time.</p><h3>So Why Use TypeScript Despite the Extra Complexity?</h3><p>If it adds complexity and increases build time, why bother with TypeScript?</p><p>Frontend code doesn’t run on a server — it runs on each user’s device.</p><p>That device could be a latest Android phone, an iPhone, or a Windows PC. In extreme cases, it might even be a device like a Galaxy S2 that no longer receives updates.</p><p>Modern JavaScript syntax might not work on these older devices. To support them, we use <strong>polyfills</strong> — which convert code into older JavaScript syntax that legacy engines can handle.</p><p>In other words, frontend code almost always goes through a polyfill conversion step before deployment. (This process also includes optimizations like minification to reduce code size.)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KAgskfbtDBsbMT_AsMjwJQ.png" /></figure><p>So converting TypeScript to JavaScript fits naturally into this existing workflow — it doesn’t add significant overhead.</p><p>The reasoning boils down to this: “Since we’re already going to transform the code for browser compatibility and optimization anyway, let’s use TypeScript for safety and convenience during development and just include it in the transformation.”</p><p>Switching from JavaScript to TypeScript can feel tedious at first, because you lose the “flexibility” that JavaScript provided. But in return, TypeScript gives you <strong>stability</strong>. The scope of errors caused by type issues is larger than you might expect, and discovering them in production is costly — both in terms of engineering time and user experience. By using TypeScript, you can improve the stability and user experience of your web applications, and that’s its greatest advantage.</p><h4>References</h4><ul><li><a href="https://www.youtube.com/watch?v=KlpPYjCo0Do">Understanding TypeScript in 5 Minutes (Transpile, Compile, Polyfill)</a></li><li><a href="https://www.youtube.com/watch?v=50N_NiDUKpw">Data Types and Static/Dynamic Typing (+ Relationship with Compiled/Interpreted Languages)</a></li><li><a href="https://devlog-of-yein.tistory.com/6">What Is Compilation, and Is JavaScript an Interpreted Language?</a></li><li><a href="https://pks2974.medium.com/v8-%EC%97%90%EC%84%9C-javascript-%EC%BD%94%EB%93%9C%EB%A5%BC-%EC%8B%A4%ED%96%89%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95-%EC%A0%95%EB%A6%AC%ED%95%B4%EB%B3%B4%EA%B8%B0-25837f61f551">How V8 Executes JavaScript Code</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=9bdbf2ee6acb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How Browsers Render a Web Page]]></title>
            <link>https://medium.com/@sojjung3/%EB%B8%8C%EB%9D%BC%EC%9A%B0%EC%A0%80%EC%9D%98-%EB%A0%8C%EB%8D%94%EB%A7%81-%EA%B3%BC%EC%A0%95-96ec62b24f29?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/96ec62b24f29</guid>
            <category><![CDATA[browser-rendering]]></category>
            <category><![CDATA[dom-tree]]></category>
            <category><![CDATA[gpu-acceleration]]></category>
            <category><![CDATA[browsers]]></category>
            <category><![CDATA[reflow]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Fri, 20 Sep 2024 08:14:03 GMT</pubDate>
            <atom:updated>2026-03-18T05:36:42.787Z</atom:updated>
            <content:encoded><![CDATA[<p>As frontend developers, we deal with everything visible on a website’s screen. Understanding the browser’s internal structure — how it processes web pages and delivers them to users — enables us to build better, more efficient web applications.</p><h3>Browser Internal Structure</h3><p>A browser’s internal structure consists of the following components:</p><ul><li>User Interface</li><li>Browser Engine</li><li>Rendering Engine</li><li>Networking</li><li>JavaScript Engine</li><li>UI Backend</li><li>Data Storage</li></ul><p>Among these, the <strong>rendering engine</strong> parses web page resources like HTML, CSS, and JavaScript, and displays them on screen.</p><p>The engine primarily parses HTML to build the DOM tree, parses CSS to create the CSSOM, combines them to form the render tree, and ultimately draws the page on screen.</p><p>Let’s look at this in more detail.</p><h3>The Browser Rendering Process</h3><p>The browser rendering process broadly follows these stages: <strong>DOM Tree Creation → CSSOM Creation → Layout (Reflow) → Paint (Repaint) → Compositing</strong>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/624/1*aTEAZREEGJjfKPEtJqLwmw.png" /><figcaption>Source:<a href="https://velopert.com/3236"> https://velopert.com/3236</a></figcaption></figure><ol><li><strong>Receiving the HTML document:</strong> The browser receives the HTML document from the server. The HTML document is transmitted as plain text.</li><li><strong>Parsing the HTML document:</strong> The browser parses the received HTML document. Parsing is the process of converting HTML text into a tree structure the browser can understand. This produces the DOM (Document Object Model) tree. The DOM tree represents each HTML element as a node, with elements connected through parent-child relationships.</li><li><strong>CSS parsing and style calculation:</strong> The browser also downloads and parses CSS files linked within the HTML document. It then applies CSS style rules to construct the Render Tree. Unlike the DOM tree, the render tree only includes elements that will actually be displayed on screen. For example, elements hidden with display: none are not included in the render tree.</li><li><strong>JavaScript parsing and execution:</strong> When a &lt;script&gt; tag is encountered during HTML parsing, the browser downloads and executes the JavaScript. Since JavaScript can manipulate the DOM, DOM parsing may be blocked. To prevent this, you can add defer or async attributes to the &lt;script&gt; tag to enable asynchronous execution.</li><li><strong>Layout calculation:</strong> After both the DOM and CSSOM (CSS Object Model) have been parsed, the browser calculates the size and position of each element. This is called the layout stage, where the on-screen placement of elements is determined.</li><li><strong>Painting:</strong> Once the layout of all elements is determined, the browser draws them onto the screen. Elements are rendered according to their style properties — text, colors, shadows, and so on.</li><li><strong>Compositing:</strong> The browser splits certain areas of the page into separate layers and composites them. This optimizes performance for operations like scrolling and animations.</li></ol><h3>Improving Browser Performance</h3><p>The size and complexity of the DOM and CSSOM directly impact the browser’s rendering performance. If the DOM is too large or CSS rules are overly complex, rendering takes longer, which can delay page load times and negatively affect the user experience.</p><p><strong>Reflow</strong> occurs when there are changes that affect an element’s layout. It’s relatively expensive because the browser has to recalculate the position and size of all child and surrounding elements.</p><p><strong>Repaint</strong>, on the other hand, occurs when only an element’s visual properties (color, background, border) change. Since it doesn’t affect layout, it’s relatively less expensive than reflow.</p><p>To improve browser performance, it’s best to minimize reflow and repaint. Ways to do this include minimizing DOM changes, using a virtual DOM, reducing complex CSS selectors, minimizing style changes, and leveraging GPU acceleration by using CSS animations instead of JavaScript.</p><h3>Benefits of GPU Acceleration</h3><p>By default, the browser uses the CPU to process and render DOM elements. However, certain CSS properties like transform and opacity can leverage GPU acceleration — the GPU handles graphics and video rendering tasks instead of the CPU, resulting in better performance.</p><p>The benefits include:</p><p><strong>Parallel processing:</strong> GPUs have many cores, allowing them to handle many tasks simultaneously at high speed. This parallel processing makes graphics and animation work much more efficient.</p><p><strong>Reduced CPU load:</strong> With GPU acceleration handling the graphics rendering, the CPU can focus on more important tasks like application logic and data management.</p><p><strong>Improved graphics performance:</strong> GPU acceleration enables smoother and faster processing of complex graphics in web pages and applications, delivering a better user experience. It’s particularly effective for smooth animations, high-resolution video playback, and 3D graphics.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=96ec62b24f29" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[JavaScript Execution Context]]></title>
            <link>https://medium.com/@sojjung3/%EC%9E%90%EB%B0%94%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%8B%A4%ED%96%89-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-execution-context-7c70ab6962ba?source=rss-387cb432e7c8------2</link>
            <guid isPermaLink="false">https://medium.com/p/7c70ab6962ba</guid>
            <category><![CDATA[javascript]]></category>
            <category><![CDATA[web-development]]></category>
            <category><![CDATA[execution-context]]></category>
            <category><![CDATA[call-stack]]></category>
            <dc:creator><![CDATA[Soojin Jung]]></dc:creator>
            <pubDate>Thu, 19 Sep 2024 08:20:44 GMT</pubDate>
            <atom:updated>2026-03-18T05:56:28.428Z</atom:updated>
            <content:encoded><![CDATA[<h3>Execution Context</h3><p>An execution context is the environment in which JavaScript code runs.</p><p>There are two types of execution context in JavaScript: the <strong>Global Execution Context</strong> and the <strong>Function Execution Context</strong>.</p><h4>1. Global Execution Context</h4><p>When the JavaScript engine first runs your code, it creates a Global Execution Context. During this creation, it generates the global object — the Window object (or Global in Node.js) — and sets this to point to it.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/288/1*cETcKDOLk-78ECdmrMwfEA.png" /></figure><p>Even with no code at all, the JavaScript engine creates a Global Execution Context the moment it runs the file.</p><h4>2. Function Execution Context</h4><p>The JavaScript engine creates a new Execution Context every time a function is called. Each function has its own Execution Context at the point of invocation.</p><blockquote>👉🏻 The JavaScript engine creates the Global Execution Context exactly once when it first runs the code, and creates a Function Execution Context each time a function is called.</blockquote><h3>Call Stack</h3><p>The call stack is a data structure that stores the Execution Contexts created as code runs. When the engine first executes a script, it creates the Global Execution Context and pushes it onto the call stack. After that, each time the engine encounters a function call, it creates a Function Execution Context and pushes it onto the stack.</p><p>The JavaScript engine executes whichever function is at the top of the call stack. When that function finishes, it’s removed (popped) from the stack, and control moves to the next function at the top.</p><p>Given the following code:</p><pre>function first() {<br>    console.log(&#39;first&#39;);<br>    second();<br>}<br><br>function second() {<br>    console.log(&#39;second&#39;);<br>}<br><br>first();</pre><p>Execution Contexts are pushed and popped from the call stack in the following order:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/898/1*uArO7Nf5l5VZbZdfonIeNg.png" /></figure><p><strong>Step-by-step breakdown:</strong></p><ol><li>The engine starts executing the JavaScript file, creates the Global Execution Context, and pushes it onto the call stack.</li><li>The engine encounters the first() function call, creates an Execution Context for first, and pushes it onto the call stack.</li><li>Inside first, the engine encounters console.log, creates an Execution Context for it, and pushes it onto the call stack.</li><li>console.log(&#39;first&#39;) finishes and is removed from the call stack.</li><li>The engine encounters the second() call, creates an Execution Context for second, and pushes it onto the call stack.</li><li>Inside second, the engine encounters console.log, creates an Execution Context for it, and pushes it onto the call stack.</li><li>console.log(&#39;second&#39;) finishes and is removed from the call stack.</li><li>second finishes and is removed from the call stack.</li><li>first finishes and is removed from the call stack.</li><li>After all code has finished executing, the JavaScript engine removes the Global Execution Context from the call stack.</li></ol><p>So far, we’ve covered what execution contexts are and when they’re created and added to the call stack.</p><p>Now let’s look at what’s inside an execution context and how the JavaScript engine creates one.</p><h3>Execution Context Creation Process</h3><p>Execution Context creation is divided into two phases:</p><ul><li><strong>Creation Phase</strong></li><li><strong>Execution Phase</strong></li></ul><p>JavaScript stores information like variables somewhere within the execution context — that place is called the <strong>Environment Record</strong>. The environment record is an object that maps identifiers to their bound values. How variables are stored in the environment record follows the rules of hoisting. (During the creation phase, new identifiers are recorded in the environment record, and this is when hoisting occurs.)</p><p>(See: <a href="https://medium.com/@sojjung3/%EB%B3%80%EC%88%98-%EC%84%A0%EC%96%B8-%EB%B0%A9%EC%8B%9D-const-let-var-%EC%97%90-%EB%94%B0%EB%A5%B8-%ED%98%B8%EC%9D%B4%EC%8A%A4%ED%8C%85%EA%B3%BC-%EC%B4%88%EA%B8%B0%ED%99%94-89349d4344bf">hoisting reference 🔗</a>)</p><p>This scanning and preparation step before actual execution is called the <strong>creation phase</strong> — declaration statements are executed first and recorded in the Environment Record.</p><p>Then the remaining code (everything other than declarations) is executed sequentially — this is the <strong>execution phase</strong>. During this phase, the engine references or updates the recorded information as needed.</p><h3>Viewing Execution Context in DevTools</h3><p>Let’s verify this execution context using browser DevTools.</p><p>Open DevTools, select the <strong>Sources</strong> tab, set a breakpoint on a specific line, and refresh the page — you’ll be able to inspect the Scope.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*W5jNAmyncAX2yUOKgeO9iA.png" /></figure><p>Note that the (anonymous) entry in the Call Stack on the left is the Global Execution Context created first, while fn1 and fn2 are Function Execution Contexts.</p><p>l0 and l2 are logged correctly in the console — but how did JavaScript retrieve these values?</p><p>First, JavaScript looks within the <strong>Local</strong> scope of the currently executing function fn2. Since l0 isn&#39;t in the local scope, it checks the <strong>Script</strong> scope below it. l0 exists there, so it returns &quot;l0&quot;. (The Script scope is a globally accessible scope.)</p><p>For l2, since it exists in the Local scope, JavaScript returns &quot;l2&quot; and stops looking further.</p><p>That’s why l0 and l2 are logged correctly.</p><p>The way the Local scope, Script scope, and Global scope are linked together is called <strong>scope chaining</strong>.</p><p>Now, if we uncomment the previously commented-out console.log and run it again, a reference error occurs.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*2z_ZuAKK-h3DwPgIYpKKsg.png" /></figure><p>Why does this error happen?</p><p>l2 is defined inside fn2. Even though fn1 calls fn2, it can&#39;t access variables defined inside fn2 — hence the error.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*rD0LfkaJFddcZiPGf0HMsw.png" /><figcaption>Refreshing and checking fn1’s scope confirms that l2 is not there.</figcaption></figure><p>In other words, even if fn2 is called from within fn1, l2 lives inside fn2&#39;s local scope — where it was declared.</p><p>If the accessible scope changed based on <em>where</em> a function is called, that would be called <strong>dynamic scope</strong>.</p><p>But JavaScript doesn’t use dynamic scope — it uses <strong>static scope</strong> (also known as <strong>lexical scope</strong>).</p><p>Back in the ES3 era, execution contexts weren’t central to identifier resolution (determining the value of variables and functions in code) the way they are now. At that time, scope varied depending on where a function was called. This meant that every time a function was invoked, the engine had to dynamically create a matching scope and wire up the scope chain.</p><p>Starting with ES5 (2009~), the mechanism for identifier resolution changed — it became managed as a single bundle called the execution context.</p><p>Scope is now determined “statically” based on where code is <em>declared</em>, regardless of where it’s <em>called</em>. Because everything is bundled into a single context concept, the JavaScript engine can resolve identifiers much faster and more efficiently.</p><p>In this sense, we’ve seen how the execution context serves as a mechanism for more efficient identifier resolution.</p><h4>References</h4><ul><li><a href="https://medium.com/sjk5766/call-stack%EA%B3%BC-execution-context-%EB%A5%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-3c877072db79">Call Stack and Execution Context</a></li><li><a href="https://www.youtube.com/watch?v=bwwaSwf7vkE">YouTube — JavaScript Closure</a></li><li><a href="https://www.youtube.com/watch?v=EWfujNzSUmw">YouTube — Execution Context</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=7c70ab6962ba" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>