[React] - 번들링과 웹팩
🌱 번들링
여러 제품이나 코드, 프로그램을 묶어서 패키지로 제공하는 행위
번들 : 사용자에게 웹 애플리케이션을 제공하기 위한 파일 묶음
- 사용자가 더 쉽고 빠르게 프론트엔드 애플리케이션에 접근할 수 있도록 용량을 줄이거나 파일을 최소화하여 유저에게 전달하는 과정
- 사용자가 브라우저를 열고 주소를 입력하면, 해당 주소에서 개발자가 번들링한 여러 파일을 받음
- 이 파일을 브라우저가 실행하여 웹 애플리케이션을 제공
🌱 번들링의 필요성
HTML, CSS, JavaScript 파일을 그대로 전송할 시 생길 수 있는 문제점
- 두 개의 .js파일에서 같은 변수를 사용하고 있는 경우 → 변수 간 충돌이 일어남
- 딱 한 번 불러오는 프레임워크 코드가 8MB → 인터넷 속도가 느린 국가의 모바일 환경에서는 사용이 불편
- 번들 파일 사이즈를 줄이기 위해 파일의 공백을 모두 지움 → 가독성이 너무 떨어져 코딩하기 어려움
- 배포 코드가 너무 읽기 쉬워 개발을 할 줄 아는 사용자가 프론트엔드 애플리케이션을 임의로 조작하여 피해 발생
번들링 작업에서는 필연적으로 용량을 줄이고 파일을 통일하는 툴링 작업이 필요하다.
➡︎ 소프트웨어를 잘 만들어도 사용자에게 배포하기 위해 번들링이 꼭 필요하다.
🗂️ 웹팩
현재 프론트엔드 애플리케이션 배포를 위해 가장 많이 사용하는 번들러
🗂️ Webpack이란
여러 개의 파일을 하나의 파일로 합쳐주는 모듈 번들러
- 모듈 번들러
- HTML, CSS, JavaScript 등의 자원을 전부 각각의 모듈로 보고 이를 조합해 하나의 묶음으로 번들링(빌드)라는 도구
모듈 번들러(Module Bundler)의 등장
모던 웹으로 발전하면서 JavaScript 코드의 양이 증가
+
대규모의 의존성 트리를 가진 대형 웹 애플리케이션의 등장으로 세분화된 모듈 파일이 폭발적으로 증가
→ 모듈 단위의 파일들을 호출해 브라우저에 띄우려면
- 자바스크립트 언어의 특성에 따라 발생하기 쉬운 각 변수들의 스코프 문제 해결
- 각 자원을 호출할 때 생겨나는 네트워크 쪽의 코스트도 신경써야함
➡︎ 이러한 복잡성에 대응하기 위해 하나의 시작점(ex. React App 의 index.js)으로부터 의존성을 가지는 모듈을 모두 추적하여 하나의 결과물을 만들어내는 모듈 번들러가 등장하게 되었다.
웹팩에서의 모듈
- HTML, CSS, JavaScript
- .jpg나 .png같은 이미지 파일들도 전부 포함
→ 웹팩은 주요 구성 요소인 로더(loader)를 통해 다양한 파일도 번들링 가능
빌드와 번들링
- 빌드
- 개발이 완료된 앱을 배포하기 위해 하나의 폴더(directory)로 구성하여 준비하는 작업
- React앱에서 npm run build 실행 시 React build작업이 진행되고, index.html파일에 압축되어 배포에 최적화된 상태를 제공
- 번들링
- 파일을 묶는 작업. 모듈간의 의존성 관계를 파악해 그룹화하는 작업
- 파일은 의존적 관계에 있는 파일들(import, export) 그 자체 혹은 내부적으로 포함되어 있는 모듈을 의미
- 파일을 묶는 작업. 모듈간의 의존성 관계를 파악해 그룹화하는 작업
🗂️ Webpack의 필요성
웹팩이 필요한 가장 큰 이유는 웹 애플리케이션의 빠른 로딩 속도와 높은 성능을 위함
웹 페이지를 구성하는 코드의 양이 많은 것을 “무겁다”라고 표현하고, 무거울 수록 웹 페이지의 로딩 속도와 성능은 저하된다.
유저는 하나의 웹 사이트에 접근하는 순간부터 3초 이내에 웹 페이지가 뜨지 않으면 이탈하기 때문에 로딩 속도를 개선하기 위한 노력이 필요했다.
→ 브라우저에서 서버로 요청하는 파일의 숫자를 줄이자!
- 웹팩으로 같은 타입의 파일들을 묶어 요청 및 응답을 받을 수 있다.
→ 네트워크 코스트가 획기적으로 줄어듬
- webpack loader를 사용하면 일부 브라우저에서 지원하지 않는 JavaScript ES6의 문법들을 ES5로 변환해주는 babel-loader를 사용할 수 있게된다.
- Vue의 경우 vue-loader
- scss파일의 경우 css파일로 변환해주는 scss-loader
- Webpack 4버전 이상부터는 Development, Production 두 가지의 모드를 지원한다.
- Production모드로 번들링을 진행하는 경우, 코드 난독화, 압축, 최적화(Tree Shacking)작업을 지원하기도함
🗂️ 웹팩의 핵심 컨셉
📍 웹팩의 핵심 개념
웹팩 공식 문서에서는 아래 항목을 핵심 개념으로 제안하고 있다.
- Entry
- Output
- Loaders
- Plugins
- Mode
- Browser Compatibility
// webpack의 config 파일 예시
module.exports = {
target: ["web", "es5"],
entry: "./src/script.js",
output: {
path: path.resolve(__dirname, "docs"),
filename: "app.bundle.js",
clean: true
},
module: {
rules: [
{
test: /\\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader"],
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html"),
}),
new MiniCssExtractPlugin(),
],
optimization: {
minimizer: [
new CssMinimizerPlugin(),
]
}
};
Target
웹팩은 다양한 환경과 target을 컴파일한다.
- target의 기본값은 web (적용하지 않을시 기본값 적용)
- web외에도 다양한 환경을 컴파일 할 수 있다.
- esX를 넣으면 지정된 ECMAScript 버전으로 컴파일
module.exports = { target: ["web", "es5"], };
- config 파일에 es5를 배열 안에 넣음→ 작성된 코드를 es5버전으로 컴파일하겠다고 지정한것임 ( 브라우저와 동일한 환경에서 config파일을 사용하기 위해)➡︎ Browser Compatibility와 연관된 속성으로 볼 수 있다.
Entry
개발자가 작성한 코드의 시작점
- React도 index.js에서 HTML 엘리먼트 하나에 React 코드를 적용하는 것 부터 시작한다.
- Entry 속성은 Entry point라고 한다.
- webpack이 내부의 디펜던시 그래프를 생성하기 위해 사용해야하는 모듈
- webpack은 entry point를 기반으로 직간접적으로 의존하는 다른 모듈과 라이브러리를 찾아낼 수 있음
//기본 값 module.exports = { ... entry: "./src/index.js", }; //지정 값 module.exports = { ... entry: "./src/script.js", };
Output
생성된 번들을 내보낼 위치와 이 파일의 이름을 지정하는 방법을 웹팩에 알려주는 역할을 한다.
- 기본 출력 파일의 경우 ./dist/main.js 폴더로 설정
- 생성된 기타 파일의 경우 ./dist 폴더로 설정
const path = require('path');
module.exports = {
...
output: { path: path.resolve(__dirname, "docs"), // 절대 경로로 설정을 해야함
filename: "app.bundle.js", clean: true
},
};
- output.filename과 output.path 속성을 사용하여 webpack에 번들의 이름과 내보낼 위치를 알려줌
- path 속성을 사용할 때는 path 모듈을 사용해야 함
Loader
웹팩은 기본적으로 자바스크립트와 JSON 파일만 이해한다.
loader를 사용하면 웹팩이 다른 유형의 파일을 처리하거나, 유효한 모듈로 변환해 애플리케이션에 사용하거나 디펜던시 그래프에 추가할 수 있다.
- 상위 수준에서 loader는 웹팩 설정에 몇 가지 속성을 가진다.
- test : 변환이 필요한 파일들을 식별하기 위한 속성. 필수 속성
- use : 변환을 수행하는데 사용되는 로더를 가리키는 속성. 필수 속성
- exclude : 바벨로 컴파일하지 않을 파일이나 폴더를 지정
- 반대로 include 속성을 이용해 반드시 컴파일해야 할 파일이나 폴더 지정 가능
module.exports = { ... module: { rules: [ { test: /\\.css$/, use: [MiniCssExtractPlugin.loader, "css-loader"], exclude: /node_modules/, }, ], }, };
- 속성을 넣어 규칙을 정하기 위해서는 module.rules 아래에 정의해야함
- rules 아래에 정의할 경우 웹팩은 경고를 보냄
Plugins
번들을 최적화하거나 에셋을 관리하고, 또는 환경변수 주입 등 광범위한 작업을 수행한다.
- 플러그인을 사용하기 위해서는
- require()를 통해 플러그인을 요청
- plugins배열에 사용하고자 하는 플러그인을 추가
- 대부분의 플러그인은 사용자가 옵션을 통해 지정할 수 있다.
→ 다른 목적으로 플러그인을 여러 번 사용하도록 설정할 수 있기 때문에 new 연산자를 사용해 호출하여 플러그인의 인스턴스를 만들어줘야 한다.
const webpack = require('webpack');
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
module.exports = {
...
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "src", "index.html"),
}),
new MiniCssExtractPlugin(),
],
};
- html-webpack-plugin은 생성된 모든 번들을 자동으로 삽입하여 애플리케이션용 HTML 파일을 생성
- mini-css-extract-plugin은 CSS를 별도의 파일로 추출해 CSS를 포함하는 JS 파일 당 CSS 파일을 작성해주게끔 지원
Optimization(최적화)
웹팩은 버전 4부터는 선택한 항목에 따라 최적화를 실행한다.
- 최적화하기 위해 다양한 옵션이 지원된다.
- minimize
- TerserPlugin 또는 optimization.minimize에 명시된 plugins로 bundle 파일을 최소화(=압축)시키는 작업 여부를 결정
- minimizer
- defualt minimizer을 커스텀된 TerserPlugin 인스턴스를 제공해서 재정의
module.exports = { ... optimization: { minimizer: [ new CssMinimizerPlugin(), ] } };
- mini-css-extract-plugin에 관련된 번들을 최소화하도록 지시하고 있다.
- minimize
📍 webpack의 주요 컨셉
1.
- entry 파일이 필요한 모든 것을 webpack이 모아서 번들링 해준다.
- 번들링의 결과물이 output 경로로 산출된다.
📍 웹팩은 하나의 파일에 넣는 과정에서 자바스크립트의 특이점들을 극복하기 위해 복잡다단한 과정을 거친다.
+과정은 추가적으로 학습
2.
- loader는 JavaScript, JSON이 아닌 파일을 불러오는 역할을 한다.
3.
- 플러그인은 번들링 과정 중에 개발자가 원하는 다양한 작업을 할 수 있도록 도와준다.
- html-webpack-plugin은 번들링 과정 중 html파일을 자신이 원하는 형태로 가공하여 번들에 포함할 수 있게 도와준다.
➰ 로더와 다르게, 플러그인은 명칭 하나로 해당 플러그인의 역할을 파악하기 애매하다.
이런 경우 사용량을 기준으로 많이 사용하는 플러그인이 왜 많이 사용되는지 먼저 찾아보는 방식으로 우선순위를 정하는게 좋다. 사용량이 높다고 원하는 플러그인인 경우가 아닐 수도 있지만, 개발 환경을 구성할 때 내가 필요한게 무엇인지를 정확하게 구분하는 것이 중요하다.
🗂️ 웹팩과 리액트
📍 리액트가 번들링이 필요한 이유
리액트가 주목받던 이유 중 하나는 프레임워크가 아닌 라이브러리라는 점이다.
리액트는 2010년대 주류였던 앵귤러의 단점을 보완할 수 있는 대체제로 생겨났다.
필요한 코드의 양이 많고, 배우는데 필요한 시간이 오래 걸리고, 번들 사이즈가 커져 성능 문제가 큰 프레임워크가 아닌 개발자의 필요에 따라 설치가 가능한 라이브러리라는 점이 리액트를 주목받게 했다.
리액트 개발진은 개발자가 어떤 기술 스택을 사용할지 미리 가정하지 않아 개발자의 자유도에 따라 기능을 추가할 수 있게 했다. 하지만 이런 특성 때문에 ‘리액트만 알아서는 개발하기가 어렵다’는 문제가 발생했다.
이런 문제를 해결하기 위해, 리액트 개발진은 create-react-app이라는 툴 체인을 개발했다. create-react-app은 리액트를 ‘간단하게’ 시작하기 위한 것으로 react-script에 사용되는 라이브러리 목록이 매우 많다.
반대로, 사용자에게 최적의 번들을 제공하기 위한 전문 프론트엔드 개발자들은 create-react-app의 거대한 라이브러리 목록을 줄이고자 직접 웹팩을 설치하여 하나씩 필요한 라이브러리 설정을 하기 시작했다.
➡︎ 리액트는 프론트엔드 라이브러리로서 최소한의 기능을 제공하고자 가볍게 만들어졌지만, 개발자의 다양한 니즈를 충족시키기 위해 더 많은 라이브러리를 필수적으로 사용해야만 했다. 따라서 개발자가 필요한 라이브러리를 골라서 번들링 할 수 있는 웹팩이 필요하게 되었다.
📍 리액트 개발에 꼭 필요한 라이브러리
- react
- 리액트 컴포넌트와 Hooks, 라이프 사이클에 대한 정보가 모두 들어있음
- react-dom
- 리액트 코드를 브라우저에 보여줄 수 있음
- babel
- JSX를 JavaScript로 변경하여 entry에서 불러올 수 있게 만듬
- 브라우저에서 JavaScript는 읽을 수 있지만 JSX는 읽을 수 없음
- → babel이 jsx를 js로 변경해주어 번들링
- css-loader
- import로 CSS를 적용하기 위해 필요
📍 리액트 개발에 도움이 되는 라이브러리
- react-hot-loader
- webpack-dev-server처럼 저장할 때 마다 변경사항을 개발 환경에 적용해주는 라이브러리
- 리액트의 상태를 유지시켜줌
- eslint
- JavaScript로 개발 시 자주 접하는 에러를 방지하기 위한 린터
- eslint에 많은 config와 plugin이 있는데, 이를 잘 조합하면 리액트에서 자주 접하는 에러를 미리 발견하는데 도움이 됨
- prettier
- JavaScript로 개발 시 통일성 있게 코드 형식을 맞출 수 있게 도와주는 툴
- eslint와 조합해서 통일된 코드 형식까지 강요할 수 있음