webpack은 정적 모듈 번들러로 웹에서 사용되는 모든 자원을 번들링 해주는 도구이다.
- HTML 파일에 들어가는 자바스크립트 파일들을 하나의 자바스크립트 파일로 만들어주는 방식을 모듈 번들링 이라고 한다.
그동안 프로젝트를 할 때 CRA로 프로젝트를 시작했는데, CRA로 프로젝트를 생성한다면 필요없는 의존성을 가지게 되는 경우가 있기도 한다. 따라서 이번 프로젝트를 시작하면서는 우리가 필요한 설정만 입맛대로 할 수 있도록 webpack을 이용해보았다.
공부한다고 >유스<한테 이것저것 물어봤는데 덕분에 확실하게 알 수 있었다.(감사링)
- 참고) 우리의 서비스는 익스플로어를 지원하지 않을 것이기 때문에 babel을 따로 설치하지 않았다.
- 참고) 우리는 css-in-js 스타일 컴포넌트를 사용할 예정이기 때에 css 설치는 건너뛰었다.
1. package.json 생성
프로젝트의 관련된 정보와 패키지를 관리하는 package.json을 먼저 설치한다.
npm init -y
1-1. package.json 설정
package.json에서 main을 삭제하고 private을 true로 설정한다.
또한 명령어를 추가한다. 아래는 모든 설치가 완료된 후의 모습이다.
{
"name": "frontend",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "jest --watchAll",
"start": "webpack serve --open",
"build": "webpack --mode production",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
... 생략한다.
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-router-dom": "^6.14.1",
"styled-components": "^6.0.2"
},
"msw": {
"workerDirectory": "public"
}
}
2. webpack 설치
npm i -D webpack webpack-cli
webpack을 설치하고 나서 package.json에서 main을 삭제하고 private을 true로 설정한다.
private로 표기하는 이유는 실수로 코드를 배포하는 것을 막기 위해서이다.
npm 패키지 배포할 때만 영향이 있으며 배포할 때는 false로 설정해두면 된다.
다만 우린 배포할 생각이 없기 때문에 신경쓰지 않아도 된다.
2-1. html-webpack-plugin 설치
html-webpack-plugin는 html파일에 javascipt 번들을 자동으로 묶어주는 플러그인이다. 이후 실행 시점을 잡아주면 알아서 번들링해준다.
npm install -D html-webpack-plugin
2-2. webpack-dev-server 설치
webpack-dev-server는 한마디로 개발용 서버(로컬)를 제공해준다. 실제 빌드는 오래걸리기 때문에 webpack-dev-server를 이용해 압축된 결과물을 빠르게 볼 수 있다.
npm install -D webpack-dev-server
2-3. webpack.config 파일 설정
아래는 모든 초기 설정이 끝난 후의 webpack 설정이다. 따라서 글에서 아직 나오지 않은 것도 존재한다.
devtool - 원본 소스와 난독화된 소스를 매핑해주는 방법이다.
- hidden-source-map - source-map과 동일하지만 번들에 참조 주석을 추가하지 않는다. 배포환경과 같이 개발 도구에는 소스맵을 노출하지 않는 경우에 사용한다.
- eval-source-map - 처음에는 느리지만, 리빌드를 빠르게 제공하고 실제 파일을 생성한다. 줄 번호는 원본 코드에 매핑되므로 올바르게 매핑되어 개발 환경에서 유용하다.
const { join, resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const isProduction = process.env.NODE_ENV === 'production'; //배포환경인지 개발환경인지 확인
module.exports = {
mode: isProduction ? 'production' : 'development', //webpack에 내장된 환경별 최적화를 활성화 하기 위함이다.
entry: './src/index.tsx', //단일 엔트리 구문. webpack의 최초 진입점을 설정한다.
devtool: isProduction ? 'hidden-source-map' : 'eval-source-map', //원본 소스와 난독화된 소스를 매핑해주는 방법이다.
devServer: {
static: {
directory: join(__dirname, 'public'), //개발 서버가 실행될 때 해당 디렉토리의 정적파일을 제공하도록 한다.
},
historyApiFallback: true, //개발 환경에서 404에러의 경우 다이렉트로 index.html로 이동하도록 한다.
port: 3000,//프로젝트가 보일 localhost 포트 번호
compress: true,//개발환경에서 압축하여 보여준다. 빠른 실행에 좋다.
},
resolve: { // 모듈을 해석하는 방식이다.
extensions: ['.js', '.ts', '.jsx', '.tsx'], //이러한 확장자를 순서대로 해석한다. webpack은 앞에서 부터 해석하고 남은 것은 해석하지 않는다.
modules: ['node_modules'], //모듈을 해석할 때 검색할 디렉터리를 설정한다.
alias: { '~': join(__dirname, '.', 'src/') }, //우리는 '~'를 붙여절대경로를 사용하는데 절대경로를 알려주기 위한 설정이다.
},
module: {
rules: [{ test: /\\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }], //해당 파일명으로 끝나면 ts-loader로 처리하겠다는 의미이다.
},
output: {
filename: 'bundle.js', //번들링 된 결과물의 이름이다.
path: resolve(__dirname, 'dist'), //절대 경로로 출력 디렉터리를 설정한다.
clean: true, // 번들링 할 때마다 디렉터리를 정리하고 다시 결과물을 만드는 설정이다.
},
plugins: [
new HtmlWebpackPlugin({
template: join(__dirname, './public/index.html'), //생성되는 HTML 파일의 기반이 되는 템플릿 파일의 경로를 설정한다.
}),
],
};
3. react, react-dom, react-router-dom 설치
npm i react react-dom
npm i react-router-dom
4. typescript 설치
webpack에서 타입 체크를 하기위해 typescript와 ts-loader를 설치한다.
npm i -D @types/react @types/react-dom
npm i -D typescript ts-loader
4-1. tsconfig.json 파일 설정
typescript를 설치했으면 프로젝트에서 사용할 타입 설정을 해주도록 한다.
우리 팀에서 사용하는 속성은 아래와 같다.
{
"compilerOptions": { //어떻게 컴파일 할건지
/* Language and Environment */
"target": "esnext" //컴파일할 자바스크립트 버전. esnext는 가장 최신버전
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
] //컴파일에 포함될 라이브러리 파일 목록을 지정한다.
"jsx": "react-jsx" //jsx 코드를 어떻게 생성하는지 지정한다.
"sourceMap": true, //js파일 생성할 때 해당 파일과 연결된 소스 맵 파일을 생성한다.
/* Modules */
"module": "esnext" //컴파일된 JavaScript 코드가 어떤 모듈 시스템을 사용할지 지정한다.
"moduleResolution": "node" //모듈 해석 방법을 결정한다. CommonJS 모듈 형식과 Node.js의 모듈 해석 규칙을 따른다.
"baseUrl": "." //상대적인 경로를 해석하는 기준이 되는 기본 디렉터리를 지정한다.
"paths": {
"~/*": ["src/*"]
} //baseUrl을 기준으로 관련된 위치에 모듈 이름의 경로 매핑 목록을 나열한다. 절대 경로 설정을 한다.
"resolveJsonModule": true //.json 확장자로 import된 모듈을 포함한다. mock data를 사용할 예정이기 때문에 추가했다.
/* JavaScript Support */
"allowJs": true //JavaScript 파일의 컴파일을 허용한다. .js도 typescript가 컴파일 대상으로 인식
/* Interop Constraints */
"esModuleInterop": true //모듈 가져오기/내보내기 작업을 더 편리하게 처리한다. jest.config파일이 모듈이기 때문에 true로 설정한다.
"forceConsistentCasingInFileNames": true // 동일 폴더내 파일의 이름의 대소문자가 정확히 일치하게하는 설정이다.
/* Type Checking */
"strict": true // 타입체크와 코드 검사를 엄격하게 하도록 한다.
"noImplicitAny": true // 암시적 amy에 오류를 발생시킨다.
"noFallthroughCasesInSwitch": true //switch문의 case 절에서 명시적인 종료문을 작성하지 않으면 컴파일 오류가 발생한다.
/* Completeness */
"skipLibCheck": true //모든 선언 파일(*.d.ts)의 타입 검사를 건너뛴다.
},
"include": ["src"] //컴파일 대상으로 포함할 파일이나 폴더를 지정한다."src" 폴더 내에 있는 TypeScript 파일들을 컴파일 대상으로 인식한다.
}
5. styled-components 설치
CSS-in-JS 방식을 사용하기 위해 styled-components를 설치한다. 다른 CSS 방식을 사용한다면 맞는 모듈을 설치하도록 한다.
npm i styled-components
npm i -D @types/styled-components
6. storybook 설치
협업을 하며 컴포넌트를 쉽게 확인할 수 있는 테스트 도구인 스토리북을 사용하기로 했다. 따라서 설치해준다. babel을 따로 설치하지 않았지만 stroybook이 사용하기 때문에 stroybook이 사용하는 부분은 설치된다.
가장 최신 버전을 설치한 이유는 .bind를 사용할 필요가 없다는 것이다. 문법이 간편해지고 직관적이라 사용하기에 편할 것이라고 판단했다. (단, 최신버전인 만큼 참고자료가 부족하고 각종 버그가 있을 수도 있다는 점..)
npx storybook@latest init
npm i -D tsconfig-paths-webpack-plugin
//절대 경로를 사용한다면 스토리북이 경로를 인식할 수 있도록 설치
6-1. storybook main.ts 설정
절대 경로를 설정하기 위해 webpackFinal부분을 추가한다.
import path from 'path';
import TsconfigPathsPlugin from 'tsconfig-paths-webpack-plugin';
import type { StorybookConfig } from '@storybook/react-webpack5';
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',
},
staticDirs: [path.join(__dirname, '..', 'public')],
webpackFinal: async (config, { configType }) => {
config.resolve!.plugins = [new TsconfigPathsPlugin()];
return config;
},
};
export default config;
6-2. storybook preview.tsx 설정
storybook에서 msw를 사용하기 위해 preview.tsx에 설정을 추가한다.
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { initialize, mswDecorator } from 'msw-storybook-addon';
import { handlers } from '../src/mocks/handlers/index';
import { theme } from '../src/styles/theme';
import GlobalStyle from '../src/styles/GlobalStyle';
import type { Preview } from '@storybook/react';
initialize();
const preview: Preview = {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
msw: handlers,
},
};
export const decorators = [
(Story) => (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Story />
</ThemeProvider>
),
mswDecorator,
];
export default preview;
7. msw 설치
API가 나오기 전까지 개발할 때 사용하는 도구인 msw를 설치한다.
npm i -D msw msw-storybook-addon
//msw-storybook-addon은 stroybook에서 msw를 사용하기 위해 설치하는 것
npx msw init public/ --save
8. jest 설치
유틸 함수를 테스트하기 위해 테스트 도구 jest를 설치한다.
npm i -D jest ts-jest
npm i -D @types-jest @types-node
8-1. jest.config.js 설정
jest에서 타입을 사용하기 위해서 파일을 만든다.
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^~/(.*)$': '<rootDir>/src/$1',
},
clearMocks: true,
};
9. eslint, prettier 설치
여러 사람이 협업하기 때문에 코드에 통일을 주기위해 eslint와 prettier을 설치한다.
npm init @eslint/config
npm install --save-dev --save-exact prettier
9-1. pretiierrc.json 설정
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "auto"
}
9-2. eslintrc.json 설정
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "react"],
"rules": {
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/consistent-type-imports": "error",
"react/react-in-jsx-scope": "off"
}
}
10. index.html 만들기
클라이언트가 직접 접근할 수 있는 public 폴더와 index.html을 만든다.
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>팀바팀</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
11. src 파일과 index.ts App.tsx 만들기
컴파일의 시작점인 index.ts와 App.tsx를 만든다.
다음은 모든 설정을 적용한 index.ts이다.
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import { ThemeProvider } from 'styled-components';
import App from '~/App';
import GlobalStyle from '~/styles/GlobalStyle';
import { theme } from './styles/theme';
import { worker } from '~/mocks/browser';
if (process.env.NODE_ENV === 'development') {
worker.start();
}
const router = createBrowserRouter([
{
path: '/',
element: <App />,
},
]);
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(
<StrictMode>
<ThemeProvider theme={theme}>
<GlobalStyle />
<RouterProvider router={router} />
</ThemeProvider>
</StrictMode>,
);
다음은 App.tsx이다
import { styled } from 'styled-components';
const App = () => {
return <div>Hello World!</div>;
};
export default App;
자! 이제 Webpack 기반의 React & TypeScript 환경 세팅이 완료되었다. 이제 알아서 개발하도록 하자.
'FE 기술 > React' 카테고리의 다른 글
React와 SSE(Server Sent Events)로 실시간 채팅 구현하기 (0) | 2024.11.18 |
---|---|
[React] 무한스크롤 구현하기 (useInfiniteQuery, Cursor-based Pagination) (0) | 2024.11.18 |
React) useEffect 사용방법, dependency array (0) | 2023.04.25 |
React) ThemeProvider로 필요한 스타일만 바꾸기 [재사용 가능한 Input 컴포넌트 2] (0) | 2023.04.24 |
React) JSX Spread Operator [재사용 가능한 Input 컴포넌트 1] (0) | 2023.04.24 |