Dog foot print

Next.js StaticImageData With Storybook 본문

REACT

Next.js StaticImageData With Storybook

개 발자국 2023. 4. 28. 09:09

nextJs/Image를 storybook에서 사용 할 때.

Next.js에서는 import 키워드로 소스 파일로 끌어 올 때 StaticImageData라는 타입으로 전달 받게 된다. 이 타입의 형태는 다음과 같다.

interface StaticImageData {
  src: string
  height: number
  width: number
  blurDataURL?: string
}

보통 이 StaticImageData를 이용해서,image태그를 사용하지 않고 next/Image와 함께 사용한다. Next.js/Image는 이미지의 경로를 문자열 혹은 StaticImageData타입을 Props.src로 받기 때문에, 아래와 같이 보통 작성한다.

 

import Image from "next/image";
import LogoImage from "./Logo.png"
export default function Logo(){
    return <Image src={LogoImage} />
}

 

이 때 스토리북을 실행시켜, LogoImage를 확인하면 다음과 같이 static/media 접두사를 붙여, 해당 이미지의 경로를 제공한다.

"static/media/src/Atoms/Image/Logo.png"

 

위의 LogoImage를 스토리 북으로 내보내기 하면, 다음과 같이 이미지를 처리 할 수 없다고 에러가 발생한다.

이는 next/Image가 경로를 받아 최적화를 실행하려고 하였지만 nextjs가 실행되어 있지 않기 때문에 발생하는 에러이다. 이를 해결 하려면 스토리북에서 최적화를 실행하지 않고 이미지 경로를 그대로 가져 올 수 있게 해야한다.

 

/storybook/preview.js

import * as NextImage from "next/image";
// storybook 환경에선 next image 최적화를 쓰지 않기에 해제해줘야함.
const OriginalNextImage = NextImage.default;

Object.defineProperty(NextImage, "default", {
  configurable: true,
  value: (props) => (
    <OriginalNextImage
      {...props}
      unoptimized
      loader={({ src }) => src}
    />
  ),
});

 

unoptimized 옵션을 true로 주게되면, next/Image는 이미지의 퀄리티나 크기를 조정하지 않고 기본 상태로 전달하게 된다. 이 옵션에 관련된 자세한 내용은 링크를 참조하면 된다. next/image | Next.js

 

next/image | Next.js

Enable Image Optimization with the built-in Image component.

nextjs.org

loader옵션은 Image경로를 호출 할 때, next/Image가 아닌 다른 곳에서 최적화를 시켜줄 수 있는 경로로 변경 할 수 있는 옵션이다. 그렇기에 그냥 다른 곳으로 전달되지 않고 이미지를 보여 줄 수 있도록 src를 바로 넘겨주는 것이다. 이 옵션에 관련된 자세한 내용은 링크를 참조하면 된다. next/image | Next.js

 

next/image | Next.js

Enable Image Optimization with the built-in Image component.

nextjs.org

 

정적 이미지를 base64로 처리하고 싶을 때.

Note : Webpack의 비교적 최신버전에는 file-loader와 url-loader가 asset 이라는 이름의 모듈로 내장되어 있다. 여기서는 webpack5를 기준하여, 작성되었다.

크기에 따라 정적 이미지를 경로가아닌 base64로 처리하고 싶다면, webpackasset/inline 과 같은 모듈을 사용하면된다.

const path = require("path")

const webpackFinal = async (config, { configType }) => {
    ...
  config.module.rules.push({
    test: /\.(png|jpg|gif)$/i,
    type: "asset",
      parser : {dataUrlCondition : {maxSize 1024 * 4}} //4Kb
  })
    ...
    return config;
}
module.exports = {
  ...
  webpackFinal
}

위와 같이 아셋 모듈을 추가하면 다음과 같이 정적이미지 크기에 따라 경로가 아닌dataUrl로 변경된다.

정적 이미지를 StaticImageData 형식으로 처리하고 싶을 때.

Next/image의 src는 StaticImageData 타입 뿐만아니라, string타입으로 사용 가능하기 때문에, 위의 처리가 되었을 때, 작동에 문제가 없다. 그러나, 어떤 사유로 StaticImageData를 사용해야 하는 이유가 존재할 때, 다음과 같이 처리하여, 타입을 적용 시킬 수 있다.

 

StaticImageData 타입은 자바스크립트의 객체처럼 취급되기 때문에, 기존에 존재하던 asset과 같은 모듈을 사용 할 수 없고, 이미지 포맷을 객체타입으로 변환할 수 있는 모듈을 따로 제작해야한다.

Image로 부터, width, height를 추출하기 위해서 buffer-image-size 라이브러리를 설치한다.

yarn add -D buffer-image-size

 

이미지 데이터 처리를 위한 모듈을 작성한다. (해당 모듈의 첫번째 인자로 파일의 소스값이 전달되는데, 바이너리값 형태의 문자열이 들어오지만 어딘가 깨진것 같다. 이 값을 이용해 정상적인 버퍼로 컨버팅 하는 방법을 안다면 댓글 부탁드립니다.)

 

./storybook/StaticImageData

const path = require("path");
const fs = require("fs");
const sizeOf = require("buffer-image-size");

module.exports = function (source) {
    const buffer = fs.readFileSync(this.resourcePath);
    const { width, height, type } = sizeOf(buffer);
    const prefix=  path.resolve(__dirname,"../");
      let src = `/static/media` + this.resourcePath.replace(prefix,"");

    const staticImport = {
        src,
        width,
        height
    }

    return `export default ${JSON.stringify(staticImport)}`
}

./storybook/main.js

const path = require("path")

const webpackFinal = async (config, { configType }) => {
    ...
  config.module.rules.push({
    test: /\.(png|jpg|gif)$/i,
    type: "javascript/esm",
    use: [
      {
        loader: path.resolve(__dirname, "./StaticImageData.js"),
      },
    ],
  })
    ...
    return config;
}
module.exports = {
  ...
  webpackFinal
}

위와 같이 작성하고 정적 임포트된 이미지들을 콘솔로 찍어보면, staticImageData형태로 제공된다.

 

 

만약 크기에 따라 base64로 변경할 수 있도록 하고 싶다면 다음과 같이 크기에 대한 옵션을 설정 할 수 있도록 한다.

./storybook/main.js

const path = require("path")

const webpackFinal = async (config, { configType }) => {
    ...
  config.module.rules.push({
    test: /\.(png|jpg|gif)$/i,
    type: "javascript/esm",
    use: [
      {
        loader: path.resolve(__dirname, "./StaticImageData.js"),
          options: {
          inlineLimit: 1024 * 4
        }
      },
    ],
  })
    ...
    return config;
}
module.exports = {
  ...
  webpackFinal
}

./storybook/StaticImageData

const path = require("path");
const fs = require("fs");
const sizeOf = require("buffer-image-size");
module.exports = function (source) {
    const {inlineLimit =(1024 * 4) } = this.getOptions();
    const buffer = fs.readFileSync(this.resourcePath);
    const { width, height, type } = sizeOf(buffer);
    let src = ""

    if(Buffer.byteLength(buffer) <= inlineLimit){
        const encoded = buffer.toString("base64");
        src = `data:/image/${type};base64,${encoded}`
    }else{
        const prefix=  path.resolve(__dirname,"../");
        src =`/static/media` + this.resourcePath.replace(prefix,"");
    }

    const staticImport = {
        src,
        width,
        height
    }

    return `export default ${JSON.stringify(staticImport)}`
}

위와 같이 작성 후, 실행해보면 이미지 크기에 따라 src프로퍼티가 dataURL로 처리된다.

 

반응형
Comments