이미지 dynamic import

mogooee·2024년 4월 29일
0

React와 Webpack 환경의 프로젝트입니다. (CRA O)

개발 환경에서는 문제 없었던 이미지 파일들이 배포 환경에서 제대로 렌더링되지 않는 문제를 해결해본다.

배포환경에서 dynamic import 파일이 깨지는 오류

개발 환경배포 환경

기존 코드를 살펴보면 다음과 같다.

아이콘 svg 파일을 모두 src/assets 디렉토리에 esModule로 import 하고 묶어서 reexport 했다.

// src/assets/icons
import cancel from './cancel.svg';
import success from './check-circle.svg';
import info from './info-circle.svg';
import warning from './exclamation-triangle.svg';
import error from './times-circle.svg';

export { cancel, success, info, warning, error };

Notification 컴포넌트에서 모든 아이콘 파일들을 import한 icon 객체로 접근하고자 했다.
cancel 아이콘은 단일로 가져오고 success, info, warning, error 아이콘들은 알림 유형에 따라 동적으로 모듈을 가져오고 있다.

// src/notification.js
import * as icon from '../../assets/toast-notification/icons';

const Notification = ({type}) => {
  (...)
  return ( 
   <>
   	(...)
   	<img src=${icon[type]}/>
	<img src=${icon.cancel} />
   </>
}

네트워크 탭을 살펴보면 다음과 같이 경로로 모듈을 dynamic import 한 이미지들은 모두 500 에러가 발생한다.

여기서 정상적으로 import 된 svg 파일과는 달리 오류가 발생하는 파일은 파일 유형이 svg+xml 아닌 text/html 유형으로 전송되고 있다.

Webpack에서 이미지는 어느 디렉토리에 위치해야 하는가?

일반적으로 이미지 파일은 /public 또는 src/assets 디렉토리에 저장한다.

/public

정적 파일을 넣는 디렉토리다.
정적 파일이란 html와 이미지 같은 정적자원들을 의미한다.
모듈 시스템 밖에서 자원을 추가하는 방식으로, public 디렉토리 안에 있는 파일들은 webpack이 처리하지 않으므로 원본 그대로 복사본을 build 폴더에 복사한다.
public 디렉토리에 있는 자원을 참조하려면 PUBLIC_URL 환경 변수를 사용한다.

  • html
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
  • javascript
render() {
  return <img src={process.env.PUBLIC_URL + '/img/logo.png'} />;
}
  • 수천 개 이미지 파일을 동적으로 참조해야 할 때
  • manifest.webmanifes처럼 build 된 결과물에서 특정한 파일 이름이 필요한 경우
  • favicon 과 같이 애플리케이션 밖에서 사용하는 이미지
  • 절대 경로 사용이 가능하다.
  • 웹팩 번들링 및 최적화 과정을 거치지 않아서 압축되지 않고 용량이 커질 수 있다.
  • 컴파일 시간에 호출되지 않아 404 에러를 발생시킬 수 있다.
  • 결과 파일명이 컨텐츠 해시를 포함하지 않으므로 파일이 변경될 때마다 쿼리 인자를 추가하거나 파일명을 변경해야 한다.
    => public 디렉토리에 있는 파일들은 빌드 프로세스에 의해 처리되지 않으며, 서버가 이 파일들을 복사본으로 그대로 제공한다.

/src/assets

작업하는 파일들을 넣는 디렉토리로 주로 js파일, css 파일들이 들어있다.
JS파일 내에서 stylesheet, 이미지, 폰트를 추가할 때 모듈로서 import 하는 것을 권장한다.

  • webpack은 src 디렉토리 파일들을 모듈로서 처리하므로 번들링하여 최적화한다.
  • 과도한 네트워크 요청을 피하기 위한 경량화와 번들링을 할 수 있다.
  • 파일을 찾지 못하는 경우, 컴파일 단계에서 에러를 잡을 수 있다.
  • import, require을 이용해 모듈을 불러올 수 있다.
  1. import
import cancel from './cancel.svg';
 
function App(){
	return(
		<img src={cancel} alt='icon' />
	);
}
 
export default App
  • ES6 모듈 시스템으로 정적으로 모듈을 가져온다.
  • 코드 상단에 위치해야 한다.
  • 기본적으로 동기적으로 모듈을 로드한다. 그러나 import() 함수를 사용하면 비동기적으로 모듈을 로드할 수 있다.
    ex) 코드스플리팅
    const NotFound = lazy(() => import('@/pages/NotFound'));
    • 라우팅시 필요한 페이지 컴포넌트를 비동기적으로 로드한다.
    • lazy() 함수를 이용해 해당 컴포넌트가 실제로 필요한 시점에 로드하고
      import()를 통해 비동기적으로 로드한다.
    • 이를 통해 애플리케이션 초기 로딩 시간을 줄이고 사용자 경험을 향상시킬 수 있다.
  1. require
<img src={require('../assets/cancel.svg').default}
  • CommonJS 모듈 시스템으로 동적으로 모듈을 가져온다.
  • 필요한 위치에서 호출될 수 있고 인라인으로 사용할 수 있다.
  • 동기적으로 모듈을 로드한다. 블로킹 작업으로 앱이 지연될 수 있다.
  • 자바스크립트를 사용하는 Node.js 환경으로 require를 사용할 수 있다.
  • 객체로 리턴되므로 default 속성으로 string을 가져올 수 있다.

예시) 디렉토리에 따른 이미지 불러오기

public

process.env.PUBLIC_URL

process.env.PUBLIC_URL은 빌드시 public 디렉토리의 절대 경로를 나타낸다.
package.jsonhomepage 속성값으로 설정할 수 있다.
즉, 개발 환경에서는 빈 값, 배포 환경에서는 homepage의 속성 값을 나타내고 값이 달라질 수 있다.

다음은 public 디렉토리 경로를 . 으로 설정한 경우다. (homepage: '.')

첫번째 케이스에서 경로가 달라지게 되므로 두번째 케이스인 /img.svg 는 루트 경로가 변했으므로 deploy 배포 환경에서 이미지가 로드되지 않는다.

이러한 점을 고려해 process.env.PUBLIC_URL 변수를 사용해서 경로 설정을 해야 가장 정확하고 오류가 생기지 않을 것임을 알 수 있다.

<img src="">developmentdeploy
process.env.PUBLIC_URL
+ '/img.svg'
/img.svg./img.svg
/img.svg/img.svg/img.svg
img.svgimg.svgimg.svg
./img.svg./img.svg./img.svg

여기서 잠시 경로에 대해서 짚어보면
경로는 절대 경로상대 경로로 나뉜다.

  • 절대 경로: root 부터 해당 파일까지의 전체 경로(URL)을 의미하며, 어느 곳에서든 경로에 접근할 수 있다.
  • 상대 경로: 현재 파일의 위치를 기준으로 한다.
    • /: root
    • ./: 현재 위치
    • ../: 상위 경로

src

src 디렉토리에 있는 이미지를 import 및 require 구문을 통해 모듈로서 가져왔을 때는 개발환경과 배포환경에서 모두 같은 경로로 인식했다.
이때 svg 파일은 해시값으로 가져오고, svg 파일을 제외한 jpg, png 파일 등은 1000 byte 미만시 date url 형태로 더욱 빠른 파일 전송을 위한 유형으로서 가져온다.

<img src="">developmentdeploy
import img from './img.svg'
으로 최상단에서 import 한 후, img를 src에 넣는다.
./static/media/img.681ba9d...svg./static/media/img.681ba9d...svg
require('./cancel.svg').default./static/media/img.681ba9d...svg./static/media/img.681ba9d...svg

결론

1. public 디렉토리

📦public
 ┣ 📂assets
 ┃ ┗ 📜img.svg
 ┗ 📜index.html
  • 웹팩이 번들링 처리하지 않고 그대로 복사본을 build 디렉토리에 넣는다.
  • 수천개의 이미지를 동적으로 불러올 때 사용한다.
  • 경로가 바뀌지 않으므로 앱 밖에서 사용하는 정적 파일들을 넣는다.
  • PUBLIC_URL 환경 변수를 사용해서 절대경로를 사용할 수 있다.
  const App = () => {
    return <img src={process.env.PUBLIC_URL + '/assets/img.svg'} alt={'public-img'} />
  }

2. src/assets 디렉토리

📦src
 ┣ 📂assets
 ┃ ┗ 📜img.svg
 ┣ 📜index.tsx
 ┗ 📜App.tsx
  • src 디렉토리 내의 파일들은 webpack이 모듈로서 처리하므로 번들링 및 경량화를 통해 네트워크 부하를 줄일 수 있다.

  • 컴파일 단계에서 에러를 잡을 수 있다.

  • import 또는 require 로 모듈을 불러올 수 있다.

  • import 는 webpack의 tree shaking 을 사용해 사용하지 않는 코드를 제거하여 성능이 개선된다. (권장)

     import Img from './src/assets/img.svg';
    
     const App = () => {
       return <img src="Img" alt="import-img"/>
    }
  • require는 동적 import 가 가능하지만 tree shaking 을 사용하지 않아 불필요한 코드가 포함된다.

    const App = () => {
     return  <img src={require('./src/assets/img.svg').default} alt={'require-img'} />
    }

프로젝트 해결방안 - public 디렉토리

프로젝트에서는 여러개의 이미지를 동적 이미지 경로를 사용하여 가져오므로 public 디렉토리에 아이콘 svg 파일들을 이동하고, process.env.PUBLIC_URL 로 절대경로를 설정했다.

📦public
 ┣ 📂assets
 ┃ ┣ 📜cancel.svg
 ┃ ┣ 📜check-circle.svg
 ┃ ┣ 📜exclamation-triangle.svg
 ┃ ┣ 📜info-circle.svg
 ┃ ┗ 📜times-circle.svg
 ┗ 📜index.html
// src/notification.js

const iconMap = <{
  cancel: 'cancel',
  success: 'check-circle',
  info: 'info-circle',
  warning: 'exclamation-triangle',
  error: 'times-circle',
};

const Notification = ({type}) => {
  (...)
  return ( 
   <>
   	(...)
   	 <img src='${process.env.PUBLIC_URL}/assets/toast-notification/${iconMap[type]}.svg'/>
	 <img src='${process.env.PUBLIC_URL}/assets/toast-notification/cancel.svg' />
   </>
}

Ref

CRA 공식문서

profile
개발의 숲

0개의 댓글

관련 채용 정보