Discover us

About us

Projects

Blog

Events

Members

Development Blog

GDGoC CAU 개발자와 디자이너의 작업 과정과
결과물을 공유하는 공간입니다.

어떻게 프로젝트를 시작하게 되었고,
진행하면서 느낀 개발자와 디자이너의
생생한 스토리를 직접 확인해보세요!

Development

Static/Dynamic Import

  • #Front-End
  • Junghyun Song
  • 2024. 11. 27.

Static/Dynamic Import

리소스 로딩 최적화란?

📌
웹 애플리케이션이 사용하는 리소스(JavaScript, CSS, 이미지, 폰트 등)를 효율적으로 로드하는 것
 

Why? 웹 애플리케이션의 성능을 향상시키고, 사용자 경험을 개선하기 위해

  1. 페이지 로드 속도 개선 : 사용자가 빠르게 콘텐츠를 볼 수 있도록 지원
  1. 사용자 경험(UX) 향상 : 빠르고 원활한 사용자 인터페이스 제공
  1. 네트워크 대역폭 효율화 : 데이터 크기를 줄여 네트워크 트래픽을 줄일 수 있음

How?

  • 이미지 최적화 : 이미지 크기 줄이기, 포맷 변경, Lazy Loading 등
  • 코드 최적화 : JavaScript와 CSS 난독화 및 병합, 불필요한 코드 제거
  • 리소스 로드 순서 최적화 : 중요한 리소스 우선적으로 로드하고, 나머지는 비동기로 로드
  • Lazy Loading : 사용자가 필요로 할 때만 리소스를 로드
 

Static Import 정적 자원 로딩 패턴

애플리케이션의 정적 자원(이미지, CSS, 폰트 등)을 효율적으로 로드하기 위한 패턴
정적 자원의 URL이나 경로를 소스 코드에 명시적으로 정의 ⇒ 런타임 아닌 빌드 타임에 자원을 준비한다!
 
import logo from './assets/logo.png'; import './styles/global.css'; import data from './data/info.json'; function App() { return ( <img src={logo} alt="Logo" />, <h1>Hello, World!</h1>, <div>{data.title}</div> ); }
이미지, CSS, JSON 데이터를 정적으로 가져온다. 빌드 타임에 번들링하여 런타임에 동적으로 요청하지 않는다.
 

예제 : 이모지 사용 채팅 앱

아래와 같이 이모지를 사용할 수 있는 채팅 앱을 만든다고 가정하자. 입력창에서 이모지 버튼을 누르면 이모지창이 나타난다.
notion image
notion image
 
// App.js import React from "react"; // 정적 로드 import UserInfo from "./components/UserInfo"; import ChatList from "./components/ChatList"; import ChatInput from "./components/ChatInput"; import "./styles.css"; console.log("App loading", Date.now()); const App = () => ( <div className="App"> <UserInfo /> <ChatList /> <ChatInput /> </div> ); export default App;
ChatInput 으로 입력을 구현한다.
// ChatInput.js import React from "react"; import Send from "./icons/Send"; import Emoji from "./icons/Emoji"; import EmojiPicker from "./EmojiPicker"; const ChatInput = () => { const [pickerOpen, togglePicker] = React.useReducer(state => !state, false); return ( <div className="chat-input-container"> <input type="text" placeholder="Type a message..." /> <Emoji onClick={togglePicker} /> {pickerOpen && <EmojiPicker />} <Send /> </div> ); }; console.log("ChatInput loaded", Date.now()); export default ChatInput;
Emoji를 누르면 이모지창이 나타나는 EmojiPicker가 실행된다.
// EmojiPicker.js import React from "react"; import Picker from "emoji-picker-react"; const EmojiPicker = () => ( <div className="emoji-picker"> <Picker /> </div> ); export default EmojiPicker;
라이브러리를 삽입해 Picker로 이모지를 선택해 삽입한다.
모든 리소스는 정적으로 로드되었다.
이 앱의 소스코드는 main.bundle.js 라는 하나의 번들을 생성한다.
notion image
위처럼 main 번들은 1.5Mib 크기를 가진다.
 

장점

  1. 정적 자원이 빌드 타임에 최적화되어서 런타임 성능 개선
  1. 브라우저 캐싱을 통해 재요청 없이 빠르게 제공
  1. 의존성이 코드 내에 명확히 정의되어 관리하기 쉽고 디버깅 용이
  1. 경로변경이 발생해도 유지보수 용이

단점

  1. 번들 크기 증가
  1. 빌드 시간 증가
  1. 유연성 감소
 
 

Dynamic Import 동적 자원 로딩 패턴

필요한 시점에만 특정 리소스(모듈, 컴포넌트, 라이브러리 등)를 로드하여 성능 최적화 ⇒ 런타임에 로드!
 

동작원리

  1. 런타임에서 리소스 로드
    1. import() 사용하여 JavaScript 모듈을 런타임에서 비동기적으로 로드
  1. 코드 스플리팅
    1. 빌드 도구(Webpack, Vite 등)가 동적 import 감지하고, 모듈을 별도의 번들로 분리
    2. 특정 시점에 이 번들을 요청하여 로드
  1. Lazy Loading 지연 로딩
    1. 사용자가 특정 경로나 기능에 접근할 때만 필요한 모듈로 로드
 
import React, { Suspense } from "react"; // Lazy Loading const MyComponent = React.lazy(() => import('./MyComponent')); function App() { return ( <Suspense fallback={<div>Loading...</div>}> // 로딩 상태 표시 <MyComponent /> // 렌더링 시점에 로드 </Suspense> ); } export default App;
React의 Suspense와 React.lazy를 사용하여 동적 로딩을 구현한다.
 

예제 : 이모지 사용 채팅 앱

// ChatInput.js import React, { Suspense, lazy } from "react"; import Send from "./icons/Send"; // 정적 로드 import Emoji from "./icons/Emoji"; // 정적 로드 // EmojiPicker 동적 로드 const EmojiPicker = lazy(() => import(/*webpackChunkName: "emoji-picker" */ "./EmojiPicker") ); const ChatInput = () => { const [pickerOpen, togglePicker] = React.useReducer(state => !state, false); return ( <Suspense fallback={<p id="loading">Loading...</p>}> <div className="chat-input-container"> <input type="text" placeholder="Type a message..." /> <Emoji onClick={togglePicker} /> {pickerOpen && <EmojiPicker />} <Send /> </div> </Suspense> ); }; console.log("ChatInput loaded", Date.now()); export default ChatInput;
동적으로 EmojiPicker를 import 했다.
Emoji를 누르기 전까지 EmojiPicker를 불러오지 않는다. 이모지 버튼을 클릭 시, EmojiPicker가 랜더링되며 번들링한다.
notion image
이 앱은 총 main.bundle.jsemoji-picker.bundle.js 두 개의 번들이 생성된다.
영상처럼 앱이 시작될 때 main 번들이 생성되고, 이모지 버튼을 클릭할 때 emoji-picker 번들이 생성된다.
 
notion image
main 번들의 크기가 1.5 MiB에서 1.33 MiB로 줄었다.
이모지를 쓰려면 사용자는 조금의 시간을 더 기다려야 하지만, 앱을 더 빨리 보고 인터랙션 할 수 있다.
 
 

장점

  1. 초기 로드 속도 최적화
  1. 코드 스플리팅
  1. 사용자 경험 UX 향상
  1. 리소스 효율성

단점

  1. 동적 로드 시 지연 가능성
  1. 코드 복잡도 증가
  1. SEO 문제
      • 동적로드된 콘텐츠는 검색 엔진이 크롤링하지 못할 수 있음 ⇒ SSR 또는 프리렌더링을 결합해야 함
 

When?

  • 대규모 애플리케이션 : 여러 페이지와 기능이 있는 애플리케이션에서, 페이지별로 코드 분리하여 로드
  • 사용 빈도가 낮은 기능
  • 제3자 라이브러리 : 무거운 라이브러리
  • 동적 콘텐츠
 

Static Import vs Dynamic Import

특징
Static Import
Dynamic Import
로드 시점
빌드 타임
런타임
번들 크기
하나의 번들로 병합
모듈별로 번들 분리
초기 로드 시간
느림
빠름
유연성
조건부 로드 불가능
조건부 로드 가능
SEO 적합성
좋음
나쁨(SSR로 보완 가능)