minTech

[Project, React] 한 파일에 있는 이미지 한 번에 import 하기 본문

Project

[Project, React] 한 파일에 있는 이미지 한 번에 import 하기

pushzzeong 2024. 3. 15. 13:45

자기 소개 웹페이지 중에서 프로필 페이지 구현을 모두 마치고 프로젝트 페이지를 구현 중에 있다.

프로젝트 페이지의 경우 한 프로젝트 당 들어가는 이미지가 여러장 되기 때문에 프로젝트 페이지 하나에 프로젝트 하나의 이미지들만 import 해도 기본 6장은 되었다. 이를 하나하나 import 하려니 soso 귀찮,,,

 

 

어떻게 하면 하나의 파일에 있는 이미지를 모두 가져올 수 있을까? 바로 구글링을 해보았다!

 

그 결과, 내가 인터넷 검색을 통해 찾아보았던 방법은 두✌️개이다. 

 

 

1. 이미지를 import 하는 js 파일을 따로 생성 후에 거기서 컴포넌트처럼 import 해온다.

import project1_1 from "../assets/images/project1_results/project1_1.png";
import project1_2 from "../assets/images/project1_results/project1-2.png";
import project1_3 from "../assets/images/project1_results/project1-3.png";

const project1 = {
  project1_1,
  project1_2,
  project1_3,
};

export const images = () => {
  return project1;
};

 

이러한 방식은 상대적으로 import 해오는 파일에서는 코드가 간단해지고, 사용 방법이 복잡하지 않다.

 

하지만 export 하는 images.js 파일의 경우 파일이 하나 추가되거나 제거 될 때마다 import 코드를 두 줄이나 추가적으로 작성해야한다.

  1. 해당 이미지 import 해오기
  2. project1 객체에 넣기

이러한 작업은 프로젝트가 늘어남에 따라 이미지 개수도 늘어나게 되면 찾는 것도 힘들고, 수정하기도 힘들어진다는 단점을 갖는다.

 

 

2. require.context 를 이용한다.

 

🤷‍♂️이 과정에 들어가기 전에 궁금해졌다.

require이란 무엇이고, require.context는 어떤 것인가? 각각 어떤 동작을 하는 것일까?

 

 

 

require

  • require은 외무 모듈을 가져올 때 사용하는 메서드로 리턴 값은 module.exports이다.
  • 파라미터로 추가할 모듈의 파일 경로 값을 받는다.
require('파일 경로')
  • 그렇다면 어떻게 동작할까?
  • 동작을 코드로 작성하면 다음과 같다.
let require = function(src) {       // 파일 경로를 인자로 받는다.
	var fileAsStr = readFile(src);  // 소스파일을 읽어서 fileAsStr에 저장한다.
	var module.exports = {};        // module.exports 이름의 빈 해시를 만든다.
	eval(fileAsStr);                // fileAsStr을 가져온다. 
                                    // 즉 이 부분은 가져온 파일의 소스 파일을 복붙한 것과 같음
	return moudule.exports;         // module.exports를 리턴한다.
}

 

결론적으로, 파라미터로 받은 파일 경로의 소스코드를 읽고, export 한 부분의 변수와 그 값을 key와 value형태인 해시 형태로 변환 후에 module.exports 에 넣어준다. 그리고 이를 리턴한다.

 

그렇다면 require.context는 어떠할까?

 

require.context()

  • 여러 컴포넌트를 한 개의 root만을 이용해 한꺼번에 가져올 때 사용하는 메서드이다.
  • 파라미터로는 파일 경로, 하위 디렉터리를 검색해야 하는지의 여부, 가져올 파일들을 필터링할 정규식이 있다.
require.context(가져올 폴더의 경로, true/false, 정규식)

 

 

 

 

이 require.context를 이용해 프로젝트 이미지 파일을 한 번에 가져와보았다.

export const getProjectImages = (projectId) => {
  function importAll(r) {   // 가져온 후, forEach를 이용하여 해당 파일에 접근한 후,
                            // image 배열에 저장한다.
    let images = {};
    r.keys().forEach((key) => {
      images[key] = r(key);
    });
    return images;
  }

  let images = importAll(
    require.context(
      "../assets/images/project1_results", // 해당 경로를 가진 파일에 접근한다.
      true,
      /\.(png|jpe?g|svg)$/  // png, jpg, svg 형태의 파일을 가져온다.
    )
  );

  return images;
}

 

해당 코드 작성 후, console.log(images)를 이용해 images 값을 확인해보았다.

 

잘 출력됨을 확인하였다.

 

 

문제 상황

테스트까지 모두 완료된 후에 나는 함수의 파라미터로 프로젝트의 id 값을 입력으로 받아

파라미터 값에 따라 파일 경로를 동적으로 변하도록 작성해보았다.

export const getProjectImages = (projectId) => {
  function importAll(r) {
    let images = {};
    r.keys().forEach((key) => {
      images[key] = r(key);
    });
    return images;
  }

  let images;

  images = importAll(
    require.context(
      `../assets/images/project${projectId}_results`,
      true,
      /\.(png|jpe?g|svg)$/
    )
  );
  console.log(images);
  return images;
}

 

그랬더니 에러가 발생했다.

 

왜 잘 되다가 안되는거지..?😭😭😭😭😭

 

계속 인터넷으로 찾아보다가 나와 비슷한 상황에 처한 분을 발견했고, 나는 답을 찾았다.

출처 : https://github.com/webpack/webpack/issues/4772

원인

require.context() 같은 경우 웹팩에서 제공하는 기능으로, 정적 코드 분석을 실행한다.

정적 분석은 프로그램 실행 전에 코드를 검사하는 컴퓨터 프로글매 디버깅 방법이다.

 

따라서 동적으로 분석해야하는 변수를 context 의 인수로 넣어줄 경우, 해당 에러가 발생하는 것이다.

 

💡결국 require.context의 인수에는 리터럴만 넣을 수 있다.

 

결론

 

이 부분에 대해서는 webpack.config.js 파일을 손대서 해결할 수 있는지 찾아보았지만, 마땅한 해결방법이 없기에 ,,

 

결국, 프로젝트 id 값을 파라미터로 받고, 그 값을 require context 메서드를 이용해 해당 프로젝트 image 들을 동적으로 가져오기 위해 나는 프로젝트 id를 기준으로  switch로 상황을 나누었고, 상황별로 맞는 리터럴 값을 일일히 지정해주었다.

 

이 말이 이해가 잘 안될 수 있기에 직접 코드를 넣어보았다.

export const getProjectImages = (projectId) => {
  function importAll(r) {
    let images = {};
    r.keys().forEach((key) => {
      images[key] = r(key);
    });
    return images;
  }

  let images;

  switch (projectId) {
    case 1:
      images = importAll(
        require.context(
          `../assets/images/project1_results`,
          true,
          /\.(png|jpe?g|svg)$/
        )
      );
      console.log(images);
      break;
    case 2:
      images = importAll(
        require.context(
          "../assets/images/project1_results",
          true,
          /\.(png|jpe?g|svg)$/
        )
      );
      break;
    case 3:
      images = importAll(
        require.context(
          "../assets/images/project1_results",
          true,
          /\.(png|jpe?g|svg)$/
        )
      );
      break;
    case 4:
      images = importAll(
        require.context(
          "../assets/images/project1_results",
          true,
          /\.(png|jpe?g|svg)$/
        )
      );
      break;

    default:
      break;
  }

  return images;
};

 

각 프로젝트별로 같은 코드를 반복해서 쓴 것이 정말,, 아쉽긴 하지만,, 

 

require.context를 이용해 이미지를 가져올 경우, 각 프로젝트 내에서 이미지의 이름이 수정되거나, 이미지 자체가 추가/삭제 되어도 일일히 코드를 작성해주지 않아도 된다는 장점이 있다. 

 

그래서 난 이 프로젝트에선  두 번째 방법을 채택했다.

 


결과적으로 많이 아쉽지만, 새롭게 require메서드와 require.context 메서드를 경험하고, 완벽하진 않지만 상황에 맞게 사용해보았다는 것에 만족한다. 

 

그리고 require.context는 리터럴 값만 인수로 작성해야한다는 것도 꼭꼭 기억해두어야겠다!!!!

 

 

참고자료

https://medium.com/@chullino/require-exports-module-exports-%EA%B3%B5%EC%8B%9D%EB%AC%B8%EC%84%9C%EB%A1%9C-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-1d024ec5aca3

 https://github.com/webpack/webpack/issues/4772