require.include
는 더 이상 지원되지 않으며 사용 시 기본적으로 경고를 표시Rule.parser.requireInclude
를 사용하여 동작을 허용, 지원 중단, 또는 비활성화로 변경webpack은 정적 모듈 번들러로 웹에서 사용되는 모든 자원을 번들링 해주는 도구이다.
그동안 프로젝트를 할 때 CRA로 프로젝트를 시작했는데, CRA로 프로젝트를 생성한다면 필요없는 의존성을 가지게 되는 경우가 있기도 한다. 따라서 이번 프로젝트를 시작하면서는 우리가 필요한 설정만 입맛대로 할 수 있도록 webpack을 이용해보았다.
프로젝트의 관련된 정보와 패키지를 관리하는 package.json을 먼저 설치한다.
npm init -y
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"
}
}
npm i -D webpack webpack-cli
webpack을 설치하고 나서 package.json에서 main을 삭제하고 private을 true로 설정한다.
private
로 표기하는 이유는 실수로 코드를 배포하는 것을 막기 위해서이다.
npm 패키지 배포할 때만 영향이 있으며 배포할 때는 false로 설정해두면 된다.
다만 우린 배포할 생각이 없기 때문에 신경쓰지 않아도 된다.
html-webpack-plugin는 html파일에 javascipt 번들을 자동으로 묶어주는 플러그인이다. 이후 실행 시점을 잡아주면 알아서 번들링해준다.
npm install -D html-webpack-plugin
webpack-dev-server는 한마디로 개발용 서버(로컬)를 제공해준다. 실제 빌드는 오래걸리기 때문에 webpack-dev-server를 이용해 압축된 결과물을 빠르게 볼 수 있다.
npm install -D webpack-dev-server
아래는 모든 초기 설정이 끝난 후의 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 파일의 기반이 되는 템플릿 파일의 경로를 설정한다.
}),
],
};
npm i react react-dom
npm i react-router-dom
webpack에서 타입 체크를 하기위해 typescript와 ts-loader를 설치한다.
npm i -D @types/react @types/react-dom
npm i -D typescript ts-loader
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 // 암시적 any에 오류를 발생시킨다.
"noFallthroughCasesInSwitch": true //switch문의 case 절에서 명시적인 종료문을 작성하지 않으면 컴파일 오류가 발생한다.
/* Completeness */
"skipLibCheck": true //모든 선언 파일(*.d.ts)의 타입 검사를 건너뛴다.
},
"include": ["src"] //컴파일 대상으로 포함할 파일이나 폴더를 지정한다."src" 폴더 내에 있는 TypeScript 파일들을 컴파일 대상으로 인식한다.
}
CSS-in-JS 방식을 사용하기 위해 styled-components를 설치한다. 다른 CSS 방식을 사용한다면 맞는 모듈을 설치하도록 한다.
npm i styled-components
npm i -D @types/styled-components
협업을 하며 컴포넌트를 쉽게 확인할 수 있는 테스트 도구인 스토리북을 사용하기로 했다. 따라서 설치해준다. babel을 따로 설치하지 않았지만 stroybook이 사용하기 때문에 stroybook이 사용하는 부분은 설치된다.
가장 최신 버전을 설치한 이유는 .bind
를 사용할 필요가 없다는 것이다. 문법이 간편해지고 직관적이라 사용하기에 편할 것이라고 판단했다. (단, 최신버전인 만큼 참고자료가 부족하고 각종 버그가 있을 수도 있다는 점..)
npx storybook@latest init
npm i -D tsconfig-paths-webpack-plugin
//절대 경로를 사용한다면 스토리북이 경로를 인식할 수 있도록 설치
절대 경로를 설정하기 위해 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;
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;
API가 나오기 전까지 개발할 때 사용하는 도구인 msw를 설치한다.
npm i -D msw msw-storybook-addon
//msw-storybook-addon은 stroybook에서 msw를 사용하기 위해 설치하는 것
npx msw init public/ --save
유틸 함수를 테스트하기 위해 테스트 도구 jest를 설치한다.
npm i -D jest ts-jest
npm i -D @types-jest @types-node
jest에서 타입을 사용하기 위해서 파일을 만든다.
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'^~/(.*)$': '<rootDir>/src/$1',
},
clearMocks: true,
};
여러 사람이 협업하기 때문에 코드에 통일을 주기위해 eslint와 prettier을 설치한다.
npm init @eslint/config
npm install --save-dev --save-exact prettier
{
"singleQuote": true,
"trailingComma": "all",
"endOfLine": "auto"
}
{
"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"
}
}
클라이언트가 직접 접근할 수 있는 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>
컴파일의 시작점인 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 환경 세팅이 완료되었다. 이제 알아서 개발하도록 하자.