홍승아블로그

vite

Vite를 사용해야 하는 이유

기존 모듈 번들러 문제점

  • 브라우저에서 Native ESM 방식을 지원하기 전까지는 Javascript 모듈화를 브라우저 레벨에서 진행할 수 없었고, 해당 소스 모듈을 브라우저에서 지원하기 위해서 require/IIFE/import,expor를 통한 모듈화 문법을 이용하여서 여러개의 파일로 합쳐주거나 의미 있는 단위로 묶어주는 기능을 번들링이라고 하며, 현재는 WebpackRollup 그리고 Parcel을 도구들이 지원하고 있습니다.

  • 하지만 애플리케이션이 점점 더 발전함에 따라서 JS 도구들의 성능 병목 현상이 발생, HMR 사용하더라도 변경된 파일 적용 시 수초가 걸려서 개발자 생산성을 떨어뜨는 문제가 발생하고 있습니다.

  • ESM(ECMAScript Modules)

    • ESM은 모듈화 문법인 import, export를 별도의 도구 없이 브라우저 자체에서 소화해 낼 수 있는 모듈 방식을 의미한다. 만약 아래와 같은 코드를 웹팩과 같은 번들러 없이 브라우저에서 실행하면 에러가 발생합니다.
    // app.js
    import { sum } from 'test.js'
    console.log(sum(1,2));
    
    <script src="app.js></script>

    Untitled 예전에는 브라우저가 import와 export를 해석할 수 있는 능력이 없었지만 이제는 다음과 같이 속성을 추가하면 브라우저에서 import와 export를 소화할 수 있게 되었습니다.

    <script type="module" src="app.mjs></script>

Vite 특징

  • 콜드 스타트 방식의 개발 서버 구동에 대한 성능 개선 Untitled

    • Dependencies: node_modulesd와 같이 내용이 바뀌지 않는 소스코드에 대해서 esbuild 이용한 사전 번들링 기능 제공

      • esbuild는 webpack, parcel과 같은 기존의 번들러 대비 10-100배 빠른 속도를 제공

      • esbuild 로 node_modules 의 모든 패키지를 사전 번들링하여 아래와 같이 serving 하고 캐시

        // Dependencies package import 할 경우
        import dayjs from 'dayjs';
        
        // vite esm 지원을 위한 변환/캐싱
        /node_modules/.vite/deps/dayjs.js?v=b09621d8
    • Source code: Native ESM 방식을 적용해서 브라우저가 모듈을 직접 가져오고 변환 및 제공하도록 처리 Untitled

  • HMR 성능 개선 기존 번들러 기반에서는 소스 코드 변경 시 다시 번들링 과정을 다시 수행하다보니 성능이 느려지는 이슈를 Native ESM 방식을 사용하여 변경된 모듈만 교체해서 성능 개선 HTTP 헤더를 활용하여 페이지 로드 속도 개선(요청 횟수 최소화)

    • 304 Not Modified: 클라이언트 캐시 사용
    • Cache-Control: max-age=31536000, immutalble 이용한 캐시 사용
  • 프로덕션 환경 Rollup 사용

    • 가장 빠른 성능을 자랑하는 esbuild이지만 아쉬운 생태계와 브라우저용 번들링에서는 안정성이 떨어진다는 평가가 있어서 vite에서는 프로덕션 환경에서 Rollup 번들러를 사용(트리 셰이킹, 지연로딩, 파일 분활 등 성능 이점 제공)
    • Rollup은 v4에서 파서를 SWC 전환 → Rust 포팅인 Rolldown 만드는 작업을 진행 중이며 개발이 완료된다면 esbuild를 모두 대체 가능(개발/빌드 사이에 불이치 제거 목표)
    • https://www.youtube.com/watch?v=EKvvptbTx6k

CRA to Vite Migration

base

  • Remove CRA package for Vite 기존 CRA 환경에서 사용되는 패키지 삭제 기존에 설치된 node_modules가 있는경우 폴더 자체를 삭제

    yarn remove react-scripts react-app-rewired babel-loader webpack-merge tsconfig-paths-webpack-plugin
    
    rm -rf node_modules
  • Setting the Stage with Vite Installation vite: 모던 웹 프로젝트 빌드 도구

    • @vitejs/plugin-react: @vitejs/plugin-react는 vite에서 React를 사용할 때 필요한 구성을 추가하여 React 애플리케이션의 빠른 개발 및 번들링을 지원
    • vite-tsconfig-paths: tsconfig.json 파일에 설정된 경로 별칭을 Vite에서도 적용할 수 있도록 지원. vite-tsconfig-paths를 사용하면 TypeScript 설정에 정의된 경로 별칭이 Vite에서도 인식되어 모듈 경로를 해석할 때 사용 가능
    • vite-plugin-svgr
      • SVG를 React 컴포넌트로 변환하는 Vite 플러그인
      • 단, 3.3.0 이후 버전 설치 시 빌드, 타입 에러 발생(3.3.0 사용 시 아래와 같은 svgr을 리액트 컴포넌트로 변경 가능)
      import { ReactComponent as ChannelLogo } from '../../assets/artwork-illust-pm-assign-info-channel-talk.svg';
    • vite-plugin-checker
      • vite를 사용하는 프로젝트에서 타입 체크, ESLint, VLS (Volar Language Server) 등을 포함한 여러 검사 기능을 제공하는 플러그인
      • 비동기로 체크를 하기 때문에 개발 서버 환경 로딩 속도에 영향을 주지 않는 장점이 있다.
    yarn add vite @vitejs/plugin-react vite-tsconfig-paths vite-plugin-svgr@3.3.0
    
    yarn add vite-plugin-checker -D
  • Updating package.json for Vite Commands

    scripts": {
      "start": "vite",
      "build": "vite build",
    },
  • Renaming React File Extensions to .jsx or .tsx

    mv src/App.js src/App.jsx
    mv src/index.ts src/index.tsx
  • Move the index.html File

    • public 패스의 index.html 삭제하지 않을 경우 vite에서 개발 환경 로딩 시 public/index.html 선 로딩함으로 꼭 루트로 옮겨주세요!!
    mv public/index.html .
  • Add module script to the bottom of the body tag 초기 로딩 시 코드 진입점을 명시해주세요.

    <body>
      {/* others here */}
      <script type="module" src="/src/index.tsx"></script>
    </body>
  • Update tsconfig.json isolatedModules 사용이유

    • 모듈 간의 의존성 격리: 각 모듈이 서로의 타입에 의존하지 않도록 하여, 컴파일 시 모듈을 개별적으로 처리합니다. 이를 통해 TypeScript의 컴파일 시간이 줄어들 수 있습니다.
    • 성능 향상: 독립적으로 모듈을 컴파일하면, 변화가 발생한 모듈만 다시 컴파일되므로 전체 컴파일 속도가 개선됩니다.
    • ESM 호환성: ES 모듈과의 호환성을 높여, 다양한 빌드 도구와의 통합 시 더 나은 결과를 제공합니다.
    • 검사 오류 방지: 모든 모듈이 개별적으로 체크되므로, 한 모듈에서의 타입 오류가 다른 모듈에 영향을 미치는 것을 방지할 수 있습니다. include에 vite.config.js 패스 설정
    {
      "extends": "./tsconfig.extend.json",
      "compilerOptions": {
        "lib": ["dom", "dom.iterable", "es2020"],
        "target": "ES6",
        "types": ["vite/client", "react", "react-dom", "node"],
        "isolatedModules": true // 추가
      },
      "include": [
        "./src/**/*",
        "vite.config.js"
      ],
      "exclude": [
        "node_modules"
      ]
    }
  • Add module html minify CRA 프로젝트에서는 내장된 plugins중에 HtmlWebpackPlugin 통해서 html minify를 해주고 있는데, vite에서 내장이 되어 있지 않아서 직접 설치후에 환경 설정을 추가해줘야 합니다.

    // Installation
    yarn add vite-plugin-html
    
    // vite.config.js
    createHtmlPlugin({
      // input <https://www.npmjs.com/package/html-minifier-terser> options
      minify: true,
    }),

Configuration

  • Crafting the Vite Configuration

    // <root>/vite.config.js
    import { defineConfig, loadEnv } from 'vite';
    import path from 'path';
    import react from '@vitejs/plugin-react';
    import viteTsconfigPaths from 'vite-tsconfig-paths';
    import svgr from 'vite-plugin-svgr';
    import { createHtmlPlugin } from 'vite-plugin-html';
    import checker from 'vite-plugin-checker';
    
    export default defineConfig(config => {
      const env = loadEnv(config.mode, process.cwd(), '');
    
      const generateSourceMap = env.GENERATE_SOURCEMAP;
      const shouldUseSourceMap = generateSourceMap !== 'false';
    
      return {
        build: {
          outDir: 'build', // build 폴더명 변경(기본 dist)
          sourcemap: shouldUseSourceMap, // 소스맵 생성 여부
        },
        define: {
          global: 'globalThis', // 글로벌 객체를 빈 객체로 대체(global is not defined error 에러에 대한 방어코드)
        },
        plugins: [react(), viteTsconfigPaths(), svgr()],
      };
    });

Environment Variables

  • VITE라는 prefix를 설정하지 않을 경우 import.meta.env 에서 값을 얻어 올 수 없습니다.

  • 기본적으로 속성값으로 DEV(vite), PROD(vite build)을 제공해줌으로 개발/빌드 환경 구분이 가능합니다.

  • ChangeEnv Variables and Modes

    // .env Files
    .env                # loaded in all cases
    .env.local          # loaded in all cases, ignored by git
    .env.[mode]         # only loaded in specified mode
    .env.[mode].local   # only loaded in specified mode, ignored by git
    
    // Modes
    # .env.production.live
    VITE_ENVIRONMENT=live
    
    // command mode option flag
    "build:dev": "yarn vite --mode production.dev",
    "build:rc": "yarn vite --mode production.rc",
    "build:live": "yarn vite --mode production.live",
  • Update process.env.REACT_APP

    -process.env.REACT_APP_VARIABLE + import.meta.env.VITE_VARIABLE;

EJS Variables

  • Update Configuration for Vite 빌드 시 HTML CJS 문법이 치환되도록 처리하기 위해서 vite-plugin-html 이용해서 내보내기 처리 추가

    createHtmlPlugin({
      ....
      inject: {
        data: {
          VITE_VARIABLE: env.VITE_VARIABLE,
        },
      },
    }),
  • Replace EJS Variables for Vite 문자만 치환할 경우

    • “%” → “<%= ~~ =>”
    • 예) → 문자안에 글자를 치환할 경우
    // env VITE_VARIABLE='/variable' //
    <root
      >/index.html //// CRA
      <script src="%REACT_APP_VARIABLE%"></script>
    
      //// VITE
      <script src="<%= VITE_VARIABLE %>"></script
    ></root>

    엘리먼트(태그) 자체를 변경할 경우

    • % → <%- ~~ ->
     // env
    VITE_SCRIPT=<script>(function ~~ )</script>
    
    // <root>/index.html
    //// CRA
    %VITE_SCRIPT%
    
    //// VITE
    <%- VITE_SCRIPT %>
  • Updating Vite Commands(with enviroment)

    "start": "vite",
    "start:dev": "yarn start --mode development.dev",
    "start:rc": "yarn start --mode development.rc",
    "build": "vite build",
    "build:dev": "yarn build --mode production.dev",
    "build:rc": "yarn build --mode production.rc",
    "build:live": "yarn build --mode production.live",

Vite Lint, Typescript Checker

  • Add module vite-plugin-checker CRA 환경에서 내부적으로 fork-ts-checker-webpack-plugin 플러그인을 바탕으로 비동기로 타입 체크를 해주고 있지만, VITE 환경에서는 vite-plugin-checker 직접 설치 후 vite.config.js 구성에 추가해줘야 합니다.

    // vite.config.js
    import checker from 'vite-plugin-checker';
    
    checker({
      typescript: config.mode.indexOf('development') > -1,
      eslint: config.mode.indexOf('development') > -1 ? {
        lintCommand: 'eslint "./src/**/*.{ts,tsx,js,jsx}"',
      } : false,
      stylelint: config.mode.indexOf('development') > -1 ? {
        lintCommand: 'stylelint "src/**/*.scss"'
      } : false,
    }),

CRA to Vite Migration Tip

  • import.meta.glob dynamic load Vite의 import.meta.glob을 사용하여 모든 아이콘을 동적으로 가져올 수 있습니다.

    const icons = import.meta.glob('./*Icon.tsx');
    
    const iconComponentPath = './aIcon.tsx';
    
    const Icon =
      icons && icons[iconComponentPath as string]
        ? lazy(
            () =>
              icons[iconComponentPath as string]() as Promise<{
                default: ComponentType;
              }>,
          )
        : null;
  • sass-resources-loader(dart sass) → vite config preprocessorOptions.sass 공통적으로 사용되는 스타일을 전역적으로 적용할 때 사용하는 sass-resources-loader를 vite에서는 preprocessorOptions.sass로 대체가 가능

    // CRA
    rules: [
      {
        test: /\\.scss$/,
        use: [
          {
            loader: 'sass-resources-loader',
            options: {
              resources: [
                path.resolve(__dirname, './sass.scss'),
              ],
            },
          },
        ],
        include: path.resolve(__dirname, './src'),
        exclude: /node_modules/,
      },
    ]
    
    // vite.config.js
    css: {
      preprocessorOptions: {
        scss: {
          // Next line will prepend the import in all you scss files as you did with your vite.config.js file
          additionalData: `
            @import "./sass.scss";
          `,
        },
      },
    },

    단계별 에러 케이스 정리 @forward rules must be written before any other rules 에러

    • @forward 구문이 다른 모든 규칙(@import, @use, 스타일 선언) 보다 위에 위치하도록 수정 @forward
    • Dart Sass에서 도입된 Sass 모듈 시스템의 일부로, 모듈화된 스타일시트를 구성하고 관리하는 데 중요한 역할을 합니다.
    • @import 방식과는 다르게, @forward는 Sass 코드의 모듈화와 재사용성을 더 쉽게 관리할 수 있도록 돕습니다. @use: 모듈을 가져오면서 네임스페이스를 사용해 충돌을 방지하고, 명시적으로 필요한 부분만 가져올 수 있습니다. @import는 파일을 단순히 포함하는 방식으로, 여러 번 포함될 수 있고 변수나 믹스인의 충돌 위험이 있습니다.
    // reset.scss
    @forward 'reset';
    
    // ./sass.scss
    @use 'color';
    
    css: {
      preprocessorOptions: {
        scss: {
          // Next line will prepend the import in all you scss files as you did with your vite.config.js file
          additionalData: `
            @forward 'reset';
    
            @use './color.scss' as *;
            @use './sass.scss' as *
          `,
        },
      },
    },
  • setting module scss name for Vite(선택) CRA에서는 SCSS 모듈을 사용할 때, 파일명과 클래스명을 결합해 고유한 클래스명을 자동으로 생성해주만, VITE 에서는 클래스명으로만 생성되는 규칙을 다르게 가져가고 있음

    • 규칙: [파일명][원래 클래스명][hash:base64:5] 단, 서비스 내에서 module scss를 전역으로 선택하는 경우가 없다면 굳이 세팅을 해줄 필요가 없습니다.(내부적으로는 세팅을 하지 않을 시 [클래스명, hash]값을 토대로 유니크한 값으로 자동 변경)
    import styles from './Test.module.scss';
    
    const Test = () => {
    	return (
    		<div className={styles.container}></div>
    	);
    };
    
    export default Test;
    
    // 변경 전
    class="Test_container__28qL6"
    
    // 변경 후
    //// vite.config.js
    const namespaceUUID = v4();
    css: {
      ...
      modules: {
        // 파일명과 클래스명을 포함한 고유한 이름 생성
        generateScopedName: (name, filename) => {
          // 파일 경로 기반 고유한 해시 생성
          const file = path.basename(filename).replace('.module', '').replace(/\.[^/.]+$/, '');
    
          // UUID 생성 후 Base64 인코딩
          const hash = Buffer.from(v5(`${file}_${name}`, namespaceUUID)).toString('base64').slice(0, 5);
          return `${file}_${name}__${hash}`;
        },
      },
    },
  • server(proxy, outside file) CRA proxy 설정을 VITE proxy로 대체가 가능합니다. VITE 환경 외에 외부 폴더/파일 접근 시 fs.allow 객체로 추가 시 dev server에서 접근이 가능합니다. 기존에는 proxy 패스를 배열로 관리했지만 vite에서는 각각 패스로 관리를 해줘야합니다.

    // CRA
    //// 기존 패키지 삭제
    yarn remove http-proxy-middleware
    
    //// setupProxy.js
    // 수정 전
    const { createProxyMiddleware } = require('http-proxy-middleware');
    module.exports = function setupProxy(app) {
      app.use(createProxyMiddleware(['/test'], {
        changeOrigin: true,
    	  logLevel: 'debug',
    	  autoRewrite: true,
        target: 'http://www.test.kr',
        onProxyRes: (proxyRes) => {
          console.log(proxyRes);
        }),
        onProxyReq: (proxyReq) => {
          proxyReq.setHeader('origin', 'http://www.test.kr');
        },
      }));
    
    // vite.config.js
    //// 수정 후
    const env = loadEnv(config.mode, process.cwd(), '');
    server: {
    	proxy: {
    		fs: {
    	    // 허용할 디렉터리 추가 (여기에 외부 파일 경로를 추가)
    	    allow: [
    	      '../../../outside',
    	      '/'
    	    ],
    	  },
    		'/test': {
    	    changeOrigin: true,
    		  logLevel: 'debug',
    		  autoRewrite: true,
    	    target: 'http://www.test.kr',
    	    configure: (proxy) => {
    	      proxy.on('proxyRes', (proxyRes, env) => {
    	        console.log(proxyRes);
    	      }),
    	      proxy.on('proxyReq', (proxyReq, env) => {
    	        proxyReq.setHeader('origin', 'http://www.test.kr');
    	      });
    	    },
    	  },
    	},
    },
  • external file link settings(alias) CRA에서 외부 파일내에 패스(tsconfig.json)를 접근 시 현재 프로젝트 환경 기준으로 패스를 바라보지만, VITE에서는 외부 파일 기준으로 패스를 접근하다보니 alias 처리에 이슈가 있어서 tsconfig path가 아닌 절대 경로 패스로 재 지정해야 합니다.

    // CRA
    const modifiedConfig = aliasWebpack({
      tsconfig: path.resolve(__dirname, './tsconfig.json'),
    })(config);
    
    // vite.config.js
    resolve: {
      alias: {
        '@outside': path.join(__dirname, '../outside'),
      },
    },
    
  • setting external file access permissions CRA에서는 외부파일 접근 시 내부 config 파일에서 ModuleScopePlugin 플러그인에 추가해주는 번거로움이 있었지만, VITE에서는 빌드 과정에서는 외부 파일 접근이 자유로우나, 개발 환경에서는 외부 파일 패스 경로를 설정해줘야합니다.

    • 단, 파일 패스 설정 시 외부 파일 패스외에 내부 파일 패스도 같이 설정해줘야 합니다.
    fs: {
      // 허용할 디렉터리 추가 (여기에 외부 파일 경로를 추가)
      allow: [
        '../outside',
        '/'
      ],
    },
  • setting baseurl for Vite CRA에서는 내부적으로 BaseURL(PUBLIC_URL)을 기준으로 리소스 패스 경로를 설정해주지만, VITE에서는 해당 경로를 base 속성값으로 변경해줘야합니다. 단, DEV 환경에서는 내부 리소스 패스로 설정해야함으로 패스를 지정하지 말아야합니다.

    // CRA //// index.html
    <head>
      <meta charset="utf-8" />
      <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
    </head>
    
    // VITE //// env.production.dev VITE_PUBLIC_URL=https://test
    <head>
      <meta charset="utf-8" />
      <link rel="shortcut icon" href="/favicon.ico" />
    </head>
    
    //// 결과물
    <head>
      <meta charset="utf-8" />
      <link rel="shortcut icon" href="https://test/favicon.ico" />
    </head>

CRA to Vite Migration Storybook

  • Installation 이전 버전 Storybook 삭제

    yarn remove @storybook/addon-*(knobs, controls 등등)

    vite 연동을 위한 스토리북 패키지 추가 기존 CRA 환경에서는 @storybook/react 패키지 명령어를 사용해서 문제가 없었지만, VITE에서는 @storybook/react-vite 패키지를 사용하면서 storybook 패키지를 추가적으로 설치가 필요합니다. 또한, storybook 연동 패키지가 6버전 사용 시 ‘storybook-channel-mock.js:7 Uncaught TypeError: import_channels.default is not a constructor’ 와 같은 에러가 발생하게 됩니다. 꼭 최신 버전인 8버전 이상에 패키지를 설치를 해주세요

    yarn add @storybook/builder-vite @storybook/react @storybook/react-vite storybook -D
  • setting .storybook/main addon 설정

     addons: [
      '@storybook/addon-links',
      '@storybook/addon-essentials',
    ],

    builder, framework 지정

    core: {
      builder: '@storybook/builder-vite', // 👈 The builder enabled here.
    },
    framework: '@storybook/react-vite',

    base static path 지정

    // CRA - @storybook/react
    "storybook": "start-storybook -p 9009 -s public",
    
    // VITE - @storybook/vite
    staticDirs: ['../public'], // 이전 버전(v6) -s public 옵션과 동일

    webpackFinal → ViteFinal 마이그레이션

    • Preprocess SCSS, 허용할 파일 패스 연동
    // webpack
    config.module.rules.push({
      test: /\.scss$/,
      use: [
        {
          loader: 'sass-resources-loader',
          options: {
            resources: [path.resolve(__dirname, '../sass.scss')],
          },
        },
      ],
      include: path.resolve(__dirname, '../src'),
      exclude: /node_modules/,
    });
    
    // Vite
    const { mergeConfig } = await import('vite');
    return mergeConfig(config, {
      // Merge custom configuration into the default config
      const { mergeConfig } = await import('vite');
      css: {
        preprocessorOptions: {
          scss: {
          // Next line will prepend the import in all you scss files as you did with your vite.config.js file
          additionalData: `
            @forward 'reset';
    
            @use './color.scss' as *;
            @use './sass.scss' as *
          `,
    	    },
        },
      },
    • 외부 파일 접근
    // VITE
    server: {
      fs: {
        // 허용할 디렉터리 추가 (여기에 외부 파일 경로를 추가)
        allow: [
          '../outside',
          '/'
        ],
      },
    },
  • storybook code migration processing 8 버전 업데이트가 되면서 argTypes에서 기본값 세팅을 할 수 없고 args에서 세팅하는걸로 변경되었습니다. 빌드 에러가 발생하진 않지만, 확인해보시면 undefined 설정되서 들어가게 되니 꼭 마이그레이션 시 변경 해주세요.

    // CRA - storybook
    argTypes: {
      isAIRecommendationModalOpened: {
        name: '모달 오픈여부',
        defaultValue: true,
        control: {
          type: 'boolean',
        },
      },
    },
    
    // VITE - storybook
    argTypes: {
      isAIRecommendationModalOpened: {
        name: '모달 오픈여부',
        control: 'boolean',
      },
    },
    args: {
      isAIRecommendationModalOpened: true,
    },
  • package.json storybook command

    // CRA
    "storybook": "start-storybook -p 9009 -s public",
    "build-storybook": "build-storybook -s public -o .storybook/build --quiet",
    
    // VITE
    "storybook": "storybook dev -p 9009",
    "build-storybook": "storybook build -o .storybook/build --quiet"

Jest to Vitest Migration

  • Installation

    yarn add -D vitest jsdom @testing-library/dom @testing-library/jest-dom @testing-library/react @testing-library/user-event
  • setting 기존에 package.json/jest.setup.js 설정된 테스트 구성을 vitest.config.js로 이전

    // 변경 전
    //// package.json
    "jest": {
      "collectCoverageFrom": [
        "!<rootDir>/node_modules/"
      ],
      "moduleNameMapper": {
        "lodash-es": "lodash",
      },
      "transformIgnorePatterns": [
        "/node_modules/",
      ],
      "setupFilesAfterEnv": [
        "<rootDir>/jest.setup.js"
      ]
    },
    
    // 변경 후
    //// vitest.config.js
    export default defineConfig({
      resolve: {
        alias: {
          "lodash-es": "lodash",
        },
      },
      plugins: [
        react(),
        viteTsconfigPaths(), // tsconfig 파일 패스 연동
      ],
      test: {
        environment: 'jsdom', // React 테스트 환경을 위해 설정
        globals: true,        // Jest와 동일한 글로벌 변수를 사용
        setupFiles: './src/setupTests.ts', // setup 파일
      },
    });
    
    // src/setupTest.ts
    import '@testing-library/jest-dom';

    기존 리액트 테스트 환경 테스트를 위해서 test 환경을 꼭 json 으로 설정해주세요. 타입스크립트 타입에 vitest 추가해주세요.

    // tsconfig.json
    "compilerOptions": {
      "lib": ["dom", "dom.iterable", "es2020"],
      "target": "ES6",
      "types": ["vite/client", "react", "react-dom", "node", "vitest/globals"], // vitest/globals 추가
      "isolatedModules": true
    },

    eslint 글로벌 체크 타입 변수에 vi 추가

    // .eslint.js
    module.exports = {
      globals: {
        context: 'readonly',
        vi: true, // vi 추가
      },
    };
  • jest code migration processing module mocks

    // 변경 전
    jest.mock('./some-path', () => 'hello');
    
    // 변경 후
    vi.mock('./some-path', () => 'hello');

    Importing the Original of a Mocked Package

    // 변경 전
    const { cloneDeep } = jest.requireActual('lodash/cloneDeep');
    
    // 변경 후
    const { cloneDeep } = await vi.importActual('lodash/cloneDeep');
  • package.json test commands

    "test": "vitest",
    "test:run": "vitest run",

MSW Migration Guide

  • vitest 테스트와 msw 0.43.1 버전 같이 사용 시 아래 같은 문구에 에러가 발생

  • vitest에서 ECMAscript 모듈을 지원하고 있고 msw 이전버전에서는 미지원함에 따른 에러 발생으로 추측(버전을 최신 버전으로 변경해서 수정)

  • vitest Package subpath ‘./lib’ is not defined by “exports” in node_modules/headers-polyfill/package.json

  • Installation

    • msw 버전을 최신버전으로 업그레이드 함에 따른 storybook에 사용되는 storybook-addon도 동일하게 업그레이드 처리해야 합니다.
    • msw-storybook-addon 관련 스토리부 설정을 기존과 동일합니다.
    yarn add -D msw@2.4.11 msw-storybook-addon@2.0.3
  • msw code migration processing worker-imports

    // 변경 전
    import { setupWorker } from 'msw';
    
    // 변경 후
    import { setupWorker } from 'msw/browser';

    Response resolver arguments

    // 변경 전
    rest.get('/resource', (req, res, ctx) => {});
    
    // 변경 후
    import { http } from 'msw';
    http.get('/resource', ({ request }) => {
      const url = new URL(request.url);
      const productId = url.searchParams.get('id');
    });

    Response declaration

    // 변경 전
    rest.get('/resource', (req, res, ctx) => {
      return res(ctx.status(200), ctx.json({ id: 'abc-123' }));
    });
    
    // 변경 후
    
    // 변경 후 2
    import { http, HttpResponse } from 'msw';
    
    export const handlers = [
      http.get('/resource', () => {
        return HttpResponse.json({ id: 'abc-123' }, { status: 200 });
      }),
    ];

    Request params

    // 변경 전
    rest.get('/post/:id', req => {
      const { id } = req.params;
    });
    
    // 변경 후
    import { http } from 'msw';
    
    http.get('/post/:id', ({ params }) => {
      const { id } = params;
    });

    Request cookies

    // 변경 전
    rest.get('/resource', req => {
      const { token } = req.cookies;
    });
    
    // 변경 후
    import { http } from 'msw';
    
    http.get('/resource', ({ cookies }) => {
      const { token } = cookies;
    });

    Request Body

    // 변경 전
    rest.post('/resource', req => {
      // The library would assume a JSON request body
      // based on the request's "Content-Type" header.
      const { id } = req.body;
    });
    
    // 변경 후
    import { http } from 'msw';
    
    http.post('/user', async ({ request }) => {
      // Read the request body as JSON.
      const user = await request.json();
      const { id } = user;
    });

결과

빌드 성능: 6.16s → 2.49s ( 3.67s 빠름 )

Untitled

Untitled

Dev Server 구동: 167ms → 엄청 빠름^^

Untitled

참고페이지

이전글
개발 생산성 & 빌드 성능 개선
다음글
esbuild