지난 시간에 우리가 사용할 기술들에 대해서 짧게 나마 논의한 후 결론을 내릴 수 있었습니다.
우리팀(NDD)은 Next가 아닌 SPA 서비스를 만들것이며, 특히 CRA없이 react 서비스를 구성해보려고 합니다.
본 게시글은 babel 과 webpack에 집중하여 작성되었습니다.
프레임워크는 다양하고 편리한 기능을 제공합니다.
당연히 여러 유능한 개발자들의 개발로 이루어졌기 때문이지만, 여러가지로 위험하다는 생각이 들었습니다.
- 프레임워크를 사용하면 편리한 기능들이 어떻게 제공되는지 모릅니다.
- 프레임워크를 사용하기 전 "불편함"을 알고 있어야 한다고 생각합니다.
위와 같은 생각들을 통해 저는 점점 프레임워크를 사용하지 말아야 겠다는 생각이 들었습니다.
적어도 불편함 을 모르는 상태에서 프레임워크로 단기간의 편리함을 찾기는 모순된다는 생각이 들었습니다.
무엇보다 우린 프로젝트를 진행하면서 "성장" 에 초점을 두었습니다.
단기간 프로젝트를 완성하는것이 목표가 아닌,
시간이 오래 걸리더라도 의미 있는 학습을 함께 진행하고 싶었습니다.
부스트캠프에서 제공한 그룹프로젝트 가이드라인
하지만 부스트캠프 측에서도 Webpack에 대한 셋팅은 추천하지 않았는데요.
그래도 꼭 진행해보고 싶었습니다. 늘 프레임워크를 사용하며, 부족함을 느끼던 부분을 이번 기회에 채우고 싶었습니다.
따라서 초기 프로젝트 셋팅에 필요한 것을 리스트업하게 되었습니다.
- React 기반 프로젝트 셋팅이 가능해야한다.
- TS 문법을 사용가능해야한다.
- Emotion의 css-props 문법이 사용가능해야 한다.
- eslint 나 prettier 등 코드 포멧팅이 가능해야한다.
npx create-react-app [프로젝트 명]
다음과 같은 명령어로 아래와 같은 결과물이 생성됩니다.
my-app
├── README.md
├── node_modules
├── package.json
├── .gitignore
├── public
│ ├── favicon.ico
│ ├── index.html
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── index.css
├── index.js
├── logo.svg
├── serviceWorker.js
└── setupTests.js
이 코드 어디에도 앞으로 설명할 webpack, babel 등등 번들링과 컴파일을 진행할 셋팅이 보이질 않습니다.
그 이유는 Create React App (CRA)을 사용하여 React 프로젝트를 생성하면, Webpack, Babel, ESLint 등의 설정이 내부적으로 숨겨져 있기 때문입니다. 이런 방식을 "Zero-Configuration" 또는 "No-Config" 방식이라고 합니다. CRA는 이러한 도구들을 미리 구성하여 제공하기 때문에, 사용자는 복잡한 설정 과정 없이도 바로 React 애플리케이션을 개발하기 시작할 수 있습니다.
물론 접근을 완전 못하는것은 아닙니다. npm run eject
와 같은 명령어를 사용하면, 숨겨진 webpack, babel등에 접근할 수 있습니다.
위에서 간단하게 설명을 드렸었습니다만, react는 여러가지 도구를 통해서 브라우저가 읽을 수 있도록 "변환" 해주는 과정이 필요합니다.
단편적으로 React의 JSX라는 XML과 유사한 문법을 사용합니다. 브라우저는 JSX를 이해하지 못하기 때문에, Babel은 JSX를 브라우저가 이해할 수 있는 일반 JavaScript 함수 호출로 변환합니다.
물론 이는 React 에만 한정되는 이야기는 아닙니다. es6의 모듈 시스템과 같은 최신 문법들에대해서도 브라우저가 이해할 수 있는 코드로 변환하는 과정이 필요합니다.
다음과 같이 요약할 수 있을것 같습니다.
Node.js는 기본적으로 최신 JavaScript 문법의 일부를 지원하지 않습니다. 예를 들어, React에서 많이 사용되는 JSX 문법이나 ES6의 import/export 문법 등이 그렇습니다. 이러한 문법을 Node.js가 이해할 수 있도록 변환해야 하는데, 여기서 Babel이 필요합니다.
Babel은 최신 JavaScript 문법을 Node.js가 이해할 수 있는 이전 버전의 JavaScript로 변환해주는 도구입니다. 이를 통해 우리는 React 앱에서 최신 문법을 자유롭게 사용할 수 있게 됩니다.
React 앱은 여러 JavaScript 파일로 구성되어 있습니다. 이 파일들을 웹 브라우저가 이해할 수 있도록 하나의 파일로 합치는 과정이 필요합니다. 이 과정을 '빌드(build)'라고 합니다. Webpack은 이러한 빌드 과정을 관리하는 도구로, 개발 중에 실시간으로 파일을 합치고 필요한 변환을 수행합니다. 또한, 개발 서버를 제공하여 실시간으로 앱을 테스트하고 확인할 수 있게 해줍니다.
Babel은 자바스크립트 컴파일러로, 주로 최신 버전의 자바스크립트(ES6 이상) 코드를 오래된 자바스크립트 버전으로 변환하는 데 사용됩니다. 이런 변환 과정을 통해, 최신 자바스크립트 문법을 사용하여 작성된 코드를 오래된 브라우저나 환경에서도 실행할 수 있게 합니다. Babel의 주요 특징과 기능은 다음과 같습니다:
코드 변환(Transpilation): Babel은 최신 자바스크립트 문법(예: 화살표 함수, 클래스, 템플릿 문자열 등)을 구 버전의 자바스크립트 코드로 변환합니다. 이는 구형 브라우저나 환경에서도 최신 자바스크립트 기능을 사용할 수 있게 해줍니다.
브라우저 호환성: 다양한 브라우저에서 자바스크립트 코드가 일관되게 작동하도록 도와줍니다. 이를 위해 Babel은 특정 브라우저에서 필요한 특정 변환만을 적용할 수 있는 '프리셋(presets)'과 '플러그인(plugins)'을 제공합니다.
JSX 및 React 지원: Babel은 React 개발에 필수적입니다. React에서 사용되는 JSX 문법은 기본적으로 자바스크립트에 내장되어 있지 않으므로, Babel을 사용하여 JSX를 일반 자바스크립트 코드로 변환해야 합니다.
플러그인 및 확장성: Babel은 다양한 플러그인을 통해 확장 가능합니다. 개발자는 필요에 따라 추가적인 기능을 Babel에 플러그인으로 추가하여 사용할 수 있습니다.
ES6 이상의 기능 지원: Babel은 ES6, ES7 등의 새로운 자바스크립트 버전에서 도입된 기능들을 오래된 자바스크립트 버전으로 변환하여, 이전 버전의 자바스크립트 엔진에서도 해당 기능들을 사용할 수 있도록 지원합니다.
// .babelrc
{
"presets": [
"@babel/preset-env",
[
"@babel/preset-react",
{ "runtime": "automatic", "importSource": "@emotion/react" }
],
"@babel/preset-typescript"
],
"plugins": ["@emotion/babel-plugin"],
"targets": "> 0.5%, not dead"
}
한줄씩 설명을 추가해보겠습니다.
Presets은 특정한 목적이나 환경에 맞춰진 일련의 Babel 플러그인들의 묶음입니다. 각 프리셋은 자주 사용되는 특정 설정이나 환경에 최적화된 Babel 플러그인의 조합을 포함합니다. 여기서 사용되는 presets는 다음과 같습니다.
@babel/preset-env는 개발자가 지정한 대상 환경(브라우저, Node.js 등)에 따라 필요한 JavaScript 변환을 자동으로 적용합니다. 예를 들어, 오래된 브라우저에서 실행되는 코드를 개발한다면, 이 프리셋은 ES6 이상의 JavaScript 문법을 오래된 JavaScript 문법으로 변환하여 호환성을 보장합니다.
"폴리필(polyfill)"을 적용한다는 것은 특정 기능이나 API가 지원되지 않는 브라우저나 환경에서도 그 기능을 사용할 수 있도록 하는 코드 또는 도구를 제공한다는 의미입니다. Babel과 같은 도구를 사용할 때, 폴리필은 자동으로 적용될 수 있습니다. 예를 들어, @babel/preset-env는 필요한 폴리필을 프로젝트의 대상 환경에 맞춰 자동으로 포함시킵니다. 이는 개발자가 별도로 각 브라우저의 호환성을 체크하고 필요한 폴리필을 수동으로 추가하는 수고를 덜어줍니다.
Babel을 사용하여 TypeScript 코드를 일반 JavaScript로 변환하기 위한 Babel 프리셋입니다. TypeScript는 JavaScript에 타입을 추가한 확장 언어로, 더 안정적이고 유지보수하기 쉬운 코드 작성을 가능하게 합니다. 그러나 브라우저나 일반적인 JavaScript 환경에서 직접 실행될 수 없기 때문에, TypeScript 코드를 일반 JavaScript 코드로 변환하는 과정이 필요합니다. 이때 @babel/preset-typescript가 사용됩니다.
emotion에서 제공하는 babel-plugin 입니다. 여러가지 편의 기능을 제공합니다. 필수적이진 않습니다.
이 플러그인은 Emotion 스타일 코드의 성능을 최적화 하는 기능을 수행합니다. 예를 들어, 스타일이 컴파일 타임에 정적으로 결정될 수 있다면, 런타임 오버헤드를 줄이기 위해 이를 미리 계산합니다.
해당 설정은 babel이 지원할 수 있는 브라우저의 범위에 대한 조건을 명시합니다.
위의 문맥은 다음과 같은 의미를 가집니다.
"> 0.5%": 전 세계 사용자의 0.5% 이상이 사용하는 브라우저 버전을 대상으로 합니다. 즉, 전체 인터넷 사용자의 상당 부분이 사용하는 브라우저 버전에 대해 코드를 최적화하고 변환합니다.
"not dead": '죽은' 브라우저는 제외합니다. 여기서 '죽은' 브라우저란 공식적인 지원이 종료된 브라우저를 의미합니다. 예를 들어, 보안 업데이트나 기술 지원이 더 이상 제공되지 않는 브라우저는 이 범주에 속합니다.
이 밖에도 Source Maps (sourceMaps),Ignore / Only (ignore, only),Environment (env),Comments (comments),Compact (compact) 등등의 속성이 있지만, 좀 더 프로젝트가 성숙해 진 이후에 필요성이 대두된다면 도입할 예정입니다.
webpack의 모든 설정을 다루기 보단, babel과 연결되는 부분을 중심으로 나머지 부분들은 상대적으로 얕게 다뤄보겠습니다.
웹 애플리케이션은 수많은 JavaScript 파일, CSS, 이미지 등으로 구성됩니다. Webpack은 이러한 다양한 자원들을 모아서 브라우저가 이해할 수 있는 하나 또는 여러 개의 파일(번들)로 결합합니다. 이 과정은 웹사이트의 로딩 시간을 줄이고 성능을 향상시키는 데 도움이 됩니다. Webpack은 다양한 타입의 파일들을 처리할 수 있는 로더 시스템을 제공합니다. 예를 들어, CSS, 이미지 파일, HTML 등을 JavaScript 모듈로 변환할 수 있습니다. 또한, 플러그인을 통해 번들링 프로세스에 커스텀 작업을 추가할 수 있습니다.
천천히 webpack의 구조와 방식에 대해서 간단히 설명을 해보겠습니다.
프로젝트 내부의 모든 읽기 과정이 시작되는 위치를 의미합니다.
일반적으로 /src 의 index.ts 혹은 index.tsx를 시작되는 위치로 선정합니다.
프로젝트 내부의 모든 파일들의 번들된 결과물을 반환하는 파일입니다.
//webpack.config.json
...
output: {
publicPath: '/',
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js', // 임시로 사용되는 결과 파일입니다. hash값으로 변경예정입니다.
clean: true,
},
...
또한 dist폴더 내부에 bundle.js 뿐 아니라 index.html이 포함되어 있는데, 이는 output option이 아닌 plugin에서 처리해줍니다.
new HtmlWebpackPlugin({
template: './public/index.html',
filename: 'index.html',
}),
해당 방식을 통해서 index.html이 dist파일 내부에 선언됩니다. 이때 index.html이 생성되는 위치는 output 옵션의 path규칙을 따릅니다.
module: {
rules: [
{
test: /\.(ts|tsx)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
},
},
....
]
다음과 같이 선언하여 babelrc에 작성한 내용을 바탕으로 컴파일시킨 후 번들링 시킬 수 있습니다.
babel의 관련된 옵션은 상단의 babelrc에서 처리되었기 때문에, 재 선언하지 않습니다. (하지만 해당 pr을 올릴 당시에는 이 문제를 깨닫지 못하고 중복선언 되어 있습니다. 이 문제의 처리를 위해 백로그에 기입후 pr에도 추가적으로 기록을 남겨두었습니다.)
추가적으론 tsconfig와 연결하여 alias 설정, devServer 관련설정이 있습니다
사실 나머지는 크게 어렵지 않은 옵션이었습니다.
// .prettierrc.json
{
"tabWidth": 2,
"printWidth": 80,
"singleQuote": true,
"endOfLine": "auto",
"arrowParens": "always",
"trailingComma": "es5"
}
// ts와 react, css-props 기준 필요한 옵션만을 찾아 사용했습니다.
{
"root": false,
"parser": "@typescript-eslint/parser",
"env": {
"browser": true,
"node": true
},
"ignorePatterns": ["webpack.config.js"],
"parserOptions": {
"project": ["./FE/tsconfig.json"] // 진행중인 프로젝트가 모노레포로 구성되어 다음과 같은 옵션을 주어야 했습니다.
},
"plugins": ["@typescript-eslint", "prettier", "unused-imports"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"plugin:react-hooks/recommended",
"prettier"
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"no-console": "warn",
"react/no-unknown-property": ["error", { "ignore": ["css"] }],
"react/prop-types": "off"
},
"settings": {
"react": {
"version": "detect" // 자동으로 리액트 버전 감지
}
}
}
prettier와 eslint는 되도록 엄격하게 검사하도록 설정했습니다.
지금까지 CRA 없이 프로젝트 셋팅에 대해서 진행해 보았습니다.
해당 코드를 작성한 PR이며,
아직 시작에 불과하지만, 프로젝트를 진행하며 webapck에 대해서 더 깊은 학습이 가능하면 좋겠습니다.