티스토리 뷰

Webpack을 이용해 React 프로젝트를 설정하게 되었습니다. 생각보다 간단하지만은 않아서 환경 설정하는 과정을 기록해두려고 합니다.

 

모든 세팅이 끝난 Wepack-React 보일러플레이트 저장소

 

GitHub - Gilpop8663/webpack-react-boilerplate: react-18, webpack5, typescript, storybook, jest, msw, @tanstack/react-query_v4, s

react-18, webpack5, typescript, storybook, jest, msw, @tanstack/react-query_v4, styled-components, eslint, dotenv, whatwg-fetch - GitHub - Gilpop8663/webpack-react-boilerplate: react-18, webpack5, ...

github.com

 

1. package.json파일 생성

npm init -y

 

2. 기본 패키지 설치 및 설정

 

.gitignore 설정

/node_modules
.env
/dist

 

React 필수 패키지 설치

npm i react react-dom react-router-dom

 

타입스크립트 및 타입 패키지 설치

npm i -D typescript @types/react @types/react-dom

 

tsconfig.json 파일 생성

 

절대 경로가 설정되어 있고, 테스트 폴더 및 styled-components.d.ts가 include 되어 있는 파일이니 사용하시는 용도에 따라 수정이 필요합니다.

 

{
  "compilerOptions": {
    "target": "es2021",
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "react-jsx",
    "noEmit": false,
    "baseUrl": "src",
    "paths": {
      "@assets/*": ["assets/*"],
      "@pages/*": ["pages/*"],
      "@components/*": ["components/*"],
      "@hooks/*": ["hooks/*"],
      "@styles/*": ["styles/*"],
      "@utils/*": ["utils/*"],
      "@constants/*": ["constants/*"],
      "@type/*": ["types/*"],
      "@atoms/*": ["atoms/*"],
      "@selectors/*": ["selectors/*"],
      "@routes/*": ["routes/*"],
      "@api/*": ["api/*"],
      "@mocks/*": ["mocks/*"]
    },
    "outDir": "./dist"
  },
  "include": ["src", "src/custom.d.ts", "__tests__", "styled-components.d.ts"],
  "exclude": ["node_modules"]
}

 

.prettierrc.js 설정

module.exports = {
  printWidth: 100, // 한줄당 문자 100개로 제한
  singleQuote: true, // "" => ''
  arrowParens: 'avoid', // arrow function parameter가 하나일 경우 괄호 생략
};

 

Webpack 설정

npm install --save-dev webpack
npm install --save-dev webpack-cli
npm install webpack-dev-server --save-dev

npm i --save-dev html-webpack-plugin
npm install --save-dev clean-webpack-plugin
npm install dotenv-webpack --save-dev

npm install ts-loader --save-dev

npm install --save-dev css-loader
npm install --save-dev style-loader

 

webpack.common.js

빌드 시 dist 폴더에 결과물이 생깁니다. 스토리북에서 msw를 설정하기 위해 devServer의 static을 public으로 해주었습니다. dotenv를 사용하기 위한 설정이 있습니다. 

 

const path = require('path');

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

module.exports = {
  mode: 'development',
  entry: './src/index.tsx',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist'),
    clean: true,
    publicPath: '/',
  },
  resolve: {
    extensions: ['.ts', '.tsx', '.js', '.jsx'],
    alias: {
      '@assets': path.resolve(__dirname, 'src/assets'),
      '@pages': path.resolve(__dirname, 'src/pages'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@hooks': path.resolve(__dirname, 'src/hooks'),
      '@styles': path.resolve(__dirname, 'src/styles'),
      '@utils': path.resolve(__dirname, 'src/utils'),
      '@constants': path.resolve(__dirname, 'src/constants'),
      '@type': path.resolve(__dirname, 'src/types'),
      '@atoms': path.resolve(__dirname, 'src/atoms'),
      '@selectors': path.resolve(__dirname, 'src/selectors'),
      '@routes': path.resolve(__dirname, 'src/routes'),
      '@api': path.resolve(__dirname, 'src/api'),
      '@mocks': path.resolve(__dirname, 'src/mocks'),
    },
  },
  module: {
    rules: [
      {
        test: /\.(js|ts|tsx)$/i,
        exclude: /node_modules/,
        use: {
          loader: 'ts-loader',
        },
      },
      {
        test: /\.css$/i,
        use: ['style-loader', 'css-loader'],
      },
      {
        test: /\.svg/,
        type: 'asset/inline',
      },
    ],
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html',
    }),
    new CleanWebpackPlugin(),
    new DotenvWebpack(),
  ],
  devtool: 'inline-source-map',
  devServer: {
    static: 'public',
    hot: true,
    open: true,
  },
};

 

webpack.dev.js

 

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

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

module.exports = merge(common, {
  mode: 'development', // 현재 개발 모드
  devtool: 'eval', // 최대성능, 개발환경에 추천
  devServer: {
    historyApiFallback: true,
    port: 3000,
    hot: true,
  },
});

 

webpack.prod.js

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

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

module.exports = merge(common, {
  mode: 'production', // 현재 배포 모드
  devtool: 'hidden-source-map', // 느리지만 안전 배포에 추천
});

 

webpack 명령어 설정

 

package.json

  "scripts": {
    "dev": "webpack-dev-server --config webpack.dev.js --open --hot",
    "build": "webpack --config webpack.prod.js",
    "start": "webpack --config webpack.dev.js",
    }

 

 

public/index.html , public 폴더에 index.html 생성 (react code를 집어넣을 html)

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>React</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

 

src/index.tsx, src 폴더에 index.tsx 생성 (html과 App 컴포넌트를 연결해 주는 파일)

import React from 'react';

import ReactDOM from 'react-dom/client';

import App from './App';

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

 

src/App.tsx 생성

import React from 'react';

const App = () => (
  <>
    <h1>Hi!</h1>
  </>
);

export default App;

 

 

여기까지만 해도 실행 됩니다!


 

추가적인 패키지 설치 및 설정

MSW

 

npx msw init public/ save을 하면 public 폴더에 mockServiceWorker 파일이 생기게 됩니다.

npm install msw --save-dev

npx msw init public/ --save

 

 

src/mocks/worker.ts

import { setupWorker } from 'msw';
import { handlers } from './handlers';

export const worker = setupWorker(...handlers);

 

src/mocks/handlers.ts

import { mockPostList } from './postList';

export const handlers = [...mockPostList];

src/mocks/postList.ts

예제  파일입니다.

import { rest } from 'msw';

export const MOCK_POST_LIST = [
  {
    id: 1,
    text: 'hi',
  },
  {
    id: 2,
    text: 'hi2',
  },
  {
    id: 3,
    text: 'hi3',
  },
];

export const mockPostList = [
  rest.get('/posts', (req, res, ctx) => {
    return res(ctx.status(200), ctx.json(MOCK_POST_LIST));
  }),
];

 

src/index.tsx

import React from 'react';

import ReactDOM from 'react-dom/client';

import App from './App';

if (process.env.NODE_ENV === 'development') {
  const { worker } = require('./mocks/worker');
  worker.start();
}

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);

root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

 

MSW가 켜진 모습

 

 

Storybook

 

npx storybook@latest init

npm install --save-dev tsconfig-paths-webpack-plugin

npm i msw msw-storybook-addon -D

Webpack 5를 선택해줍니다.

 

스토리북 절대경로를 하지 않았을 경우

스토리북 MSW 설정

 

.storybook/public/mockServiceWorker.js 파일 생성 후 public 폴더에 있는 mockServiceWorker.js를 그대로 복사 붙여 넣기 해줍니다.

 

mockServiceWorker

 

 

.storybook/preview.tsx

import type { Preview } from '@storybook/react';

import { initialize, mswDecorator } from 'msw-storybook-addon';

import React from 'react';

import { BrowserRouter } from 'react-router-dom';

import { handlers } from '../src/mocks/handlers';

initialize();

const preview: Preview = {
  parameters: {
    msw: handlers,
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
  decorators: [
    mswDecorator,
    Story => (
      <BrowserRouter>
        <Story />
      </BrowserRouter>
    ),
  ],
};

if (typeof global.process === 'undefined') {
  const { worker } = require('../src/mocks/worker');
  worker.start();
}

export default preview;

 

 

스토리북 절대경로 설정

 

.storybook/main.ts

import path from 'path';
import type { StorybookConfig } from '@storybook/react-webpack5';

const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/react-webpack5',
    options: {},
  },
  docs: {
    autodocs: 'tag',
  },
  webpackFinal: async config => {
    if (!config.resolve) {
      config.resolve = {};
    }

    if (!config.resolve.plugins) {
      config.resolve.plugins = [];
    }

    config.resolve.plugins.push(
      new TsconfigPathsPlugin({
        configFile: path.resolve(__dirname, '../tsconfig.json'),
      })
    );
    return config;
  },
  staticDirs: ['./public'],
};
export default config;

 

스토리북에서 절대 경로와 MSW가 작동하는 모습

 

Jest

npm install --save-dev  jest jest-environment-jsdom @types/jest


// 리엑트 컴포넌트, 훅 테스팅 패키지

npm install --save-dev @testing-library/react

// node 환경에서 fetch를 테스트할 수 있도록 하기 위해 설치하는 패키지

npm install --save-dev whatwg-fetch

 

jest.config.js

절대경로 설정이 되어있는 세팅입니다. 따로 수정이 필요해요

 

module.exports = {
  testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[jt]s?(x)'],
  testEnvironment: 'jsdom',
  moduleNameMapper: {
    '^@assets/(.*)$': '<rootDir>/src/assets/$1',
    '^@pages/(.*)$': '<rootDir>/src/pages/$1',
    '^@components/(.*)$': '<rootDir>/src/components/$1',
    '^@styles/(.*)$': '<rootDir>/src/styles/$1',
    '^@api/(.*)$': '<rootDir>/src/api/$1',
    '^@type/(.*)$': '<rootDir>/src/types/$1',
    '^@utils/(.*)$': '<rootDir>/src/utils/$1',
    '^@constants/(.*)$': '<rootDir>/src/constants/$1',
    '^@hooks/(.*)$': '<rootDir>/src/hooks/$1',
    '^@mocks/(.*)$': '<rootDir>/src/mocks/$1',
  },
  setupFilesAfterEnv: ['./jest.setup.js'],
  transformIgnorePatterns: ['<rootDir>/node_modules/'],
};

jest.setup.js

테스트에 필요한 환경설정을 하는 파일입니다. msw 테스트에 필요한 세팅이 적용된 파일입니다.

import 'whatwg-fetch';

import { setupServer } from 'msw/node';

import { handlers } from './src/mocks/handlers';

export const server = setupServer(...handlers);

beforeAll(() => {
  server.listen();
});

afterEach(() => {
  server.resetHandlers();
});

afterAll(() => {
  server.close();
});

루트에 __tests__ 폴더 생성 후 테스트 파일 생성

__tests__/unit.test.ts

describe('테스트 설정한다.', () => {
  test('1 + 1 = 2', () => {
    expect(1 + 1).toBe(2);
  });
});

 

__tests_/hook.test.ts

import { renderHook, act } from '@testing-library/react';

import { useCount } from '@hooks/useCount';

test('useCount hook을 테스트한다.', () => {
  const { result } = renderHook(() => useCount());

  act(() => {
    result.current.increase();
  });

  expect(result.current.count).toBe(1);
});

 

src/hooks/useCount.ts

예제 파일입니다.

import { useState } from 'react';

export const useCount = () => {
  const [count, setCount] = useState(0);

  const increase = () => {
    setCount(count + 1);
  };

  return { count, increase };
};

 

__tests__/postList.ts

import { MOCK_POST_LIST } from '@mocks/postList';

describe('게시글 목록을 통신하여 불러올 수 있다.', () => {
  test('게시글 목록을 볼러올 수 있다.', async () => {
    const data = await fetch('/posts').then(response => response.json());

    expect(data).toEqual(MOCK_POST_LIST);
  });
});

 

유닛 테스트, 훅 테스트, MSW를 이용한 테스트가 정상 작동하는 모습

 

styled-component

 

npm install styled-components -D

 

src/styles/reset.ts

export const reset = /*css*/ `
/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
html,
body,
p,
ol,
ul,
li,
dl,
dt,
dd,
blockquote,
figure,
fieldset,
legend,
textarea,
pre,
iframe,
hr,
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  padding: 0;
}
h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: 100%;
  font-weight: normal;
}
ul {
  list-style: none;
}
button,
input,
select {
  margin: 0;
}
html {
  box-sizing: border-box;
}
*,
*::before,
*::after {
  box-sizing: inherit;
}
img,
video {
  height: auto;
  max-width: 100%;
}
iframe {
  border: 0;
}
table {
  border-collapse: collapse;
  border-spacing: 0;
}
td,
th {
  padding: 0;
}

button{
  background: none;
}

a{
  color: inherit;
  text-decoration: none;
}
`;

 

src/styles/globalStyle.ts

import { createGlobalStyle } from 'styled-components';

import { reset } from './reset';

export const GlobalStyle = createGlobalStyle`
  ${reset}
    
  * {
    padding: 0;
    margin: 0;
    box-sizing: border-box;
    border:none
  }
  
  ul,
  li {
    list-style: none;
  }
  
  html,
  body {
    font-family: sans-serif;
    font-size: 62.5%;
  }

  :root {
    /* Colors *****************************************/
    --primary-color: #FA7D7C;
    --white: #ffffff;
    --slate: #94A3B8;
    --gray: #F4F4F4;
    --red: #F51A18;
    --dark-gray: #929292;
    --header: #1f1f1f;
    --graph-color-purple:#853DE1;
    --graph-color-green:#5AEAA5;
    
    /* Fonts *****************************************/
    --text-title: 600 2rem/2.4rem san-serif;
    --text-subtitle: 600 1.8rem/2.8rem san-serif;
    --text-body: 400 1.6rem/2.4rem san-serif;
    --text-caption: 400 1.4rem/2rem san-serif;
    --text-small: 400 1.2rem/1.8rem san-serif;
  }  
`;

 

src/styles/theme.ts

import { DefaultTheme } from 'styled-components';

const breakpoint = {
  /** @media (min-width: 576px) { ... } */
  sm: '576px',
  /** @media (min-width: 960px) { ... } */
  md: '960px',
  /** @media (min-width: 1440px) { ... }*/
  lg: '1440px',
};
const zIndex = {
  header: 100,
  modal: 200,
};

export type ZIndex = typeof zIndex;
export type Breakpoint = typeof breakpoint;

export const theme: DefaultTheme = {
  breakpoint,
  zIndex,
};

 

src/styles/styled-components.d.ts 파일 생성

이 파일에 theme에 사용되는 CSS 속성들을 선언함으로써 theme을 사용할 때 자동 완성이 가능하게 됩니다.

import { Breakpoint, ZIndex } from '@styles/theme';
import 'styled-components';

declare module 'styled-components' {
  export interface DefaultTheme {
    breakpoint: Breakpoint;
    zIndex: ZIndex;
  }
}

자동 완성이 되는 모습

 

src/App.tsx

App.tsx에 글로벌 스타일 적용 및 theme 적용

import React from 'react';

import { GlobalStyle } from '@styles/globalStyle';
import { theme } from '@styles/theme';
import { ThemeProvider } from 'styled-components';

const App = () => (
  <ThemeProvider theme={theme}>
    <GlobalStyle />
  </ThemeProvider>
);

export default App;

 

.storybook/preview.tsx

GlobalStyle을 추가해 줍니다.

import type { Preview } from '@storybook/react';

import { GlobalStyle } from '../src/styles/globalStyle';

import { initialize, mswDecorator } from 'msw-storybook-addon';

import React from 'react';

import { BrowserRouter } from 'react-router-dom';

import { handlers } from '../src/mocks/handlers';

initialize();

const preview: Preview = {
  parameters: {
    msw: handlers,
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
  decorators: [
    mswDecorator,
    Story => (
      <BrowserRouter>
        <GlobalStyle />
        <Story />
      </BrowserRouter>
    ),
  ],
};

if (typeof global.process === 'undefined') {
  const { worker } = require('../src/mocks/worker');
  worker.start();
}

export default preview;

 

 

SVG 파일 인식 못하는 문제

svg를 사용하려고 하면 선언해 달라는 에러가 나옵니다.

 

 

src/svg.d.ts 생성

declare module '*.svg' {
  const content: any;
  export default content;
}

 

@tanstack-react-query 설치

 

npm i @tanstack/react-query

 

src/App.tsx 앱에 React-Query 적용

 

QueryClientProvider를 추가해 줍니다.

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from 'styled-components';

import { GlobalStyle } from '@styles/globalStyle';
import { theme } from '@styles/theme';

const queryClient = new QueryClient();

const App = () => (
  <ThemeProvider theme={theme}>
    <GlobalStyle />
    <QueryClientProvider client={queryClient}></QueryClientProvider>
  </ThemeProvider>
);

export default App;

 

. storybook/preview.tsx 스토리북에 React-Query 적용

 

QueryClient, QueryClientProvider과 같은 설정을 해줍니다.

 

import type { Preview } from '@storybook/react';

import { initialize, mswDecorator } from 'msw-storybook-addon';

import { GlobalStyle } from '../src/styles/globalStyle';
import React from 'react';

import { BrowserRouter } from 'react-router-dom';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { handlers } from '../src/mocks/handlers';

const queryClient = new QueryClient();
initialize();


const preview: Preview = {
  parameters: {
    msw: handlers,
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
  },
  decorators: [
    mswDecorator,
    Story => (
      <QueryClientProvider client={queryClient}>
        <BrowserRouter>
          <GlobalStyle />
          <Story />
        </BrowserRouter>
      </QueryClientProvider>
    ),
  ],
};

if (typeof global.process === 'undefined') {
  const { worker } = require('../src/mocks/worker');
  worker.start();
}

export default preview;

 

 

router 설정

src/App.tsx

import { RouterProvider } from 'react-router-dom';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from 'styled-components';

import router from '@routes/router';

import { GlobalStyle } from '@styles/globalStyle';
import { theme } from '@styles/theme';

const queryClient = new QueryClient();

const App = () => (
  <ThemeProvider theme={theme}>
    <GlobalStyle />
    <QueryClientProvider client={queryClient}>
      <RouterProvider router={router} />
    </QueryClientProvider>
  </ThemeProvider>
);

export default App;

 

src/routes/router.tsx

import { createBrowserRouter } from 'react-router-dom';

import Home from '@pages/Home';
import Post from '@pages/Post';

const router = createBrowserRouter([
  {
    path: '/',
    children: [
      { path: '', element: <Home /> },
      {
        path: 'posts',
        element: <Post />,
      },
    ],
  },
]);
export default router;

 

 

 

eslint, eslint 순서 정렬 설정

npm i eslint -D
npm i eslint-config-prettier -D
npm i eslint-config-react-app -D
npm i eslint-plugin-import -D
npm install eslint-plugin-storybook --save-dev

 

.eslintrc

 

import 순서가 설정되어 있습니다. 임의대로 수정하여 사용하시면 됩니다.

{
  "extends": ["react-app", "eslint:recommended", "react-app/jest", "plugin:storybook/recommended"],
  "rules": {
    "no-var": "error", // var 금지
    "no-multiple-empty-lines": "error", // 여러 줄 공백 금지
    "no-console": ["error", { "allow": ["warn", "error", "info"] }],
    "eqeqeq": "error", // 일치 연산자 사용 필수
    "dot-notation": "error", // 가능하다면 dot notation 사용
    "no-unused-vars": "error", // 사용하지 않는 변수 금지
    "import/order": [
      "error",
      {
        "groups": [
          "type",
          "builtin",
          "external",
          "internal",
          "parent",
          "sibling",
          "index",
          "unknown"
        ],
        "newlines-between": "always",
        "pathGroups": [
          {
            "pattern": "react*",
            "group": "external",
            "position": "before"
          },
          {
            "pattern": "@type/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@hooks/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@atoms/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@selectors/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@routes/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@api/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@pages/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@components/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@constants/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@utils/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@styles/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@assets/**",
            "group": "internal",
            "position": "after"
          },
          {
            "pattern": "@mocks/**",
            "group": "internal",
            "position": "after"
          }
        ],
        "alphabetize": {
          "caseInsensitive": true,
          "order": "asc"
        },
        "pathGroupsExcludedImportTypes": []
      }
    ]
  }
}

 

 

설정 끝!

 

참고 자료

 

 

Install Storybook

Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It’s open source and free.

storybook.js.org

 

 

CRA 없이 리액트 시작하기 with webpack

1. CRA 없이 리액트를 시작해야하는 이유 create-react-app을 이용하면 정말 편하게 리액트 프로젝트를 시작할 수 있다. 웹팩과 바벨, 타입스크립트 설정까지 제공해준다. 하지만 내 방식대로 설정을

yogjin.tistory.com

 

 

CRA없이 TypeScript, Eslint, Prettier, Husky, Jest, StoryBook, Styled-Components 환경 구축하기

CRA없이 React환경을 구축합니다.

kagrin97-blog.vercel.app

 

 

Eslint Import/Order (import 순서) 설정하기

React/Next/Javscript Eslint Import/Order (import 순서) 설정하는 방법 0. 이 글을 쓰게 된 계기 과거부터 지금까지 import를 순서를 관리한다면 일일이 수동으로 알파벳 순으로 맞춰주었었는데 리엑트에서 폴

hell-of-company-builder.tistory.com

 

 

Install - Getting Started

Mock Service Worker Docs

mswjs.io

 

 

Installation | TanStack Query Docs

You can install React Query via NPM, or a good ol' `` via unpkg.com.

tanstack.com

 

 

Mock Service Worker Addon | Storybook: Frontend workshop for UI development

Mock API requests in Storybook with Mock Service Worker.

storybook.js.org

 

 

styled-components: Basics

Get Started with styled-components basics.

styled-components.com

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
글 보관함