cra없이 리액트 환경 설정하기

CRA없이 한땀한땀 리액트 환경 설정을 해보자

원활한 설명을 위해 중복되는 설정 코드는 제거했습니다. 전체 코드는 여기서 확인가능하니, 본 글은 이해의 용도로만 활용해 주시기 바랍니다!

Create-React-App없이 웹팩과 바벨 설정으로 리액트 개발 환경을 구성해보겠습니다. Tech Stack은 다음과 같습니다.

  1. Wepback
  2. Babel
  3. ESLint & Prettier
  4. Storybook
  5. React
  6. Typescript
  7. Emotion

먼저 React없이 그냥 Typesctipt + Babel + Webpack 설정을 해보고, 이후에 React, Emotion 등 을 추가해보겠습니다.

React없이 세팅하기

우선 npm init -y 를 해서 package.json을 생성합니다.

typescript

설치

npm install --save-dev typescript

설정

// tsconfig.json

{
  "compilerOptions": {
    "target": "esnext", // default : es3
    // 몇 버전의 ECMAScript로 컴파일할것인지 지정합니다.
    // 예를들어, 'es5'로 지정하면 화살표 함수를 함수선언식으로 변경합니다.
    // esnext로 지정하면 현재 프로젝트에서 사용하는 typescript버전이
    // 지원하는 가장 높은 ECMAScript 버전을 알아서 선택합니다

    "allowJs": true,
    // js파일도 ts파일에서 import할 수 있습니다

    "skipLibCheck": true,
    // 외부 라이브러리에 명시된 타입에 대한 검사를 진행하지 않습니다.
    // 다만, 우리가 외부 라이브러리에서 사용한 코드에 대해서는 검사를 해줍니다.
    // 주로 이 옵션을 true로 해주는 경우는 3가지가 있습니다
    // - 첫째 - 
    // 타입스크립트가 외부 라이브러리까지 전부 타입체크를 하면 너무 오래걸립니다.
    // 그래서 컴파일 속도를 높일때 사용합니다.
    // - 둘째 -
    // 외부 라이브러리의 타입에 문제가 있을때 이 옵션을 키면
    // 그냥 any로 해석해줍니다.
    // - 셋째 -
    // 외부 라이브러리에서 사용한 TS버전과 지금 내 프로젝트에서 사용하고 있는 TS버전이
    // 다른 경우에, 이 옵션을 키면 에러를 안뿜습니다.
    // 그리고 외부 라이브러리에서 타입 체크를 덜 엄격하게 하기로 하고 any를 썼는데
    // 내 프로젝트에서는 타입을 업격하게 하기로 설정 했다면, 이 설정의 차이에서 오는 오류들을
    // 무시할 수 있도록 해줍니다.

    "strict": true,
    // strict로 시작하는 옵션들을 true로 해줍니다.
    // strictFunctionTypes, strictNullChecks같은 옵션들이 true로 됩니다.

    "forceConsistentCasingInFileNames": true,
    // import할때 파일명을 대소문자도 고려해서 똑같이 적어줘야 합니다

    "moduleResolution": "node",
    // module 찾는 방식을 지정합니다.
    // 자세한 내용은 https://www.typescriptlang.org/docs/handbook/module-resolution.html#module-resolution-strategies
    // 요기를 보면 나와있습니다.

    "isolatedModules": true,
    // 하나의 파일이 독립된 모듈로 되기를 강제합니다
    // 여기서 독립된 모듈이란 import나 export가 파일안에 꼭 있어야 한다는 의미입니다
    // 이것은 babel같은 외부 컴파일러를 위한 옵션이라고는 하는데, 이것이 무엇을 의미하는지는 모르겠습니다.

    "noEmit": true,
    // 타입체크만 하고 js로 컴파일 하지 않습니다
    // 현재 babel을 사용해서 컴파일 할것이기 때문에 true로 설정해 줍시다

    "resolveJsonModule": true,
    // json파일도 모듈로 취급해서 import할 수 있게 합니다

    "baseUrl": "./src"
    // import something from './src/some/thing.ts' 이렇게 안하고
    // import something from 'some/thing.ts' 이렇게 해도 잘 작동하도록 합니다.
    // 즉, 번거롭게 앞에 path를 계속 적어줘야 하는 불편함을 줄여줍니다.

  },

  "include": ["src"]
  // ts가 인식하고 컴파일할 파일의 경로를 지정합니다.
  // 현재 src폴더만 ts를 사용할것이기 때문에, src를 넣어줍니다.
}

babel을 사용해서 컴파일 할것이기 때문에 설정이 많지 않습니다.

webpack

설치

npm install webpack webpack-cli webpack-dev-server webpack-merge html-webpack-plugin clean-webpack-plugin --save-dev

webpack

webpack은 모듈 번들러 입니다. javascript파일들의 의존성을 고려해 번들링 합니다. js뿐만 아니라 image, svg등 다른 자원들도 번들링 할 수 있습니다.

webpack-cli

터미널 환경에서 webpack을 사용할 수 있도록 명령어들을 제공합니다. 예를들어서, 터미널에서 webpack --config ./webpack.prod.js 이렇게 치면 webpack이 작동합니다.

webpack-dev-server

webpack을 위한 노드 서버입니다. 파일의 변경사항을 감지해서 다시 빌드하고 브라우저를 자동으로 새로고침해줍니다.

html-webpack-plugin

index.html파일을 생성해주는 플러그인입니다. 빌드시에 index.html에 여러가지 값들을 넘길 수 있습니다.

clean-webpack-plugin

빌드할때 dist폴더의 내용물을 싹 지우고, 새로 빌드된 파일을 넣습니다. 종종 이전에 빌드할때 생성된 파일들이 남아있는 경우가 있어서 이 플러그인이 필요합니다.

webpack-merge

webpack설정 파일을 병합해주는 함수를 제공하는 라이브러리입니다.

설정

개발 환경용 설정 파일과 프러덕션용 설정 파일을 분리합니다.

webpack.common.js

const { join, resolve } = require('path');

const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
  mode: 'development',
  entry: join(__dirname, '../src/index.ts'),
  devtool: 'eval-source-map',
  output: {
    filename: 'main.js',
    path: join(__dirname, '../dist'),
  },
  module: {
    rules: [
      {
        test: /\.(ts)$/, // .ts파일을 만나면
        exclude: /node_modules/, // node_modules를 제외하고
        use: ['babel-loader'], // babel-loader에게 일을 맡긴다!
      },
      {
        test: /\.(png|jpg|jpeg)$/i,
        type: 'asset/resource', // asset/resource는 webpack5 에서 url-loader, file-loader대신 지원하는 기능입니다
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: join(__dirname, '../public/index.html'),
      favicon: join(__dirname, '../public/favicon.png'), // favicon경로도 설정할 수 있습니다
    }),
    new CleanWebpackPlugin(),
  ],
  resolve: {
    extensions: ['.ts', '.js'], 
  }
};

webpack.dev.js

const { merge } = require('webpack-merge');

const common = require('./webpack.common');

module.exports = merge(common, { // webpack.common.js와 merge해줍니다
  mode: 'development',
  devServer: {
    open: true,
    port: 3000,
    compress: true,
    client: {
      overlay: {
        errors: true,
        warnings: true,
      },
    },
    historyApiFallback: true,
    // url을 찾을 수 없을때 그냥 index.html을 돌려줍니다.
    // react-router-dom쓸때 필요합니다.
  },
});

webpack.prod.js

const { merge } = require('webpack-merge');
const common = require('./webpack.common');

module.exports = merge(common, {
  mode: 'production',
});

Babel

설치

npm install @babel/core @babel/cli @babel/preset-env @babel/preset-typescript babel-loader --save-dev

@babel-core

js코드를 transform하는 핵심 기능이 모아져 있습니다. 하지만, 변환을 하려면 core만 단독으로 사용하는것이 아닌 plugin이 필요합니다.

@babel-cli

babel을 터미널에서 사용할 수 있도록 명령어들을 제공합니다.

@babel/preset-env

자주 쓰는 바벨 플러그인들을 세트로 모아놓았습니다.

https://github.com/babel/babel/blob/master/packages/babel-preset-env/src/available-plugins.js

@babel/preset-typescript

내부적으로 @babel/plugin-transform-typescript플러그인을 사용합니다. 이 플러그인은 단순히 type을 때내어주는 역할을 합니다.

// input
const x: number = 0;

// output
const x = 0;

babel-loader

Webpack이 파일을 빌드할때 js파일을 만나면 이 babel-loader가 babel을 작동시켜 js파일을 변환합니다.

설정

.babelrc.json

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-typescript"
  ]
}

ESLINT & prettier

설치

npm install eslint prettier eslint-config-prettier eslint-plugin-prettier eslint-plugin-import eslint-import-resolver-typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser @trivago/prettier-plugin-sort-imports --save-dev

eslint

js파일 실행시에 발생할 수 있는 문제를 사전에 검사해주고, 코딩 컨벤션을 강제합니다. prettier처럼 코드 스타일에 관련된 룰도 있긴 합니다.

prettier

eslint가 에러를 방지하기 위함이라면, prettier는 코드의 스타일을 강제합니다. 예를들어서 indentation으로 space를 사용할것인지 tab을 사용할 것인지 설정할 수 있습니다.

eslint-config-prettier

ESLint의 formatting 관련 설정 중 Prettier와 충돌하는 부분을 비활성화 합니다.

eslint-plugin-prettier

Prettier를 ESLint 플러그인으로 추가합니다. 즉, Prettier에서 인식하는 코드상의 포맷 오류를 ESLint 오류로 출력해줍니다.

주의! 공식 문서의 설명에 따르면, plugin:prettier/recommended 했을때, eslint-config-prettier가 알아서 적용되니, 따로 설정할 필요가 없다고 합니다.

eslint-plugin-import

ES6에 추가된 import/export 구문을 사용할때 혹여나 파일 경로를 잘못 지정 했는지, 파일 이름은 똑바로 썼는지 검사해 주는 플러그인입니다.

eslint-import-resolver-typescript

eslint-plugin-import에 ts지원을 추가해줍니다. 다시말해서, ts나 tsx도 import할때 lint를 적용할 수 있게 됩니다.

추가적으로, tsconfig.json에서 정의한 paths속성을 사용합니다. 나중에 설명드리겠지만, 덕분에 tsconfig.jsonpaths에 설정한 절대 경로를 eslint가 인식할 수 있게됩니다.

@typescript-eslint/eslint-plugin

eslint는 본래 js용인데, 이 플러그인을 사용하면 ts도 인식해줍니다. 또한 ts에만 적용되는 특별한 rule들도 지정할 수 있습니다.

@trivago/prettier-plugin-sort-imports

import 순서를 깔끔하게 정리해줍니다.

import { css } from '@emotion/react';

import MainPage from '@pages/MainPage';

import Footer from '@components/Footer';
import Header from '@components/Header';
import Wrapper from '@components/Wrapper';

설정

.eslintrc.json

{
  "env": {
    "browser": true,
    "es2022": true
  },
  "settings": {
    "import/resolver": {
      "typescript": {}
    }
  },
  "parser": "@typescript-eslint/parser",
  "parserOptions": {
    "ecmaVersion": "latest",
    "sourceType": "module"
  },
  "extends": [
    "plugin:import/recommended",
    "plugin:import/typescript",
    "plugin:prettier/recommended",
    "plugin:@typescript-eslint/recommended",
  ],
  "rules": {
    "import/extensions": [
      "error",
      "ignorePackages",
      {
        "ts": "never",
        "js": "never"
      }
    ],
  }
}

plugins속성에 일일이 eslint plugin을 추가하지 않고 extends"plugin:~~"이렇게 적어도 됩니다.

.prettierrc.json

{
  "printWidth": 120,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "bracketSpacing": true,
  "jsxBracketSameLine": false,
  "arrowParens": "avoid",
  "endOfLine": "auto",

  "importOrder": [
    "^@emotion",
    "^@pages/(.*)$",
    "^@components/(.*)$",
    "^[./]"
  ],
  "importOrderSeparation": true,
  "importOrderSortSpecifiers": true
}

이렇게 typescript, webpack, babel, eslint & prettier를 설치했습니다.

잠깐!

여기서 VSC를 한번 껏다가 켜줍시다!! 종종 eslint가 prettierrc.json을 안읽는 경우가 있기 때문입니다.

React와 함께 설정하기

React

설치

npm install react react-dom

typescript

설치

npm install @types/react @types/react-dom  --save-dev

설정

{
  "compilerOptions": {
    ...
    "esModuleInterop": true, // 필요 없는데 설명을 위해 넣었습니다
    "allowSyntheticDefaultImports": true,
    ...
    "jsx": "preserve", // 어차피 babel을 쓸것이기 때문에 그냥 변환하지 않고 <div>로 냅둔다(preserve)
    "jsxImportSource": "@emotion/react", // emotion의 css props를 사용하기 위해 필요합니다
    ...
  },
  ...
}

allow_synthetic_default_imports

(이부분은 저도 잘 몰라서 설명이 애매합니다. 그냥 참고용으로 읽어주시면 감사하겠습니다)

어떤 모듈을 import하는데, 그 모듈이 export default를 하지 않았는데, export하는 요소들을 하나의 객체로 가져오고 싶다면 보통 import * as React from 'react' 이런식으로 씁니다. 저 React객체 안에는 react모듈이 export하는 모든 요소들이 담겨져 있는것이죠. 하지만 Babel은 편의를 위해서 import React from 'react' 이렇게 작성해도 React라는 객체에 ‘react’가 export한 요소들을 다 넣어줍니다.

하지만 typescript는 이 사실을 모르기 때문에, 타입 에러를 뿜습니다.

“아니, export default가 없는데, 마치 export default한것처럼 사용하네!? 그러면 안돼!”

그래서 typescript에게 알려줘야합니다.

“걱정하지마, 어차피 Babel이 알아서 해줄꺼야”.

바로 allowSyntheticDefaultImports 옵션이 이런 역할을 합니다.

esmodule_interop

하지만 babel을 사용하지 않는 경우는 이 옵션을 써야합니다.

왜냐하면 기본적으로 react는 commonJs방식으로 export를 하고 있고, 이 프로젝트에서는 esmodule방식으로 import를 하고 있기 때문입니다.

그래서 esModuleInterop를 키고 Babel없이 import * as React from 'react' 혹은 import React from 'react' 를 사용한다면, typescript는 컴파일 할때 내부적으로 아래와 같은 함수를 생성합니다.

var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};

저도 자세히는 모르겠지만, 그냥 __importStartimport * as React from 'react' 이 코드에서 react모듈이 export하는 요소들을 React객체에 담아서 return하는 것 같고

__importDefaultimport React from 'react' 이 코드에서 react모듈이 export하는 모든 요소를 React객체에 담아서 return하는듯 싶습니다.

아무튼 바벨이 하는것처럼 해준다는것이죠.

그리고 이 옵션에 대해서 알아야 할것이 2가지 더 있습니다.

첫번째는, 이 옵션을 키면 자연스럽게 allowSyntheticDefaultImports옵션도 켜집니다.

그러면 이런 생각이 듭니다. Babel을 쓰면 필요 없는 옵션이 아닌가…? 맞습니다. 첫 문단에서 말했듯, Babel로 TS를 컴파일 하면 이 옵션은 필요 없습니다. 하지만, 너무 자주 보이는 옵션이여서 간단히 설명을 해본겁니다.

두번째는, 그럼에도 불구하고 TS로 컴파일 한다면 allowSyntheticDefaultImports 도 같이 직접 켜줘야 합니다.

이유는 모르겠지만 webpack에 이렇게 하라고 나와 있습니다.

jsx

typesript가 js를 다루는 방식을 지정합니다.

어차피 Babel로 컴파일 할것이기 때문에 preserve로 설정해 줍니다. 만약 typescript로 컴파일 한다면 react-jsx로 설정해줘야 합니다.

jsximportsource

jsx 코드를 어떻게 다룰것인지 지정할 수 있습니다. 예를들어서, emotion같은 경우에는 기존의 jsx에는 없는 css prop이 있습니다. 그리고 css prop을 받아서 처리한뒤에 className에 넣어줍니다. 그래서 이런 처리를 해주려면 기존의 방식으로 jsx를 처리하면 안되고, emotion에서 뭔가 커스텀하게 처리 해야합니다. 이럴때 그 커스텀 해석기(?)를 jsxImportSource에 지정하는것입니다.

그런데! 이것도 역시나 Babel을 사용하기 때문에 TS가 직접 컴파일 할일은 없기에 지워,,,,,,준다고 생각 할 수 있지만!!

그래도 필요합니다. VSC의 TS서버가 이거를 참고해서 css prop을 인식하기 때문입니다.

webpack

아까는 ts만 번들링 요소에 포함하도록 했는데, 이제는 tsx도 포함 시켜줍니다.

설정

webpack.common.js

module.exports = {
  mode: ...
  entry: join(__dirname, '../src/index.tsx'),
  devtool: ...
  output: {
    ...
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        ...
      },
    ],
  },
  plugins: [
    ...
  ],
};

Babel

설치

npm install @babel/preset-react --save-dev

설정

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
}

ESlint & prettier

설치

npm install eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks --save-dev

설정

{
  "env": {
    "browser": true, // browser에서 사용하는 전역 기능(변수, 함수, 객체)를 인식한다
    "es2022": true // ECMAScript 2022와 그 하위에서 지원하는 기능들을 인식한다
  },
  "settings": {
    // plugin 세팅
    "react": {
      // eslint-plugin-react의 설정이다
      "version": "detect" // react version을 현재 프로젝트에서 사용하는 version을 알아서 찾아서 지정한다
    },
    "import/resolver": {
      // eslint-plugin-import의 설정이다
      "typescript": {
        // eslint-import-resolver-typescript 설정이다
        "project": "frontend/tsconfig.json" // tsconfig의 위치를 지정한다
      }
    }
  },
  "parser": "@typescript-eslint/parser", // parser는 ts parser를 활용한다
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true // jsx를 lint가 인식하도록 한다
    },
    "ecmaVersion": "latest",
    // 사실 위에서 env.es2022 = true로 했기 때문에, 알아서 최신 ecmaVersion을 사용한다
    // 지금은 필요 없는 옵션이다. 하지만 공부용으로 냅두자!
    "sourceType": "module"
    // ESLint의 Parser가 분석하려는 JS file이 script가 아닌 module이라는것을 명시한다
  },
  "extends": [
    "plugin:jsx-a11y/recommended", // 웹 접근성을 준수하여 jsx를 작성했는지 검사한다
    "plugin:react/recommended", // react관련 rule을 적용한다
    "plugin:react/jsx-runtime", // jsx관련 rule을 off시킨다
    "plugin:react-hooks/recommended", // react-hook관련 rule을 적용한다
    "plugin:import/recommended", // import관련 rule을 적용한다
    "plugin:import/typescript", // typescript import관련 rule을 적용한다
    "plugin:prettier/recommended", // .prettierrc.json에 기입한 prettier rule을 eslint rule로 적용한다
    "plugin:@typescript-eslint/recommended" // typescript관련 rule을 적용한다
  ],
  "rules": {
    "import/extensions": [
      "error", // 이 규칙을 지키지 않으면 error로 간주하겠다
      "ignorePackages", // pacakge빼고는 모두 import할때 확장자명을 작성 해라!
      {
        "ts": "never", // ts는 확장자명 사용하지 않는다
        "tsx": "never", // tsx도 절대!
        "js": "never", // js도!
        "jsx": "never" // jsx 마저도!
      }
    ],
    "react/prop-types": "off", // typescript를 사용 하니까 prop-types안써도 될듯 싶다
  }
}

emotion

설치

npm install @emotion/babel-plugin @emotion/react @emotion/styled  --save-dev

설정

.babelrc.json

{
  "presets": [
    ...
    [
      "@babel/preset-react",
      {
        "runtime": "automatic",  // jsx를 변환하는 플러그인들을 자동으로 포함시켜준다는 의이미다.
        "importSource": "@emotion/react"
        // jsx를 처리할때 emotion에서 제공하는 함수를 사용하라는 의미이다.
        // 이렇게 해야 jsx에 달려있는 css prop를 해석하고 className으로 변환할 수 있기 때문이다.
      }
    ],
    ...
  ],
  "plugins": [
    [
      "@emotion",
      {
        "sourceMap": true, // sourceMap을 생성한다
        "autoLabel": "dev-only", // label은 개발모드에서만 적용한다
        "labelFormat": "[local]",
        // const CardList = styled.ul 이런식으로 되어 있다면,
        // local은 CardList가 된다.
        "cssPropOptimization": true
        // @emotion/react의 jsx함수를 모든 jsx파일에서 사용한다는 전제가 있다면
        // true로 해준다. 그러면 뭔가 최적화를 해주는것 같다.
      }
    ]
  ]
}

절대경로 설정하기

절대경로를 쓰면 코드가 깔끔해집니다. 설정도 쉽습니다.

tsconfig.json

{
  "compilerOptions": {
    ...
    "baseUrl": "./src",
    "paths": {
      "@components/*": ["components/*"],
      "@styles/*": ["styles/*"],
    },
    ...
  },
  ...
}

webpack.common.js

module.exports = {
  mode: ...,
  entry: ...,
  devtool: ...,
  output: {
    ...
  },
  module: {
    rules: [
      ...
    ],
  },
  plugins: [
    ...
  ],
  resolve: {
    extensions: ['.tsx', '.ts', '.jsx', '.js'],
    alias: {
      '@components': resolve(__dirname, '../src/components'),
      '@styles': resolve(__dirname, '../src/styles'),
    },
  },
};

Storybook

설치

npx sb init --type react

이렇게 설치하다가 보면, storybook관련 eslint-plugin을 설치하라고 나옵니다. y를 눌러 설치해주고,

eslintrc.json파일에 아래와 같이 플러그인을 추가해줍니다.

"extends": [
  ...,
  "plugin:storybook/recommended",
  ...
]

설정

사실 설정도 어지간한건 sb init으로 다 해주지만, storybook용 webpack/babel/lint 설정을 또 따로 해줘야 합니다.

const { resolve } = require('path');

module.exports = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
  framework: '@storybook/react',
  core: {
    builder: 'webpack5', // webpack5를 사용한다
  },
  webpackFinal: async config => {
    // storybook이 작성한 webpack config객체를 받는다

    // 절대 경로를 설정한다
    config.resolve.alias = {
      ...config.resolve.alias,
      '@root': resolve(__dirname, '../'),
      '@src': resolve(__dirname, '../src/'),
      '@components': resolve(__dirname, '../src/components'),
      '@styles': resolve(__dirname, '../src/styles'),
      '@types': resolve(__dirname, '../src/types'),
      '@pages': resolve(__dirname, '../src/pages'),
      '@assets': resolve(__dirname, '../src/assets'),
      '@constants': resolve(__dirname, '../src/constants.ts'),
      '@api': resolve(__dirname, '../src/api'),
      '@context': resolve(__dirname, '../src/context'),
    };

    // babel설정을 추가한다
    config.module.rules[0].use[0].options.presets = [
      require.resolve('@babel/preset-env'),
      [
        require.resolve('@babel/preset-react'),
        {
          runtime: 'automatic',
          importSource: '@emotion/react',
        },
      ],
    ];

    // emotion babel plugin을 설정한다
    config.module.rules[0].use[0].options.plugins = [
      [
        require.resolve('@emotion/babel-plugin'),
        {
          sourceMap: true,
          autoLabel: 'dev-only',
          labelFormat: '[local]',
          cssPropOptimization: true,
        },
      ],
    ];

    return config;
  },
};

이상입니다. 긴 글 읽어주셔서 감사합니다.

Leave a Reply

Your email address will not be published.