1. CRA 없이 리액트를 시작해야하는 이유
create-react-app
을 이용하면 정말 편하게 리액트 프로젝트를 시작할 수 있다.
웹팩과 바벨, 타입스크립트 설정까지 제공해준다.
하지만 내 방식대로 설정을 바꾸기가 힘든 단점이 있다.
craco
같은 라이브러리를 이용하여 설정을 override 해야하는데, 번거롭고 이럴바엔 처음부터 리액트를 구축하는 것이 낫겠다 라고 판단하게 된다.
나의 경우 import 방식을 바꾸려고 했었는데, 이를 위해 웹팩, ts 설정까지 전부 override 해야하는 문제점이 있었다. 배보다 배꼽이 더 큰 경우였다.
그래서 CRA없이 리액트 프로젝트를 구축해보려고 한다.
2. 구축 순서
1. package.json 생성
2. React, Typescript 설치
3. webpack, webpack plugins, loader, devServer 설치 // bundling
4. webpack, typescript 설정
5. prettier 설정
6. src 디렉터리 생성
7. public 디렉터리 생성
8. package.json 설정
1. package.json 생성
yarn init -y
: package.json 생성
2. React, Typescript 설치
react 필수 라이브러리 설치yarn add react react-dom
typescript, react @types 설치yarn add -D typescript @types/react @types/react-dom
tsconfig.json 생성yarn tsc --init
3. webpack, webpack plugins, loader, devServer 설치
yarn add webpack —dev
, yarn add webpack-cli —dev
webpack: 웹팩 라이브러리
webpack-cli: 명령어로 웹팩을 이용할 수 있는 라이브러리
yarn add html-webpack-plugin webpack-dev-server ts-loader --dev
html-webpack-plugin: 번들링 후, 만들어 놓은 템플릿 html
파일을 이용해 번들링된 js
를 import
(<script type="module" src="./dist/about.bundle.js"></script>
)하는 html
파일을 새로 만들어서 output
디렉터리에 생성
webpack-dev-server: 개발할 때 사용하는 웹 서버
제공 기능
live reload : 코드를 수정해서 저장하면 웹페이지가 자동으로 reload
hot module replacement: 수정된 부분만 바뀌고, 입력해놓은 정보들은 유지되는 기능
ts-loader: ts 컴파일러를 사용(tsconfig.json에 의존한다)해서 typescript 파일을 javascript 로 변환하는 webpack loader
4. webpack, typescript, babel 설정
각 필드에 대한 설명은 webpack 설정하기 포스팅을 참조
webpack 설정파일을 common(공통설정), develop, production으로 나눠서 작성하여 개발환경, 배포환경에 따라 다르게 번들링하도록 하자.
1.1. webpack.common.js
const path = require('path');
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,
},
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx'],
},
module: {
rules: [
{
test: /\.(js|ts|tsx)$/i,
exclude: /node_modules/,
use: {
loader: 'ts-loader',
},
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html',
}),
],
devtool: 'inline-source-map',
devServer: {
static: './dist',
hot: true,
open: true,
},
};
1.2. 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,
},
});
1.3. webpack.prod.js
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'hidden-source-map',
});
2. tsconfig.json
각 필드에 대한 설명은 타입스크립트 설정하기 포스팅을 참조
{
"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": {
"@components/*": ["components/*"]
},
"outDir": "./dist"
},
"include": ["src"],
"exclude": ["node_modules"]
}
5. prettier 설정
.prettierrc
{
"endOfLine": "auto",
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
}
각자 입맛대로 커스텀하면 된다.
각 필드에 대한 정보는 공식문서를 참조.
자신만의 설정을 만들어보자.
6. src 디렉터리 생성
실제 코드가 작성될 장소이다.
index.tsx
와 App.tsx
를 만들자
index.tsx
import App from './App';
import React from 'react';
import ReactDOM from 'react-dom/client';
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
App.tsx
const App = () => {
return <></>;
};
export default App;
7. public 디렉터리 생성
index.html 본체가 위치하고, assets 같이 정적파일이 위치한다.
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CRA없이 React 시작하기</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
8. package.json 필드 설정
프로젝트에서 사용할 라이브러리들도 적당히 설치해주자.
{
"name": "[프로젝트이름]",
"version": "0.0.1",
"main": "index.tsx",
"repository": "https://github.com/[레포지터리이름]/client.git",
"author": "[깃헙아이디] <[깃헙이메일]>",
"license": "MIT",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack-dev-server --config webpack.dev.js --open --hot",
"build": "webpack --config webpack.prod.js",
"start": "webpack --config webpack.dev.js",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@tanstack/react-query": "^4.29.13",
"axios": "^1.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"styled-components": "^6.0.0-rc.3"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.0.20",
"@storybook/addon-interactions": "^7.0.20",
"@storybook/addon-links": "^7.0.20",
"@storybook/blocks": "^7.0.20",
"@storybook/react": "^7.0.20",
"@storybook/react-webpack5": "^7.0.20",
"@storybook/testing-library": "^0.0.14-next.2",
"@types/react": "^18.2.12",
"@types/react-dom": "^18.2.5",
"@types/styled-components": "^5.1.26",
"html-webpack-plugin": "^5.5.3",
"storybook": "^7.0.20",
"ts-loader": "^9.4.3",
"typescript": "^5.1.3",
"webpack": "^5.86.0",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
}
}
3. webpack 관련 추가 정보
1. webpack에 babel-loader를 추가하지 않은 이유
babel
을 도입하지 않은 이유는 더이상 JS 최신 문법을 이전 문법으로 변환할 필요가 없다고 판단했기 때문이다.
ECMAScript 버전별 브라우저 지원 여부를 알려주는 웹사이트: https://kangax.github.io/compat-table/es2016plus/
babel
을 사용하는 이유는 대부분 이전 버전의 Internet Explorer(IE)를 지원하기 위해서인데, IE는 이미 지원종료 된지 1년이 넘었다.
공공기관에서도 Edge로 전부 갈아탄 것으로 파악된다.
또한 대한민국의 브라우저 점유율(https://www.koreahtml5.kr/front/stats/browser/browserUseStats.do)을 보면, ie는 2023년 6월 기준 0.47%에 불과하다.
Chrome, Edge, Whale, Safari, Firefox 순으로 이용하고 있다. 전부 ES2021 문법을 100% 지원하는 최신 브라우저들이다.
따라서 더이상 babel
은 필요없다고 생각했고, babel-loader
를 사용하지 않았다.
2. ts-loader 와 @babel/preset-typescript의 차이점
ts-loader
와 @babel/preset-typescript
는 어떤 차이가 있을까?
둘 다 typescript 파일을 javascript로 트랜스파일링 하는 것은 동일하다.
ts-loader
는 typescript 컴파일러인 tsc
를 사용하고, tsconfig.json
설정에 의존한다.
babel-loader
는 babel preset인 @babel/preset-typescript
를 이용해서 typescript 파일을 javascript 파일로 트랜스파일링하게 할 수 있다. 하지만 @babel/preset-typescript
에 포함된 @babel/plugin-transform-typescript
플러그인은 트랜스파일링 시 타입검사를 수행하지 않는다(!) 그래서 빌드 시에는 타입오류가 나지 않지만, 빌드된 프로덕션 코드를 실행했을 때 타입 오류가 터지게 된다(!!)
그 외에도 만약 트랜스파일링 대상 코드가 라이브러리일 때 babel
의 preset만 이용할 경우 typescript에 대한 d.ts
파일을 생성(tsc
의 "declaration": true 옵션) 하지 않으므로 번거로울 수 있다. (https://www.typescriptlang.org/docs/handbook/babel-with-typescript.html)
3. babel-loader와 ts-loader를 같이 사용하는 경우
typescript를 이용하는 많은 프로젝트들에서 babel-loader
와 ts-loader
를 함께 사용하고 있다.
babel
을 이용해 하위호환성을 지원하기 위함이다.
이 경우 ts-loader
로 ts -> js(es6) 트랜스파일링을 하고 babel-loader
로 es6 -> es5로 변환하는 과정을 거친다.
나는 위에서 이미 언급했듯이 babel
이 현재 필요없다고 판단했기 때문에 babel-loader
를 사용하지 않았다.
4. css-loader, style-loader
webpack
을 이용하여 .css
파일을 번들링하기 위해서는 css-loader
, style-loader
가 필요하다.
css-loader
는 .css
파일을 읽을 수 있게 해주고, style-loader
는 읽어온 .css
파일을 <style>
태그를 이용해 실제로 DOM에 주입해주는 역할을 한다.
기본 css나 moduleCSS
를 이용할 때 필요하다.
이 외에도 css 스타일링 방식에 따라 필요한 loader
는 달라진다.
SCSS는 sass-loader
를 이용하며, css-in-js 인 styled-components
나 emotion
의 경우 js파일로(동적으로 가짜 css를 만든다) 스타일링을 하기 때문에 loader
가 필요없다.
5. Asset Modules
프로젝트에서 폰트, 이미지 등의 asset을 사용할 수 있도록 해주는 module이다(loader가 아니다!)
webpack5 이전버전에서는 file-loader
같은 loader를 이용했었지만 webpack5 에 들어서서는 asset module로 해당 기능들을 제공하고 있다.
자세한 내용은 공식문서에서 더 잘 알려주고 있으니 참고하자
https://webpack.js.org/guides/asset-modules/
4. 부록 - tsc의 target 설정으로 하위호환성을 챙길 수 있을까?
tsc
가 typescript 코드를 어떤 버전의 javascript로 변환할지 tsconfig
에서 target
옵션을 통해 설정할 수 있다.
위 예시에서는 target
을 es2021
로 설정했는데, es5
로 설정하면 하위호환성을 챙길 수 있지 않을까?
절반은 맞고 절반은 틀렸다.
tsc
가 typescript코드를 es5
로 트랜스파일링 하는 건 맞지만 polyfill을 넣어주지 않기 때문이다.
polyfill을 넣어주지 않는 이유는 해당 PR에 잘 설명되어 있다.
이게 바로 babel
과의 차이점이다(babel
은 필요한 polyfill을 직접 넣어줄 수 있음).
5. 마무리
이렇게 아주 기본적인 리액트 프로젝트 설정이 끝났다.
이제 CRA
없이 리액트 프로젝트를 시작할 수 있다!
2023년 부터는 vite
으로 프로젝트를 시작하는 걸 많이들 추천하는 것 같은데..
CRA
없이 리액트 프로젝트를 한번도 구축해보지 않았다면,
해당 과정을 따라하면서 리액트 프로젝트를 설정하기 위해 어떤 기술들이 필요한지 알아보는 것이 의미있다고 생각한다.
'우아한테크코스 5기 프론트엔드' 카테고리의 다른 글
[우아한테크코스 프로젝트 기록] 3차 데모데이 ~ 론칭 페스티벌 (5) | 2023.08.22 |
---|---|
[우아한테크코스 프로젝트 기록] 2차 데모데이 ~ 3차 데모데이 (1) | 2023.08.05 |
[우아한테크코스 프로젝트 기록] 1차 데모데이 ~ 2차 데모데이 (2) | 2023.07.23 |
[우아한테크코스 프로젝트 기록] 동글 첫 만남 ~1차 데모데이 (2) | 2023.07.09 |
webpack 설정하기 (0) | 2023.03.14 |