이 글은 ejs와 express로 웹어플리케이션을 만들 때 webpack을 설정하며 왜 내가 만든 웹팩 설정과 React의 웹팩설정이 다를까 하는 의구심으로부터 작성되었습니다.
부정확한 부분이 다소 있을 수 있으니 크로스체킹이 필요합니다.
3부작으로 작성되었습니다.
먼저 import 구문을 사용할 수 있게 해주는 패키지 및 모듈의 예시를 보여드리겠습니다.
Babel : Babel은 최신 JavaScript 문법을 지원하지 않는 브라우저에서도 동작할 수 있도록 JavaScript 코드를 변환해주는 도구입니다. Babel을 설정하면 import 구문과 같은 최신 문법을 사용할 수 있습니다.
Webpack : webpack은 모듈 번들러로서, 프론트엔드 애플리케이션의 여러 모듈과 자원들을 하나의 번들 파일로 묶어줍니다.
webpack은 babel-loader
와 preset
을 사용하여 import 구문을 사용할 수 있습니다.
ES Modules: ES Modules는 JavaScript의 공식적인 모듈 시스템입니다. 최신 브라우저에서는 기본적으로 ES Modules를 지원하며, import 구문을 사용하여 모듈을 로드할 수 있습니다. 이를 사용하기 위해 스크립트 태그의 type 속성을 module
로 설정하고, import 구문을 사용하여 모듈을 로드할 수 있습니다.
(하지만 React에서는 모든 파일이 번들링에 종속되기에 ES module을 사용할 수 없습니다.)
CRA로 생성한 프로젝트를 보면 컴포넌트 확장자에 .js | .jsx
는 봤지만, .bundle.js | jsx
는 못봤습니다. React는 어떻게 import와 같은 ES6+ 문법을 사용할 수 있는건가요?
React에서 *.bundle.js
와 같은 번들 파일을 생성하지 않고, 작성한 JS 또는 JSX 확장자 파일에서 import 구문과 ES6+ 이상의 문법을 사용할 수 있는 것은 Babel을 통해 가능합니다.
Babel은 JavaScript 컴파일러로, 주요 목적은 최신 JavaScript 문법을 이전 버전의 JavaScript 문법으로 변환하여 모든 브라우저에서 동작할 수 있도록 지원하는 것입니다.
React와 함께 사용할 때는 주로 JSX 문법과 ES6+ 문법을 변환해주는 역할을 합니다.
Babel은 다양한 플러그인과 프리셋을 제공하여 개발자가 필요한 변환을 선택적으로 적용할 수 있습니다.
주요 프리셋 중 하나는 @babel/preset-react
이며, 이를 사용하면 JSX 문법을 변환할 수 있습니다.
또한, @babel/preset-env
프리셋을 함께 사용하면 ES6+ 이상의 문법을 이전 버전의 JavaScript로 변환할 수 있습니다.
모두가 알고 계시겠지만 CRA에 Webpack은 기본으로 적용되어 있습니다.
(다만 추상화 되어 있기 때문에, 커스터마이징에는 eject가 필요합니다.)
Webpack에는 개발 환경에서 변환 작업을 실시간으로 처리 해주기 위해 babel-loader
가 Webpack에 포함되어 있으며, 이를 통해 개발 중에도 최신 문법과 모듈 시스템을 사용할 수 있습니다.
Babel의 변환 작업을 실시간으로 처리하기 위해 Webpack의 dev server
와 함께 사용됩니다. Webpack의 dev server
는 코드의 변경을 감지하고, 변경된 코드를 다시 빌드하고 브라우저에 자동으로 적용해줍니다.
여기서 브라우저에 자동으로 적용이 가능한 부분은 번들된 파일을 메모리에 저장한 뒤 브라우저와 연결된 dev server
가 소켓 통신을 통해 변경된 내용을 브라우저에 실시간으로 전달하기 때문입니다.
이렇게 함으로써 코드의 변경사항이 브라우저에 실시간으로 반영되어 개발자는 새로고침 없이 즉시 결과를 확인할 수 있습니다.
loader는 전선이고, preset은 그 안을 흐르는 전류와 같다고 생각합니다.
전선만 있다면 전류가 없으니 전기를 공급받을 수 없고,
전류가 흘러도 전류에 알맞고 원하는 곳을 이어줄 전선이 없다면 전기를 공급 받을 수 없습니다.
babel-loader
는 주로 개발 환경에서 ES6+ 이상의 최신 문법과 모듈 시스템을 사용할 수 있도록 도와줍니다. 다만 babel-loader가 그 역할만을 하는 것이 아니며 Babel의 프리셋 중 하나인 @babel/preset-env
가 그 역할을 합니다.
@babel/preset-env
프리셋은 브라우저에서 해석할 수 있는 이전 버전의 JavaScript로 변환하기 위해 필요한 플러그인과 트랜스포머를 동적으로 결정합니다. 이를 위해 @babel/preset-env
는 환경 설정 옵션을 제공하여 어떤 JavaScript 엔진(브라우저)을 대상으로 변환할지 정의할 수 있습니다.
이 설정을 통해 필요한 플러그인과 트랜스포머를 선택하고, 변환 작업을 수행합니다.
결과적으로, Webpack의 babel-loader
는 @babel/preset-env
프리셋과 함께 사용하여 개발 환경에서 작성된 최신 문법의 코드를 이전 버전의 JavaScript로 변환하여 브라우저에서 해석할 수 있도록 도와줍니다. 이를 통해 개발자는 최신 문법과 모듈 시스템을 사용하면서도 크로스 브라우저 호환성을 유지할 수 있습니다.
프리셋(Preset)은 Babel에서 제공하는 미리 설정된 변환 옵션들의 집합입니다.
Babel은 다양한 문법 변환 및 트랜스파일 작업을 수행하는 도구인데, 프리셋은 이러한 변환 옵션들을 미리 정의해 놓은 것입니다.
예를 들어, Arrow Function 변환, 템플릿 리터럴 변환, 클래스 변환 등 각각의 문법 변환에 해당하는 플러그인이 있습니다. 하지만 매번 필요한 플러그인을 개별적으로 설정하는 것은 번거로울 수 있습니다.
이런 경우에 프리셋을 사용하면 미리 해당 문법 변환에 필요한 여러 개별 플러그인들을 하나의 묶음으로 제공합니다. 따라서 개발자는 프리셋을 선택하고 설정함으로써 필요한 변환 옵션들을 한 번에 적용할 수 있습니다. 프리셋은 일종의 변환 규칙 세트라고 볼 수 있습니다.
그리고 Babel을 통해 변환되고 프론트엔드 측에서 사용하는 모듈들의 묶음이 .bundle.js
입니다
웹팩을 사용하여 빌드하면서 Babel을 통해 변환된 JavaScript 파일은 일반적으로 *.bundle.js
와 같은 이름을 갖는 파일로 생성됩니다.
이 파일은 웹팩에 의해 번들링되고 최종적으로 브라우저에서 실행될 JavaScript 파일입니다.
일반적으로 *.bundle.js
와 같은 네이밍 규칙은 번들링된 파일의 구분을 돕기 위한 것입니다.
웹팩을 사용하면 여러 개의 JavaScript 파일이 하나의 번들 파일로 결합되는데, 이때 번들 파일의 이름을 구분하기 위해 보통 이러한 네이밍 규칙을 사용합니다.
예를 들어, main.js
와 app.js
라는 두 개의 JavaScript 파일이 있다고 가정한다면main.js
와 app.js
는 bundle.js
로 결합되어 하나의 파일로 생성될 수 있습니다.
따라서 bundle.js
는 번들링된 결과물의 이름이 될 수 있습니다.
React에서 *.bundle.js
와 같은 형식의 확장자가 보이지 않는 이유는, 웹팩은 진입점(entry point) 파일을 기반으로 모든 종속성을 해결하고 하나의 번들 파일로 생성하는데, 번들 파일의 확장자가 .js로 설정되어 있기 때문입니다.
개발 환경에서도 웹팩을 사용하여 *.bundle.js
형식이 아닌 *.js
확장자로 번들링할 수 있습니다.
웹팩의 설정 파일에서 output.filename
옵션을 설정하여 번들 파일의 이름을 지정할 수 있습니다. 예를 들어, 아래와 같이 설정하면 번들 파일의 이름이 bundle.js로 생성됩니다.
module.exports = {
// ...기타 웹팩 설정...
output: {
filename: 'bundle.js',
// ...추가적인 output 설정...
},
// ...기타 웹팩 설정...
};
이렇게 생성된 파일은 브라우저에서 실행 가능한 JavaScript 파일이며, import 구문과 ES6+ 이상의 문법을 사용할 수 있습니다.
React에서 .js나 .jsx 파일은 모두 하나의 파일에 종속된 모듈과 같은 존재입니다.
React 프로젝트에서 각 파일은 컴포넌트 또는 모듈로 사용되며, 이들은 상호작용하거나 의존성을 가질 수 있습니다.
일반적으로 React 애플리케이션은 여러 개의 컴포넌트로 구성되며, 각 컴포넌트는 독립적으로 작동하면서도 다른 컴포넌트와 상호작용할 수 있습니다.
컴포넌트는 JSX 문법을 사용하여 작성되며, 이는 JavaScript 확장 문법으로 React 엘리먼트를 생성하는 데 도움을 줍니다.
React 컴포넌트 파일들은 상호 의존성을 가지며, 필요한 경우 다른 컴포넌트를 임포트(import)하여 사용할 수 있습니다.
이렇게 컴포넌트 간의 의존성을 관리하고 모듈화된 구조를 구성함으로써 코드의 유지 보수성과 재사용성을 높일 수 있습니다.
cra로 생성된 앱은 실제로 하나의 진입점(entry point)을 가지고 있습니다.
일반적으로 React 애플리케이션에서는 하나의 최상위 컴포넌트를 루트 컴포넌트로 설정하고,
이 루트 컴포넌트가 ReactDOM.render()
(version 17이하 기준) 메서드를 통해 DOM에 렌더링됩니다.
이 루트 컴포넌트를 기준으로 컴포넌트 트리가 형성되며, 다른 컴포넌트들이 하위에 포함됩니다.
이렇게 하나의 진입점으로 설정된 루트 컴포넌트는 웹팩의 entry
속성에 해당하는 파일에 위치하게 됩니다. 보통은 이 파일을 index.js 또는 App.js와 같은 이름으로 지정합니다.
이 파일은 앱의 진입점으로서, 필요한 다른 컴포넌트를 임포트하고 사용합니다.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
위 예시에서 index.js 파일은 CRA로 생성된 앱의 진입점입니다.
App 컴포넌트가 임포트되고, ReactDOM.render()
메서드를 통해 App 컴포넌트가 root라는 HTML 요소에 렌더링됩니다.
이 파일은 웹팩의 entry 속성에 설정되어 있으며, 웹팩은 해당 파일을 기준으로 의존성 그래프를 분석하여 필요한 모듈을 번들링합니다.
CRA 프로젝트에서 Webpack 설정에서의 진입점은
src/index.js
파일이 되고, 이 파일에서 다른 컴포넌트를 임포트하여 앱을 구성하게 됩니다.
이렇게 설정된 진입점을 기반으로 CRA는 내부적으로 Webpack을 사용하여 번들링 작업을 수행합니다.